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


.org 0x1000ENTRY(s*** 0x2000ENTRY(pg0).org 0x3000ENTRY(pg1)/** empty_zero_page must immediately follow the page tables ! (The* initialization loop counts until empty_zero_page)*/.org 0x4000ENTRY(empty_zero_page)/** Initialize page tables*/movl $pg0-__PAGE_OFFSET,%edi /* initialize page tables */movl $007,%eax /* "007" doesn't mean with right to kill, butPRESENT+RW+USER */2: stosladd $0x1000,%eaxcmp $empty_zero_page-__PAGE_OFFSET,%edijne 2b内核的这段代码执行时,因为页机制还没有启用,还没有进入保护模式,因此指令寄存器 EIP 中的地址还是物理地址,但因为 pg0 中存放的是虚拟地址(gcc 编译内核以后形成的符号地址都是虚拟地址),因此,“$pg0-__PAGE_OFFSET ”获得 pg0 的物理地址(__PAGE_OFFSET 为 0xC0000000,也即是 3GB),可见 pg0 存放在相对于内核代码起点为0x2000 的地方,即物理地址为 0x00102000,而pg1 的物理地址则为0x00103000 。Pg0 和 pg1 这个两个页表中的表项则依次被设置为 0x007、0x1007、0x2007 等 。其中最低的 3 位均为 1,表示这两个页为用户页,可写,且页的内容在内存中(参见下图) 。所映射的物理页的基地址则为 0x0、0x1000、0x2000 等,也就是物理内存中的页面 0、1、2、3 等等,共映射2K 个页面,即 8MB 的存储空间 。由此可以看出,Linux 内核对物理内存的最低要求为 8MB 。紧接着存放的是 empty_zero_page 页(即零页),零页存放的是系统启动参数和命令行参数 。


linux内核虚拟地址空间

文章插图


.org 0x1000ENTRY(swapper_pg_dir).long 0x00102007.long 0x00103007.fill BOOT_USER_PGD_PTRS-2,4,0/* default: 766 entries */.long 0x00102007.long 0x00103007/* default: 254 entries */.fill BOOT_KERNEL_PGD_PTRS-2,4,0/** Enable paging*/3:movl $swapper_pg_dir-__PAGE_OFFSET,%eaxmovl %eax,%cr3 /* set the page table pointer.. */movl %cr0,%eaxorl $0x80000000,%eaxmovl %eax,%cr0 /* ..and set paging (PG) bit */jmp 1f /* flush the prefetch-queue */1:movl $1f,%eaxjmp *%eax /* make sure eip is relocated */1:/* Set up the stack pointer */lss stack_start,%esp // 将CPU的堆栈设置在 stack-start处这段代码就是把页目录 swapper_pg_dir 的物理地址装入控制寄存器cr3,并把 cr0 中的最高位置成1,这就开启了分页机制 。


但是,启用了分页机制,并不说明Linux 内核真正进入了保护模式,因为此时,指令寄存器 EIP 中的地址还是物理地址,而不是虚地址 。“jmp 1f” 指令从逻辑上说不起什么作用,但是,从功能上说它起到丢弃指令流水线中内容的作用(这是 Intel 在 i386 技术资料中所建议的),因为这是一个短跳转,EIP 中还是物理地址 。紧接着的 mov 和 jmp 指令把第 2 个标号为 1 的地址装入EAX 寄存器并跳转到那儿 。在这两条指令执行的过程中, EIP 还是指向物理地址“1MB+某处” 。因为编译程序使所有的符号地址都在虚拟内存空间中,因此,第2 个标号1 的地址就在虚拟内存空间的某处(PAGE_OFFSET+某处),于是,jmp 指令执行以后,EIP 就指向虚拟内核空间的某个地址,这就使 CPU 转入了内核空间,从而完成了从实模式到保护模式的平稳过渡 。


然后再看页目录 swapper_pg_dir 中的内容 。从前面的讨论我们知道 pg0 和pg1 这两个页表的起始物理地址分别为 0x00102000 和 0x00103000 。页目录项的最低12位用来描述页表的属性 。因此,在 swapper_pg_dir 中的第0 和第1 个目录项 0x00102007、0x00103007,就表示 pg0 和 pg1 这两个页表是用户页表、可写且页表的内容在内存 。


接着,把 swapper_pg_dir 中的第 2~767 共 766 个目录项全部置为0 。因为一个页表的大小为 4KB,每个表项占 4 个字节,即每个页表含有 1024 个表项,每个页的大小也为 4KB,因此这 768 个目录项所映射的虚拟空间为768×1024×4K=3G,也就是 swapper_pg_dir 表中的前 768 个目录项映射的是用户空间 。最后,在第 768 和 769 个目录项中又存放 pg0 和 pg1 这两个页表的地址和属性,而把第 770~1023 共 254 个目录项置 0 。这 256 个目录项所映射的虚拟地址空间为256×1024×4K=1G,也就是 swapper_pg_dir 表中的后 256 个目录项映射的是内核空间 。

推荐阅读