一、怎么用fork
fork是分叉,走叉路的意思。在代码时常常需要在一个进程中创建另一个新的进程,fork就是用来创建新进程的。创建完成之后原来的进程管它叫它父进程,而新的进程叫做子进程。其实父子是相对的,子有时候也会是父。
关于fork()的一些说明可以参考一下这个:http://man.he.net/?topic=fork§ion=all
下面看一段代码
1 /***************************************** 2 filename: testfork.c 3 Author: zhouyoulie 4 date: 2013.05.27 5 *****************************************/ 6 #include <stdlib.h> 7 #include <stdio.h> 8 #include <sys/types.h> 9 #include <unistd.h> 10 int main() 11 { 12 pid_t pid; 13 int count = 0; 14 printf("Before fork,process id is:%d\n",getpid()); 15 pid = fork(); 16 if( pid < 0 ) 17 { 18 printf("Fork Failure!\n"); 19 } 20 else if( pid == 0 ) 21 { 22 printf("This is son process,and my own ID parent ID are:%d and %d\n",getpid(),getppid()); 23 count++; 24 } 25 else if( pid > 0 ) 26 { 27 printf("the back pid is:%d\n",pid); 28 printf("This is parent process,and my own ID parent ID are:%d and %d\n",getpid(),getppid()); 29 count++; 30 } 31 32 printf("count equals %d\n",count); 33 exit(0); 34 }
通过这段代码应该可以初略的了解fork的使用方法了。先说明两个函数:getpid()使用来获取当前进程的ID的,getppid()是用来获取当前进程的父进程ID。代码运行的结果是这样的
图1
在调用fork之前本进程的ID是7301,调用fork之后返回7302,父进程和它自己的父进程ID分别为7301和4893,子进程和其父进程ID分别为7302和7301。结合上面的代码和运行结果不难看出fork被调用之后其实返回两次,返回的进程ID大于零说明该进程为父进程(其实返回的是他的子进程的ID,上图结果中的7302),返回值为零说明是子进程。那么两次count都为1说明什么呢?说明两个进程都独自拥有自己的count,实际上在生成子进程是就相当于克隆了一个自己,这种克隆包括数据、代码以及分配给进程的资源。
二、fork的内部实现
在用户态模式下调用fork时实际上调用的是C库提供的fork函数,在C库中封装了系统调用fork。在最新的内核代码linux3.9.3的
linux-3.9.3\include\uapi\asm-generic\unistd.h头文件中可查看相关系统调用号的定义,并且从unistd.h中还可以知道fork的系统调用例程为sys_fork,见图2
图2
通过sys_fork可以追踪到linux-3.9.3\kernel\Fork.c,有如下实现
图3
在上述kernel实现里调用了do_fork,do_fork又是做什么的呢?继续查找代码可以觅得如下实现
1 long do_fork(unsigned long clone_flags, 2 unsigned long stack_start, 3 unsigned long stack_size, 4 int __user *parent_tidptr, 5 int __user *child_tidptr) 6 { 7 struct task_struct *p; 8 int trace = 0; 9 long nr; 10 11 /* 12 * Do some preliminary argument and permissions checking before we 13 * actually start allocating stuff 14 */ 15 if (clone_flags & (CLONE_NEWUSER | CLONE_NEWPID)) { 16 if (clone_flags & (CLONE_THREAD|CLONE_PARENT)) 17 return -EINVAL; 18 } 19 20 /* 21 * Determine whether and which event to report to ptracer. When 22 * called from kernel_thread or CLONE_UNTRACED is explicitly 23 * requested, no event is reported; otherwise, report if the event 24 * for the type of forking is enabled. 25 */ 26 if (!(clone_flags & CLONE_UNTRACED)) { 27 if (clone_flags & CLONE_VFORK) 28 trace = PTRACE_EVENT_VFORK; 29 else if ((clone_flags & CSIGNAL) != SIGCHLD) 30 trace = PTRACE_EVENT_CLONE; 31 else 32 trace = PTRACE_EVENT_FORK; 33 34 if (likely(!ptrace_event_enabled(current, trace))) 35 trace = 0; 36 } 37 38 p = copy_process(clone_flags, stack_start, stack_size, 39 child_tidptr, NULL, trace); 40 /* 41 * Do this prior waking up the new thread - the thread pointer 42 * might get invalid after that point, if the thread exits quickly. 43 */ 44 if (!IS_ERR(p)) { 45 struct completion vfork; 46 47 trace_sched_process_fork(current, p); 48 49 nr = task_pid_vnr(p); 50 51 if (clone_flags & CLONE_PARENT_SETTID) 52 put_user(nr, parent_tidptr); 53 54 if (clone_flags & CLONE_VFORK) { 55 p->vfork_done = &vfork; 56 init_completion(&vfork); 57 get_task_struct(p); 58 } 59 60 wake_up_new_task(p); 61 62 /* forking complete and child started to run, tell ptracer */ 63 if (unlikely(trace)) 64 ptrace_event(trace, nr); 65 66 if (clone_flags & CLONE_VFORK) { 67 if (!wait_for_vfork_done(p, &vfork)) 68 ptrace_event(PTRACE_EVENT_VFORK_DONE, nr); 69 } 70 } else { 71 nr = PTR_ERR(p); 72 } 73 return nr; 74 }
在do_fork中又调用了copy_process函数。所以可以总结一下用户态与产生新进程的整体流程:
图3