引用:#include
#include/* contains fork prototype */
int main(int argc, char **argv) {
if (fork() == 0) {
printf("I am the child process.\n");
} else {
printf("I am the parent process.\n");
}
}
在Linux0.11中,每个进程都有一个进程控制块结构task_struct。系统支持最多64个进程,定义在全局数组task中。
引用:struct task_struct * task[NR_TASKS] = {&(init_task.task), };
其中进程0为初始进程,其它所有的进程都是通过fork产生的。用户态的fork函数最终调用系统调用sys_fork()。sys_fork()系统调用分成2步完成,第一步调用函数find_empty_process(),在task[]数组中找一项空闲项;第二步调用copy_process()函数,复制进程。
对所有fork()调用产生的进程,通过递增并循环的方式为其分配进程号。有一个全局变量last_pid用来记录上次使用的进程号:
long last_pid=0;
在find_empty_process中,不断递增last_pid,寻找第一个未被其它进程使用的进程号作为新进程的进程号。如果递增后的值超出正数表示范围,则重新从1开始。
进程控制块中还保存有进程的任务状态段数据结构tss,用于存储处理器管理进程的所有信息。也就是说,在任务切换过程中,首先将处理器中各寄存器的当前值被自动保存当前进程的tss中;然后,下一进程的tss被加载并从中提取出各个值送到处理器的寄存器中。由此可见,通过在tss中保存任务现场各寄存器状态的完整映象,实现任务的切换。
struct tss_struct tss;
因此,一旦在task[]数组中找到空闲项和进程号,我们就可以为该进程的进程控制块结构申请一个页面的内存。这个工作是在copy_process()函数中完成的。当然copy_process()函数的最主要的任务是为子进程复制父进程信息,并设置子进程的任务状态段,其中最关键的两步是:
1. 把子进程tss中的eip设置为父进程系统调用返回地址,这样当子进程被调度程序选中后,将从父进程的fork()返回地址处开始执行。
p->tss.eip = eip;
2. 把子进程tss中的eax设置为0,而eax是存放函数返回值的地方,这样子进程中返回的是0。注意子进程并没有执行fork()函数,子进程的系统堆栈没有进行过操作,当然不会有像父进程那样的fork函数调用。但是当子进程开始运行时,就好像它从fork中返回。
p->tss.eax = 0;