提要:系列文章主要参考MIT 6.828课程
以及两本书籍《深入理解Linux内核》``《深入Linux内核架构》
对Linux内核内容进行总结。
内存管理的实现覆盖了多个领域:
- 内存中的物理内存页的管理
- 分配戴爱内存的伙伴系统
- 分配较小内存的slab、slub、slob分配器
- 分配非连续内存块的vmalloc分配器
- 进程的地址空间
传统的内存管理主要包括段式存储
、页式存储
、段页式存储
,这里我们会以这部分开始,逐步介绍Linux内核中的内存管理,而要学习内存管理,首先需要了解内存寻址。所以本节内容主要讲解内存寻址的相关知识,并介绍Linux内核中的段、页式存储。
内存地址
在编程过程中,难免需要通过内存地址来访问内存中的某些内容,那么这个过程中地址是如何映射到对应的物理单元的呢?解决这一问题首先要区分三种不同的地址:
- 逻辑地址:逻辑地址是包含在机器语言指令中用来指定一个操作数或者一条指令的地址。
每一个逻辑地址都由一个段和一个偏移量组成
,偏移量指明了从段开始的地方到实际地址之间的距离。(这在分段结构
中表现的极为明显)。 - 线性地址:线性地址是一个32位的无符号数,可以用来表示高达4GB的地址。线性地址通常使用十六进制数字表示,值的范围从0x00000000到0xffffffff(常用于页式存储)。
- 物理地址:
用于内存芯片级内存单元寻址
。它们与微处理器的地址引脚发送到内存总线上的电信号相对应。物理地址由32位或36位无符号整数表示。
内存控制单元(MMU)通过分段单元(硬件设备)将逻辑地址转化为线性地址。使用分页单元(硬件设备)把线性地址转化为物理地址:
-------- -------
|逻辑地址| --> |分段单元| --> |线性地址| --> |分页单元| --> |物理地址|
-------- -------
从格式上简单区别逻辑地址与线性地址,逻辑地址包含了两部分:段和偏移量(注意这两者是分开的
),而线性地址知识一个32位无符号数,虽然后期也会根据地址的位数再次进行划分(详见分页部分),但终归只是一个线性的无符号数。
段式存储
硬件将逻辑地址转化为线性地址主要由分段单元完成该任务。逻辑地址由两部分组成:
- 段标识符:该字段是一个16位长的字段,称为
段选择符
,负责从众多段中选择出正确的段
,因为段信息会存储在一张段描述符表中,因此需要通过段选择符从段描述符表中索引找到正确的段描述符
。 - 指定段内相对地址的偏移量:32位长的字段(因为一个段可能很长,因此偏移量要足够大)
段选择符格式如下:
15 3 2 1 0
-------- ---- ---
段选择符 | index | TL | RPL|
-------- ---- ---
对于每个字段的含义,后续在详细讲解通过段选择符寻找对应段
时会对每个字段给出解释。
段选择符被存放在段寄存器中,这使得可以方便快速地找到段选择符,段寄存器主要包括6个,分别为cs,ss,ds,es,fs,gs。其中有3个具有专门的用途:
段寄存器 | 描述 |
---|---|
cs | 代码段寄存器,指向包含指令序列的段 |
ss | 栈段寄存器,指向包含当前程序栈的段 |
ds | 数据段寄存器,指向包含静态数据或者全局数据段 |
其他3个段寄存器作一般用途,可以指向任意的数据段。
注意:cs寄存器还有一个很重要的功能:它含有一个两位的字段,用以指明CPU的当前特权级(GPL)。值为0代表最高优先级,而值为3代表最低优先级。Linux只用0级和3级,分别称为内核态和用户态。
段描述符
刚才提到过,每个段由一个8字节的段描述符表示
,它描述了段的特征。段描述符放在全局描述符表(GDT)
或者局部描述符表里(LDT)
中(前面提到过)。通常只定义一个GDT
,而每个进程除了存放在GDT中的段之外如果还需要创建附加段
,就会创建自己的LDT。GDT
在主存中的地址和大小存放在gdtr控制寄存器
中,当前正被使用的LDT地址和大小存放在ldtr寄存器
中。如下给出一个全局段描述符例子:
在段描述符表中通常会使用如下几种段描述符(简单了解各个段的作用即下面第一个表就好,具体字段的名称可以用到再回来查):
描述符名称 | 描述 |
---|---|
代码段描述符 | 这个段描述符代表一个代码段,它可以放在GDT或LDT中。该描述符置S标志位1(非系统段) |
数据段描述符 | 这个段描述符代表一个数据段,它可以放在GDT或LDT中。该描述符置S标志为1。栈段是通过一般的数据段实现的。 |
任务状态段描述符(TSSD) | 这个段描述符代表一个任务状态段(Task State Segment, TSS),也就是说这个段用于保存处理器寄存器的内容。它只能出现在GDT中。根据相应的进程是否正在CPU上运行,其Type字段的值分别为11或9。这个描述符的S标志置为0。 |
局部描述符表描述符(LDTD) | 这个段描述符代表一个包含LDT的段,它只出现在GDT中。相应的Type字段的值为2,s标志置为0 |
段描述符格式如下:
段描述符中各个字段含义如下:
字段表 | 描述 |
---|---|
Base | 包含段的首字节的线性地址 |
G | 粒度标志:如果该位清0,则段大小以字节为单位,否则以4096的倍数计 |
Limit | 存放段中最后一个内存单元的偏移量,从而决定段的长度。如果G被置为0,则一个段的大小在1个字节到1MB之间变化;否则,则在4KB到4GB之间变化。 |
S | 系统标志:如果它被清0,则这是一个系统段,存储诸如LDT这种关键的数据结构,否则它是一个普通的代码段或者数据段。 |
Type | 描述了段的类型特征和他的存取权限 |
DPL | 描述符特权级(Descriptor Privilege Level)字段:用于限制对这个段的存取。它表示为访问这个段而要求的CPU最小的优先级。因此,DPL设为0的段只能当CPL为0时(即在内核态)才是可以访问的,而DPL设为3的段对任何CPL值都是可以访问的。 |
P | Segment-Present标志:等于0表示段当前不在主存中。Linux总是把这个标志(第47位)设为1,因为它从来不把整个段交换到磁盘上去。 |
D或B | 成为D或B的标志,取决于是代码段还是数据段。D或B的含义在两种情况下稍微有区别,但是如果段偏移量的地址是32位长,就基本上把它置为1,如果这个偏移量是16位长,它被清0。 |
AVL标志 | 可以由操作系统使用,但是被Linux忽略 |
快速访问段描述符
在本节主要介绍分段单元将逻辑地址转化为线性地址的过程。我们知道逻辑地址主要包括:16位的段选择符和32位的段偏移量,段选择符存放在段寄存器中。段选择符格式如下:
15 3 2 1 0
-------- ---- ---
段选择符 | index | TL | RPL|
-------- ---- ---
这里我们需要了解3个字段的含义:
字段名 | 描述 |
---|---|
index | 指定了放在GDT或者LDT中相应的段描述符的入口 |
TI | TI(Table Indicator)标志:指明段描述符是在GDT中(TI = 0)或在LDT中(TI=1) |
RPL | 请求者特权级:当相应的段选择符装入到cs寄存器中时指示出CPU当前的特权级;它还可以用于在访问数据段时有选择地削弱处理器的特权级。 |
由于一个段描述符是8个字节长,因此它在GDT或LDT内的相对地址是由段选择符的最高13位的值乘以8得到的(8=2^3,13+3=16)。例如如果GDT在0x00020000(这个值保存在gdtr寄存器中),切由段选择符所指定的索引号为2,那么相应的段描述符地址为0x00020000+(2*8)
即0x00020010
。
逻辑地址转换规则如下图:
标签:GDT,寄存器,描述符,地址,选择符,内存,Linux,内核 From: https://www.cnblogs.com/yanlishao/p/17613495.html