1. 示例程序
#include <unistd.h>; #include <sys/types.h>; main () { pid_t pid; pid=fork(); if (pid < 0) printf("error in fork!"); else if (pid == 0) printf("i am the child process, my process id is %dn",getpid()); else printf("i am the parent process, my process id is %dn",getpid()); }
结果是:i am the child process, my process id is 2139
i am the parent process, my process id is 2138之前看过很多解释,觉得一次调用fork()函数,但返回两次,很神奇。下面我们来看看fork()函数源代码,你就会豁然大明白了。
2. fork() 源代码
fork()是一个系统调用,内核中sys_fork是其实现:
这个函数先调用find_empty_process()函数,之后调用copy_process函数_sys_fork: call _find_empty_process testl %eax,%eax js 1f push %gs pushl %esi pushl %edi pushl %ebp pushl %eax call _copy_process addl $20,%esp 1: ret
I. 在进程一文中讲到进程一定要有一个task_struct结构,find_empty_process()函数就是在全局结构数组task中找了一个空的task_struct结构和一个未使用的进程id。返回任务号,即在数组中的位置。下面是其代码:
int find_empty_process(void) { int i; repeat: if ((++last_pid)<0) last_pid=1; for(i=0 ; i<NR_TASKS ; i++) if (task[i] && task[i]->pid == last_pid) goto repeat; for(i=1 ; i<NR_TASKS ; i++) if (!task[i]) return i; return -EAGAIN; }
II. copy_process()函数 核心代码如下所示
第7行:先在内核空间中申请一页内存int copy_process() { struct task_struct *p; int i; struct file *f; p = (struct task_struct *) get_free_page(); if (!p) return -EAGAIN; task[nr] = p; *p = *current; /* NOTE! this doesn't copy the supervisor stack */ p->state = TASK_UNINTERRUPTIBLE; p->pid = last_pid; p->father = current->pid; p->tss.esp0 = PAGE_SIZE + (long) p; p->tss.ss0 = 0x10; p->tss.eax = 0; p->tss.eip = eip; p->tss.cs = cs & 0xffff; p->tss.ss = ss & 0xffff; p->tss.ds = ds & 0xffff; set_tss_desc(gdt+(nr<<1)+FIRST_TSS_ENTRY,&(p->tss)); set_ldt_desc(gdt+(nr<<1)+FIRST_LDT_ENTRY,&(p->ldt)); p->state = TASK_RUNNING; /* do this last, just in case */ return last_pid; }
第10行:把此页的地址赋给全局数组中相应的位置,也就是find_empty_process函数的返回值。
第11行:把当前的任务数据结构复制到刚申请的内存p处。
第12-16行:给新进程结构做一些修改,如:状态,进程id,父进程,内核栈等等
第17行:eax赋值为0,这个0就是新进程(子进程)fork的返回值。
第18-19行:关键的两行,cs:eip在当前进程指向的fork()的下一个指令:即示例程序的if(pid < 0)。当前进程将要执行此代码。而把cs:eip指向的代码赋值给新进程。因此新进程下一条指令也是if(pid < 0). 分别由两个进程从if(pid < 0)分别向下执行,因此执行到最后有两次返回。我说的清楚吗?
第20-21行:设置cs,ss,ds等代码段,堆栈段和数据段。
第22-23行:把task_struct中的tss结构与gdt相联系。在进程切换的时候,要保持原任务的上下文,也就是保存到了tss中的结构中。
第24行:设置新进程的状态为running
第25行:返回新进程的进程id,此值也是当前进程(父进程)fork对应的返回值,为子进程的进程id。
总的来说fork()做的工作就是创建了一个新的task_struct结构,对新进程结构做了一些修改。关键的就是对cs:eip的复制,让新老进程都共同指向了if(pid < 0) 指令,靠进程切换,分别向下执行,返回两次。