Linux 0.11中的页目录表及页表内容分析
Linux0.11针对的内存是16M。采用了两级分页机制进行内存的管理。
根据head.s中第114行的.org 0x1000可知,物理地址0x1000之前的所有数据都将被页目录表覆盖(这个覆盖,是指更改了内存中的内核镜像文件,而不是磁盘上的内核镜像文件)。
1、首先,Linux从0x00000地址开始对五页内存进行清零。(1页页目录表+4页页表)
stosl store EAX at address ES:(E)DI;EDI+=4。stosl指令相当于将eax中的值保存到 ES:(E)DI指向的地址中。
1 | setup_paging: |
2 | movl $1024*5,%ecx /* 5 pages - pg_dir+4 page tables */ |
3 | xorl %eax,%eax |
4 | xorl %edi,%edi /* pg_dir is at 0x000 */ |
5 | cld;rep;stosl |
2、接着,填写页目录表(页目录表的位置为0x00000-0x00fff,大小为4K,每一项占4字节)。因为只有4个页表,所以只填写了前四项。
1 movl $pg0+7,pg_dir /* set present bit/user r/w */ |
2 movl $pg1+7,pg_dir+4 /* --------- " " --------- */ |
3 movl $pg2+7,pg_dir+8 /* --------- " " --------- */ |
4 movl $pg3+7,pg_dir+12 /* --------- " " --------- */ |
这里,4个页目录项的内容分别是$pg0(1,2,3)+7,分别是4个页表的物理地址+111B。前面的$pg0(1,2,3)是页表的物理地址,而111B则代表这4个页表权限为可读写。
3、填写页表项的内容
1 movl $pg3+4092,%edi |
2 movl $0xfff007,%eax /* 16Mb - 4096 + 7 (r/w user,p) */ |
3 std |
4 1: stosl /* fill pages backwards - more efficient :-) */ |
5 subl $0x1000,%eax |
6 jge 1b |
这里的填写是逆序填写的,也就是首先将16M物理内存最后一页的启始地址+权限(16M-4K+111B)填写到第4张页表的最后一项。地址为$pg3+4092,其中$pg3为(第4张页表的起始地址),4092是因为1024*4(1024项,每项占用4字节)-4(最后一项页占用4字节)。所以第4张页表的最后一项是(16M-4K)0xfff000+111B=0xfff007。
最终,std以4递减edi寄存器(一个页表项占4字节,edi指向正在操作的页表项),subl $0x1000,%eax将减去0x1000(一页内存的大小,eax指向正在操作的内存边界),l:stosl是填写页表项,知道eax的内容为0,这样就填写完了4个页表。
这样,内存中的页目录表和页表分布就是:
物理内存地址 | 所含信息及备注 | 单元内容 |
0X00FF FFFC …… |
…… …… |
|
0X0000 4FFC 0X0000 4FF8 … … 0X0000 4004 0X0000 4000 |
页表4 4K (0X00004000-0X00004FFC) 4字节为一项 |
0X00FF F000+7 0X00FF E000+7 … … 0X00C0 2000+7 0X00C0 1000+7 0X00C0 0000+7 |
0X0000 3FFC 0X0000 3FF8 … … 0X0000 3004 0X0000 3000 |
页表3 4K (0X00003000-0X00003FFC) 4字节为一项 |
0X00BF F000+7 0X00BF E000+7 … … 0X0080 2000+7 0X0080 1000+7 0X0080 0000+7 |
0X0000 2FFC 0X0000 2FF8 … … 0X0000 2004 0X0000 2000 |
页表2 4K (0X00002000-0X00002FFC) 4字节为一项 |
0X007F F000+7 0X007F E000+7 … … 0X0040 2000+7 0X0040 1000+7 0X0040 0000+7 |
0X0000 1FFC 0X0000 1FF8 … … 0X0000 1004 0X0000 1000 |
页表1 4K (0X00001000-0X00001FFC) 4字节为一项 |
0X003F F000+7 0X003F E000+7 … … 0X0000 2000+7 0X0000 1000+7 0X0000 0000+7 |
0X0000 0FFC 0X0000 0FF8 … … 0X0000 0004 0X0000 0000 |
PDT(页目录表) 4K (0X000000-0X00000FFF) 只有前四项有内容 |
… 0x0000 4000+7 0x0000 3000+7 0x0000 2000+7 0x0000 1000+7 |
最后,把页目录表的地址(0x000000)写到控制寄存器CR3,然后置控制寄存器CR0的PG位,这样就开启了内存的分页管理功能。
Linux0.11在分页机制下的寻址(两级表寻址)
第一级表称为页目录。
存放在1页4k页面中。具有1k个4字节长度的表项。这些表项指向第二级表。线性地址的最高10位(位31-22)用作一级表(页目录)中的索引值来选择某个页目录项,用以选择某个二级表。
第二级表称为页表。
长度也是1个页面,每个表含有1k个4字节的表项。每个4字节表项含有相关页面的20位物理地址。二级页表使用线性地址中间的10位(位21-12)作为表项索引值,在表内索引含有页面20位物理地址的表项。该20位页面物理地址和线性地址中的低12位(页内偏移)组合在一起就得到了分页转换中的输出值,也就是最终的物理地址。
也就是说:
线性地址高10位---------索引页目录表----------->找到相应页表
线性地址中间10位---------索引页表----------->得到页表中相应的项,其中的高20位就是物理地址的高20位
线性地址低12位-------------------->物理地址的低12位
下面举两个例子说明Linux0.11的分页寻址。
1、寻址0x38
首先写成32位地址为0x0000 0038。取最高10位,0000 0000 00(2进制),索引页目录项。这里找到页目录表的第0项,内容为$pg0+7=0x0000 1007。取线性地址的中间10位,00 0000 0000(2进制),索引页表1(pg0)中的第0项,内容为
0x0000 0000+7,取他的高20位0x0000 0加上线性地址的低12位0x038就得到最终的物理地址0x0000 0038
2、寻址0x00f5 9f50
首先取最高10位,0000 0000 11(2进制),索引页目录表。这里为3就找到页目录表的第3项,指向页表4,内容为$pg3+7=0x0000 4000+7。取线性地址的中间10位,11 0101 1001B,索引页表4(pg3)中的0x359项,内容为0x00f5 9000+7。取其高20位0x00f5 9000加上线性地址的低12位0xf50就得到最终的物理地址0x00f5 9f50