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




图中出现的符号是由编译程序在编译内核时产生的 。你可以在System.map 文件中找到这些符号的线性地址(或叫虚拟地址),System.map 是编译内核以后所创建的 。


启用分页机制


当 Linux 启动时,首先运行在实模式下,随后就要转到保护模式下运行 。
将 Linux 内核的映像转入内存中,并且做好了一些必要的准备后,CPU 就通过一条转移指令转到映象代码段开头的入口 startup_32, 从那里开始执行 。
Linux 内核代码的入口点就是 /arch/i386/kernel/head.S 中的 startup_32 。(内核版本 2.4.16) 。


内核映象的起点时 stext,也就是 _stext, 引导和解压缩以后的整个映象存放在内存中从 0x100000 也即是 1M 开始的区间 。CPU 执行内核映象的入口startup_32 就在内核映象开头的地方,因此其物理地址也是 0x100000 。
在正常运行时整个内核映象都应该在系统空间中,系统空间的地址映射时线性的、连续的,虚拟地址与物理地址间有个固定的转移,这就是 0xC0000000,也即是 3GB 。所以,在连续内核映象时已经在所有的符号地址上加了一个偏移量 0xC0000000,这样 startup_32 虚拟地址就成了 0xC0100000 。


在进入 startup_32 时都运行于保护模式下的段式寻址方式 。段描述表中与__KERNEL_CS 和 __KERNEL_DS 相对应的描述项所提供的基地址都是0,所以实际产生的就是线性地址 。


其中代码段寄存器 CS 已在进入 startup_32 之前设置成 __KERNEL_CS,数据段寄存器则尚未设置成 __KERNEL_DS 。不过,虽然代码段寄存器已经设置成 __KERNEL_CS,从而 startup_32 的地址为 0xC0100000 。但是在转入这个入口时使用的指令时 “ljmp 0x”100000” 而不是 “ljmp startup_32”,所以装入CPU中寄存器IP的地址是物理地址 0x100000 而不是虚拟地址0xC0100000 。


这样 CPU 在进入 startup_32 以后就会继续以物理地址取指令 。只要不在代码段中引用某个地址,例如向某个地址作绝对转移或者调用某个子程序,就可以一直这样运行下去,而与 CS 内容无关 。另外,CPU 的中断已在进入 startup_32 之前关闭 。


/* page table for 0-4MB for everybody */extern unsigned long pg0[1024];pte_t pg1[1024];pgd_t swapper_pg_dir[1024];在系统初始化的时候,内核就要创建内核页表 swapper_pg_dir 了 。
struct mm_struct init_mm = INIT_MM(init_mm);#define INIT_MM(name) \{ \.mm_rb = RB_ROOT, \.pgd = swapper_pg_dir, \.mm_users = ATOMIC_INIT(2), \.mm_count = ATOMIC_INIT(1), \.mmap_sem = __RWSEM_INITIALIZER(name.mmap_sem), \.page_table_lock = __SPIN_LOCK_UNLOCKED(name.page_table_lock), \.mmlist = LIST_HEAD_INIT(name.mmlist), \.cpu_vm_mask = CPU_MASK_ALL, \}内核启动过程中,存在一个实模式保护模式的切换过程 。在 linux 启动的最初阶段,内核刚刚被装入内存时,分页功能还未启用,此时是直接存取物理地址的(或者说线性地址就等于物理地址) 。但初始化完成后,内核也需要有自己的虚拟地址空间(1个G大小),该虚拟地址空间的地址映射关系,会被作为模版拷贝到其他进程的内核地址空间中 。


临时内核页表只用来映射物理地址的前 8M 空间内容 。目的是允许 CPU 在实模式(直接存取物理地址)和保护模式(根据虚拟地址映射)之间切换的过程中,都能对这前 8M 的地址进行访问 。(假如内核使用的全部内存可以存放在 8M 的空间里,因为一个页表可以映射 4M 的地址,所以8M的空间需要两个页表,也就是需要两个页目录项 。这两张页表我们称为临时内核页表 pg0 和 pg1 。


从 startup_32 开始的汇编代码在 /arch/i386/kernel/head.S,这就是初始化的第一阶段 。

推荐阅读