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) 指令,靠進程切換,分別向下執行,返回兩次。