linux内核虚拟地址空间( 五 )




由此可以看出,在初始的页目录 swapper_pg_dir 中,用户空间和内核空间都只映射了开头的两个目录项,即 8MB 的空间,而且有着相同的映射,如图:


linux内核虚拟地址空间

文章插图


内核开始运行后运行在内核空间,那么,为什么把用户空间的低区(8M)也
进行映射,而且与内核空间低区的映射相同?


简而言之,是为了从实模式到保护模式的平稳过渡 。具体地说,当 CPU 进入内核代码的起点 startup_32 后,是以物理地址来取指令的 。在这种情况下,如果页目录只映射内核空间,而不映射用户空间的低区,则一旦开启页映射机制以后就不能继续执行了,这是因为,此时 CPU 中的指令寄存器 EIP 仍指向低区,仍会以物理地址取指令,直到以某个符号地址为目标作绝对转移或调用子程序为止 。所以,Linux 内核就采取了上述的解决办法 。


比如不映射用户空间的低区,内核代码的起点 startup_32 后,是以物理地址来取指令的,比如 eip 里面的地址为 0x0010010,当开启页面映射后,eip 里面的地址就要按照虚拟地址来处理了,这个时候要通过查页表进行把虚拟地址 0x0010010 转换为物理地址,这个时候没有映射用户空间的低区,找不到虚拟地址 0x0010010 到物理地址的映射,这个时候就会出现问题 。


在 CPU 转入内核空间以后,应该把用户空间低区的映射清除掉 。后面将会看到,页目录 swapper_pg_dir 经扩充后就成为所有内核线程的页目录 。在内核线程的正常运行中,处于内核态的 CPU 是不应该通过用户空间的虚拟地址访问内存的 。清除了低区的映射以后,如果发生 CPU 在内核中通过用户空间的虚拟地址访问内存,就可以因为产生页面异常而捕获这个错误 。


经过这个阶段的初始化,初始化阶段页目录及几个页表在物理空间中的位置如图所示 。
linux内核虚拟地址空间

文章插图


/*
* ZERO_PAGE is a global shared page that is always zero: used
* for zero-mapped memory areas etc..
*/
extern unsigned long empty_zero_page[1024];


其中 empty_zero_page 中存放的是在操作系统的引导过程中所收集的一些数据,叫做引导参数 。因为这个页面开始的内容全为 0,所以叫做“零页”,代码中常常通过宏定义 ZERO_PAGE 来引用这个页面 。不过,这个页面要到初始化完成,系统转入正常运行时才会用到 。


那 swapper_pg_dir 和 pg0 、pg1 怎么对物理内存进行映射的呢?


从上面的物理内存分布可知,swapper_pg_dir 、pg0 、pg1存在物理内存中,swapper_pg_dir [0] 和 swapper_pg_dir [768] 指向 pg0 所在的物理地址,swapper_pg_dir [1] 和 swapper_pg_dir [769]指向pg1所在的物理地址 。而他们每一项对应的映射为 4M 。pg0 和 pg1 二者映射物理内存的前 8M 空间 。如下图:
linux内核虚拟地址空间

文章插图


比如当访问虚拟内核地址空间 0xC0001002,通过 swapper_pg_dir 进行虚拟地址到物理地址转换时,发现 0xC0001002 处于 swapper_pg_dir [768],而 swapper_pg_dir [768] 指向 pg0 的物理内存地址,然后经过 pg0 找到其对应的物理页框 。


关于整个虚拟地址空间和物理空间分布关系如下:
【linux内核虚拟地址空间】

linux内核虚拟地址空间

文章插图

推荐阅读