1. fork函数

  fork函数用于克隆一份当前的进程资源,调用fork函数之后,进程一分为二,并且两个进程的资源是一样的(只是资源内容完全一样,并不是同一份资源)。fork函数的函数原型为:pid_t fork(void); 需要包含unistd.h,返回值pid_t类型实际上就是int型

  在调用fork函数之后,进程被一分为二,他们的资源都是相同的,如果在调用fork前,程序打开了某个文件,那么fork之后会出现两个一样的文件描述符(并非出现两个相同的文件),如果在调用fork前程序缓冲区中保存了某些数据,那么fork之后就会出现两个相同的缓冲区,里面存放的数据都是一模一样的。在fork之后他们可以进行不同的操作,他们可以去修改同一个文件,也可以改变自己的缓冲区。这个机制就好像克隆人一样,从本体克隆出来一个克克隆体,在克隆体被克隆出来那一刻,克隆人与本体是一模一样的,克隆人与本体有着相同的记忆(虽然他并没有真正经历过这些事情)。在克隆人被克隆出来之后,他们经历的事情就会不一样了,比如本体去了A地,他记住了A第发生的事情,而克隆体去了B地,他便记住了B地发生的事情,就好像fork函数执行完的那一刻,缓冲区的数据是一样的,而之后程序的走向可以让缓冲区的数据不一样。如果本体在以前有一个女朋友,那她同样也是克隆体的女朋友,但是“女朋友”这个人只有一个,这就好像fork之前打开了某个文件,在fork之后文件描述符变成了两份,但文件其实只有一份是相同的道理。

  实际上在fork函数返回之后,两个程序已经不一样了,区别就是fork的返回值。返回值大于0的是父进程,其返回值是子进程的ID,返回值等于0的是子进程(但这并不是说子进程的ID是0),返回值小于0表示fork失败。通过geipid函数可以得到自己的进程ID(PID),通过getppid函数可以得到父进程的ID,这两个函数是不会执行失败的,因为在函数的描述里写道:“These functions are always successful.”,这两个函数的原型如下:

pid_t getpid(void);
pid_t getppid(void);

  通过一个简单的例子说明:

 1 #include <stdio.h>
 2 #include <sys/types.h>
 3 #include <unistd.h>
 4 
 5 int main(int argc, const char *argv[])
 6 {
 7     int pid = 0;        /* fork函数的返回值 */
 8     int mypid = 0;      /* PID */
 9     int myppid = 0;     /* PPID */
10     
11     pid = fork();
12     
13     if (pid > 0) {          /* 父进程 */
14         sleep(1);
15         printf("##################\n");
16         printf("I'm parent\n");
17         printf("pid = %d\n", pid);
18         printf("mypid = %d\n", getpid());
19         printf("myppid = %d\n\n", getppid());
20     } else if (pid == 0) {  /* 子进程 */     
21         printf("##################\n");
22         printf("I'm child\n");
23         printf("pid = %d\n", pid);
24         printf("mypid = %d\n", getpid());
25         printf("myppid = %d\n", getppid());        
26     }
27 }

执行结果如下图:

 

2. 进程ID

  由上图可知,子进程打印pid的值为0,而他自己的PID为4244,他的父进程ID为4243,父进程打印的pid值为4244,这恰好是子进程通过getpid函数得到的PID值,而父进程的PID为4243,父进程的父进程PID为2732。

  上边的代码在父进程处加一个sleep(1)的目的是让子进程先运行,之后子进程退出,父进程再运行,然后父进程退出,如果父进程退出了,子进程再获取父进程的的ID会发生什么呢,代码作如下改动:

 1 #include <stdio.h>
 2 #include <sys/types.h>
 3 #include <unistd.h>
 4 
 5 int main(int argc, const char *argv[])
 6 {
 7     int pid = 0;        /* fork函数的返回值 */
 8     int mypid = 0;      /* PID */
 9     int myppid = 0;     /* PPID */
10     
11     pid = fork();
12     
13     if (pid > 0) {          /* 父进程 */
14         sleep(1);
15         printf("##################\n");
16         printf("I'm parent\n");
17         printf("pid = %d\n", pid);
18         printf("mypid = %d\n", getpid());
19         printf("myppid = %d\n\n", getppid());
20     } else if (pid == 0) {  /* 子进程 */     
21         printf("##################\n");
22         printf("I'm child\n");
23         printf("pid = %d\n", pid);
24         printf("mypid = %d\n", getpid());
25         printf("myppid = %d\n", getppid()); 
26         sleep(2);
27         printf("I'm child\n");
28         printf("myppid = %d\n", getppid());
29     }
30 }

  运行结果如下图:

 

   从运行结果发现,如果父进程退出而子进程还没退出,那么子进程的父进程PID将变为1,子进程此时变为孤儿进程,PID为1的进程就是linux系统下负责收留孤儿进程的进程。

  通过前面的例子可以看出,fork函数虽然将资源一分为二了,但分出来的两部分还是不能当成完全相同的两部分来看待,他们之间存在父子关系。如果父进程退出,那么子进程会变成孤儿进程,如果子进程退出,那么子进程会给父进程发一个信号,这在下一章讨论。因此当使用fork的时候要考虑进程是否会退出,从而选择合适的分支。