一、实验目的
1、熟悉Minix操作系统的进程管理
2、学习Unix风格的内存管理
二、内容与设计思想
修改Minix3.1.2a的进程管理器,改进brk系统调用的实现,使得分配给进程的数据段+栈段空间耗尽时,brk系统调用给该进程分配一个更大的内存空间,并将原来空间中的数据 复制至新分配的内存空间,释放原来的内存空间,并通知内核映射新分配的内存段。
三、使用环境
物理机:Windows10
虚拟机:Minix3.1.2
虚拟机软件:Vmware
终端控制软件:MobaXterm
物理机与虚拟机文件传输:FileZilla
四、实验过程
(1)Minix3.1.2实验环境配置
· 下载Minix3.1.2镜像文件并创建虚拟机
· 修改虚拟机兼容性,选择5.x版本
· 启动虚拟机,在setup步骤中,网卡选择AMD LANCE以便获得IP地址。安装完成后,输入shutdown,然后输入boot d0p0重启。
· 输入mv /etc/rc.daemons.dist /etc/rc.daemons。在网络模式设置为NAT模式时,重启后可以看到IP地址。
· 在虚拟机终端输入packman安装额外的软件包,为了方便,选择全部安装(400MB左右)
(2)实现alloc.c函数
· 修改/usr/src/servers/pm/alloc.c中的alloc_mem函数,把first-fit修改成best-fit,即在分配内存之前,先遍历整个空闲内存块列表,找到最佳匹配的空闲块。
· 函数实现思路:
遍历空闲块的链表,best_ptr记录当前最佳适配的空闲块,若找到满足条件且更小的空闲块,那么则对best_ptr进行更新,直至遍历结束。
hp=hole_head;
best_ptr=NIL_HOLE;//记录当前找到的最佳适配块
best_prev_ptr=NIL_HOLE;
prev_ptr=NIL_HOLE;
while(hp!=NIL_HOLE && hp->h_base < swap_base){
if(hp->h_len >= clicks && best_ptr== NIL_HOLE){
best_ptr=hp;
best_prev_ptr=prev_ptr;
}
if(hp->h_len>=clicks && best_ptr!=NIL_HOLE){
//如果块大小小于当前的最佳适配块,则更新当前最佳适配块
if(hp->h_len < best_ptr->h_len){
best_ptr=hp;
best_prev_ptr=prev_ptr;
}
}
prev_ptr= hp ;
hp=hp->h_next;
}
(3)实现break.c函数
· 修改/usr/src/servers/pm/break.c中的adjust函数,完成函数allocate_new_mem的定义并增加一个allocate_new_mem局部函数在adjust函数中调用。
break.c函数的作用:do_brk函数计算数据段新的边界,然后调用adjust函数计算程序当前的空闲空间是否足够分配:
(a)若足够,则调整数据段指针,堆栈指针并通知内核程序的映像发生了变化,返回do_brk函数。
(b)若不够,则调用allocate_new_mem函数申请新的足够大的内存空间。
//计算栈底指针=栈顶指针+栈大小
base_of_stack = (long) mem_sp->mem_vir + (long) mem_sp->mem_len;
//byte与click单位转换,得到新的栈顶指针
sp_click = sp >> CLICK_SHIFT;
if (sp_click >= base_of_stack) return(ENOMEM);
/* Compute size of gap between stack and data segments. */
delta = (long) mem_sp->mem_vir - (long) sp_click;
lower = (delta > 0 ? sp_click : mem_sp->mem_vir);
/* Add a safety margin for future stack growth. Impossible to do right. */
#define SAFETY_BYTES (384 * sizeof(char *))
#define SAFETY_CLICKS ((SAFETY_BYTES + CLICK_SIZE - 1) / CLICK_SIZE)
gap_base = mem_dp->mem_vir + data_clicks + SAFETY_CLICKS;
//lower为栈顶指针,gap_base为数据段的边界
//如果lower<gap则说明栈段,说明发生了冲突
//调用allocate_new_mem函数,分配新的空间
if(lower < gap_base){
res=allocate_new_mem(rmp,data_clicks,delta,(phys_clicks)(rmp->mp_seg[S].mem_vir - rmp->mp_seg[D].mem_vir + rmp->mp_seg[S].mem_len));
if(res==OK){
return (OK);
}
if(res== ENOMEM){
return (ENOMEM);
}
}
lower为栈顶指针,gap_base为数据段的边界,如果lower<gap_base则说明栈段和数据段发生了冲突,当前空闲空间不够分配,因此调用allocate_new_mem函数分配新的空间。
· allocate_new_mem函数实现思路:
调用alloc_mem函数申请一块大小为原空间两倍的新空间。调用sys_abscopy函数将程序现有的数据段和堆栈段的内容分别拷贝至新内存区域的底部和顶部。修改进程表中记录的数据段和栈段的物理地址、虚拟地址。再调用sys_newmap函数更新虚拟地址到物理地址的映射。最后,调用free_mem函数释放掉原来的空间。
主要用到的三个函数sys_abscopy、sys_newmap、free_mem,利用source insight查看三个函数的调用方式。
通过查看sys_abscopy函数在其他函数中被调用的方式,可知传入的第一个参数为对象的旧地址,第二个参数为对象的新地址,第三个参数为总的字节数。其中两个地址都是以byte为单位。
sys_newmap传入的第一个参数为rmp->mp_endpoint,第二个参数为rmp->mp_seg。
free_mem传入的第一个参数是释放空间的起始地址,第二个参数是空间大小。
函数头如图,其中传递的参数申明放在函数名和函数体大括号之间,是一种老式的参数传递方式。
PUBLIC int allocate_new_mem(rmp,data_clicks,delta,clicks)
register struct mproc *rmp;
phys_clicks data_clicks;
long delta;
phys_clicks clicks;
{
根据传入的clicks大小,计算出新空间的大小,按原来的2倍进行计算。调用alloc_mem函数申请一块新的空间。
new_clicks = clicks*2;
old_clicks = clicks;
if((new_address_data=alloc_mem(new_clicks)) == NO_MEM){
return(ENOMEM);
}
分别计算出栈段和数据段的长度以及栈段和数据段的旧地址、新地址,其中栈顶指针的位置等于数据段地址(起始地址)加上地址空间的长度减去栈段的长度,即new_address_stack=new_address_data+new_clicks - mem_sp->mem_len。将他们转化为以byte为单位。
databytes=(phys_bytes)mem_dp->mem_len << CLICK_SHIFT;
stackbytes=(phys_bytes)mem_sp->mem_len << CLICK_SHIFT;
old_address_data=mem_dp->mem_phys;
new_address_stack=new_address_data + new_clicks - mem_sp->mem_len;
old_address_stack=mem_sp->mem_phys;
new_address_data_byte=(phys_bytes)new_address_data << CLICK_SHIFT;
old_address_data_byte=(phys_bytes)old_address_data << CLICK_SHIFT;
new_address_stack_byte=(phys_bytes)new_address_stack << CLICK_SHIFT;
old_address_stack_byte=(phys_bytes)old_address_stack << CLICK_SHIFT;
将栈段和数据段的内容复制到新的地址,并修改进程表中记录的栈段与数据段的虚拟地址和物理地址。其中数据段的虚拟地址不用进行修改,因为在minix中代码段与数据段和栈段是分开管理的,数据段虚拟地址是零。
d = sys_abscopy(old_address_data_byte,new_address_data_byte,databytes);
if (d < 0)
panic(__FILE__, " can't copy data segment in alloc_new_mem", d);
s = sys_abscopy(old_address_stack_byte,new_address_stack_byte,stackbytes);
if (s < 0)
panic(__FILE__, " can't copy stack segment in alloc_new_mem", s);
rmp->mp_seg[D].mem_phys = new_address_data;
rmp->mp_seg[S].mem_phys = new_address_stack;
rmp->mp_seg[S].mem_vir = rmp->mp_seg[D].mem_vir+new_clicks -mem_sp->mem_len;
类似adjust函数中记录下栈段和数据段的地址、长度改变。
if(data_clicks != mem_dp->mem_len){
mem_dp->mem_len = data_clicks;
change |= DATA_CHANGED;
}
if(delta > 0){
mem_sp->mem_vir -= delta;
mem_sp->mem_phys -= delta;
mem_sp->mem_len += delta;
change |= STACK_CHANGED;
}
判断新的栈段、数据段大小是否适合地址空间,若适合则调用sys_newmap函数更新虚拟地址到物理地址的映射,并调用free_mem释放旧的空间;若不适合则恢复栈段、数据段到旧的空间。
ft = (rmp->mp_flags & SEPARATE);
#if (CHIP == INTEL && _WORD_SIZE == 2)
r = size_ok(ft, rmp->mp_seg[T].mem_len,rmp->mp_seg[D].mem_len,
rmp->mp_seg[S].mem_len,rmp->mp_seg[D].mem_vir, rmp->mp_seg[S].mem_vir);
#else
r = (rmp->mp_seg[D].mem_vir + rmp->mp_seg[D].mem_len >
rmp->mp_seg[S].mem_vir) ? ENOMEM : OK;
#endif
if (r == OK) {
int r2;
if (change && (r2=sys_newmap(rmp->mp_endpoint, rmp->mp_seg)) != OK)
panic(__FILE__,"couldn't sys_newmap in adjust", r2);
free_mem(old_address_data, old_clicks);
return(OK);
}
/* New sizes don't fit or require too many page/segment registers. Restore.*/
if (change & DATA_CHANGED) mem_dp->mem_len = cur_data_clicks;
if (change & STACK_CHANGED) {
mem_sp->mem_vir += delta;
mem_sp->mem_phys += delta;
mem_sp->mem_len -= delta;
}
return(ENOMEM);
}
(4)编译Minix
· 进入/usr/src/servers目录,输入make image, 等编译成功之后输入make install 安装新的PM程序。
· 进入/usr/src/tools目录,输入make hdboot, 成功之后再键入make install命令安装新的内核程序。并记录下生成的新内核版本号。
· 键入shutdown 命令关闭虚拟机,进入boot monitor界面。设置启动新内核的选项,在提示符键入:
newminix(5,start new kernel) {image=/boot/image/3.1.2ar3;boot;}
注:5为启动菜单中的选择内核版本的数字键,也可设置为其他数字;3.1.2ar3为上一步中记录的新生成的内核版本号。
· 输入menu命令,然后敲数字键(上一步骤中设置的数字)启动新内核,登录进minix 3中测试。
五、实验结果
测试程序test1测试sbrk调用,不断地调整数据段的上界并未对新分配的内存空间进行访问;测试程序test2则对新分配的内存空间进行了访问。
测试结果如下:
至此Project就完成啦~
完整代码与测试用例: https://github.com/RachelllYe/OSProject
欢迎指正,谢谢!