一、怎么用fork
fork是分叉,走叉路的意思。在代码时常常需要在一个进程中创建另一个新的进程,fork就是用来创建新进程的。创建完成之后原来的进程管它叫它父进程,而新的进程叫做子进程。其实父子是相对的,子有时候也会是父。
关于fork()的一些说明可以参考一下这个:
下面看一段代码
1 /***************************************** 2 filename: testfork.c 3 Author: zhouyoulie 4 date: 2013.05.27 5 *****************************************/ 6 #include7 #include 8 #include 9 #include 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 we13 * actually start allocating stuff14 */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. When22 * called from kernel_thread or CLONE_UNTRACED is explicitly23 * requested, no event is reported; otherwise, report if the event24 * 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 else32 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 pointer42 * 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