fork那点事

fork那点事

fork 总结

fork()通过复制调用进程来创建一个新进程。在Linux下,fork()是通过使用写时复制页面实现的,所以它唯一的缺点是复制父页表的时间和内存,并为子进程创建独特的任务结构。

fork调用的一个奇妙之处就是它仅仅被调用一次,却能够返回两次,它有三种不同的返回值:

    1)在父进程中,fork返回新创建子进程的进程ID;

    2)在子进程中,fork返回0;

    3)如果出现错误,fork返回一个负值;

fork出错可能有两种原因:

    1)当前的进程数已经达到了系统规定的上限,这时errno的值被设置为EAGAIN。

    2)系统内存不足,这时errno的值被设置为ENOMEM。

创建新进程成功后,系统中出现两个基本完全相同的进程,这两个进程执行没有固定的先后顺序,哪个进程先执行要看系统的进程调度策略。 fork后这两个进程的变量都是独立的,存在不同的地址中。

       在fork函数执行完毕后,如果创建新进程成功,则出现两个进程,一个是子进程,一个是父进程。在子进程中,fork函数返回0,在父进程中,fork返回新创建子进程的进程ID。

       我们可以通过fork返回的值来判断当前进程是子进程还是父进程。

父子进程不同点

1)子进程有自己独特的进程ID,并且此ID与任何现进程组里的ID不一样。

2)子进程不会继承父进程的记忆锁(mlock(2),mlockall(2) )。

3)子进程资源利用率(getrusage(2))和CPU时间计数器(次数(2) )重置为零。

4)子进程的待决信号集最初是空的(sigpending(2))。

5)子项不会从父进程项继承信号量调整(semop(2))。

6)子进程不从父进程继承记录锁(fcntl(2))。

7)子进程不从其父进程(setitimer(2),alarm(2),timer_create(2))继承定时器。

8)子进程不从父级继承未完成的异步I/O操作(aio_read(3),aio_write(3)),也不从父级继承任何异步I/O上下文.

9)子进程不会从其父进程继承目录更改通知(dnotify)(请参阅fcntl(2)中的F_NOTIFY说明)

10)子进程终止后会发出SIGCHLD信号。

11)由ioperm(2)设置的端口访问权限位不被子进程继承; 子进程必须使用ioperm打开它需要的任何位

12)如果进程的一个线程调用fork()函数,父进程的整个虚拟地址空间在子级中复制,包括状态互斥量,条件变量和其他线程对象(pthreads);(可以使用pthread_atfork(3)对复制范围进行限制)。

子进程从父进程可继承的资源

1)子进程继承父母的一组打开文件描述符的副本(参考open(2))

2)子进程继承父母的一组开放消息队列描述符的副本(参考mq_overview(7))

3)子进程继承父母打开的目录流集的副本(opendir(3))

fork和vfork的异同

1)vfork()就像fork(2)一样为调用进程创建子进程。

2)vfork()是clone(2)的特例。 它用于创建新进程而不复制父进程的页表。 它可能在子进程创建后对性能敏感的应用程序中很有用,然后立即运行一个execve()。

3)vfork()与fork(2)的不同之处在于,调用线程被挂起直到子进程终止(通常通过调用_exit(2),或者在传送致命信号后异常),或者调用execve2)。在那之前,孩子与父母共享所有内存,包括堆栈。孩子不得从当前函数返回或调用exit(3),但可以调用_exit(2)。

4)与fork(2)一样,由vfork()创建的子进程继承了各种调用者的进程属性(例如,文件描述符,信号处置和当前工作目录)的副本。vfork()调用仅在处理虚拟地址空间方面有所不同。

 

测试代码

这里我就先分析下面几个例子,后续的例子在进程通信、线程池、进程同步中会附带的举例说明。本次我们就先深入了解fork()函数。

fork()进程的变量都是独立的,存在不同的地址中,不是共用的

#include<unistd.h>

#include<stdio.h>

intmain(void)

{

   int i=0;

   pid_t rpid;

   int count=0;

   rpid=fork();

   if(rpid<0){

        return -1;

   }

   if(rpid==0){

     printf(" child  %d %d %d\n",getpid(),getppid(),rpid);

     count++; //child process add count

   } else{

    printf(" parent %d %d%d\n",getpid(),getppid(),rpid);
    count=100; //parent process assign 100 to count
    }

   printf("pid%d,%d\n",getpid(),count); //print process id and count value;
   return 0;
}

 

运行结果:

parent1417 1095 1418

pid1417,100

 child 1418 1 0

pid 1418,1

从运行结果上可以看出父进程先运行,子进程如果没有了父进程那么此时它的父进程ID就会是1。并且,count在父进程和子进程的值不一样。父进程的count为100,子进程的count为1。

fork()函数的调用次数与数量分析

两次fork()

#include<unistd.h>

#include<stdio.h>

intmain(void)

{
   int i=0;

   pid_t rpid;
   rpid=fork();

   if(rpid<0){
        return -1;
   }

   if(rpid==0)
     printf(" child  %d %d %d\n",getpid(),getppid(),rpid);
       else
    printf(" parent %d %d%d\n",getpid(),getppid(),rpid);

   rpid=fork();

   if(rpid<0){
        return -1;
   }

   if(rpid==0)
     printf(" child  %d %d %d\n",getpid(),getppid(),rpid);
       else
    printf(" parent %d %d%d\n",getpid(),getppid(),rpid);
    return 0;

}

测试结果

parent1689 1095 1690

 child 1690 1689 0

 parent 1689 1095 1691

 parent 1690 1 1692

 child 1691 1 0

 child 1692 1 0

从测试结果可以看出来,进程1689为调用fork的进程,经过第一次调用生成1690子进程。再调用第二次fork的时候1689又生成一个子进程为1691,由于系统先运行父进程,所以子进程的父进程ID重新设置为1 。第一次生成的子进程也会调用第二个fork函数来生成子进程1692。同理,父进程先运行,子进程1692的父进程也设置为1。如下图所示:


三次fork()

 

#include<unistd.h>

#include<stdio.h>

intmain(void)

{

   int i=0;

   pid_t rpid;
   rpid=fork();

   if(rpid<0){
        return -1;
   }
   if(rpid==0)
     printf(" child  %d %d %d\n",getpid(),getppid(),rpid);
       else
    printf(" parent %d %d%d\n",getpid(),getppid(),rpid);

   rpid=fork();

   if(rpid<0){
        return -1;
   }

   if(rpid==0)
     printf(" child  %d %d %d\n",getpid(),getppid(),rpid);
      else
    printf(" parent %d %d%d\n",getpid(),getppid(),rpid);

   rpid=fork();

   if(rpid<0){
        return -1;
   }

   if(rpid==0)
     printf(" child  %d %d %d\n",getpid(),getppid(),rpid);
       else
    printf(" parent %d %d%d\n",getpid(),getppid(),rpid);
   return 0;

}

 

运行结果:

parent1842 1095 1843

 parent 1842 1095 1844

 parent 1842 1095 1845

 child 1843 1842 0

 child 1845 1 0

 parent 1843 1 1846

 child 1844 1 0

 child 1846 1843 0

 parent 1843 1 1847

 parent 1844 1 1848

 child 1847 1 0

 child 1848 1 0

 parent 1846 1 1849

 child 1849 1 0

 

从上面的运行结果来看,主进程1842总是优先执行。在执行的过程中调用了3次fork。第一次fork生成1843进程,第二次fork生成1844进程,第三次fork生成1845进程。接下来1843运行第二个fork和第三个fork分别生成进程1846和进程1847。1846进程又运行了最后一个fork生成1849。总进程第二个fork生成的1844进程运行第三个fork生成子进程1848。进程1845是进程在调用第三个fork生成的子进程,由于程序没有fork了,所以它就没有子进程。进程的关系如下图所示:

进程的总数与调用fork的关系

     从数学推导中可以推出进程的总数与调用fork的关系为f(N) = 2^N。

参考资料

1.man fork

 


猜你喜欢

转载自blog.csdn.net/xiangguiwang/article/details/80142398