目录
1.如何将段和页结合在一起
2.段页结合时进程对内存的使用
在对内存进行使用的过程中,用户更希望程序以段的形式被放入内存,这样符合用户的使用习惯,譬如找内存中“代码段的第40条指令”。而内存更希望将自己等分成若干页,可以避免因内存碎片导致的内存利用效率的降低。
为了同时满足用户和内存的要求,操作系统需要一种中间结构来完成段与页机制的统一,这就是虚拟内存。
虚拟内存是一个抽象的概念,本身并不存在,他是一连串的虚拟地址构成的。当程序分段后,从虚拟内存上分割出相应的分区与各段建立映射关系,完成分段机制;再将虚拟内存分割成页,将这些页放在页框中,并建立页和页框的映射,完成分页机制。
提出了虚拟内存的概念后,我们已经能够将分段机制和分页机制有机地结合在一起了。现在又要引出两个问题?程序又是如何放置到内存中去的了?又是如何得以正确执行的了?
如上图所示,当一个程序想要放入内存,会经历以下的步骤:
(1)在虚拟内存中划分区域,将程序分段载入到虚拟内存中,其实就是建立了程序段与虚拟内存各个分区间的映射关系。
(2)建立如上图所示的段表,记录各段与虚拟内存的映射关系。
(3)将虚拟内存中的各段打散成页,将每页的内容,实际上指被映射的各程序段的内容放入内存的页框中。
(4)建立如上图所示的页表,建立虚拟页号和物理页框号之间的映射关系。
以指令“call 40”为例,逻辑地址CS=40。设代码段为第一段,页面大小为100。段号为0,找到该段的偏移地址为1000,1000+40=1040,这是在虚拟内存中的地址。再用1040除以页面大小100,得到虚拟页号为10,查找页表发现对应的物理页框号为5,那么实际内存地址为5*100+40=540,就是“mov 1, [300]”指令。
只要将特定的寄存器(LDTR、CR3)的值设置为正确段表初始地址和页表初始地址,执行每条指令时MMU都会自动完成上述地址转换过程。
3.段页式内存机制实例
一个真实的段页式内存机制的实例,就是将上述所说的概念轮廓用代码表示出来。
代码的实现要从进程创建fork()开始。接着进程调用copy_process函数,进程管理要利用这个函数完成进程PCB的创建、完成内核栈的分配与初始化、完成内核栈和PCB之间的关联。现在,内存管理要在这个函数中给进程建立地址空间:
int copy_process(int nr, long ebp,……)
{
......
copy_mem(nr, p);
......
}
这里的copy_mem(nr, p)就是要为进程建立内存空间,copy是指复制父进程的内存空间。接下来要实现在虚拟内存中分段、建立段表、将虚拟页映射到空闲物理页框,建立页表。
copy_mem(int nr, task_struct *p)
{
unsigned int new_data_base;
new_data_base = nr * 0x4000000;//64Mb * nr
set_base(p->ldt[1], new_data_base);
set_base(p->ldt[2], new_data_base);
}
上图程序中的核心就是set_base,就是向LDT表中填入内容,即建立段表。
new_data_base = nr * 0x4000000即是为虚拟段分割内存。0号进程的nr=0,1号进程的nr=1,每个进程都分到了64MB的虚存空间。如上图所示。
接下来就是进行分页。其实在系统初始化的时候,mem_init函数中的mem_map数组就是用来记录页的空闲情况,初始化时全部为0,表示空闲状态。紧接着就是将虚拟内存分割成页,并建立和物理页框号的映射关系。
int copy_mem(int nr, task_struct *p)
{
unsigned long old_data_base;
old_data_base = get_base(current->ldt[2]);//得到父进程的虚拟内存空间
copy_page_tables(old_data_base, new_data_base, data_limit)
......
}
copy_page_tables的作用就是将父进程的虚拟内存对物理内存的映射关系,复制给子进程的虚存空间new_data_base。会得到如下图所示的结构。
copy_page_tables的代码实现如下:
int copy_page_tables(unsigned long from, unsigned long to, long size)
{
from_dir = (unsigned long*)((from>>20) & 0xffc);//获得父进程虚拟内存中的页表地址
to_dir = (unsigned long*)((to>>20) &0xffc);
for(; size->0; from_dir ++, to_dir ++)//将父进一个页目录项下的所有页表项复制到子进程的目录项下
{
from_page_table = (0xfffff000 & from_dir);
to_page_table = get_free_page();
*to_dir = ((unsigned long)to_page_table)|7;
for(; nr->0; from_page_table ++, to_page_table ++)
{
this_page = *from_page_table;
this_page &= 2;//设置为只读
*to_page_table = this_page;
*from_[age_table = this_page;
this_page -= LOW_MEM;
this_page >>= 12;
mem_map[this_page++];
}
}
}