linux的启动涉及到一个解压与定位的过程,对于x86体系结构而言,系统被加载到0x100000的地方,那么swapper_pg_dir的值是什么呢?我们知道swapper_pg_dir是一个很重要的东西,它是所有进程内核空间的页表的模板,而且在涉及到896M以上的内存分配时,swapper_pg_dir也是一个同步的根,这些内存分配包括vmalloc区,高端永久区,高端临时区等。这里需要说明的是,swapper_pg_dir这个东西其实就是一个页目录的指针,页目录指针在x86中是要被加载到cr3寄存器的,每个进程都有一个页目录指针,这个指针指示这个进程的内存映射信息,每当切换到一个进程时,该进程的页目录指针就被加载到了cr3,然后直到切换到别的进程的时候才更改,既然swapper_pg_dir是一个页目录指针,那么这个指针是被哪个进程用的呢?现代操作系统的含义指示了进程间内存隔离,那么一个页目录指针只能被一个进程使用,那么到底是哪个特定的进程使用了swapper_pg_dir指针呢?遗憾的是,答案是没有任何用户进程使用swapper_pg_dir作为页目录指针,swapper_pg_dir只是在内核初始化的时候被载入到cr3指示内存映射信息,之后在init进程启动后就成了idle内核线程的页目录指针了,/sbin/init由一个叫做init的内核线程exec而成,而init内核线程是原始的内核也就是后来的idle线程do_fork而成的,而在do_fork中会为新生的进程重启分配一个页目录指针,由此可见swapper_pg_dir只是在idle和内核线程中被使用,可是它的作用却不只是为idle进程指示内存映射信息,更多的,它作为一个内核空间的内存映射模板而存在,在linux中,任何进程在内核空间就不分彼此了,所有的进程都会公用一份内核空间的内存映射,因此,内核空间是所有进程共享的,每当一个新的进程建立的时候,都会将swapper_pg_dir的768项以后的信息全部复制到新进程页目录的768项以后,代表内核空间。另外在操作3G+896M以上的虚拟内存时,只会更改swapper_pg_dir的映射信息,当别的进程访问到这些页面的时候会发生缺页,在缺页处理中会与swapper_pg_dir同步。
了解到swapper_pg_dir的意义与实际作用,我们来看一下它的初始化吧,首先要看它的定义,在arch/i386/kernel/head.S中:
.long 0x00102007 //第一个页目录项,指示前4M的页面映射信息,0x00102000是前4M页表所在的物理页,而0x00000007是访问控制权限,二者相加构成一个页表,以下同义
.long 0x00103007 //第二个页目录项,指示4-8M的页面映射信息
.fill BOOT_USER_PGD_PTRS-2,4,0 //填充到内核边界
.long 0x00102007 //第768个页目录项,指示前4M的页面映射信息
.long 0x00103007 //第769个页目录项,指示第4-8M的页面映射信息
org 0x1000说明了让系统将swapper_pg_dir加载到地址0x1000处,可是内核最终搬到了0x100000处,那么swapper_pg_dir也就到了0x101000处,现在物理地址已经搞定了,那么最终进入保护模式并且启动分页时,swapper_pg_dir的虚拟地址会在哪里呢?我们看一下内核加载到了哪里然后加上0x1000就是swapper_pg_dir加载到的虚拟地址了,在vmlinux.lds中可以看出内核被加载到了0xc0100000处,于是swapper_pg_dir加载的虚拟地址就是0xc0101000,在初始化的时候,内核将0到8M的物理内存分别映射到了虚拟地址的0到8M和3G到3G+8M两个地方,而且过了初始化阶段到了最终的稳定页表,也同样有前XM的物理内存一一映射到虚拟内存的3G+XM的地方,于是swapper_pg_dir的虚拟地址就是0xc0101000,我们可以打印出swapper_pg_dir看看到底是多少,不幸的是,swapper_pg_dir并不从内核导出,那么怎么办呢?难道非要将打印信息加入内核启动函数然后从新编译一遍内核吗?其实不用,虽然swapper_pg_dir没有被导出,可是init_mm被导出了啊,我们知道init_mm就是内核启动时也就是idle进程的mm_struct,其中一个字段是pgd,就是swapper_pg_dir,我们可以打印init_mm->pgd的值,看看是多少。
事情到此还没有结束,如果你真的写了一个模块,并且打印init_mm.pdg的话,发现可能它并不是期望的0xc0101000,怎么回事呢?不要急, 遇到这种情况比遇到内核莫名其妙的down掉要好处理的多,再说这并不影响我们的生活,即使你最终没有弄清楚这是怎么一回事,那么也不会有什么损失的。这 种情况好解决的原因还有就是事情发生在内核初始化的阶段,也就是说swapper_pg_dir的初始化在内核初始化阶段,并且以后也不会变化,可能它的 内容会变,但是其本身的位置是不会变化的,因此,几乎不用调试,光看代码就可以解决问题,我们搜索一下近来的Changelog,发现在2.6.6中将 swapper_pg_dir从原来的固定的.org 0x1000的位置移到了BSS段当中了,因为bss段仅仅拥有占位符而不占用映像静态空间,它的真正数据并没有初始化,因此交给操作系统初始化就可以 了,既然没有初始化的数据,那么就没有必要在静态的映像中占据空间,而是让操作系统将其载入内存时将bss清零即可,linux内核本身就是操作系统内 核,因此它自己负责在启动的时候将bss段清零,因为初始化的时候,临时页表也就需要两个页目录用来映射物理内存的0到8M,这两个页目录很简单,一点不 复杂,没有必要写死到内核映像从而占据着3个页面的空间,因此放到bss段中就可以节省3页面的空间,然后在内核启动过程中再手动初始化那两个页目录的 值,这样做十分有意义。那么bss被加载到哪里就决定了swapper_pg_dir被加载到了哪里,那么bss到底在哪呢?从arch/i386 /kernel/vmlinux.lds.S中可以大致知道答案,如果想知道更加确切的,可以参考/boot/System.map文件,然后可以再打印 一下init_mm.pgd的值,看看是不是在bss里面,其实都不用打印,在System.map里面就有swapper_pg_dir的值,该值在 2.6.6之后的内核肯定在bss中,之前的肯定是0xc0101000。最后我们看一下2.6.6以后的swapper_pg_dir的定义:
.section ".bss.page_aligned","w"
ENTRY(swapper_pg_dir)
本文转自 dog250 51CTO博客,原文链接:http://blog.51cto.com/dog250/1273928