修改fork()系统调用
现在需要将新建进程的用户栈、用户程序地址和其内核栈关联在一起,因为TSS没有做这样的关联fork()要求让父子进程共享用户代码、用户数据和用户堆栈虽然现在是使用内核栈完成任务的切换(基于堆栈的进程切换),但是fork()的基本含义不应该发生变化。
综合分析:
修改以后的fork()要使得父子进程共享同一块内存空间、堆栈和数据代码块。
fork() 系统调用的代码放在 system_call.s 汇编文件中,先来看看已经写好的代码:
.align 2 sys_fork: call find_empty_process testl %eax,%eax js 1f push %gs pushl %esi pushl %edi pushl %ebp pushl %eax call copy_process//跳转到copy_process()函数 addl $20,%esp 1: ret
可以看到fork()函数的核心就是调用了 copy_process(),接下来去看copy_process()
copy_process()函数定义在kernel/fork.c中,代码和分析见注释:
int copy_process(int nr,long ebp,long edi,long esi,long gs,long none, long ebx,long ecx,long edx, long fs,long es,long ds, long eip,long cs,long eflags,long esp,long ss) { struct task_struct *p; int i; struct file *f; p = (struct task_struct *) get_free_page();//用来完成申请一页内存空间作为子进程的PCB ... /** 很容易看出来下面的部分就是基于tss进程切换机制时的代码,所以将此片段要注释掉 p->tss.back_link = 0; p->tss.esp0 = PAGE_SIZE + (long) p; p->tss.ss0 = 0x10; ... */ /** 然后这里要加上基于堆栈切换的代码(对frok的修改其实就是对子进程内核栈的初始化 */ long *krnstack; krnstack = (long)(PAGE_SIZE +(long)p);//p指针加上页面大小就是子进程的内核栈位置,所以这句话就是krnstack指针指向子进程的内核栈 //初始化内核栈(krnstack)中的内容: //下面的五句话可以完成对书上那个图(4.22)所示的关联效果(父子进程共有同一内存、堆栈和数据代码块) /* 而且很容易可以看到,ss,esp,elags,cs,eip这些参数来自调用该函数的进程的内核栈中, 也就是父进程的内核栈,所以下面的指令就是将父进程内核栈的前五个内容拷贝到了子进程的内核栈中 */ *(--krnstack) = ss & 0xffff; *(--krnstack) = esp; *(--krnstack) = eflags; *(--krnstack) = cs & 0xffff; *(--krnstack) = eip; *(--krnstack) = ds & 0xffff; *(--krnstack) = es & 0xffff; *(--krnstack) = fs & 0xffff; *(--krnstack) = gs & 0xffff; *(--krnstack) = esi; *(--krnstack) = edi; *(--krnstack) = edx; *(--krnstack) = (long) first_return_kernel;//处理switch_to返回的位置 *(--krnstack) = ebp; *(--krnstack) = ecx; *(--krnstack) = ebx; *(--krnstack) = 0; //把switch_to中要的东西存进去 p->kernelstack = krnstack; ...
kernel/fork.c 文件
注释tss进程切换片段
添加代码
long *krnstack; krnstack = (long)(PAGE_SIZE +(long)p); *(--krnstack) = ss & 0xffff; *(--krnstack) = esp; *(--krnstack) = eflags; *(--krnstack) = cs & 0xffff; *(--krnstack) = eip; *(--krnstack) = ds & 0xffff; *(--krnstack) = es & 0xffff; *(--krnstack) = fs & 0xffff; *(--krnstack) = gs & 0xffff; *(--krnstack) = esi; *(--krnstack) = edi; *(--krnstack) = edx; *(--krnstack) = (long) first_return_kernel; *(--krnstack) = ebp; *(--krnstack) = ecx; *(--krnstack) = ebx; *(--krnstack) = 0; p->kernelstack = krnstack;
上面的first_return_kernel(系统调用)的工作:
"内核级线程切换五段论"中的最后一段切换,即完成用户栈和用户代码的切换,依靠的核心指令就是iret,当然在切换之前应该恢复一下执行现场,主要就是eax,ebx,ecx,edx,esi,gs,fs,es,ds这些寄存器的恢复,
要将first_return_kernel(属于系统调用,而且是一段汇编代码)写在kernel/system_call.s头文件里面:
首先需要将first_return_kernel设置在全局可见:
.globl switch_to,first_return_kernel
将具体的函数实现放在system_call.s头文件里面:
.align 2 first_return_kernel: popl %edx popl %edi popl %esi pop %gs pop %fs pop %es pop %ds iret
最后要记得是在 kernel/fork.c 文件里使用了 first_return_kernel 函数,所以要在该文件里添加外部函数声明
extern void first_return_kernel(void);
编译运行
天道酬勤
有些人不会写文章就不要发,参考的几篇全有错误,最后居然还能有运行成功的截图,你们编译怎么通过的?真牛!虽然我文章都是看的别人的,但我至少保证自己独立运行一遍,能保证运行通过。幸好最后碰到一位博主,看了他的文章自惭形秽。写的特别简洁但重点突出,有自己的理解。反观自己废话连篇,文章东拼西凑。