如今的微处理器包括的硬件线路使内存管理即高效又健壮,所以编程错误就不会对该程序之外的内存产生非法访问。
内存地址
内存地址分三类:
- 逻辑地址:我们程序员在程序里面使用到的地址就是逻辑地址。
- 线性地址:也称作虚拟地址,是逻辑地址通过分段单元后得到的地址。
- 物理地址:由线性地址通过分页单元后得到的地址,真正的物理地址了。
硬件中的分段
段选择符和段寄存器
一个逻辑地址由两部分组成:段标识符+段内偏移量。段标识符是一个16位长的字段,称为段选择符。段内偏移量是一个32位的字段。
为了快速方便的找到段选择符,处理器提供段寄存器:cs、ss、ds、es、fs和gs,其中前三个有专门的用途:
- cs:代码段寄存器
- ss:栈段寄存器
- ds:数据段寄存器
其中cs寄存器还有一个特殊用途:它包含了一个两位的字段,用来指出当前cpu的特权级,0为内核态,3为用户态。
段描述符
每一个段描述符有一个8字节的段描述符表示,他描述了段的特征。段描述符放在全局描述符表GDT或局部描述符表LDT中。
通常只定义一个GDT,每个进程除了存放在GDT中的段之外如果还需要定义段就存放在自己的LDT中。GDT的地址和大小存放在gdtr寄存器中,当前正在被使用的LDT存放在ldtr寄存器中。
快速访问段描述符
这里使用了一种附加的非编程的寄存器,当作快表使用,每次访问一个段只需要第一次访问,然后就把该段描述符存放在快表中,只要不改变段寄存器内容,就不需要访问内存中的段描述符了。
分段单元
当把一个逻辑地址转化为一个线性地址时,分段单元执行如下操作:
- 先检查段选择符的TI字段,判断该段描述符是在GDT还是在激活的LDT中。
- 从段选择符的index字段计算出段描述符的地址,index字段乘以8,然后加上gdtr或ldtr寄存器的内容即可。
- 把逻辑地址偏移量与段描述符base字段相加就可以得到线性地址。
如果有与段寄存器相关的不可编程寄存器,只有当段寄存器的内容改变时才需要执行前两步。
Linux中的分段
Linux更加喜欢使用分页,而不是分段,主要是因为:
- 当所有进程使用相同的段寄存器值时,内存管理变得更加简单,也就是说他们能共享同样的一组线性地址。
- 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个空的、未使用的或保留的项。
- 用户态和内核态下的代码段和数据段一共4个。
- 任务状态段TSS,每个处理器对应一个。每个TSS的线性地址都是内核数据段相应线形地址空间的一个小子集。所有的任务状态段都顺序的存放在init_tss数组中,第n个cpu的tss描述符的base字段指向init_tss的第n个元素,limit字段为0xeb,表示tss段的长度为236字节,dpl字段设置为0,只允许内核态访问!!!
- 其余的段省略。
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项。
页目录项和页表项都有同样的结构,包含如下字段:
- Present字段:如果是1,所指向的页表或页在主内存中,可以直接访问。如果是0,这一页不再内存中,那么分页单元会把线性地址存放在cr2寄存器中,并产生14号异常:缺页异常。
- Field字段:20位,指向的是物理页框编号。
- Accessed标志:每当分页单元对应的物理叶匡被访问就设置这个位,表示被访问过。
- Dirty标志:当对一个页框进行写操作时就设置这个位。
- Read/Write标志:含有页或页表的存取权限。
- User/Supervisor标志:含有访问页或页框的特权级。
- PCD和PWT标志:控制硬件高速缓存处理页或页框的方式。
- Page size标志:只用于页目录项,如果为1,页目录项指的是2MB或4MB的页框。
- 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开始使用四级分页模型:
- 页全局目录(PGD)
- 页上级目录(PUD)
- 页中间目录(PMD)
- 页表(PTE)

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

被折叠的 条评论
为什么被折叠?



