Linux应用编程—2.fork()函数
fork()函数用来创建子进程,函数具体功能与使用方法一起看编程手册。Linux终端命令下输入:man fork,敲击回车键即可打开fork函数详情页。
2.1 fork()函数详情
首先看SYNOPSIS:
我们可以知道调用fork函数所需的头文件,以及fork函数的函数原型。pid_t fork(void).这个函数不需要传参,返回值是一个pid_t类型的。
其次看具体描述:
DESCRIPTION
fork() creates a new process by duplicating the calling process. The new process is referred to as the child process. The calling process is referred to as the parent
process.
The child process and the parent process run in separate memory spaces. At the time of fork() both memory spaces have the same content. Memory writes, file mappings
(mmap(2)), and unmappings (munmap(2)) performed by one of the processes do not affect the other.
大意是讲,fork()函数通过拷贝调用进程创建了一个新的进程。这个新的进程被称作子进程,这个调用进程被称作父进程。子进程和父进程运行在独立的内存空间内,他们有着相同的内容。
最后看返回值的含义:
RETURN VALUE
On success, the PID of the child process is returned in the parent, and 0 is returned in the child. On failure, -1 is returned in the parent, no child process is created,
and errno is set appropriately.
当fork()函数调用成功后,父进程返回子进程的id,子进程返回值为0,当返回值为-1的时候,子进程创建失败。
2.2 fork()编程应用
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main(void)
{
pid_t pid_1;
pid_1 = fork();
printf("pid_1 = %d.\n", pid_1);
printf("Hello World.\n");
return 0;
}
运行结果:
pid_1 = 4015.
Hello World.
pid_1 = 0.
Hello World.
看到运行结果,总共有4条语句被打印了出来,这是fork函数的作用。执行 pid_1 = fork();这条语句时,创建了子进程,我们称它为子进程B,调用进程我们称为父进程A。子进程B是父进程A的拷贝,所以这两个进程都会执行后面的printf打印。所以如我们看到的打印结果,一共有4条语句。前两条是父进程A打印出来的,其中pid_1的值4015是子进程的id号。后两条是子进程B打印出来的,所以返回值为0。
拓扑如下:
总结:fork()函数会创建一个子进程,这个子进程是父进程的拷贝,运行在不同的内存空间里。所以子进程也会执行父进程里相同的代码。
2.3 fork()编程进阶应用
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
int main(void)
{
pid_t pid_1, pid_2;
pid_1 = fork();
pid_2 = fork();
printf("pid_1 = %d, pid_2 = %d.\n", pid_1, pid_2);
return 0;
}
运行结果:
pid_1 = 4209, pid_2 = 4210.
pid_1 = 4209, pid_2 = 0.
pid_1 = 0, pid_2 = 0.
sgk@ubuntu:~/Documents/Linux_Program$ pid_1 = 0, pid_2 = 4211.
main函数中只有一句printf函数,但实际有4条语句被打印出来。
2.3.1 为什么打印出来了4条语句呢?
直接看拓扑结构:
当执行pid_1 = fork()时,调用进程我们称为父进程A,它创建了子进程B。AB俩进程分别继续执行pid_2 = fork(),所以A进程继续创建了进程C,B进程也创新的它的子进程,我们称它为进程D。到此为止,一共有4个进程。每个进程分别执行printf里的内容,所以一共打印了4条。
2.3.2 这4条语句具体是由哪一个进程打印出来的呢?
上面我们分析过,fork()函数的返回值有3种,1是父进程返回它创建的子进程的id号;2是子进程返回0;3是子进程创建失败,返回-1。
pid_1 = 4209, pid_2 = 4210.
pid_1 = 4209, pid_2 = 0.
pid_1 = 0, pid_2 = 0.
pid_1 = 0, pid_2 = 4211.
第一条语句,它打印的结果是两个子进程的ID号。说明它第一次调用fork()函数时,是作为父进程,第二次调用fork()函数时也是作为父进程。所以它是进程A打印出来的。
第二条语句,它打印结果是一个子进程的ID号,”好像是一个父进程“。打印的第二个值是0,说明第二次作为了子进程。似乎ABCD没有符合这样一个关系逻辑的进程。因为子进程是父进程的拷贝,父进程的内容有pid_1 = 4209,所以这个子进程也将pid_1 = 4209打印了出来,自己是作为一个子进程,第二次调用fork()函数时被创建,返回值为0。所以它是进程C打印出来的,它的父进程是进程A。
第三条语句,pid_1 = 0,pid_2 = 0;第一次调用fork函数时,作为子进程被创建,第二次调用fork函数时,也作为子进程被创建。它的父进程也是有其它进程创建的一个子进程,所以,符合这个关系特点的进程时进程D,它的父进程是进程B。
第四条语句,pid_1 = 0, pid_2 = 4211;第一次调用fork函数时,作为子进程被创建,第二次调用fork函数时,作为父进程创建了新的子进程,返回了子进程的ID号。满足这个关系的进程只有进程B,它的父进程是进程A。
综上,上述语句从上到下依次由进程A、进程C、进程D与进程B打印。
2.3.2 ABCD四个进程的id号是多少?
进程A第一次调用fork函数创建了子进程B,所以子进程B的id号为:4209。进程A第二次调用fork函数创建了进程C,所以进程C的id号为4210。进程B创建了子进程D,所以D的id号是:4211。至于父进程A的id号,目前打印的信息无法得到。但是可以通过进程B调用getppid函数获得。
2.4 子进程与父进程的特点
2.4.1
函数中调用fork函数后,后面的代码父子进程就会分开执行。我们利用父子进程调用fork函数后返回的值不同来做一个判断,如果是父进程,则执行一段代码;如果是子进程,则执行另一段代码。
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main(void)
{
pid_t pid;
pid = fork();
if(pid > 0) // 父进程
{
while(1)
{
printf("Parent process.\n"); // 死循环,每隔1秒钟,打印一次。
sleep(1);
}
}
else if(pid == 0) // 子进程
{
while(1)
{
printf("Child process.\n"); 死循环,每隔1秒钟,打印一次。
sleep(1);
}
}
else
perror("child process create failed!");
return 0;
}
运行结果:
Parent process.
Child process.
Parent process.
Child process.
Child process.
Parent process.
Parent process.
Child process.
Parent process.
Child process.
Parent process.
Child process.
Parent process.
Child process.
Parent process.
Child process.
Child process.
Parent process.
Child process.
Parent process.
Parent process.
Child process.
Child process.
Parent process.
Child process.
Parent process.
Child process.
Parent process.
Child process.
Parent process.
^C
根据打印结果来看:打印内容每隔1秒钟,成对打印。父子进程看上去是并发执行的,也就是同时执行的。如果继续探讨父子进程谁先执行,谁后面执行。根据邴老师的讲解,Linux内核2.6版本以后,官方介绍,默认情况下父进程先运行,因为父进程的代码处于活跃状态,也是为了程序效率,所以父进程先运行。
2.4.2
我们定义并初始化一个全局变量count = 0,分别在父子进程中对它进行+1,并且打印出来。如果父进程先执行+1操作,那么子进程中会从1开始进行+1操作嘛?
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int count = 0; // 定义一个全局变量,并初始化为0.
int main(void)
{
pid_t pid;
pid = fork();
if(pid > 0)
{
while(1)
{
printf("Parent process, count = %d.\n", count++); // +1
sleep(1);
}
}
else if(pid == 0)
{
while(1)
{
printf("Child process, count = %d.\n", count++); // +1
sleep(2);
}
}
else
perror("child process create failed!");
return 0;
}
运行结果:
Parent process, count = 0.
Child process, count = 0.
Parent process, count = 1.
Parent process, count = 2.
Child process, count = 1.
Parent process, count = 3.
Parent process, count = 4.
Child process, count = 2.
Parent process, count = 5.
Child process, count = 3.
Parent process, count = 6.
Parent process, count = 7.
Child process, count = 4.
Parent process, count = 8.
Parent process, count = 9.
Child process, count = 5.
Parent process, count = 10.
Parent process, count = 11.
Child process, count = 6.
Parent process, count = 12.
Parent process, count = 13.
Child process, count = 7.
Parent process, count = 14.
Parent process, count = 15.
Child process, count = 8.
Parent process, count = 16.
Parent process, count = 17.
Child process, count = 9.
Parent process, count = 18.
Parent process, count = 19.
^C
可以看出,父进程每隔1秒钟+1,从0加到了19。子进程每隔2秒+1,从0加到了9。尽管count是一个全局变量,但是在父子进程中分别独立,只是变量名相同。
2.4.3
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int count = 0;
int main(void)
{
pid_t pid;
int i = 0;
pid = fork();
if(pid > 0)
{
for(i = 0; i < 5; i++)
{
printf("Parent process, count = %d.\n", count++);
sleep(1);
}
}
else if(pid == 0)
{
while(1)
{
printf("Child process, count = %d.\n", count++);
sleep(1);
}
}
else
perror("child process create failed!");
return 0;
运行结果:
Parent process, count = 0.
Child process, count = 0.
Parent process, count = 1.
Child process, count = 1.
Child process, count = 2.
Parent process, count = 2.
Parent process, count = 3.
Child process, count = 3.
Child process, count = 4.
Parent process, count = 4.
Child process, count = 5.
sgk@ubuntu:~/Documents/Linux_Program$ Child process, count = 6.
Child process, count = 7.
Child process, count = 8.
Child process, count = 9.
Child process, count = 10.
Child process, count = 11.
Child process, count = 12.
Child process, count = 13.
Child process, count = 14.
^C
根据结果可以看到,父进程结束后,子进程任然在执行。所以,父进程不影响子进程。
2.5 总结
fork函数用来创建子进程,调用后,父进程返回子进程id号。子进程返回0,创建失败返回-1 。子进程是父进程的拷贝,与父进程执行相同的代码。严格意义上来说,父进程先运行,但是宏观上看,父子进程是并发执行的。父子进程运行在不同的代码空间内,相互不影响。父进程的生命周期对子进程也无影响。