32位操作系统意味着程序员可以使用任意一个32位地址。譬如定义一个指针char* p,可以令p=1000、100000,这里的定义的p是逻辑地址,还需要和段基址相加得到虚拟地址,32位逻辑地址意味虚拟内存空间为4GB大小。物理内存的空间大小不一定是4GB,但虚拟内存到物理内存的映射被操作系统给屏蔽了,方便用户的使用。
虚拟内存只有和物理内存建立映射关系后,即相当于将程序存放到物理内存中了,才可以被调用。但倘若虚拟内存为4GB,而物理内存只有1GB,虚拟内存中的内容是无法全部与物理内存建立映射关系的,那么这就引出了换入\换出的概念。
就像是一间杂货铺只有一个货架,而仓库里却存放着不止一货架的货物。那么当货架上的商品销量不好的时候,就可以把目前货架上的商品换出到仓库中去,而将要卖的仓库中的商品换入到货架上。
与此类似,上图中的虚拟内存相当于仓库,而物理内存相当于货架。当虚拟内存中的阴影部分要被访问的时候,将它映射到物理内存中;而当需要访问右边所示的虚拟内存阴影部分,而物理内存不能同时容纳这两部分的虚拟内存时,就需要将原先物理内存中建立的映射关系释放掉(换出),建立新的映射关系(换入)。
那换入又是在什么地方实现的了?回顾段页式内存机制下的地址转换过程:根据段寄存器中的段号从段表中查到基址为0x4000,与逻辑地址相加得到虚拟地址为0x4040,而根据虚拟地址到页表中查询页号时发现是缺页的,这就说明该页内容还没有与物理内存的页框建立映射关系,这里就需要进行换入。实际过程中,当MMU发现该页缺失后,就会向CPU发出调页申请,进入中断服务程序。这一过程中,要从磁盘中找到虚拟页,将虚拟页读取到空闲页框中,建立虚拟页和物理页框之间的映射关系,更新页表记录下这个映射关系 。
中断结束后,返回原来的程序继续执行。整个过程对于用户来说除了时间略长一些没有其他影响。
页面调入的具体实现代码从缺页中断处理程序开始:
void trap_init(void)
{
set_trap_gate(14, &page_fault);
}
14号中断就是缺页中断,14是由硬件决定的。page_fault的代码实现如下:
page_fault:
xchg1 %eax,(%esp)//将错误类型取出赋给EAX寄存器
movl %cr2, %edx//CR2中存放的是缺页时的虚拟地址
push %edx
push %eax
test1 $1, %eax
jne 1f
call do_no_page
jmp 2f
1: call do_wp_page
2: ......
iret
根据EAX中存放的错误类型决定是要调用do_no_page(对应缺页)还是do_wp_page(写一个只读页)。而换入的核心就是有函数do_no_page实现的。
void do_no_page(unsigned long error_code, unsigned long address)
{
address &= 0xfffff000;//计算虚拟页号
page = get_free_page();//获得空闲物理内存页框
bread_page(page, current->executable->i_dev, nr);//启动磁盘读写来读取虚拟页面的内容
put_page(page, address);//填写页目录项和页表项
}