内存换入-请求调页
用换入、换出实现“大内存”
我们设置虚拟内存大小位4G,但是实际内存只有1G,所以虚拟内存没办法完整的映射到实际内存中,那么如何才能让用户觉得虚拟内存就是实际的内存呢?换入换出可以实现这一点,当用户使用虚拟内存的(0G-1G)时,就将这一段的虚拟内存映射到物理内存上,当用户使用(3G-4G)处的虚拟内存时,就将虚拟内存(3G-4G)映射到物理内存上,这样就相当于4G的虚拟内存都可以使用了
请求的时候才映射
请求调页
当MMU进行重定位时,发现虚拟内存没有映射,就会发起中断,同时不让PC+1,然后从磁盘中查找对应的页,再从内存中申请一段内存,将磁盘中读到的页放入内存中,再建立映射,最后中断返回,MMU重新计算,然后重新执行中断时的那一条指令。
一个实际系统的请求调页
请求调页,当然从缺页中断开始
中断号 名称 说明
12 Segment not present 描述符所指的段不存在
14 Page fault 页不在内存
void trap_init(void)
{
set_trap_gate(14, &page_fault);
}
#define set_trap_gate(n,addr) \
_set_gate(&idt[n], 15, 0, addr);
//在linux/mm/page.s
.globl _page_fault
xchgl %eax,(%esp) //错误码esp被压入栈中
...
movl $0x10, %edx
...
movl %cr2, %edx //cr2寄存器里存放着页错误线性地址,将其赋给edx;
pushl %edx
pushl %eax
...
call _do_no_page //调用_do_no_page函数,参数就是eax、edx,保存在栈中
...
//在linux/mm/memory.c中
void do_no_page(unsigned long error_code, unsigned long address)
{
address &= 0xfffff000 //将后面三位去掉,得到页面地址
tmp = address-current->state_code; //页面对应的偏移
if(!current->executable || tmp>=current->end_data)
{
get_empty_page(address);
return;
}
page = get_free_page(); //申请内存空间
bread_page(page, current->executable->i_dev, nr); //从磁盘中读缺少的页到page中
put_page(page, address); //建立页表映射
}
//在linux/mm/memory.c中
unsigned long put_page(unsigned long page, //物理内存
unsigned long address)
{
unsigned long tmp, *page_table;
page_table = (unsigned long *)((address>>20)&ffc); //找到页目录项
if((*page_table)&1)
page_table = (unsigned long *)(0xfffff000&*page_table);
else
{
tmp = get_free_page();
*page_table = tmp|7;
page_table = (unsigned long *)tmp;
}
//根据页目录项找到页表项
page_table[(address>>12)&0x3ff] = page|7; //将页表项映射到新建的物理内存上
return page;
}
内存换出
虚拟内存的实现靠的是换入和换出,上面我们只是实现了换入,但是内存终究是有限的,必须从内存中取出一部分页来换出才能得到空闲的内存
page = get_free_page(); //申请内存空间
bread_page(page, current->executable->i_dev, nr); //从磁盘中读缺少的页到page中
FIFO页面置换
因为程序具有局部性和连续性,一般刚用到的页会多次被调用,所以FIFO可能导致一个页刚被置换出去,接下来又需要用到这个页,因此效率太差
MIN页面置换
MIN页面置换是最优方案,选最远将被使用的页淘汰,但是不可能预知将来会用到什么页,因此也没有办法能够实现MIN算法
LRU页面置换
LRU算法:选最近最长一段时间没有使用的页淘汰(因为程序具有局部性,一般最近使用的页未来也会使用,而长时间没有被使用的页未来一段时间也不会被使用)
LRU算法实现
LRU算法准确实现
使用时间戳
实现思路:每一个页维护一个时间戳,当被使用时,修改时间戳,换页时选最小的时间戳换出
该思路是可以实现LRU,但是代价太大,每执行一条指令就需要在页中修改时间戳,并且还要处理时间戳溢出的情况,在实际中不可行
页面栈
实现思路:维护一个栈,当一个页被用到时,将其换到栈顶,每次页面换出就淘汰栈底的页
该思路可行,但是同样的代价较大,每一次执行指令需要修改几次指针。
LRU算法近似实现 --将时间计数变为是和否
SCR(Second Chance Replacement)算法 | Clock Algorithm 算法
- 每个页加一个引用位R(reference bit)
- 选择淘汰页:扫描R,是1时清0,并继续扫描;是0时淘汰该页
因为只是修改一位,因此这一项工作可以由MMU来完成,速度非常快。
Clock算法的分析和改造
如果缺页很少(程序具有局部性),那所有的R都会变成1,这样这个算法就退化成FIFO算法了,因此需要定时清除R位,也就是定义一个定时清除R位指针,该扫描指针要比选择淘汰页的扫描指针速度快,这就是这个算法叫做Clock算法的原因。
进程页框分配
-
给进程分配多少页框(帧frame)
- 分配的多,请求调页导致的内存高效利用就没有意义了,而且内存也是有限的
- 分配的少
- 颠簸现象
因此,给进程分配的页框数量必须适中,不能太多也不能太少,还要限制进程的个数