• 趣谈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)

    总结:

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

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

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

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

    分段机制下的虚拟地址由两部分组成,段选择子和段内偏移量
  • 相关阅读:
    在C#程序设计中使用Win32类库
    Quartz.Net学习笔记(一)
    XDView网络视频监控
    Quartz.Net学习笔记(二) Jobs And Triggers
    关于web.config中<customErrors>节点说明
    业余水准,给朋友设计的LOGO
    JS 获取浏览器、显示器 窗体等宽度和高度【转载】
    Quartz.Net学习笔记(三) Jobs And Triggers再深入
    自己asp.net项目错误处理机制
    asp.net错误处理机制
  • 原文地址:https://www.cnblogs.com/mysky007/p/12315295.html
Copyright © 2020-2023  润新知