UNIX 中进程创建有两个函数分别是 fork 和 vfork 函数,下面对这两个函数进行分析。
fork 函数
在 UNIX 系统中,一个现有进程可以调用 fork 函数创建一个新进程。调用 fork 函数的进程称为父进程,由 fork 创建的新进程被称为子进程(child process)。fork函数被调用一次但返回两次,两次返回的唯一区别是子进程中返回0值而父进程中返回子进程ID。首先看下fork函数的原型;
/* 创建进程 */
/* fork 函数 */
/*
* 函数功能:创建一个新的进程;
* 返回值:
* (1)在父进程中,返回新创建子进程的进程ID;
* (2)在子进程中,返回0;
* (3)若出错,则返回-1;
* 函数原型:
*/
#include <unistd.h>
pid_t fork(void);
fork 函数调用一次,返回两个值,在父进程中,返回新建子进程的进程ID,因为一个父进程可能有多个子进程,没有获取子进程ID的函数,所以返回子进程的进程ID;在子进程中,返回0,因为子进程的父进程ID可以通过函数 getppid 获取;子进程是父进程的副本,它将获得父进程数据空间、堆、栈等资源的副本(UNIX 系统是采用写时复制),意味着父子进程间不共享这些存储空间。UNIX将复制父进程的地址空间内容给子进程,因此,子进程有了独立的地址空间。fork
之后父进程和子进程的执行顺序是不确定的,根据内核所使用的进程调度算法来执行。
下面先看一个比较简单的例子:
#include "apue.h"
#include <unistd.h>
int main(void)
{
pid_t pid;
int count = 0;
pid = fork();//创建一个子进程;
if(pid < 0)
{
err_sys("Created child process error.\n");
exit(-1);
}
else if(0 == pid)//在子进程中,返回0
{
printf("I am back in the child process, my ID: %d and my parent ID: %d\n",getpid(),getppid());
printf("the count is: %d\n",++count);
}
else //在父进程中,返回新建子进程的ID
{
printf("I am back in the parent process, my ID: %d and my parent ID: %d\n",pid,getpid());
printf("the count is: %d\n",++count);
}
exit(1);
}
输出结果:
I am back in the parent process, my ID: 10684 and my parent ID: 10683
the count is: 1
I am back in the child process, my ID: 10684 and my parent ID: 10683
the count is: 1
从输出结果我们可以知道,调用一次fork函数,会返回两次,根据程序的输出,返回之后是先执行父进程,在父进程中,返回值是新建子进程的进程ID,所以此时pid的值即为新建子进程的进程ID,count的值增加1;当父进程结束后,调用子进程,在子进程中返回值是 pid=0,所以想要获取新建子进程的进程ID需要使用 getpid函数(相当于获取当前进程的进程ID),注意:count的值依然为1,因为父进程和子进程的数据空间是独立的,所以父子进程的数据不相互共享,count的初始值都还是0;
接着看第二个例子:
#include <unistd.h>
#include <stdlib.h>
#include "apue.h"
int main(void)
{
pid_t pid;
int count = 0;
printf("before fork,enter\n");
printf("before fork,no enter:pid=%d",getpid());
pid = fork();
if(pid < 0)
{
err_sys("Created child process error.\n");
exit(-1);
}
else if(0 == pid)//在子进程中,返回0
{
printf("\n");
printf("I am back in the child process, my ID: %d and my parent ID: %d\n",getpid(),getppid());
printf("the count is: %d\n",++count);
printf("\n");
}
else //在父进程中,返回新建子进程的ID
{
printf("\n");
printf("I am back in the parent process, my ID: %d and my parent ID: %d\n",pid,getpid());
printf("the count is: %d\n",++count);
printf("\n");
}
exit(1);
}
输出结果:
before fork,enter
before fork,no enter:pid=11384
I am back in the parent process, my ID: 11385 and my parent ID: 11384
the count is: 1
before fork,no enter:pid=11384
I am back in the child process, my ID: 11385 and my parent ID: 11384
the count is: 1
从结果可以看到,有换行符的 printf 语句输出一次,就是说只在一个进程中输出;
printf("before fork,enter\n");
没有换行符的 printf 语句输出二次,就是说在每一个进程都输出,这里只有两个进程,所以输出两次,多个进程会输出多次;
printf("before fork,no enter:pid=%d",getpid());
出现以上 printf 输出不同的原因很简单,因为 printf 是把数据存储在缓冲区中,在缓冲队列等待输出,若遇到换行符或者刷新缓冲区时,会直接打印到屏幕。所以第一个语句带有换行符时是直接把数据打印到屏幕,不在缓冲队列等待输出,因此,子进程复制父进程的数据段时,stdout输出缓冲区并不存在数据,则只在父进程输出一次;没有换行符的 printf 函数把数据保存在缓冲区队列中,子进程把该数据复制,因此,父、子进程各自输出一次。
vfork 函数
vfork 函数也是在现有的进程上创建子进程,基本操作和fork 函数类似,与 fork 的区别如下:
- fork 的子进程的数据是复制父进程的数据,即数据独立;
- vfork 的子进程和父进程共享数据;
- fork 子进程和父进程的执行顺序根据内核进程调度算法决定;
- vfork 的执行顺序是,先执行子进程,再执行父进程;
例如例子1使用 vfork 创建子进程时,输出结果跟上面的不一样;
#include "apue.h"
#include <unistd.h>
int main(void)
{
pid_t pid;
int count = 0;
pid = vfork();//创建一个子进程;
if(pid < 0)
{
err_sys("Created child process error.\n");
exit(-1);
}
else if(0 == pid)//在子进程中,返回0
{
printf("I am back in the child process, my ID: %d and my parent ID: %d\n",getpid(),getppid());
printf("the count is: %d\n",++count);
}
else //在父进程中,返回新建子进程的ID
{
printf("I am back in the parent process, my ID: %d and my parent ID: %d\n",pid,getpid());
printf("the count is: %d\n",++count);
}
exit(1);
}
输出结果:
I am back in the child process, my ID: 12171 and my parent ID: 12170
the count is: 1
I am back in the parent process, my ID: 12171 and my parent ID: 12170
the count is: 2
从结果中可以知道,vfork 函数创建的子进程是和父进程共享数据的,看count值的输出就知道,并且是先执行子进程,再执行父进程。
参考资料:
《UNIX高级环境编程》