• 哈工大李治军老师操作系统笔记【18】:内存使用与分段(Learning OS Concepts By Coding Them !)


    0 回顾

    • 前面讲了操作系统怎么运行起来的
    • 到多进程图像

    1 如何让内存用起来?

    • 将程序放到内存中,PC指向开始地址

    • 程序需要放到内存中,取址要从内存中去取,然后让程序执行起来,就相当于使用了内存
      在这里插入图片描述

    1.1 内存使用的直观想法

    • 现在引出两个问题:
    1. 如何将程序放到内存中?
    2. 如何让程序执行起来?

    • 解答:使用汇编指令放到内存中,能跑起来就能执行起来
      在这里插入图片描述

    • 这是一个C程序,那么C程序编译成汇编了,里面的标号偏移是从0开始的,例如_enrty是0,_main的偏移是40
    • 这段程序被放入内存后,为了让他正常工作,直观想法就是按照偏移放到物理内存的对应位置,这段程序也就必须放到物理内存的0地址处
    • 问题:0地址是随便用的吗? 一般0地址是操作系统用的,0地址一般不是空闲的;难道所有的程序加载进来都要放到0地址吗?那不就冲突了吗?
    • 所以我们应该找一块空闲的内存放进去,例如放到1000的位置,但是这个时候地址就对不上了,怎么办?
    • 重定位

    综上即:把内存分为页,每次装入程序中的一个页,就在页表中加一项(建立页号+页内位置与地址的关系),然而页太多了的话页表太大查找的时候太费时间,所以把多个页分为一个段,再用一个段表(一样的逻辑)建立段号与页表位置的连接,这样每个页表就只有段大小/页大小那么大,查得更快。call 40里,就用段表和页表得到程序中40号位置所在的地址,跳到那里去执行

    1.2 重定位

    • 原先是相对地址,现在到物理内存当中要变成物理地址

    在这里插入图片描述


    • 原来的这个40相对地址(逻辑地址)要想放到内存当中,必须变成物理地址,所以40就必须修改
    • 重定位:修改程序中的地址(是相对地址)
    • 编译时重定位?(在编译的时候加上基址)
    • 一次编译后,内存地址就固定了,那么还是有可能存在冲突的问题
    • 编译时无法预测哪一段内存是空闲的
    • 除非是一些特殊的专用场合,目的就是要放到内存的固定位置
    • 例如,可能编译的时候确实是空闲地址,但是执行的时候,可能这地址已经被别人占了
    • 载入时重定位? 在载入的时候,将程序中的全部地址加上一个偏移,对应当前内存中的空闲空间
    • 载入只有一次,但是一些情况下,载入后的程序还可能需要移动在内存中的位置
    • 例如操作系统为了合理利用内存,程序载入内存后,如果进入阻塞或者睡眠,很可能会被交换到磁盘上,临时给别的程序腾出空间,再交换回内存后,不一定是原来的位置了
    • 载入时重定位的程序一旦载入内存就不能动了

    在这里插入图片描述


    • 所以说,重定位最合适的时机——运行时重定位
    • 在运行每条指令时才完成重定位,每执行一条指令都要从逻辑地址算出物理地址:地址翻译,也就是说,指令无论放到哪里都可以,根据这里的地址才能找到实际放到哪里
    • 不需要去修改程序中的地址,而是动态的去翻译这个地址为物理内存地址
    • 每个进程有各自的基地址,放在哪里?PCB,这个base会随着交换而被PCB进行记录,都是从PCB中找到这个基址,再加上逻辑地址,从而找到这个真实地址
    • 对应的有相应的基地址寄存器,专门用来存放基地址,硬件加速;在进程切换的时候,会将PCB中的基地址放到基地址寄存器中,那么之后这个进程执行的每条指令的寻址,都会加上这个基地址

    在这里插入图片描述


    综上,步骤就是,在内存中找一段空闲的内存,然后把这个空闲的地址(段地址/段基址)找到,假如是1000,把这个1000赋给PCB,创建进程了,自然有PCB了,现在PCB当中就有base了,每次执行的时候,都要进行地址翻译,比如说300这个逻辑地址取出来,再把PCB当中的1000(1000会变化的,比如说switch_to(),都会跟着变化并且在PCB当中进行记录下来)这个基址取出来,然后合成物理地址即可


    在这里插入图片描述


    • 是将整个程序一起载入内存中吗?引入分段的概念
    • 现在说的是物理地址,把物理地址的基地址放到Cr3寄存器,然后放到Tcb,经过mmu的映射才有了4g的线性地址,就是内存的分段分页

    1.3 分段


    在这里插入图片描述


    • 程序由若干部分(段)组成,每个段有各自的特点、用途,用户可独立考虑每个段怎么存放数据(分治)
      • 例如代码段是只读的,data段是可写的,这两种数据放在一起显然不合适
      • 代码段/数据段不会动态增长
      • 栈段应该符合栈的操作逻辑,而且可以动态增长
      • 函数库段可能并不需要一开始就载入
    • 每个段内的逻辑地址都是从0开始的
    • 引入段后的地址定位:<段号,段内偏移>

    在这里插入图片描述


    • 不是将整个程序,是将各段分别放入内存

      • 栈是可以动态增长的,如果空间不够了,那么栈段可以单独的再重新分配,而不需要给整个程序的数据重新分配
      • 寻址要用段地址+段内偏移
      • PCB中需要存放所有段的地址
      • 不同的段会用到不同的段基址寄存器,例如数据段是DS,指令段是CS
      • 下图中,PCB中有一个进程段表,保存了每个段的段号、基址、长度、读写标志
    • 课程刚开始讲引导的时候,提到的GDT表,就是操作系统这个进程的进程段表,而其他进程的进程段表是LDT表

    • 进程切换的时候需要切换LDT表,进程寻址是到LDT表中查出段基址

    • 疑问:LDT表是PCB的一部分吗?,会把 LDT 赋给 PCB


    • 综上,操作系统进程放在 GDT 表当中,每个进程有自己的 LDT 表,在进程切换的时候,要 LDT表进行切换,LDT 表一旦切换,在每次进行地址翻译的时候,根据数据段代码段的基址再找到 LDT 的段号,即可找到物理地址
      在这里插入图片描述

    在这里插入图片描述


    • 这样就是载入一个程序的示意图

    2 总结

    程序按照数据的不同用途分成多个段,每个段分别加载到内存的空闲区域,每个段的基址等信息存放在LDT表中,PCB保存了LDT,还是说PCB保存了LDT的指针?反正可以通过PCB找到该进程的LDT表,然后通过LDT表查出段基址。段基址+程序中的相对地址=物理内存地址。Idtr寄存器是做什么用的?存放段编号,是查找LDT表的索引之前讲进程切换的时候,提到的内存映射表,就是LDT表

  • 相关阅读:
    天翼云Web应用防火墙(边缘云版)通过首批可信认证
    Codeforces Round #818 (Div. 2)
    创建表
    总结ES11—ES13新特性——提升开发效率
    Springboot实现国际化以及部署Linux不生效问题
    安全+Linux!IBM新一代大型机Z14全新发布
    内边距(padding会影响盒子内边距大小)
    世界港口数据获取
    C++设计模式---代理模式
    C++递归lambda出现的循环初始值捕获问题分析与解决
  • 原文地址:https://blog.csdn.net/weixin_44673253/article/details/126951255