趣谈Linux操作系统学习笔记-内存管理(21讲)

分段机制的原理

分段机制下的虚拟地址由两部分组成,段选择子和段内偏移

分段机制下的虚拟地址由两部分组成,段选择子和段内偏移量

段描述符

段寄存器的值是通过段描述符填充的。

GDT(全局描述符表) LDT(局部描述符表)

当我们执行类似MOV DS, AX指令时,CPU会查表,根据AX的值来决定查找GDT还是LDT,查找表的什么位置,查出多少数据

段选择子:

段选择子是一个16位的段描述符,该描述符指向了定义该段的段描述符.

段选择子里面最重要的是段号,用作段表的索引。段表里面保存的是这个段 的基地址、段的界限和特权等级等

段权限检查

CPU分级

如何查看程序处于第几环?

CS寄存器的段选择子(共16bit)的后两个bit,表示的是CPL(Current Privilege Level):CPU当前特权等级
CS和SS中存储的段选择子后2个bit

DPL(Descriptor Privilege Level) 描述符特权等级

DPL存储在段描述符中,规定了访问该段所需要的特权级别是什么
举例说明:
如果AX指向的段DPL = 0 但当前程序的CPL = 3 这行指令是不会成功的

RPL(Request Privilege Level) 请求特权等级

举例说明:
mov ax, 0008 与 mov ax, 000B //段选择子
mov ds, ax //将段描述指向的是同一个段描述符,但RPL是不一样的

数据段的权限检查

参考如下代码:
比如当前程序处于0环, 也就是说CPL=0
mov ax, 000B //1011 RPL=3
mov ds, ax //ax指向的段描述符的DPL=0

数据段的权限检查:
CPL <= DPL 并且 RPL <= DPL(数值上的比较)

段偏移量

虚拟地址中的段内偏移量应该位于 0 和段界限之间。如果段内偏移量是合法的,就将段基地址加上段内偏移量得到物理内存地址

例如,我们将上面的虚拟空间分成以下 4 个段,用 0~3 来编号。每个段在段表中有一个项,在物理空间中,段的排列如下图的右边所示。

如果要访问段 2 中偏移量 600 的虚拟地址,我们可以计算出物理地址为,段 2 基地址 2000 + 偏移量 600 = 2600。

段表

1 #define GDT_ENTRY_INIT(flags, base, limit) { { { \
2         .a = ((limit) & 0xffff) | (((base) & 0xffff) << 16), \
3         .b = (((base) & 0xff0000) >> 16) | (((flags) & 0xf0ff) << 8) | \
4             ((limit) & 0xf0000) | ((base) & 0xff000000), \
5     } } }

一个段表项由段基地址 base、段界限 limit,还有一些标识符组成

 1 DEFINE_PER_CPU_PAGE_ALIGNED(struct gdt_page, gdt_page) = { .gdt = {
 2 #ifdef CONFIG_X86_64
 3     [GDT_ENTRY_KERNEL32_CS]     = GDT_ENTRY_INIT(0xc09b, 0, 0xfffff),
 4     [GDT_ENTRY_KERNEL_CS]       = GDT_ENTRY_INIT(0xa09b, 0, 0xfffff),
 5     [GDT_ENTRY_KERNEL_DS]       = GDT_ENTRY_INIT(0xc093, 0, 0xfffff),
 6     [GDT_ENTRY_DEFAULT_USER32_CS]   = GDT_ENTRY_INIT(0xc0fb, 0, 0xfffff),
 7     [GDT_ENTRY_DEFAULT_USER_DS] = GDT_ENTRY_INIT(0xc0f3, 0, 0xfffff),
 8     [GDT_ENTRY_DEFAULT_USER_CS] = GDT_ENTRY_INIT(0xa0fb, 0, 0xfffff),
 9 #else
10     [GDT_ENTRY_KERNEL_CS]       = GDT_ENTRY_INIT(0xc09a, 0, 0xfffff),
11     [GDT_ENTRY_KERNEL_DS]       = GDT_ENTRY_INIT(0xc092, 0, 0xfffff),
12     [GDT_ENTRY_DEFAULT_USER_CS] = GDT_ENTRY_INIT(0xc0fa, 0, 0xfffff),
13     [GDT_ENTRY_DEFAULT_USER_DS] = GDT_ENTRY_INIT(0xc0f2, 0, 0xfffff),
14 ......
15 #endif
16 } };
17 EXPORT_PER_CPU_SYMBOL_GPL(gdt_page);

总结

分页机制本质上来说就是类似于linux文件系统的目录管理一样,页目录项和页表项相当于根目录和上级目录,
页内便宜量就是相对路径,
绝对路径就是整个32位地址,分布式存储系统也是采用的类似的机制,先用元数据存储前面的路径,
再用块内偏移定位到具体文件,感觉道理都差不多

内存分页

对于物理内存,操作系统把它分成一块一块大小相同的页,这样更方便管理,例如有的内存页面长时间不用了,可以暂时写到硬盘上,称为换出。一旦需要的时候,再加载进来,叫作换入。这样可以扩大可用物理内存的大小,提高物理内存的利用率

 虚拟地址分为两部分,页号和页内偏移。页号作为页表的索引,页表包含物理页每页所在物理内存的基地址。这个基地址与页内偏移的组合就形成了物理内存地址

设计思路:

换入和换出都是以页为单位的。页面的大小一般为 4KB。为了能够定位和访问每个页,需要有个页表,保存每个页的起始地址,再加上在页内的偏移量,组成线性地址,就能对于内存中的每个位置进行访问了

页表的例子

 上图缺陷:

页表中所有页表项必须提前建好,并且要求是连续的。如果不连续,就没有办法通过虚拟地址里面的页号找到对应的页表项了

改进1:

上图理解:

上面图中,我们假设只给这个进程分配了一个数据页。如果只使用页表,也需要完整的 1M 个页表项共 4M 的内存,但是如果使用了页目录,页目录需要 1K 个全部分配,占用内存 4K,但是里面只有一项使用了。到了页表项,只需要分配能够管理那个数据页的页表项页就可以了,也就是说,最多 4K,这样内存就节省多了

分页机制本质上来说就是类似于linux文件系统的目录管理一样,页目录项和页表项相当于根目录和上级目录,
页内变量就是相对路径,
绝对路径就是整个32位地址,分布式存储系统也是采用的类似的机制,先用元数据存储前面的路径,
再用块内偏移定位到具体文件,感觉道理都差不多 

缺陷:

当然对于 64 位的系统,两级肯定不够了

改进2:

 变成了四级目录: 分别是全局页目录项 PGD(Page Global Directory)、上层页目录项 PUD(Page Upper Directory)、中间页目录项 PMD(Page Middle Directory)和页表项 PTE(Page Table Entry)

总结:

我们可以把内存管理系统精细化为下面三件事情:

第一,虚拟内存空间的管理,将虚拟内存分成大小相等的页;

第二,物理内存的管理,将物理内存分成大小相等的页;

第三,内存映射,将虚拟内存也和物理内存也映射起来,并且在内存紧张的时候可以换出到硬盘中。

分段机制下的虚拟地址由两部分组成,段选择子和段内偏移量

猜你喜欢

转载自www.cnblogs.com/mysky007/p/12315295.html