深入理解Linux内核(学习笔记)_第二章内存寻址

值得庆幸的是,操作系统自身不必完全了解物理内存,如今的微处理器包括的硬件线路使内存管理既高效又健壮,所以编程错误就不会对该程序之外的内存产生非法访问。本章将详述80x86微处理器怎么进行芯片级的内存寻址。

一.内存寻址

程序员偶尔会引用内存地址作为访问内存单元内容的一种方式,但是,当使用80x86微处理器时,必须区分以下三种不同的地址:

  • 逻辑地址:包含在机器语言指令中用来指定一个操作数或一条指令的地址。每个逻辑地址都由一个段和偏移量组成,偏移量指明了从段开始的地方到实际地址之间的距离。
  • 线性地址(虚拟地址):是一个32位无符号整数,可以用来表达高达4GB的地址,线性地址通常用16位进制数字表示,值的范围从0x00000000到0xffffffff。
  • 物理内存:用于内存芯片级内存单元寻址。它们与从微处理器的地址引脚发送到内存总线上的电信号相对应。物理内存由32位或36位无符号整数表示。

      内存控制单元(MMU)通过一种称为分段单元的硬件电路把逻辑地址转换成线性地址,接着,第二个称为分页单元的硬件电路把线性地址转换成一个物理地址

二.硬件中的分段

      Intel微处理器以两种不同的方式执行地址转换,这两种方式分别称为实模式和保护模式。实模式存在的主要原因是要维持处理器与早期模型兼容,并让操作系统自举。

  • 段选择符和段寄存器:一个逻辑地址由两部分组成:一个段标识符和一个指定段内相对地址的偏移量。为了快速方便地找到段选择符,处理器提供段寄存器,段寄存器的唯一目的是存放段选择符。这些段寄存器称为cs,ss,ds,es,fs和gs。尽管只有6个段寄存器,但程序可以把同一个段寄存器用于不同的目的。6个寄存器中3个有专门的用途:(1)cs代码段寄存器,指向包含程序指令的段;(2)ss栈段寄存器,指向包含当前程序栈的段;(3)ds数据段寄存器,指向包含静态数据或者全局数据段;其它3个段寄存器作一般用途,可以指向任意的数据段。cs寄存器还有一个很重要的功能:包含有一个两位的字段,用以指明CPU的当前特权级(0最高,3最低)。
  • 段描述符:每个段由一个8字节描述符表示,它描述了段的特征。段描述符放在全局描述符(GDT)或局部描述符表(LDT)中。通常只定义一个GDT,而每个进程除了存放在GDT中的段之外如果还需要创建附加的段,就可以有自己的LDT。GDT在主存中的地址和大小存放在gdtr控制寄存器,当前正被使用的LDT地址和大小放在ldtr控制寄存器中。有几种不同类型的段以及和它们对应的段描述符,几种Linux广泛采用的类型:代码段描述符,数据段描述符,任务状态段描述符(TSSD),局部描述表描述符(LDTD)。
  • 快速访问段描述符:为了加速逻辑地址到线性地址的转换,80x86处理器提供一种附加的非编程的寄存器供6个可编程的段寄存器使用。
  • 分段单元:(1)先检查段选择符的TI字段,以决定段描述符保存在哪一个描述符中;(2)从段选择符的index字段计算段描述符的地址,index字段的值乘以8,这个结果与gdtr或ldtr寄存器中的内容相加;(3)把逻辑地址的偏移量与段描述符Base字段的值相加就得到线性地址。

三.Linux中的分段

       分段可以给每一个进程分配不同的线性地址空间,而分页可以把同一线性地址空间映射到不同的物理空间。与分段相比,Linux更喜欢使用分页方式,因为:(1)当所有进程使用相同的段寄存器时,内存管理变得更简单,也就是说它们能共享同样的一组线性地址;(2)Linux设计目标之一是可以把它移植到绝大多数流行的处理器平台上,然而,RISC体系结构对分段的支持很有限。运行在用户态的所有Linux进程都使用一对相同的段来对指令和数据寻址,这两个段就是所谓的用户代码段和用户数据段。

  • Linux GDT:每一个GDT中包含的18个段描述符指向下列的段:(1)用户态和内核态下的代码段和数据段共4个;(2)任务状态段(TSS),每个处理器有1个,每个TSS相应的线性地址空间都是内核数据段相对应线性地址空间的一个小子集;(3)1个包括缺省局部描述表的段,这个段通常是被所有的进程共享的段;(4)3个局部线程存储(TLS)段:这种机制允许多线程应用程序使用最多3个局部于线程的数据段,系统调用set_thread_area()和get_thread_area()分别为正在执行的进程创建和撤销一个TLS段;(5)与高级电源管理(AMP)相关的字段:由于BIOS代码段,所以当Linux APM驱动程序调用BIOS函数来获取或者设置APM设备的状态时,就可以使用自定义的代码段和数据段;(6)与支持即插用(PnP)功能的BIOS服务程序相关的5个段;(7)被内核用来处理“双重错误”异常的特殊的TSS段。
  • Linux LDT:缺省的局部描述符表存放在default_ldt数组中。在某些情况下,进程仍然需要创建自己的局部描述符表,modify_ldt()系统调用允许进程修改自己的局部描述符表。

四.硬件中的分页

       分页单元把线性地址转换为物理地址。其中一个关键任务是把所请求的访问类型与线性地址的访问权限相比较,如果这次访问是无效的,就产生一个缺页异常。为了效率起见,线性地址被分成以固定长度的组,称为(page)。页内部连续的线性地址被映射到连续的物理地址中。这样可以指定一个页的物理地址和其存取权限,而不用指定页所包含的全部线性地址的存取权限。分页单元把所有的RAM分成固定长度的页框(物理页)。把线性地址映射在物理内存地址的数据结构称为页表。页表存放在主存中,并在启动分页单元之前必须由内核对页表进行适当的初始化。

  • 常规分页:32位线性地址被分为个域(目录、页表、偏移量),线性地址的转换分两步完成,每一步都基于一种转换表,第一种转换表称为页目录表,第二种转换表称为页表。使用这种二级模式的目的在于减少每个进程页表所需RAM的数量。二级模式通过只为进程实际使用的那些虚拟内存区请求页表来减少内存使用量。页目录项和页表项有同样的结构,每项都包含下面的字段(present标志、Field包含页框物理地址最高20位的字段、Accessed标志、Dirty标志、Read/Write标志、User/Supervisor标志、PCD和PWT标志、Page Size标志、Global标志)。
  • 扩展分页:扩展分页用于把大段连续的线性地址转换成相应的物理地址,在这些情况下,内核可以不用中间页进行地址转换,从而节省内存并保留TLB项(转换后援缓冲器)。通过设置页目录项的Page size标志启用扩展分页功能。在这种情况下,分页单元把32位线性地址分成两个字段(Directory最高10位、Offset其余22位)。扩展分页和正常分页的页目录项基本相同,除了:(1)Page SIze标志位必须被设置;(2)20位物理地址字段只有最高10位是有意义的,这是因为每个物理地址都是在以4M为边界的地方开始的,故这个地址的最低22位为0。
  • 硬件保护方案:分页单元和分段单元的保护方案不同。与段的3种存取权限(读、写、执行)不同的是,页的存取权限只有两种(读、写)。如果页目录项或页表项的Read/Write标志等于0,说明相应的页表或页是只读的,否则是可读写的。
  • 物理地址扩展(PAE)分页机制:处理器所支持的RAM容量受连接到地址总线上的地址管脚数限制。通过将管脚数增加至36已经满足这些需求2^36=64GB,不过需要把32位线性地址转换为36位物理地址才能使用所增加的物理地址。Intel为了支持PAE已经改变了分页机制:(1)64GB的RAM被分为2^24个页框,页表项的物理地址字段从20位扩展到了24位;(2)引入一个叫作页目录指针表的页表新级别,它由4个64位表项组成;(3)cr3控制寄存器包含一个27位的页目录指针表(PDPT)基地址字段;(4)当把线性地址映射到4KB的页时(页目录项中的PS标志清0),32位线性地址按下列方式解释:cr3指向一个PDPT、位31-30指向PDPT中4个项的一个、位29-21指向页目录中的512个项中的一个、位20-12指向页表中512项中的一个、位11-0 4KB页中的偏移量;(5)当把线性地址映射到2MB的页时(页目录项中的PS标志置为1),32位线性地址按下列方式解释:cr3指向一个PDPT、位31-30指向PDPT中4个项的一个、位29-21指向页目录中的512个项中的一个、位20-0 2MB页中的偏移量。
  • 64位系统中的分页:所有64位处理器的硬件分页系统都使用了额外的分页级别。使用的级别数量取决于处理器的类型。
  • 硬件高速缓存:为了缩小CPU和RAM之间的速度不匹配,引入了硬件高速缓存内存。硬件高速缓存基于著名的局部性原理,该原理既适用程序结构也适用于数据结构。80x86体系结构中引入了一个叫行的新单元,行由几十个连续的字节组成,它们以脉冲突发模式在慢速DRAM和快速的用来实现高速缓存的片上静态RAM(SRAM)之间传送,用来实现高速缓存。高速缓存再被细分为行的子集。在一种极端的情况下,高速缓存可以是直接映射的,这时主存中的一个行总是存放在高速缓存中完全相同的位置。在另一种极端情况下,高速缓存是充分关联的,这意味着主存中的任意一个行可以存放在高速缓存中的任意位置。但大多数高速缓存再某种程度上是N-路组关联的,意味着主存中的任意一个行可以存放在高速缓存N行中的任意一行中。高速缓存单元插在分页单元和主内存之间,它包含一个硬件高速缓存内存和一个高速缓存控制器。高速缓存内存存放内存中真正的行。高速缓存控制器存放一个表项数组,每个表项对应高速缓存内存中的一个行。这种内存物理地址通常分为3组:最高几位对应标签,中间几位对应高速缓存控制器的子集索引,最低几位对应行内的偏移量。多处理器系统的每个处理器都有一个单独的硬件高速缓存,因此它们需要额外的硬件电路用于保持高速缓存内容同步。
  • 转换后援缓存器(TLB):80x86处理器还包含了另一个称为转换后援缓存器或TLB的高速缓存用于加快线性地址的转换。当一个线性地址被第一次使用时,通过慢速访问RAM中的页表计算出相应的物理地址,同时物理地址被存放在一个TLB表项中,以便以后对同一线性地址的引用可以快速地得到转换。

五.Linux中的分页

Linux采用了一种同时适用于32位和64位系统的普通分页模型。从2.6.11版本开始采用四级分页模型:页全局目录、页上级目录、页中间目录、页表。Linux的进程处理很大程度上依赖于分页。事实上,线性地址到物理地址的自动转换使下面的设计目标变得可行:(1)给每个进程分配不同一块不同的物理地址空间,这确保了可以有效地防止寻址错误;(2)区别页(即一组数据)和页框(即主存中的物理地址)之不同,这就允许存放在某个页框中的一个页,然后保存到磁盘上,以后重新装入这同一页时又可以被装在不同的页框中,这就是虚拟内存机制的基本要素。

  • 线性地址段:PAGE_SHIFT、PMD_SHIFT、PUD_SHIFT、PGDIR_SHIFT等。
  • 页表处理:pte_t,pmd_t,pud_t和pgd_t分别描述页表项,页中间目录项、页上级目录和页全局目录项的格式。
  • 物理内存布局:内核将下列页框记为保留(在不可用的物理地址范围内的页框、含有内核代码和已初始化的数据结构的页框),保留页框中的页绝不能被动态分配或交换到磁盘上。一般来说,Linux内核安装在RAM中从地址0x00100000开始的地方,也就是说从第二个MB开始,为什么内核没有安装在RAM第一个MB开始的地方?因为PC体系结构有几个独特的地方必须考虑。
  • 进程页表:进程的线性地址空间分成两部分:(1)从0x00000000到0xbfffffff的线性地址,无论进程运行到用户态还是内核态都可以寻址;(2)从0xc0000000到0xffffffff的线性地址,只有内核态的进程才能寻址。但是在某些情况下,内核为了检索或存放数据必须访问用户态线性地址空间。
  • 内核页表:内核维持着一组自己使用的页表,驻留在所谓的主页内核全局目录中。第一阶段内核创建一个有限的地址空间,包括内核的代码段和数据段、初始化页表和用于存放动态数据结构的共128KB大小的空间。第二阶段内核充分利用剩余的RAM并适当地建立分页表。具体实施:临时内核页表、当RAM小于896MB时的最终内核页表、当RAM大小在896MB和4096MB之间时的最终内核页表、当RAM大于4096MB时的最终内核页表。
  • 固定映射的线性地址:基本上是一种类似于0xffffc000这样的常量线性地址。概念上类似于对RAM前896MB映射的线性地址。
  • 处理硬件高速缓存和TLB:内存寻址的最后一个主题是关于内核如何使用硬件高速缓存来达到最佳效果。硬件高速缓存和转换后援缓存器(TLB)在提高现代计算机体系结构的性能上扮演着重要角色。为了使高速缓存的命中率达到最优化,内核在下列决策中考虑体系结构:(1)一个数据结构中最常使用的字段放在该数据结构内的低偏移部分,以便它们能够处于高速缓存的同一行中;(2)当为一大组数据结构分配空间时,内核试图把它们都存放在内存中,以便所有高速缓存行按同一方式使用。一般来说,任何进程切换都会暗示着更换活动页表集,相对于过期页表,本地TLB表项必须被刷新,不过在下列情况下将避免TLB被刷新:(1)当两个使用相同页表的普通进程之间执行进程切换时;(2)当在一个普通进程和一个内核线程间执行进程切换时。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值