ULK3读书记录-CHAP2-内存管理

1. 逻辑地址 线性地址 物理地址

逻辑地址:和机器语言中出现的操作数,指令地址相关;

线性地址:0 - 2^32  4GB

物理地址:内存总线相关

                            分段单元                   分页单元

MMU: 逻辑地址 -----------> 线性地址 -----------> 物理地址

2. 硬件中的分段

a)Intel处理器有两种方式进行地址转换,保护模式和实模式(为了兼容与自举);
b)段选择符 段寄存器 段描述符 分段单元:

段选择符,段描述符,段描述符表之间的关系:逻辑地址分为两部分,16位的段标识符(也称段选择符)和32位的段内相对地址偏移量,而段选择符的15-3指定了段描述符的索引号,利用这个索引号在段描述表里能够找到需要的一项段描述符,每个段描述符8个字节,描述了段的特征。

段选择符和描述符的存储:(1)段选择符一般存储在段寄存器中,cs、ss、ds是程序指令段,栈段以及静态数据段的专用段寄存器,另外的三个段寄存器是通用的。(2)段描述符一般存储在段描述表里,段描述符表分为两类GDT和LDT,LDT存放用户进程的段描述符,GDT存放内核的代码段、数据段、堆栈段描述符以及每个用户进程的一个LDT索引,用于指向用户进程的当前LDT。一般GDT只有一个,存放在GDTR中,每个进程都可以有自己的进程描述符表,故LDT数量多,而LDTR只有一个,一般只是存放当前进程的当前LDT。

段选择符和描述符特殊字段含义:(1)段选择符低三位,分别是一位的T1和2位的RPL,T1表示指示器,指示当前索引的描述符表式GDT还是LDT,RPL表示请求者特权等级。(2)描述符的字段G,表示段中基本单元的粒度,G=0表示以字节为粒度,G=1表示以4k字节为粒度。字段limit表示,段的范围,20bit,当G=0,表示的段的范围在1B-1MB,G=1表示的段的范围在于4KB-4GB。典型的描述符类型有:代码段描述符、数据段描述符、任务状态段描述符(TSSD)、局部描述符表描述符(LDTD)。段描述符中的字段E表示扩展方向,分别指示地址向上扩展或者向下扩展,当表示向上扩展时,逻辑offset的范围就在0~Limit*粒度,当表示向下扩展时,offset的范围在Limit*粒度~max(offset),当检测到offset超出界限,则产生保护异常。

分段单元主要是将逻辑地址转为线性地址的功能单元,主要是利用上述表项来完成。段描述符中有base字段,base字段加上32bit的逻辑地址偏移就得到线性地址。此外,值得注意的是硬件上为了加速这一变换,引入了一种非编程的段描述符寄存器,用于存储当前段描述符,避免了同一段内每个线性地址计算都需要查找段选择器,段描述符表这一过程,从而缩短延时。

为了加速逻辑地址到线性地址的转换,80x86处理器提供一种附加的非编程寄存器,供6个可编程的段寄存器使用。每一个非编程的寄存器含有8个字节的段描述符,由相应的段寄存器中的段选择符来指定。每当一个段选择符被装入段寄存器时,相应的段描述符就由内存装入到对应的非编程寄存器。

3.Linux中的分段

a)http://blog.csdn.net/forsakening/article/details/9427939

b)Linux GDT:用户态 内核态代码段和数据段 TSS任务状态段

c)Linux LDT

4.硬件中的分页

a)页框:物理页,主存的一部分,一个存储区域;页:一个数据块,可以存放在任何页框或磁盘中;
b)把线性地址映射到物理地址的数据结构称为页表,页表存放在主存中,并在启用分页单元之前必须由内核对页表进行适当的初始化;
c)常规分页:4KB,3个域directory10 +table10 + offset12 
d)常规分页举例:
这个简单的例子将有助于阐明常规分页是如何工作的。我们假定内核已给一个正在运行的进程分配的线性地址空间范围是0x20000000 到 0x2003ffff(正如我们在后面章节所看到的那样,3GB 线性地址空间是一个上限,但是用户态进程只允许引用其中的一个子集)。这个空间正好由64页组成。我们不必关心包含这些页的页框的物理地址,事实上,其中的一些页甚至可能不在主存中。我们只关注页表项中剩余的字段。
让我们从分配给进程的线性地址的最高10 位(分页单元解释为Directory字段)开始。这两个地址都以2开头后面跟着0,因此高10 位有相同的值,即0x080 或十进制的128。因此,这两个地址的Directory 字段都指向进程页目录的第129项。相应的目录项中必须包含分配给该进程的页表的物理地址(见图2-9)。如果没有给这个进程分配其它的线性地址,则页目录的其余1023 项都填为0。
中间10 位的值(即Table 字段的值)范围从0 到0x03f,或十进制的从0 到63。因而只有页表的前64 个表项是有意义的,其余960 个表项都填0。
假设进程需要读线性地址0x20021406 中的字节。这个地址由分页单元按下面的方法处理:
1. Directory 字段的0x80 用于选择页目录的第0x80 目录项, 此目录项指向和该进程的页相关的页表。
2. Table 字段0x21 用于选择页表的第0x21 表项, 此表项指向包含所需页的页框。
3. 最后,Offet 字段0x406 用于在目标页框中读偏移量为0x406中的字节。
如果页表第0x21 表项的Present 标志为0,则此页就不在主存中;在这种情况下,分页单元在线性地址转换的同时产生一个缺页异常。无论何时,当进程试图访问限定在0x20000000 到0x2003ffff 范围之外的线性地址时,都将产生一个缺页异常,因为这些页表项都填充了0,尤其是它们的Present标志都被清0。
e)硬件高速缓存 TLB

5.Linux中的分页

a)Linux的分页模式(4级);
b)Linux的进程处理很大程度上依赖与分页,线性地址到物理地址的自动转换使得下面的设计目标变得可行:
        *给每一个进程分配一块不同的物理地址空间,这确保了可以有效的防止寻址错误;
        *区别页和页框之间不同,这就允许存放在某个页框中的一个页,然后保存到磁盘上,以后重新装入这同一页时又可以被装在不同的页框中,这就是虚拟内存机制的基本要素;
c) Linux内核代码之初始化内核临时页表:(http://linux.chinaitlab.com/kernel/821338.html

漫长而黑暗的史前时代终于到了setup。在setup汇编函数中,linux通过设置cr0寄存器的PE位(从实模式切换到保护模式)完成了史前文明到现代文明的转变。在setup时期,linux已经了解到世上可用的内存资源远远不止1MB。此时linux的欲望开始膨胀,最为满足它欲望的第一步,它开始抢占内存资源的前8MB。

初始化临时内核页表是在startup_32汇编语言函数中完成的。在ULK所述中,假设内核能容纳于RAM的前8MB空间,然后对RAM的前8MB进行恒等映射(例如用户地址0x00003000映射物理地址0x00003000,0xc0003000映射到物理地址0x00003000),来初始化临时页全局目录swapper_pg_dir和相应的页表。映射8MB只需要填充swapper_pg_dir中第0项,1项,768项和769项。前两项是给用户线性地址映射,后两项给内核线性地址映射。用页全局目录里的两项就能对8MB映射的理由是2×1024(页表有1024项)×4K(一页的大小)=8M。实际上初始化内核页表来对RAM的前8MB映射不是个硬性的规定。这取决于你的内核的配置(我认为大多数情况下是对8MB映射)。在startup_32中可以看到,对多少内存进行映射是通过pg0动态判断的。

  linux/arch/i386/kernel/head.S
  page_pde_offset = (__PAGE_OFFSET >> 20);
  /*__PAGE_OFFSET是0xc0000000,内核线性空间的起始地址。
  page_pde_offset=0xc00(十进制为3072)*/
  movl $(pg0 - __PAGE_OFFSET), %edi
  /*pg0的线性地址可以在/boot/System.map文件中找到。我的Ubuntu8.04机器上是0xc04f4000。
  减去0xc0000000就是pg0的物理地址(004f4000),放入edi中。*/
  movl $(swapper_pg_dir - __PAGE_OFFSET), %edx
  /*swapper_pg_dir的线性地址也可以在/boot/System.map文件中找到。我机器上是0xc047d000。
  减去0xc0000000就是swapper_pg_dir的物理地址(0047d000),放入edx中。*/
  movl $0x007, %eax    /* 0x007 = PRESENT+RW+USER */
  /*页目录项和页表项的低12位是标志位,把标志位0x007放入eax中。*/
  10:
  leal 0x007(%edi),%ecx    /* Create PDE entry */
  /*第一循环时把edi指向的pg0的物理地址加上0x007放入ecx中。
  第二次循环时把edi指向的物理地址0x4f5000加上0x007放入ecx中。*/
  movl %ecx,(%edx)    /* Store identity PDE entry */
  /*第一次循环时把ecx中的内容放入swapper_pg_dir的第0项里。
  第二次循环时把ecx中的内容放入swapper_pg_dir的第1项里。*/
  movl %ecx,page_pde_offset(%edx)   /* Store kernel PDE entry */
  /*第一次循环时把ecx中的内容放入swapper_pg_dir的第768项里。因为前面算出page_pde_offset的值为3072,而swapper_pg_dir中每项是4个字节,所以3072/4=768。
  第二次循环时把ecx中的内容放入swapper_pg_dir的第769项里。*/
  addl $4,%edx
  /*第一次循环时,此时edx指向swapper_pg_dir的第1项。
  第二次循环时,此时edx指向swapper_pg_dir的第2项。*/
  movl $1024, %ecx
  /*为初始化1024个页表项设置计数*/
  11:
  stosl
  /*把eax中的内容放入edi指向的物理地址中,然后edi+4。*/
  addl $0x1000,%eax
  loop 11b
  /*跳到上面的11处循环。
  第一次执行1024次后,从pg0物理地址(0x4f4000)开始存放的是0x007,0x1007,0x2007,...,0x3ff007,也就是当前能够映射到物理地址从0x000到0x3fffff处。此时edi中的值为0x4f5000。
  第二次执行1024次后,从物理地址(0x4f5000)开始存放的是0x400007,0x401007,0x402007,...,7ff007,也就是当前能够映射到物理地址0x000到7fffff处,正好8MB。此时edi中的值为0x4f6000。*/
  leal (INIT_MAP_BEYOND_END+0x007)(%edi),%ebp
  /*INIT_MAP_BEYOND_END的值为128k,在此文件中的一个宏定义。把edi指向的物理地址加上128k加上0x007放入edp中。*/
  cmpl %ebp,%eax
  /*在第一次循环中ebp中的值为0x515007,eax中的值为0x400007小于0x515007。当前所映射到的最大物理地址为0x3fffff没有包含0x515007,所以没有映射完。
  在第二次循环中ebp中的值为0x516007,eax中的值为0x800007大于0x516007。当前所映射到的最大物理地址为0x7fffff包含了0x516007,所以8MB物理地址映射完毕。*/
  jb 10b
  /*第一次循环做完时跳到上面的10处继续循环
  第二次循环做完时跳出循环。*/
  movl %edi,(init_pg_tables_end - __PAGE_OFFSET)
  /*最后把0x4f6000放入init_pg_tables_end 所表示的物理地址中。也在/boot/System.map中。*/
此时的linux胃口越来越大,8MB的资源已不能满足它的胃口了。它的黑手开始慢慢伸向896MB以下的内存了。

e)当RAM小于896MB是的最终内核页表;当RAM大小在896MB和4096MB之间时的最终内核页表

f)固定映射的线性地址


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值