《Linux0.11源码解读》理解(五) head之开启分页

13 篇文章 0 订阅

先回顾一下地址长度以及组合的演变:16位cpu意味着其数据总线/寄存器也是16位,但是地址总线(寻址能力)与此无关,可能是20位。可以参考:cpu的位宽、操作系统的位宽和寻址能力的关系_cpu位宽_brahmsjiang的博客-CSDN博客

也就因为寄存器和地址总线位数的不对等,于是16位cpu的寻址方式是: 物理地址=段地址×16+偏移地址。这叫做实模式。但随着历史的演进,cpu已经支持32位(地址总线可以36位)、甚至64位(地址总线可以40位)。为了提升寻址能力和安全性,也为了兼容16位系统,在setup阶段就开始准备从16位实模式往32位保护模式做准备。

在32位保护模式下,段寄存器中存储是段选择子。根据段选择子的索引去查询全局描述符表以获得段描述符,其中存储着基地址物理地址=基地址+偏移地址

一旦开启了分段机制,还要多一步: 

 

程序员写代码时给出的地址叫逻辑地址,其中包含段选择子和偏移地址两部分。通过分段后得到的叫线性地址,线性地址通过分页后得到的叫物理地址。比如我们得到的线性地址是32位,分页机制大概是这样运作的:

线性地址被分为高 10 位、中间 10 位、后 12 位。高 10 位在页目录表中找到一个页目录项,这个页目录项的值加上中间 10 位拼接后的地址去页表中去寻找一个页表项,这个页表项的值,再加上后 12 位偏移地址,就是最终的物理地址。这个过程是由硬件MMU(内存管理单元)完成的。

现在我们继续上一节,将head程序跳转至setup_paging开启分页:

setup_paging:
    mov ecx,1024*5    ;用来计数
    xor eax,eax
    xor edi,edi
    pushf             ;保存所有标志, 这里主要为了DF(方向标志位)
    cld               ;让DF=0,用于串操作指令中。决定内存地址递增
    rep stosd         ;重复执行后面的指令stos。每次执行时从ecx-1,直到ecx=0则结束重复
    mov eax,_pg_dir   ;设置页目录中的项,仅需4个。_pg_dir为页表目录标号(0地址开始)
    mov [eax],pg0+7   ;pg0+7表示:0x00001007,是页目录表中的第1项
    mov [eax+4],pg1+7 
    mov [eax+8],pg2+7
    mov [eax+12],pg3+7
    mov edi,pg3+4092
    mov eax,00fff007h ;16Mb-4096+7 (r/w user,p)
    std
L3: stosd             ;将eax的内容复制到edi,复制4字节,并将edi加/减4个字节
    sub eax,00001000h
    jge L3
    popf
    xor eax,eax
    mov cr3,eax
    mov eax,cr0
    or  eax,80000000h
    mov cr0,eax
    ret

上述代码的意义就是在内存中一次写好页目录表和页表,之后开启cr0寄存器的分页开关。

首先按当前分页机制:

页目录项有10位,最多可以指示1K项的页表
页表项也有10位,最多可以指示1K项的页起始地址
线性地址低12位,最多可以指示4K偏移地址,4K为一个页。
分页后总共可访问的虚拟内存空间为:1024*1024*4K=4G。但当时Linux 0.11认为,总共可使用的内存不会超过16M。于是4(页表数)* 1024(页表项数) * 4KB(一页大小)= 16MB,即1个页目录表 + 4个页表就可搞定。这也就是ecx计数器设为1024*5的原因,意在通过rep stosd将5个页结构全部清0。

接着填写页目录中的项,共有4个页表只需设置4项(页目录和每个页表项本身也占据4K,每项4byte,一共可以设置1k个项/条目)。来看mov [eax],pg0+7
回顾下在实模式下,ds基地址,eax偏移地址所代表的内存单元:ds×16+eax
在保护模式下,ds段选择子,如0x10指向的是全局描述符表中的第二个段描述符(数据段描述符),里面内容中的段基址是 0。exa偏移地址所代表的内存单元:0+eax (这里+4/+8/+12是因为每一项4字节,32位)。
而页目录项结构与页表中项结构一样,"pg0+7"表示:0x00001007,是页目录表中的第1项。则
第1个页表所在的地址 = 0x00001007 & 0xfffff000 = 0x1000
第1个页表的属性标志 = 0x00001007 & 0x00000fff = 0x07,表示该页存在、用户可读写。
于是我们用此方法把所需4个页目录项的地址、属性给设置了。

接着填写页表(非页目录)中的项,共有:4(页表)*1024(项/页表)=4096 项(0 - 0xfff),也即能映射物理内存4096*4K = 16M。
每项内容是:当前项映射的实际物理内存地址+该页的标志。我们从最后一个页表的最后一项始按倒退填写。一个页表的最后一项在页表中的偏移地址是1023*4 = 4092(表项从0到1023,偏移地址=序号*4)。因此最后一页页表的最后一项的位置就是$pg3+4092。这里edi作为stosd的目的地址被写入,充当了地址变量的作用。然后作为16M内存的最后一页的物理地址是16Mb - 4096 = 0xfff000,加上属性标志7,即为0xfff007。这里eax作为stosd的写入内容,每次写入递减0x1000。

等上述内容全部写入完毕,设置页目录基址寄存器cr3的值,即在0x0000处。然后开启分页(cr0 的PG 标志,位31)。大功告成!大概整个内存效果图是这样:

接下来终于可以进入main.c了! 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值