fork函数
复刻(fork,又译作派生、分支)是UNIX或类UNIX中的分叉函数,fork函数将运行着的程序分为2个(几乎)完全一样的进程,每个进程都启动一个从代码的同一位置开始执行的线程。这两个进程中的线程继续执行,就像两个用户同时启动了该应用程序的两个副本。
从一个软件包拷贝了一份源代码然后在其上进行独立的开发,创建不同的软件,这个术语不只意味着版本控制上的分支,同时也意味着开发者社区的分割,是一种形式的分裂。
fork系统调用用于创建一个新进程,成为子进程,它与进程(称为系统调用fork的进程)同时运行,此进程称为父进程。创建新的子进程之后,两个进程将执行fork()系统调用之后的下一条指令。子进程使用相同的pc,相同的CPU寄存器,在父进程中使用的相同打开文件。
它不需要参数并返回一个整数值:
负值:创建子进程失败
零:返回到新创建的子进程
正值:返回到父进程或调用者。该值包含新创建的子进程的进程ID。
分叉函数头文件:
#include<unistd.h>/*#包含<unistd.h>*/ #include<sys/types.h>/*#包含<sys/types.h>*/
函数原型:
pid_t fork(void);
(pid_t是一个宏定义,其实质是int被定义在#include<sys/types.h>中)
返回值:若成功调用一次则返回两个值,子进程返回0,父进程返回子进程ID;否则,出错返回-1
函数说明
子进程是父进程的副本,它将获得父进程的数据空间、堆、栈等资源的副本。持有的是上述存储空间的“副本”,意味着父子进程之间不共享这些存储空间。
UNIX将复制父进程的地址空间内容给子进程,因此,子进程有了独立的地址空间。在不同的UNIX系统下,我们无法确定fork之后是子进程先运行还是父进程先运行,这些依赖于系统的实现。所以在移植代码的时候我们不应该对此作出任何的假设。
为什么fork会返回两次?
由于在复制时复制了父进程和堆线段,所以两个进程都停留在fork函数中,等待返回。因此fork会返回两次,一次是在父进程中返回,另一次是在子进程中返回,这两次的返回值是不一样的。
fork调用的一个奇妙之处就是它仅仅调用一次,却能够返回两次,他可能有三种不同的返回值,可以通过返回值来判断当前进程是子进程还是父进程。
(1)在父进程中,fork返回新创建子进程的ID;
(2)在子进程中,fork返回0;
(3)如果出现错误,fork返回一个负值。
fork函数返回的值为什么在父子进程中不同。其实就相当于链表,进程形成了链表,父进程的函数返回的值指向子进程的进程id,因为子进程没有子进程,所以fork函数返回的值为0;
#include<unistd.h> #include<stdio.h> #include<stdlib.h> int main(int argc,char *argv[]){ pid_t pid=fork(); if ( pid < 0 ) { fprintf(stderr,"错误!"); } else if( pid == 0 ) { printf("子进程空间"); exit(0); } else { printf("父进程空间,子进程pid为%d",pid); } // 可以使用wait或waitpid函数等待子进程的结束并获取结束状态 exit(0); }
样例代码存在着父进程在子进程结束前结束的可能性。必要的时候可以使用wait或waitpid函数让父进程等待子进程的结束并获取子进程的返回状态。
fork()在linux系统中的返回值是没有NULL的。
Error Codes
出错返回错误信息如下:
EAGAIN
达到进程数的上限
ENOMEM
没有足够空间给一个新进程分配
fork的另一个特性是所有父进程打开的描述符都被复制到子进程中。父、子进程中相同的编号的文件描述符在内核中指向同一个file结构体,也就是说,file结构体的引用计数要增加。
#include <unistd.h> #include <string.h> #include <stdlib.h> #include <stdio.h> #include <sys/wait.h> void sys_err(const char *str) { perror(str);//将上一个函数发生错误的原因输出到标准设备,参数 s 所指的字符串会先打印出, // 后面再加上错误原因字符串。此错误原因依照全局变量errno的值来决定要输出的字符串。 exit(1); } int main(void) { pid_t pid;//创建进程利用pid_t类型定义进程号,实际上就是int型 char buf[1024]; int fd[2];//文件描述符 char *p = "test for pipe\n"; if (pipe(fd) == -1)//创建管道失败 sys_err("pipe"); pid = fork();//创建子进程 if (pid < 0) { sys_err("fork err"); } else if (pid == 0) { close(fd[1]);//关闭子进程的写端 int len = read(fd[0], buf, sizeof(buf));//读取数据 write(STDOUT_FILENO, buf, len); close(fd[0]);//关闭读端 } else { close(fd[0]);//父进程关闭读端 write(fd[1], p, strlen(p));//从写端写入 wait(NULL);//等待子进程结束 close(fd[1]);//关闭写端 } return 0; }