9099263

当前课程知识点:Linux 内核分析与应用 >  第2章 内存寻址 >  2.4 动手实践-把虚拟地址转换成物理地址 >  Video

返回《Linux 内核分析与应用》慕课在线视频课程列表

Video在线视频

Video

下一节:第二章导学-从零打造自己的操作系统

返回《Linux 内核分析与应用》慕课在线视频列表

Video课程教案、知识点、字幕

大家好今天跟大家分享的题目是动手实践把虚拟地址转换为物理地址

在开始之前先跟大家一起回顾一下相关的概念

在前面的学习中我们了解到在进程中我们不直接对物理地址进行操作

CPU在运行 指定的地址要经过MMU转换后才能访问到真正的物理内存

地址的转换过程分为两块 分段和分页

分段机制简单的来说是将进程的代码 数据 栈分在不同的虚拟地址段上

从而避免进程间的互相影响 分段之前的地址我们称之为逻辑地址

它由两部分组成 高位的段选择符和低位的段内偏移

在分段时先用段选择符在相应的段描述符表中找到段描述符 也就是某一个段的基地址

再加上段内偏移量就得到了对应的线性地址

线性地址也称之为虚拟地址

而在实际的应用中 Linux系统为了增加系统的可移植性并没有完整的使用分段机制

它让所有的段都指向相同的地址范围

段的基地址都为0 这样逻辑地址和线性地址在数值上就相同了

所以今天我们分析的重点是在分页

也就是由线性地址到物理地址转换的过程

Linux为了兼容32位和64位的CPU呢

它需要一个统一的页面地址模型 目前常用的是4级页表模型

里面有4级页表 PGD页全局目录 页上级目录 页中间目录和页表

根据不同的需要 其中某些页表可能未被使用

线性地址中每一部分索引

的大小会根据具体的计算机体系结构

做相应的改变举个例子来说 对于没有启用物理地址

扩展功能的32位系统来说 两级页表就足够了

那么Linux会让首先使线性地址中

将页上级目录和页中间目录索引

这两位置为0 从根本上就取消了这两个字段

但是这两个页目录在指针序列中的位置仍然被保留下来

也就是说寻址过程中呢

不能跳过页上级目录和页中间目录直接由页全局目录直接到

页表内核会将这两个

页目录的表项呢都置为1

由于64位处理器硬件的限制

它的地址线只有48条

所以线性地址实际使用的只有48位

在64位Linux中使用了4级页表结构

它的线性地址划分如上图所示 首先

页全局目录的索引 页上级目录的索引

页中间目录的索引和页表的索引分别占了9位

最后页内偏移占了12位 共计是48位

剩下的高位都是保留 留作以后扩展使用的

在这种情况下页面的大小都为4kb

每一个页表项大小为8bit 整个页表可以映射的空间是256TB

而新的Intel芯片的MMU硬件规定可以进行5级的页表管理

所以在4.15的内核中呢

Linux已经在这里增加了一个新的页目录 也就是在

页全局目录和页上级目录之间又增加了一个新的页目录 叫做p4d页目录

这个页目录同32位中的情况一样现在还未被使用

它的页目录项只有一个 线性地址中也没有它的索引位

在这里我们看到一个很重要的寄存器 就是CR3寄存器

它是一系列CPU控制寄存器之一

这些控制寄存器主要用来保存控制系统级别操作的数据和信息的

其中这个CR3就是用来保存当前进程的页全局目录的地址的

寻页的开始就是从页全局目录开始 那么页全局目录的地址又在哪呢

内核在创建一个进程时就会为它分配页全局目录

在进程描述符task_struct结构中有一个指向mm_struct结构的

指针 mm

而mm_struct结构是用来描述进程的虚拟地址空间的

在mm_struct中有个字段PGD

就是用来保存该进程的页全局目录的(物理)地址的

所以在进程切换的时候呢 操作系统通过访问task_struct结构

再访问mm_struct结构 最终找到PGD字段 取得新进程的页全局目录的地址

填充到CR3寄存器中就完成页表的切换

好了了解了这些之后 我们在实际的系统中来看看寻页的过程是如何完成的

为了能够看到寻页机制的过程 我们需要使用内核提供的一些函数 为此呢我们

编写一个内核模块 我们先来看一下它的代码

该内核模块的主要功能是在内核中先

申请一个页面 然后利用内核提供

的函数按照寻页的步骤一步步查询各级页目录

最终找到所对应的物理地址

这些步骤就像我们手动模拟了MMU单元的寻页过程

首先看到的这个函数get_pgtable_macro

它的作用是打印页机制中一些重要参数的

CR3寄存器的值通过这个read_cr3_pa函数来获得

接着是一些_SHIFT这些宏

这些宏的作用是用来指示线性地址中

线性地址中相应字段所能映射区域大小的对数的

PAGE_SHIFT就是指示page offset字段

所能映射区域大小的对数 page offset字段我们都知道它映射的是一个页面的大小

而一个页面的大小是4K 转换成以2为底的对数就是12

其他的SHIFT宏的作用类似

下面这个宏(PTRS_PER_x)是用来指示相应的页目录表中项的个数的

最后这个宏是page_mask 是所谓的页内偏移掩码

页内偏移掩码是用来屏蔽掉page offset字段的

这些宏都是为了方便寻页时进行位运算的

我们后面要用到它们 这里先把它们打印出来看一下

接下来这个函数就是我们用来进行线性地址到物理地址转换的函数

首先呢我们是用

为每个页目录项创建一个变量来将他们保存起来

接着我们使用的第一个函数呢就是这个

pgd_offset()函数

它传入的第一个参数是当前进程的mm_struct结构

这里我们想一下 因为我们申请的线性地址是在内核空间的 所以我们要查的

页表也应该是内核页表 但是呢

我们知道所有的进程都共享同一个内核页表 所以可以用当前进程的

mm_struct结构来进行查找 我们

查找得到了第一个

页全局目录项PGD之后 我们将其作为

下级查找的参数传入到这个p4d_offset中

做下一级的查找 我们就找到了P4d

这个页目录项

然后再去找相应的PUD页目录项 PMD页目录项 最终我们就会找到

我们的PTE页表项了

在查找页表项的时候 这个函数与上面这些函数略有不同

它是pte_offset_kernel 这是为了表示我们查找是在

内核页表主内核页表中查找的

而在进程页表中查找是有一个完全不同的一个函数的

最后我们就取得了页表的线性地址了

接着呢我们需要从

这个页表的线性地址也就是这个页表项中取出

该页表所映射页框的物理地址

我们是这样做的 将其与PAGE_MASK

这个变量进行位或的操作 取出其高48位

就得到了页框的物理地址 接着我们需要取出页偏移地址

页偏移量也就是

线性地址中的低12位 我们是

这样做的 将PAGE_MASK按位取反

然后与我们的vaddr做了一个位或的操作

这样就取出了它的页内偏移 最后我们将这两个地址

拼接起来 就得到了我们想要的物理地址了

最后我们将这些地址都打印出来 就完成线性地址到物理地址转换的过程了

最后这两个函数就是内核模块的注册和卸载函数

在注册函数中我们完成了内核模块的初始化

首先我们打印了这些主要的这些参数

然后就是使用这个get_free_

page这个函数的在内核的

ZONE_NORMAL中申请了一块页面

后面这个标志是用来指示 它是优先从内存的ZONE_NORMAL区中申请页框的

这里我补充一下 在64位系统中已经不再有所谓的高端内存的概念了

这是因为我们使用的内存的大小是比较小的

线性映射已经可以将所有的物理内存映射到线性地址空间了

所以64位体系架构上的ZONE_HIGHMEM区总是为空的

这里我们在这个地址中我们

写入一段话“hello world from kernel” 这我们后面是有用的

最后我们将调用这个线性地址到物理地址转换的函数完成

线性地址到物理地址的转换 最后我们在这个

内核模块卸载函数中将我们申请的线性地址空间释放掉

接着看一下MAKEFILE文件

MAKEFILE文件也比较简单 可以用之前写过的HELLO WORLD的

MAKEFILE文件就可以 只要将名字改一下就可以 接着我们来MAKE一下

然后我们将其插入到内核中 我们在内核中看一下它打印出来的log

这就是我们那个模块运行的那个

打印出来的参数 首先 我们的CR3寄存器的值在这里然后我们的

每一个SHIFT宏就在这里 我们可以看到

我们的P4D_SHIFT和我们的PGD_SHIFT它

都是39的这也就意味着在线性地址中

我们的P4D这个字段它是为空的

我们也可以看到在页目录项中 P4D的页目录项也是为1的

这就跟我们之前讲的是一样的 虽然我们Linux现在用了五级的

页表模型 但实际上使用的页表也只有四个

最后是这个PAGE_MASK 它是低12位都为零

其余位都为1的一个64位的数

我们申请到的这个线性地址呢 这是它的线性地址

我们依次查找了

它的PGD也就是页全局目录项的线性地址

页四级目录项的线性地址 页上级目录项的线性地址

还有页中间目录项的地址和最后的

页表项的物理地址 这些页目录项偏移我们都在后面打印了出来

最后我们将这个线性地址转化成了物理地址 是后面这个

我们可以看到这个线性地址 它的第一位是8 转换到二进制呢

就是最高位63位是为1的 这是一个X86平台上用来

标识该物理页框是不能用来执行代码的一个保护位的

这里我们不去管它 其物理页框的物理地址就是后面这九位

好了 到这里我们就完成了这个

代码的演示过程

但到这里我觉得还不够过瘾 因为我们看到的只是一些数字的变化

并没有直观地感受到线性地址和物理地址之间的这个关系

接下来我给大家来看一个更有趣的一个东西

还记得我们在刚才

内核中申请线性地址空间的时候 我们在这个线性地址中呢

输入了一段话 现在我们已经得到了该线性地址所对应的物理地址了

我们现在就想啊 能不能直接访问一下这个物理地址 看看里边

存放的东西到底是什么 如果是我们刚才

输入的那段话 也就证明了我们这个寻页的程序是正确的

所以为了做到这一点我们需要两个小工具来帮助一下

这两个小工具是我在网上找的 它最早呢是

07年的一个旧金山大学的高级系统编程

课程上为学生提供的一个

小工具 首先是一个DRAM的一个内核模块 我们先来看一下它的源码

这是它的源码 它主要的

功能呢就是通过

mmap将物理内存中的数据映射到

我们的一个设备

文件中 我们通过对于这个设备文件进行访问 就可以达到

访问物理内存的功能了

另一个工具叫做fileview 它可以让我们对

按照我们想要的一种格式阅读这种二进制文件

这两个小工具都比较老了 我们在用的时候需要先对它们进行一些修改

修改之后我们拿到这两个工具 就可以对我们物理内存进行

在用户空间下对物理内存进行访问了

那么先做的呢就是 将这个

模块编译好之后将它插入到内核中 然后呢再

dev目录下创建这样一个文件

设备文件然后它的设备号在源码中提到了 我们输入进去就行

做好了之后 我们就可以用我们的fileview

工具直接去访问这个DRAM的文件

就相当于我们直接可以阅读到物理内存中的数据了

我们来看一下刚才申请的那个线性地址

在这里我们将它转化成二进制 然后将其中每一个

索引字段都转化成

二进制提取出来我这里已经给大家算好了

首先PGD字段是这样这是它的二进制

从39位开始 一直往上占了九位

转化成十进制就是这个284 也就是这个pgd_index

我们知道每一个页目录项它们的大小都是8比特 所以我们要

在这个索引的基础上再乘一个8

我们才能得到在物理地址中它的偏移量

我们将它转化成16进制就得到了

该索引位在物理地址中的偏移量 有了这个之后呢

我们就可以

在物理内存中查找相应的数据了

首先 第一步我们是在页全局目录中

按照这个索引进行查找的 那么我们的页全局目录表呢

它的基地址在哪呢 就在我们的这个CR3寄存器中保存着

所以我们用CR3寄存器的值加上这个偏移量 我们看看里边有什么

好了 我们可以看到在这个

地址下呢 我们找到了这样的数据

7CD3F067 这是什么呢 我们来看一下

这不就是我们的pgd_val打印出来的数据吗

我们还记得pgd_val打印出来的是什么呢 就是我们的PGD

页全局目录项里边所存放的数据

也就是下一级页表的物理地址

有了这个之后我们就可以继续的去查找

我们下级查找就是按照PUD的偏移量来去查找

我们的基地址我们已经查到了7CD3F000

然后偏移量是998

998在后面这块这是990 998在这 好了我们看到了

查到了下一页表的起始地址25FFFD067 也就是我们的

pud_val打印出来的这个数据

我们看到在物理内存读出来跟我们打印出来的都是能对上的

所以我们的寻址过程呢

按这样的寻址是正确的 最后我们得到了

就是物理地址了

这里给大家来补充一下 我们大家都看到这些

目录项中查到了地址 它的结尾都是067 063这是为什么呢

因为我们的这个相应的这个

每一个目录项它的低12位其实是没用的 因为我们最后呢都其实是要

将它与页内偏移做一个拼接的

所以这低12位空出来 我们用来存放的是

相应的页目录项或者页表的属性

所以呢 我们在加这个

进行地址的加减的时候将它们置为0就可以了

最后我们的物理地址在这里 我们直接将这个物理地址的

输进去 看看里边是什么

我们看到了 “hello world from kernel”

就是我们在最开始的那个线性地址中存储的那段话

这也就证明了这个物理地址转化的程序是正确的

好了 今天的分享就到这里了 谢谢大家

Linux 内核分析与应用课程列表:

第1章 概述

-1.1 Linux操作系统概述

--1.1 Linux 操作系统概述

-1.2 Linux内核结构以及内核模块编程

--Video

-1.3 Linux内核源码中的双链表结构

--Video

-1.4 源码分析-内核中的哈希表

--Video

-1.5 动手实践-Linux内核模块的插入和删除

--Video

-第1章 概述--章节测验

-第1章导学--引领你进入Linux内核的大门

第2章 内存寻址

-2.1 内存管理之内存寻址

--Video

-2.2 段机制

--Video

-2.3分页机制

--Video

-2.4 动手实践-把虚拟地址转换成物理地址

--Video

-第2章 内存寻址--章节测验

-第二章导学-从零打造自己的操作系统

第3章 进程管理

-3.1 进程概述

--Video

-3.2 Linux进程创建

--Video

-3.3 Linux进程调度

--Video

-3.4 动手实践-打印进程描述符task_struct中的字段

--Video

-3.5工程实践-基于内核模块的负载监控

--Video

-第3章 进程管理--章节测验

-第三章导学-进程背后琳琅满目的宝贝到哪里挖?

第4章 内存管理

-4.1 Linux内存管理机制

--Video

-4.2 进程用户空间管理机制

--Video

-4.3 物理内存分配与回收机制(上)

--Video

-4.4 物理内存分配与回收机制(下)

--Video

-4.5 动手实践-Linux内存映射基础(上)

--Video

-4.6 动手实践-Linux内存映射实现(中)

--Video

-4.7 动手实践-Linux内存映射测试(下)

--Video

-4.8 初学者对内存管理的常见疑惑

--初学者对内存管理的常见疑惑(一)

--初学者对内存管理的常见疑惑(二)

--初学者对内存管理的常见疑惑(三)

-第4章 内存管理--章节测验

第5章 中断

-5.1 中断机制概述

--Video

-5.2 中断处理机制

--Video

-5.3 中断下半部处理机制

--Video

-5.4 时钟中断机制

--Video

-5.5 动手实践-中断上半部的代码分析及应用

--Video

-5.6 动手实践-中断下半部的代码分析及应用

--Video

-第5章 中断--章节测验

第6章 系统调用

-6.1 Linux中的各种API

--Video

-6.2 系统调用机制

--Video

-6.3 动手实践-添加系统调用(系统调用日志收集系统)

--Video

-第6章 系统调用--章节测验

第7章 内核同步

-7.1 内核同步概述

--Video

-7.2 内核同步机制

--Video

-7.3 动手实践-内核多任务并发实例(上)

--Video

-7.4 动手实践-内核多任务并发实例(下)

--Video

-第7章 内核同步--章节测验

第8章 文件系统

-8.1 虚拟文件系统的引入

--Video

-8.2 虚拟文件系统的主要数据结构

--Video

-8.3 文件系统中的各种缓存

--Video

-8.4 页高速缓存机制以及读写

--Video

-8.5 动手实践-编写一个文件系统(上)

--Video

-8.6 动手实践-编写一个文件系统(中)

--Video

-8.7 动手实践-编写一个文件系统(下)

--Video

-第8章 文件系统--章节测验

第9章 设备驱动

-9.1 设备驱动概述

--Video

-9.2 I/O空间管理

--Video

-9.3 设备驱动模型

--Video

-9.4 字符设备驱动程序简介

--Video

-9.5 块设备驱动程序简介

--Video

-9.6 动手实践-编写字符设备驱动程序

--Video

-9.7工程实践-编写块设备驱动的基础(上)

--Video

-9.8 工程实践-块设备驱动程序分析(中)

--Video

-9.9 工程实践-块设备驱动程序实现(下)

--Video

-第9章 设备驱动--章节测验

致谢与说明

-致谢与说明

--Video

直播视频:从Linux内核学习到自主操作系统研发

-从Linux内核学习到自主操作系统研发

附录:实验代码、课件以及相关素材

-各章实验代码

-《Linux内核分析与应用》课件

-《Linux操作系统原理与应用》教材课堂视频

Video笔记与讨论

也许你还感兴趣的课程:

© 柠檬大学-慕课导航 课程版权归原始院校所有,
本网站仅通过互联网进行慕课课程索引,不提供在线课程学习和视频,请同学们点击报名到课程提供网站进行学习。