linux进程之fork函数

来源:互联网 发布:阿波罗登月 知乎 编辑:程序博客网 时间:2024/05/22 17:02

fork函数:
fork函数的作用是从调用进程中创建一个新的进程,新的进程相当于是调用进程的副本,称为子进程,而调用进程称为父进程。
本节主要讲解父子进程之间的联系和区别。
函数原型:

#include <unistd.h>pid_t fork(void);

返回值:

  • 在父进程中,fork返回新创建子进程的进程ID。
  • 在子进程中,fork返回0。
  • 如果出现错误,fork返回一个负值。

fork调用为什么会返回两次呢?
fork调用的一个奇妙之处就是它仅仅被调用一次,却能够返回两次,这也是fork难以理解的点。
由于子进程复制了父进程的堆栈段,所以两个进程都停留在fork函数中,等待返回。因此fork函数会返回两次,一次是在父进程中返回,另一次是在子进程中返回,这两次的返回值是不一样的。

fork函数的作用:
fork()函数通过系统调用创建一个与原来进程几乎完全相同的进程。子进程是父进程的副本,子进程获得父进程数据空间、栈和堆的副本,这是子进程所拥有的副本,但是父子进程并不共享这些存储空间部分。

接下来看看示例例子,看看实验结果是不是如上所说的。
示例程序:

/* fork.c*/#include <unistd.h>  #include <stdio.h>  #include<stdlib.h>int main ()   {       pid_t pid;    int count=0;      if ((pid = fork()) < 0)    {        perror("fork");        exit(-1);    }    else if (pid == 0)    {        printf("child\n");        count++;    }    else    {        printf("parent\n");        sleep(1);    }    printf("pid = %ld,ppid = %ld, count = %d\n",(long)getpid(),(long)getppid(),count);     return 0;  }

以上程序fork之后,父进程相当于执行如下代码:

#include <unistd.h>  #include <stdio.h>  #include<stdlib.h>int main ()   {       pid_t pid;     int count=0;      printf("parent\n");    sleep(1);    printf("pid = %ld,ppid = %ld, count = %d\n",(long)getpid(),(long)getppid(),count);     return 0;  }

子进程相当于执行如下代码:

#include <unistd.h>  #include <stdio.h>  #include<stdlib.h>int main ()   {       pid_t pid; //pid表示fork函数返回的值      int count=0;      printf("child\n");    count++;    printf("pid = %ld,ppid = %ld, count = %d\n",(long)getpid(),(long)getppid(),count);     return 0;  }

实验结果:

ubuntu:~/test/process_test$ gcc fork.c -o fork  ubuntu:~/test/process_test$ ./forkparentchildpid = 88270,ppid = 88269, count = 1//隔一秒才打印下面父进程的logpid = 88269,ppid = 25872, count = 0

以上例子可以看出,子进程和父进程的count变量是不共享的,是各自存储空间的,从打印出来的count变量的值不相同可以证明这一点。
下面再来看一个例子,可以初步证明子进程是父进程的副本。

/* fork2.c*/#include <unistd.h>  #include <stdio.h>   #include<stdlib.h>int main ()   {       pid_t pid; //pid表示fork函数返回的值      int count=0;      if ((pid = fork()) < 0)    {        perror("fork");        exit(-1);    }    else if (pid == 0)    {        printf("child\n");        count++;        printf("child: pid = %ld,ppid = %ld, count = %d\n",(long)getpid(),(long)getppid(),count);     }    else    {        printf("parent\n");        sleep(1);        printf("parent:pid = %ld,ppid = %ld, count = %d\n",(long)getpid(),(long)getppid(),count);      }    return 0;  }

实验结果:

ubuntu:~/test/process_test$ gcc fork2.c -o fork2ubuntu:~/test/process_test$ ./fork2parentchildchild: pid = 88754,ppid = 88753, count = 1//隔一秒才打印下面父进程的logparent:pid = 88753,ppid = 25872, count = 0

从实验结果可以看出,fork2.c和fork.c效果是一致的,证明了fork.c中父进程printf代码会被子进程拷贝一个副本。一般来说,在fork之后是父进程先执行还是子进程先执行是不确定的,这取决于内核所使用的调度算法。所以,这里我们让父进程sleep再打印log,是为了让子进程先执行打印log,方便我们对结果的分析。
还有一个重要的点是,子进程和父进程共享同一个文件偏移量。
下面用实例程序来测试文件偏移量是否共享。

/* open_fork.c*/#include <unistd.h>  #include <stdio.h>  #include<stdlib.h>#include <sys/types.h>#include <sys/stat.h>#include <fcntl.h>#define FILENAME "file_fork"/**测试文件偏移量是否共享*/int main ()   {       pid_t pid; //pid表示fork函数返回的值      int count=0;      char write_child_buf[] = "11111";    char write_parent_buf[] = "22222";    int fd = open(FILENAME,O_RDWR | O_CREAT,0777);    if(fd < 0)    {        perror("open");    }    if ((pid = fork()) < 0)    {        perror("fork");        exit(-1);    }    else if (pid == 0)    {        printf("child\n");        if (write(fd,write_child_buf,sizeof(write_child_buf)))        {            perror("write");        }        count++;        printf("child: pid = %ld,ppid = %ld, count = %d\n",(long)getpid(),(long)getppid(),count);     }    else    {        printf("parent\n");        sleep(2);        if (write(fd,write_parent_buf,sizeof(write_parent_buf)))        {            perror("write");        }        printf("parent:pid = %ld,ppid = %ld, count = %d\n",(long)getpid(),(long)getppid(),count);      }    return 0;  }

实验结果:

ubuntu:~/test/process_test$ ./open_forkparentchildwrite: Successchild: pid = 6737,ppid = 6736, count = 1write: Successparent:pid = 6736,ppid = 25872, count = 0ubuntu:~/test/process_test$ cat file_fork 1111122222chenting

从上面的结果中,我们看到,子进程向文件file_fork写入字符串“11111”,然后父进程向file_fork向文件写入字符串“22222”,可以看到字符串“22222”是追加到字符串“11111”后面的,说明父子进程文件描述符、文件偏移量是有被共享的。
(以下内容参考《unix环境高级编程》)
这种共享文件的方式使父子进程对同一文件使用了一个文件偏移量,如果父子进程都向标准输出进行写操作,如果父进程的标准输出已经重定向,那么子进程写到标准输出时,它将更新与父进程共享的该文件的偏移量。比如当父进程等待子进程时,子进程写到标准输出,而在子进程终止后,父进程也写到标准输出,并且其输出会添加在子进程所写数据之后,如果父子进程不共享同一文件偏移量,这种形式的交互很难实现。除了打开文件之外,父进程的很多其他属性也由子进程继承,包括:

  • 实际用户ID,实际组ID,有效用户ID,有效组ID。
  • 附加组ID。
  • 进程组ID。
  • session ID。
  • 控制终端。
  • 设置用户ID标志和设置组ID标志。
  • 当前工作目录。
  • 根目录。
  • 文件模式创建屏蔽字。
  • 信号屏蔽和安排。
  • 针对任意开打文件描述符的在执行时关闭(close-on-exec)标志。
    环境。
  • 连接的共享存储段。
  • 资源映射。
  • 资源限制。

父子进程的区别是:

  • fork返回值。
  • 进程ID不同。
  • 两个进程具有不同的父进程ID。
  • 子进程的tms_utime,tms_stime,tms_cutime以及tms_ustime均设置为0.
  • 父进程设置的文件锁不会被子进程继承。
  • 子进程的未处理alarm被清除。
  • 子进程的未处理信号设置为空集。