一个现有进程能够调用fork函数建立一个新进程。该函数定义以下:html
#include <unistd.h> pid_t fork(void); // 返回:若成功则在子进程中返回0,在父进程中返回子进程ID,若出错则返回-1
fork函数调用一次,返回两次。它在调用进程(称为父进程)中返回一次,返回值是新派生进程(称为子进程)的进程ID号;在子进程中返回一次,返回值为0。所以,返回值自己告知当前进程是子进程仍是父进程。linux
fork在子进程返回0而不是父进程的进程ID的缘由在于:任何子进程只有一个父进程,并且子进程老是能够经过调用getppid取得父进程的进程ID。相反,父进程能够有不少子进程,并且没法获取各个子进程的进程ID。若是父进程想要跟踪全部子进程的进程ID,那么它必须记录每次调用fork的返回值。另外,进程ID 0老是由内核交换进程使用,因此一个子进程的进程ID不可能为0。面试
子进程和父进程继续执行fork调用以后的指令。子进程是父进程的副本。例如,子进程得到父进程数据空间、堆和栈的副本。父、子进程并不共享这些存储空间部分。父、子进程共享代码段。算法
因为在fork以后常常跟随着exec,因此如今的不少实现并不执行一个父进程数据段、堆和栈的彻底复制。做为替代,使用了写时复制(Copy-On-Write, COW)技术。这些区域由父、子进程共享,并且内核将它们的访问权限改变为只读的。若是父、子进程中的任一个试图修改这些区域,则内核只为修改区域的那块内存制做一个副本,一般是虚拟存储器系统的一“页”。shell
使fork失败的两个主要缘由是:1)系统中已经有太多的进程(一般意味着某个方面出了问题);2)实际用户的进程总数超过了系统限制。编程
用fork函数建立子进程后,子进程每每要调用一种exec函数以执行另外一个程序。当进程调用一种exec函数时,该进程执行的程序彻底替换为新程序,而新程序则从其main函数开始执行。由于调用exec并不建立新进程,因此先后的进程ID并未改变。exec函数只是用一个全新的程序替换了当前进程的正文、数据、堆和栈端。缓存
关于exec函数详细的请参考:ide
APUE 8.10节函数
进程控制spa
简单示例程序以下:
1 #include <unistd.h> 2 #include <stdio.h> 3 4 int main() 5 { 6 pid_t fpid; 7 fpid = fork(); 8 9 if(fpid < 0) 10 { 11 printf("error in fork!"); 12 } 13 else if(fpid == 0) 14 { 15 printf("\n=================================================\n"); 16 printf("I am the child process, my process ID is %d\n", getpid()); 17 printf("My parent process ID is %d\n", getppid()); 18 printf("=================================================\n"); 19 } 20 else 21 { 22 printf("\n=================================================\n"); 23 printf("I am the parent process, my process ID is %d\n", getpid()); 24 printf("=================================================\n"); 25 sleep(5); 26 } 27 28 return 0; 29 }
程序的运行结果是:
按照惯常,程序按顺序执行,最终输出应该只有if...else if...else中一个条件下的结果,但很明显咱们这边输出了两个条件下的结果。具体缘由在于经过fork函数建立的子进程也会复制父进程的存储空间(数据、堆、栈等,包括程序计数器),建立了属于本身的存储空间,并从fork函数后开始执行。利用pstree命令能够看到子进程(ID 18406)确实继承自父进程(ID 18405):
通常来讲,在fork以后是父进程先执行仍是子进程先执行是不肯定的,这取决于内核所使用的调度算法。在上述程序中,父进程先执行,子进程在其以后执行。
下边的一道题摘自陈皓的博文:
1 #include <stdio.h> 2 #include <sys/types.h> 3 #include <unistd.h> 4 5 int main() 6 { 7 int i; 8 for(i = 0; i < 2; i++) 9 { 10 fork(); 11 printf("-"); 12 } 13 14 sleep(10); 15 return 0; 16 }
请问上述程序会输出几个‘-’?6个仍是8个?
若是咱们不考虑printf函数的缓存效果,程序的最终输出是6个‘-’。但由于printf函数有缓存的效用,最终致使输出了8个‘-’。具体缘由可参照陈皓的博文。
为了去除printf函数缓存效用,咱们稍微改动一下程序:
1 #include <stdio.h> 2 #include <sys/types.h> 3 #include <unistd.h> 4 5 int main() 6 { 7 int i; 8 for(i = 0; i < 2; i++) 9 { 10 fork(); 11 printf("-\n"); 12 } 13 14 sleep(10); 15 return 0; 16 }
这下输出就正确了。下边两图是程序输出结果和相应的进程(forktest3)树状图:
为了更直观,咱们能够修改程序以下:
1 #include <stdio.h> 2 #include <sys/types.h> 3 #include <unistd.h> 4 5 int main() 6 { 7 int i; 8 for(i = 0; i < 2; i++) 9 { 10 fork(); 11 printf("ppid=%d, pid=%d, i=%d \n", getppid(), getpid(), i); 12 } 13 14 sleep(10); //让进程停留十秒,这样咱们能够用pstree查看一下进程树 15 return 0; 16 }
输出结果以下:
陈皓还给出了图示解释:
上图中,相同颜色的是同一个进程。
而对于printf("-");这个语句,咱们就能够很清楚的知道,哪一个子进程复制了父进程标准输出缓中区里的的内容,而致使了屡次输出了。以下图所示,就是阴影并双边框了那两个子进程:
《UNIX环境高级编程 第二版》