内存使用与分段
内存使用:将程序放到内存中,PC指向开始地址


原本内存中顺序常驻系统 –> 进程1 –> 进程三
- 进程一 睡眠先入磁盘
- 进程二 进入内存
- 进程三 睡眠换出
- 进程一 换入
也就是 实际内存中调用的地址是 动态基地址 + 偏移地址
- 详情如下

编译后的内存基地址通过PCB进行记录,也就是说pcb在进程调度的时候,PCB内部存的基地址 ip 动态变化的

程序段结构

翻译:
- 各个程序段 : 如数据段、代码段、栈段等都是独立的存在的 ,在内存中分布成块状

如上,每一个区域的起始地址在自己的内存区域都看作起始地址

- LDT表中记录每个块中的初始地址
- GDT表中也存入LDT表中的地址
- 即可以通过GDT找到LDT表,进而确定指令在内存中的方位
内存分区与分页
首先明确一点,只要和进程有管的信息都放在PCB中,例如(LDT表)

- 三个适配规则


比如上述的 mov [0x2240],%eax
- 2240除以4K ,也就是左移3位(这里为16进制,所以为三位即可),计算得出页号
- 随后通过PCB中的页表指针找出页号对应的页框号,随后用页框号 × 12bit 得到实际的物理地址
多级页表与快表

如何找到地址:
根据 逻辑页号 找到 页框号


解释:
这里为两个进程算
每个进程都有自己的 页目录表 + 页表 这里引出两个进程,也就是4K + 4K = 8K,又因为两个进程,所以攻占用16K的内存空间
多级页表增加了访存的次数,尤其是64位系统

TLB -> 快表,是一个reg用来快速找到 页框号 ,确定物理页号,有些像cache,如果TLB中没有查到需要的页号,则还是去多级页表中查找,找到了就放入TLB 中,下次就无需去多级页表中查找
说白了就是用一个缓冲区作为 加速器

/************
有效访问时间 先是查TLB表(查表就要访存,所以加上一次TLB的访存时间),后面是没有命中的时候,先是TLB的访存时间MA,随后还要访问多级页表中的访存时间MA,所以是2MA
************/
注意后面:
TLB的是寄存器reg价格贵,但是TLB越大带来的效果就越强,命中率就会拉高,此时我们通过多级页表就会少,提高程序的效率!!!
需要 妥协 平衡 折中

段页结合的实际内存管理

先分段再分页,用户面向虚拟地址,虚拟地址翻译之后的对用户是透明的(看不见),计算机是对分页操作的

先是 逻辑地址 –> 虚拟地址 –> 通过页号和偏移找到的实际的页框号 –> 再找出物理地址
例子 : jmp 40
则对应物理地址就是1040, 即再最后的物理地址的时候 基地址才加上偏移的 40
一个实际的段、页式内存管理


- 首先是在 虚拟内存 中选出一段内存载入
- 建立虚拟地址内存的表
- 把刚刚载入的段进行分页
- 建立LDT表进行索引(实际的物理地址)

- copy_mem 分配虚存、建段表
- p : PCB 管理进程的数据结构
- ldt : 与pcb进程相关联
- nr : task数组的索引,即第几个进程
- new_data_base : 基址

每个进程需要又自己的页表和段表,页表可以不用切换,因为互不重叠
但是在实际的系统的是很有可能重叠的

这里是子进程的创建:
- 父进程 和 子进程 公用一片内存

上图主要得到目录项的编号
为什么要 >>20 而不是 >>22呢:
答案 : 先是>>22取得页目录号,但是一个页目录号是占 4byte ,所以还要乘4( <>20 而不是 >>22

给子进程分配内存,原先子进程的页表是空的,不进行分配,不占用内存资源,需要时在进行分配
get_free_page : 分配内存空间的函数,作用是找到内存中是 0 的一项(寻找内存中空白的 地方好用来分配)
- 此时页目录已经创建完毕

将子进程和父进程指向同一个页表(此时进程共享)
- *from_page_table :是父进程的页地址
- this_page : 用this_page给子进程的to_page_table赋值,且该变量能进行只读锁定
- mem_map : 自加,如果父进程结束,内存也不能释放,子进程还需要使用

上图易于理解

总结上图 :
-
进程中的
p = 0x300指的是虚拟内存地址,而物理内存的地址如0x00400300则是实际的存储地址。 -
fork()创建一个子进程。此时,子进程会复制父进程的虚拟地址空间,但不会立即复制物理内存中的数据。父子进程在
fork()后共享相同的物理内存地址(如图中0x00007300位置)。
写时复制机制:
- 当子进程或父进程试图修改共享内存中的数据时,操作系统会触发写时复制。
- 如图所示,当子进程修改
p指向的内存(将值7改为8)时,系统会先将共享的物理内存数据复制到新地址(如0x00008300),然后子进程修改这个新地址中的数据。 - 修改后的父子进程内存地址将不再共享,避免相互影响。
上半部分表示父进程的内存状态:*p = 7 表示 p 指向的内存地址中存储的值为 7,物理内存地址为 0x00007300。
fork() 后,下半部分是子进程的状态:子进程 p 指向的内存地址中值被修改为 8,此时写时复制发生,子进程获取了新的物理内存地址 0x00008300,父进程继续保持原地址不变。
内存换入-请求调页

请求的时候再映射!!! 说白了就是一段内存复用


- 是MMU硬件执行的,人为干预不了,但是人为可以干预内核中的操作
- 系统发现缺页,去中断表中查询缺页中断号,进入中断进行补页
- 找到磁盘中程序需要使用的页
- 载入到物理内存
- cpu重新从虚拟内存中读取指令,虚拟内存 从 物理内存中调用
- 以上完全就是一个中断处理函数,所以当cpu执行中断的时候,PC值在自动的情况下会 PC++ ,但是!!!这里系统会自动不让其PC + 1,还是从缺页的地址开始执行。
用户:卡了一下,但是并不知道系统的后台调用页

复习一下:
idt : 中断向量表,初始化idt形成向量中断对应号以及相对应的中断执行函数

- 左边:
- 先是一堆压栈进行现场的保护
- movl $0x10, %edx 进入内核栈的选择子
- 利用 cr2 来保存 页错误线性地址(虚拟地址)
- 右边
- pushl %eax 后面call _do_no_page ,这两句就是调用c语言,进行传入错误页的参数

address&=0xfffff000; 与操作得到页面地址(即后12位)
- bread_page: (block_read_page) 去磁盘上读
- 读取的数据给page(刚刚的缺页地方)
- executable 进程所对应的可执行文件
put_page : 建立物理页和虚拟页之间的映射

- 根据 address 找到 页目录项
- 根据 页目录项 找到页表项
- 根据 页表项 和 刚刚找到的 page内存 进行映射
内存换出
引言 :


ABCD 进程没有一个执行完毕,导致不停的打断(缺页)

方法规则是 : 预测下一个进程,换掉不在运行队列的进程,就是”最优”。
问题 : 怎么预测是个问题!

LRU的准确实现—时间戳

每一次都要查看时间戳,每次换页的时候都要改变数字,时间戳越来越大,很有可能发生溢出
LRU的准确实现—页码栈

CLOCK算法 — 丐版

CLOCK算法 — 豪华版

如上图:
问题所在:如果程序在莫一个时间段中缺页次数非常少,此时CLOCK中的R将会逐渐变为1,如果突然缺页,则需要转一圈(很慢的速度)才能进行缺页更换
有两个指针进行监测
- 用来清除R位,移动速度要快!
- 用来选择淘汰页,移动速度慢!

总结 :






