深入理解Linux内核-内存寻址

本文详细探讨了Linux内核的内存寻址,涵盖了硬件中的分段和分页机制。从逻辑地址、线性地址到物理地址的转换,通过段选择符、段寄存器、段描述符和分段单元的介绍,揭示了段机制的工作原理。同时,文章还讲解了Linux如何利用GDT和LDT管理分段,以及硬件层面的分页细节,包括常规分页、扩展分页、硬件高速缓存和转换后援缓冲器TLB。最后,讨论了Linux内核如何在实际操作中运用这些概念进行内存管理。

如今的微处理器包括的硬件线路使内存管理即高效又健壮,所以编程错误就不会对该程序之外的内存产生非法访问。

内存地址

内存地址分三类:

  1. 逻辑地址:我们程序员在程序里面使用到的地址就是逻辑地址。
  2. 线性地址:也称作虚拟地址,是逻辑地址通过分段单元后得到的地址。
  3. 物理地址:由线性地址通过分页单元后得到的地址,真正的物理地址了。

硬件中的分段

段选择符和段寄存器

一个逻辑地址由两部分组成:段标识符+段内偏移量。段标识符是一个16位长的字段,称为段选择符。段内偏移量是一个32位的字段。

为了快速方便的找到段选择符,处理器提供段寄存器:cs、ss、ds、es、fs和gs,其中前三个有专门的用途:

  1. cs:代码段寄存器
  2. ss:栈段寄存器
  3. ds:数据段寄存器

其中cs寄存器还有一个特殊用途:它包含了一个两位的字段,用来指出当前cpu的特权级,0为内核态,3为用户态。

段描述符

每一个段描述符有一个8字节的段描述符表示,他描述了段的特征。段描述符放在全局描述符表GDT或局部描述符表LDT中。

通常只定义一个GDT,每个进程除了存放在GDT中的段之外如果还需要定义段就存放在自己的LDT中。GDT的地址和大小存放在gdtr寄存器中,当前正在被使用的LDT存放在ldtr寄存器中。

快速访问段描述符

这里使用了一种附加的非编程的寄存器,当作快表使用,每次访问一个段只需要第一次访问,然后就把该段描述符存放在快表中,只要不改变段寄存器内容,就不需要访问内存中的段描述符了。

分段单元

当把一个逻辑地址转化为一个线性地址时,分段单元执行如下操作:

  1. 先检查段选择符的TI字段,判断该段描述符是在GDT还是在激活的LDT中。
  2. 从段选择符的index字段计算出段描述符的地址,index字段乘以8,然后加上gdtr或ldtr寄存器的内容即可。
  3. 把逻辑地址偏移量与段描述符base字段相加就可以得到线性地址。

如果有与段寄存器相关的不可编程寄存器,只有当段寄存器的内容改变时才需要执行前两步。

Linux中的分段

Linux更加喜欢使用分页,而不是分段,主要是因为:

  1. 当所有进程使用相同的段寄存器值时,内存管理变得更加简单,也就是说他们能共享同样的一组线性地址。
  2. Linux的设计目之一是把它移植到大多数的处理器平台上,然而,RISC体系结构对分段支持很有限。

Linux中关键的四个段分别是用户代码段、用户数据段、内核代码段和内核数据段,他们的base(基地址)都是0,大小都是4GB,因此Linux中的线性地址都是0-4GB。

Linux GDT

在单处理器系统中只有一个GDT,多处理器系统中每个CPU对应一个GDT。所有的GDT都存放在cpu_gdt_table数组中,而所有的GDT地址和大小都被存放在cpu_gdt_descr数组中。

每个GDT包含了18个段描述符和14个空的、未使用的或保留的项。

  1. 用户态和内核态下的代码段和数据段一共4个。
  2. 任务状态段TSS,每个处理器对应一个。每个TSS的线性地址都是内核数据段相应线形地址空间的一个小子集。所有的任务状态段都顺序的存放在init_tss数组中,第n个cpu的tss描述符的base字段指向init_tss的第n个元素,limit字段为0xeb,表示tss段的长度为236字节,dpl字段设置为0,只允许内核态访问!!!
  3. 其余的段省略。

Linux LDT

大多数情况下,用户态的linux程序不适用局部描述符表,这样内核就定义了一个缺省的LDT工大多数进程共享,缺省的局部描述符表存放在default_ldt数组中。

有些情况下,进程仍然需要创建自己的局部描述符表。modify_ldt()系统调用允许创建自己的局部描述符表。在需要使用局部描述符表的时候cpu的GDT中的LDT表项就被修改了,改为自己的局部描述符地址。

硬件中的分页

从80386开始,所有的8086处理器都支持分页,它通过设置cr0寄存器的PG标志启用。PG=0时,线性地址就直接被解释成物理地址。

常规分页

32为的线性地址被分为三个部分:Directory(目录)、Table(页表)、Offset(偏移量)。

正在使用的页目录的物理地址被存放在控制寄存器cr3中。

在这里插入图片描述

Directory和Table字段都是10位,因此页目录和页表都可以达到1024项。

页目录项和页表项都有同样的结构,包含如下字段:

  1. Present字段:如果是1,所指向的页表或页在主内存中,可以直接访问。如果是0,这一页不再内存中,那么分页单元会把线性地址存放在cr2寄存器中,并产生14号异常:缺页异常。
  2. Field字段:20位,指向的是物理页框编号。
  3. Accessed标志:每当分页单元对应的物理叶匡被访问就设置这个位,表示被访问过。
  4. Dirty标志:当对一个页框进行写操作时就设置这个位。
  5. Read/Write标志:含有页或页表的存取权限。
  6. User/Supervisor标志:含有访问页或页框的特权级。
  7. PCD和PWT标志:控制硬件高速缓存处理页或页框的方式。
  8. Page size标志:只用于页目录项,如果为1,页目录项指的是2MB或4MB的页框。
  9. Global标志:只用于页表项。

扩展分页

允许页框大小为4MB而不是4KB。通过cr4寄存器的PSE标志开启扩展分页机制。

在这里插入图片描述

硬件保护方案

页目录项和页表项中的User/Supervisor标志控制访问权限,该标志为0,只有内核态才能访问,为1,则都能访问。此外Read/Write标志控制读写权限,如果该标志为0,则为只读的,否则是可读写的。

硬件高速缓存

为了缩小cpu和ram之间的速度不匹配,引入了硬件高速缓存内存。硬件高速缓存基于局部性原理,用片上静态RAM(SRAM)来实现。在8086体系中引入了一个叫“行”的新单位。行由几十个连续的字节组成,他们以脉冲突发模式在慢速DRAM(主存)和快速的SRAM之间传送,用来实现高速缓存。主存中的任意一行都可以存放在高速缓存N行中的任意一行。

如图所示,硬件高速缓存包含了一个硬件高速缓存内存和一个高速缓存控制器。高速缓存内存(SRAM)中存放了真正的行,高速缓存控制器存放了一个表项数组,每个表项对应高速缓存内存中的一行,每个表项有一个标签(tag)和描述高速缓存行状态的几个标志,tag标签让高速缓存控制器能够辨别这个行当前映射的内存单元。

在这里插入图片描述

当访问一个物理地址时,cpu从物理地址中提取出子集的索引号,并把该子集所有的行的标签与物理内存的高几位对比,如果相同就命中,否则就没有命中。

当命中后,根据不同的存取类型,高速缓存控制器进行不同的操作。对于读操作,直接从高速缓存行中将数据送到CPU中;对于写操作,有两种策略,分别是“通写”和“回写”。在“通写”中,控制器即写RAM也写SRAM,“回写”只写SRAM,不改变RAM中的数据,更加高效,当然RAM中最终也必须被更新。当cpu执行一条刷新高速缓存表项的命令后,会更新到RAM中。

在多处理器中,每个处理器都有一个硬件高速缓存区,他们需要额外的硬件电路用于保持高速缓存中的数据同步。通常叫做“高速缓存侦听”。

处理器的cr0寄存器的CD标志位用来启用或禁止硬件高速缓存电路。

转换后援缓冲器TLB

用于加快线性地址的转换!!也叫做快表。每次访问后,物理地址被存放在一个TLB表项中,以便以后对同一个地址访问直接使用!

多处理器中,TLB不需要进行同步,这是因为运行在现有的CPU上的进程可以使用同一线性地址与不同的物理地址发生联系。当cr3寄存器被修改时(进程切换),硬件会自动使本地TLB无效!

Linux中的分页

Linux从2.6.11开始使用四级分页模型:

  1. 页全局目录(PGD)
  2. 页上级目录(PUD)
  3. 页中间目录(PMD)
  4. 页表(PTE)

在这里插入图片描述

对于32位的系统,Linux通过使“页上级目录”和“页中间目录”位全为0,从根本上取消了“页上级目录”和“页中间目录”字段。

物理内存布局

评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值