0 回顾
1 如何让内存用起来?
- 程序需要放到内存中,取址要从内存中去取,然后让程序执行起来,就相当于使用了内存
1.1 内存使用的直观想法
- 如何将程序放到内存中?
- 如何让程序执行起来?
- 解答:使用汇编指令放到内存中,能跑起来就能执行起来
- 这是一个C程序,那么C程序编译成汇编了,里面的标号偏移是从0开始的,例如_enrty是0,_main的偏移是40
- 这段程序被放入内存后,为了让他正常工作,直观想法就是按照偏移放到物理内存的对应位置,这段程序也就必须放到物理内存的0地址处
- 问题:0地址是随便用的吗? 一般0地址是操作系统用的,0地址一般不是空闲的;难道所有的程序加载进来都要放到0地址吗?那不就冲突了吗?
- 所以我们应该找一块空闲的内存放进去,例如放到1000的位置,但是这个时候地址就对不上了,怎么办?
- 重定位
综上即:把内存分为页,每次装入程序中的一个页,就在页表中加一项(建立页号+页内位置与地址的关系),然而页太多了的话页表太大查找的时候太费时间,所以把多个页分为一个段,再用一个段表(一样的逻辑)建立段号与页表位置的连接,这样每个页表就只有段大小/页大小那么大,查得更快。call 40里,就用段表和页表得到程序中40号位置所在的地址,跳到那里去执行
- 原来的这个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表