首先我们要了解fork是什么函数?
复刻(英语:fork,又译作派生、分支)是UNIX或类UNIX中的分叉函数,在Linux中执行man fork
即可认识fork。
根据文档描述我们可以知道,fork系统调用用于创建一个新进程,称为子进程,它与进程(称为系统调用fork的进程)同时运行,此进程称为父进程。创建新的子进程后,两个进程将执行fork()系统调用之后的下一条指令。
#include
pid_t fork(void);
// pid_t是一个宏定义,其实质是 int 被定义在#include 中,pid_t定义的类型都是进程号类型。
// 返回值:若成功调用一次则返回两个值,子进程返回 0,父进程返回子进程PID(一个正数)。出错返回 -1。
当一个进程调用fork之后,就有两个二进制代码相同的进程。而且它们都运行到相同的地方。但每个进程都将可以开始它们自己的旅程,看如下程序。
int main()
{
pid_t pid;
printf("Before: pid is %d\n", getpid());
if ( (pid=fork()) == -1 )
{
perror("fork()");
exit(1);
}
printf("After:pid is %d, fork return %d\n", getpid(), pid);
sleep(1);
return 0;
}
//运行结果:
Before: pid is 43676
After:pid is 43676, fork return 43677
After:pid is 43677, fork return 0
这里看到了三行输出,一行Before,两行After。进程43676先打印Before消息,然后打印After。另一个After消息是进程43677打印的。注意到进程43677没有打印before,为什么呢?
进程调用fork,当控制转移到内核中的fork代码后,内核会:
分配新的内存块和内核数据结构给子进程
将父进程部分数据结构内容拷贝至子进程
添加子进程到系统进程列表当中
fork返回,开始调度器调度
所以,fork之前父进程独立执行,fork之后,父子两个执行流分别执行。注意,fork之后,谁先执行完全由调度器决定。
通常,父子进程代码共享,父子进程再不写入时,数据也是共享的,当任意一方试图写入数据时,便以写时拷贝的方式各自一份副
本。具体见下图:
下面我用几个例子帮助大家更好的理解fork调用的细节。
例1:
int main()
{
fork();//fork1
fork();//fork2
printf("hello world\n");
return 0;
}
我们规定,main进程编号为父1
第一次fork,创建新的进程子1,和原本父1。
第二次fork,父1和子1分别创建新进程子2和子3,原来存在着父1和子1。
经历两次fork后,一共四个进程,打印四次hello。
例2:
int main()
{
for(int i = 0;i < 2;i++)
{
fork();
printf("A\n");//遇到\n会自动刷新缓冲区
}
exit(0);
}
for循环执行2次,i=0时,第一次fork,父1创建新进程子1,父1和子1各自执行完毕代码,打印两个A。
父1和子1在打印过后,两个进程都i++,进入i=1的循环中,fork之后父1创建进程子2,子1创建进程子3,四个进程,打印4个A。
例3:
int main()
{
for(int i = 0;i < 2;i++)
{
fork();
printf("-");//不会刷新缓冲区
}
exit(0);
}
这题的关键点在于printf函数中没有’\n’,所以不会在执行完printf函数后立即刷新缓冲区,还会将要打印的字符放在缓冲区中,直到程序结束,才一次性打印到屏幕上。
注意:父子进程代码共享,当任意一方试图写入数据时,便以写时拷贝的方式各自一份副本。所以父子进程拥有同样的缓冲区。
这个程序一共有4个进程,执行了6次printf,前两次的printf没有打印到显示器,只是存在了缓冲区中,后四次的printf在原先的缓冲区中追加了“-”,也就是说后四次printf每一次打印“–”,一共是8个“-”。
例4:
int main()
{
fork() || fork();
printf("A\n");
exit(0);
}
首先我们要知道 || 是如何执行的,举个例子:
A || B ,从左到右,如果A为真,不执行B,直接返回。如果A为假,执行B,根据B的真假返回。
在本题中,第一次fork后出现了两种情况,父进程自己返回一个 >0 的数,可以看做为真,所以不执行第二个fork,直接printf打印,但fork出来的子进程返回 ==0 还需要执行第二个fork才可以判断,所以子进程又创建了一个子进程。
例5:
int main()
{
fork() && fork();
printf("A\n");
exit(0);
}
与上一题类似,A && B; 执行时,如果A为真,继续执行B。如果A为假,不执行B。
第一次fork后出现了两种情况,第一次fork出来的子进程返回 ==0 ,不执行第二个fork,直接printf打印。父进程自己返回一个 >0 的数,可以看做为真,执行第二个fork,父进程又创建了一个子进程,分别打印。
例6:
int main()
{
fork();
fork() && fork() || fork();
fork();
}
第一个fork之后,新建一个子进程,因为进程过多,且这两个父子进程完全相同,所以我们只分析一边。
第二个fork之后,分为两种情况,父进程 >0 继续执行第三个fork,子进程 ==0不执行第三个fork,执行第四个fork。
第三个fork之后,又分为两种情况,父进程 >0 ,&&运算之后依旧为真,不执行第四个fork。子进程 ==0,&&运算过后为假,执行第四个fork。
第四个,第五个fork均无特殊情况出现。
最后统计父进程下一共有10个进程,子进程同样也有10个,总计20个进程。