深入理解Linux内核 第二章笔记

来源:互联网 发布:淘宝商品分享到微信 编辑:程序博客网 时间:2024/05/24 01:42

深入理解Linux内核

  • 深入理解Linux内核
    • 第二章 内存寻址
      • 内存地址
      • 硬件中的分段
      • Linux中的分段
      • 硬件中的分页
      • Linux中的分页

第二章 内存寻址

内存地址

引用内存地址memory address是访问内存单元内容的一种方式。需要区分以下三种不同的地址:
- 逻辑地址 logical address
包含在机器语言指令中用来指定一个操作数或一条指令的地址。每一个逻辑地址都由一个段segment和偏移量offset或displacement组成,偏移量指明了从段开始的地方到实际地址之间的距离。
- 线性地址 linear address, virtual address
一个32位无符号整数,可以用来高达4GB的地址。通常使用十六进制表示,范围从0x00000000到0xffffffff。
- 物理地址 physical address
用于内存芯片级内存单元寻址。他们与从微处理器的地址引脚发送到内存总线上的电信号相对应。物理地址由32位或64位无符号整数表示。

内存管理单元MMU通过一种称为分段单元segmentation unit的硬件电路把一个逻辑地址转换为现行地址;接着,第二个称为分页单元paging unit的硬件电路把线性地址转换为一个物理地址。
逻辑地址–>[分段单元]–>线性地址–>[分页单元]–>物理地址

硬件中的分段

段选择符和段寄存器
一个逻辑地址由两部分组成:一个段标识符和一个指定段内相对地址的偏移量。段标识符是一个16位长的字段,称为段选择符segment selector,偏移量是一个32位长的字段。
为了快速方便地找到段选择符,处理器提供段寄存器,段寄存器的唯一目的是存放段选择符。这些段寄存器称为cs,ss,ds,es,fs和gs。程序可把一个段寄存器用于不同的目的,先将值保存到内存中,用完之后再恢复。
6个寄存器中3个有专门的用途:
- cs 代码段寄存器,指向包含程序指定的段。cs寄存器还有一个很重要的功能,它包含一个两位的字段,用以指明CPU的当前特权等级Current Privilege Level,CPL。0表示最高优先级,3表示最低优先级。Linux使用0和3来表示内核态和用户态。
- ss 栈段寄存器,指向包含当前程序栈的段。
- ds 数据段寄存器,指向包含静态数据或者全局数据段。

段描述符
每个段有一个8字节的段描述符segment descriptor表示,描述了段的特征。段描述符放在全局描述符表Global Descriptor Table,GDT或局部描述符表Local Descriptor Table,LDT中。
通常只定义给一个GDT,每个进程处理存放在GDT中的段之外,如果还需要创建附加的段,就可以有自己的LDT。GDT在主存中的地址和大小存放在gdtr控制寄存器中,当前正被使用的LDT地址和大小放在ldtr控制寄存器中。

快速访问段描述符
每当一个段选择符被装入段寄存器时,相应的段描述符就由内存装入到对应的非编程CPU寄存器。从那时起,转对那个段的逻辑地址转换就可以不访问GDT或LDT,处理器只需要直接引用存放段描述符的CPU寄存器即可。仅当段寄存器的内容改变时,才有必要访问GDT或LDT。

段选择符字段

字段名 描述 index 指定放在GDT或LDT中的相应段描述符的入口 TI Table Indicator,指明段描述符是在GDT中TI=0,或在LDT中TI=1 RPL 请求者特权级:当相应的端选择符装入到cs寄存器中时指示出CPU当前的特权等级

分段单元
逻辑地址转换成相应的线性地址,分段单元要执行如下操作:
- 先检查段选择符的TI字段,以决定段描述符保存在哪一个描述符表中。
- 从段选择符的index字段计算出段描述符的地址。
- 把逻辑地址的偏移量与段描述符Base字段的值相加就得到了线性地址。

Linux中的分段

Linux以非常有限的方式使用分段,实际上,分段和分页在某种程度上有点多余,因为他们都可以划分进程的物理地址空间:分段可以给每一个进程分配不同的线性地址空间,而分页可以把同一线性地址空间映射到不同的物理空间。Linux更喜欢使用分页的方式,因为
- 当所有进程使用相同的段寄存器值时,内存管理变得更简单,也就是说它们能共享同样的一组线性地址。
- Linux设计目标之一是可以把它移植到绝大多数流行的处理器平台上。然而RISC体系结构对分段的支持很有限。

硬件中的分页

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

常规分页
每4KB分为一页。

扩展分页
扩展分页用于把大段连续的线性地址转换成相应的物理地址,在这些情况下,内核可以不用中间页表进行地址转换,从而节省内存并保留TLB项。

硬件高速缓存
为了缩小CPU和RAM之间的速度不匹配,引入了硬件高速缓存内存hardware cache memory。硬件高速缓存基于局部性原理locality principle,该原理表明由于程序的循环结构及相关数组可以组织称线性数组,最近最常用的相邻地址在最近的将来又被用到的可能性极大。

高速缓存单元插在分页单元和主内存之间。它包含一个硬件高速缓存内存hardware cache memory和一个高速缓存控制器cache controller。高速缓存控制器存放一个表项数组,每个表项对应高速缓存内存中的一个行。每个表项有一个标签tag和描述高速缓存行状态的几个标识flag。这个标签由一些位组成,这些位让高速缓存控制器能够辨别由这个行当前所映射的内存单元。这种内存物理地址通常分为3组:最高几位对应标签,中间几位对应高速缓存控制器的子集索引,最低几位对应行内的偏移量。

当访问一个RAM存储单元时,CPU从物理地址中提取出子集的索引并把子集中所有行的标签与物理地址的高几位相比较。如果发现某一行的标签与这个物理地址的高位相同,则CPU命中一个高速缓存cache hit;否则高速缓存没有命中cache miss。

对于读操作,命中高速缓存后,控制器从高速缓存行中选择数据并送到CPU寄存器。对于写操作,控制器可采用以下两种基本策略之一,分别称为通写write-through和回写wirte-back。通写表明控制器即写RAM也写高速缓存,回写表示只更新高速缓存,而不改变RAM的内容。回写结束后,RAM最终必须被更新。只有当CPU执行一条要求刷新高速缓存的指令时,或者当一个FLUSH硬件信号产生时(通常在高速缓存不命中之后),高速缓存控制器才把高速缓存行写回到RAM中。

多处理器系统的每一个处理器都有一个单独的硬件高速缓存,因此它们需要额外的硬件电路用于保护高速缓存内容的同步。当一个CPU修改了它的硬件高速缓存,它就必须检查同样的数据是否包含在其他的硬件高速缓存中;如果是,它必须通知其他CPU用适当的值对其更新。常把这种活动叫做高速缓存侦听cache snooping。

转换后援缓冲器TLB
translation lookaside buffer用于加快线性地址的转换。当一个线性地址第一次使用时,通过慢速访问RAM中的页表计算出相应的物理地址。同时,物理地址被存放在一个TLB表项TLB entry中,以便以后对同一个线性地址的引用可以快速地得到转换。

在多处理器中,每个CPU都有自己的TLB。

Linux中的分页

Linux采用了一种同时适用于32位和64位系统的普通分页模型。从2.6.11版本开始,Linux采用了四级分页模型,四种页表分别为:
- 页全局目录 page global directory
- 页上级目录 page upper directory
- 页中间目录 page middle directory
- 页表 page table
页全局目录包含若干个页上级目录的地址,页上级目录包含若干个页中间目录的地址,页中间目录包含若干个页表的地址。每一个页表指向一个页框。

对于没有启用物理地址扩展的32位系统,两级页表已经足够。Linux通过使页上级目录和页中间目录位全为0,从根本上取消了页上级目录和页中间目录字段。

Linux的进程处理很大程度上依赖于分页。事实上,线性地址到物理地址的自动转换使下面的设计目标变得可行:
- 给每一个进程分配一块不同的物理地址空间,这确保了可以有效地防止寻址错误。
- 区别页(一组数据)和页框(即主存中的物理地址)之不同。这就允许存放在某个页框中的一个页,然后保存到磁盘上,以后重新装入这同一页时又可以被装在不同的页框中。这就是虚拟内存机制的基本要素。

物理内存布局
在初始化阶段,内核必须建立一个物理地址映射来指定哪些物理地址范围对内核可用,哪些不可用。

内核将下列页框记为保留reserved:
- 在不可用的物理地址范围内的页框。
- 含有内核代码和已初始化的数据结构的页框。
保留页框中的页绝不能被动态分配或交换到磁盘上。

一般来说,Linux内核被安装在RAM中从物理地址0x00100000开始的地方,为什么?
- 页框0由BIOS使用,存放上电自检power-on self-test POST期间检查到的系统硬件配置。
- 物理地址从0x000a0000到0x000fffff的范围通常留给BIOS例程。
- 第一个MB内的其他页框可能由特定计算机模型保留。

进程页表
进程的线性地址空间分为两部分:
- 从0x00000000到0xbfffffff的线性地址,无论进程运行在用户态还是内核态都可以寻址。
- 从0xc0000000到0xffffffff的线性地址,只有内核态的进程才能寻址。

内核页表
内核维持着一组自己使用的页表,驻留在所谓的主内核页全局目录master kernel page global directory中。系统初始化后,这组页表还未被任何进程或任何内核线程直接使用;更确切地说,主内核也全局目录的最高目录项部分作为参考模型,为系统中每个普通进程对应的页全局目录项提供参考模型。

内核如何初始化自己的页表?分为两个阶段:
- 内核创建一个有限的地址空间,包括内核的代码段和数据段、初始页表和用于存放动态数据结构的共128KB大小的空间。这个最小限度的地址空间仅够将内核装入RAM和对其他初始化的核心数据结构。
- 内核充分利用剩余的RAM并适当地建立分页表。

临时内核页表
临时页全局目录是在内核编译过程中静态地初始化的。

注:硬件高速缓存和TLB的关系与区别如下:
- TLB存放在主存中,硬件高速缓存则是处理器中一个组成部分
- 当CPU收到应用程序传递过来的虚拟地址后,先访问TLB,找到虚拟地址对应的物理地址,如果没找到,就需要访问通用页表来获取物理地址。然后访问硬件高速缓存,看是否有缓存该物理地址的数据,如果没有,就是没有命中。

原创粉丝点击