学习Linux的源码,《深入linux 内核架构》这本书看起来就让人害怕,然后就想着看看早期的linux版本的源码,从网上查看资料发现linux0.11 这个版本有很多人拿来当成教学版本,而且也有很多的参考书以这个版本作为基础来讲解,于是就从这个版本看起,本文主要是总结一下head.s这个汇编源码中的内存分页机制的建立。
linux 大神关于这个的代码很少,但是完整的建立了内存分页的页目录和页表。
/*
* I put the kernel page tables right after the page directory,
* using 4 of them to span 16 Mb of physical memory. People with
* more than 16MB will have to expand this.
*/
.org 0x1000
pg0:
.org 0x2000
pg1:
.org 0x3000
pg2:
.org 0x4000
pg3:
.org 0x5000
/*
setup_paging:
movl $1024*5,%ecx /* 5 pages - pg_dir+4 page tables */
xorl %eax,%eax
xorl %edi,%edi /* pg_dir is at 0x000 */
cld;rep;stosl
movl $pg0+7,_pg_dir /* set present bit/user r/w */
movl $pg1+7,_pg_dir+4 /* --------- " " --------- */
movl $pg2+7,_pg_dir+8 /* --------- " " --------- */
movl $pg3+7,_pg_dir+12 /* --------- " " --------- */
movl $pg3+4092,%edi
movl $0xfff007,%eax /* 16Mb - 4096 + 7 (r/w user,p) */
std
1: stosl /* fill pages backwards - more efficient :-) */
subl $0x1000,%eax
jge 1b
xorl %eax,%eax /* pg_dir is at 0x0000 */
movl %eax,%cr3 /* cr3 - page directory start */
movl %cr0,%eax
orl $0x80000000,%eax
movl %eax,%cr0 /* set paging (PG) bit */
ret /* this also flushes prefetch-queue */
现在我们就从一个初学者的角度来解读这些汇编代码然后看看最后的效果。
movl $1024*5,%ecx /* 5 pages - pg_dir+4 page tables */
xorl %eax,%eax
xorl %edi,%edi /* pg_dir is at 0x000 */
cld;rep;stosl
这一段代码是将从地址为0 开的20K 数据全部置位0. movl $1024*5,%ecx 是将ecx 寄存器设置为 1024*5. 然后将eax 和edi 寄存器全部设置为0x00000000(32位)。
cld;rep;stosl
这三条指令组合在一起用于在内存中填充数据。
cld
:这条指令设置方向标志(Direction Flag)为0,意味着接下来的字符串操作将从低地址向高地址进行。rep
:这是一个前缀,它告诉处理器接下来的字符串操作应该重复执行,直到%ecx
寄存器的值减到0为止。stosl
:这是一个字符串操作指令,它将%eax
寄存器中的值(在这里是0,因为我们之前清零了%eax
)存储到由%edi
寄存器指向的内存地址,并递增%edi
的值(一次递增4)。
然后再往页目录中写入内容,$pg0+7 是0x00001007,$pg1+7 是0x00002007,$pg2+7 是0x00003007,$pg3+7 是0x00004007
movl $pg0+7,_pg_dir /* set present bit/user r/w */
movl $pg1+7,_pg_dir+4 /* --------- " " --------- */
movl $pg2+7,_pg_dir+8 /* --------- " " --------- */
movl $pg3+7,_pg_dir+12 /* --------- " " --------- */
把0x00001007 转换成二进制 000000000000000000001 000000000111可以看出对应的是页表第一项。
后面这一段代码用来填充页表项的内容
movl $pg3+4092,%edi
movl $0xfff007,%eax /* 16Mb - 4096 + 7 (r/w user,p) */
std
1: stosl /* fill pages backwards - more efficient :-) */
subl $0x1000,%eax
jge 1b
可以看到总共有4*1024个页表项需要写入值,std 设置为1,表示要从高地址往低地址来写。第一个写入的位置是页表三的最后一项,写入的内容是0x00fff007,写完之后写下一个,地址是$pg3+4092 -4,写入的内容是0x00ffe007,一直到eax的值为0.
线性地址的通过页目录和页表来获取物理地址的位置。
13M 位置的0000 0000 1101 0000 0000 0000 0000 0000
页目录项0x3,页表项0x100,页内偏移地址0x0。
0x100 * 4k =256* 4 K =1M,而页表3 的起始地址是12M 所以就可以定位到13M的地址上面。
最后来设置页目录寄存器cr3 的值和设置启动分页处理标志位
xorl %eax,%eax /* pg_dir is at 0x0000 */
movl %eax,%cr3 /* cr3 - page directory start */
movl %cr0,%eax
orl $0x80000000,%eax
movl %eax,%cr0 /* set paging (PG) bit */
最后内存中的布局如下: