分段机制的原理
分段机制下的虚拟地址由两部分组成,段选择子和段内偏移
量
分段机制下的虚拟地址由两部分组成,段选择子和段内偏移量
段描述符
段寄存器的值是通过段描述符填充的。
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)
总结:
我们可以把内存管理系统精细化为下面三件事情:
第一,虚拟内存空间的管理,将虚拟内存分成大小相等的页;
第二,物理内存的管理,将物理内存分成大小相等的页;
第三,内存映射,将虚拟内存也和物理内存也映射起来,并且在内存紧张的时候可以换出到硬盘中。