先用几个概念铺垫下:
进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。在早期面向进程设计的计算机结构中,进程是程序的基本执行实体;在当代面向线程设计的计算机结构中,进程是线程的容器。程序是指令、数据及其组织形式的描述,进程是程序的实体。
fork()函数通过系统调用创建一个与原来进程几乎完全相同的进程,也就是两个进程可以做完全相同的事,但如果初始参数或者传入的变量不同,两个进程也可以做不同的事。一个进程调用fork()函数后,系统先给新的进程分配资源,例如存储数据和代码的空间。然后把原来的进程的所有值都复制到新的新进程中,只有少数值与原来的进程的值不同。相当于克隆了一个自己。
本篇文章主要围绕fork()展开。fork函数在新的子进程中运行相同的代码,新的子进程是父进程的一个复制品
下面先上代码主函数:
int main(int argc, char *argv[])
{
int option = 0;
if (argc > 1)
option = atoi(argv[1]);
switch(option) {
case 0: fork0();
break;
case 1: fork1();
break;
case 2: fork2();
break;
case 3: fork3();
break;
case 4: fork4();
break;
case 5: fork5();
break;
case 6: fork6();
break;
case 7: fork7();
break;
case 8: fork8();
break;
case 9: fork9();
break;
case 10: fork10();
break;
case 11: fork11();
break;
case 12: fork12();
break;
case 13: fork13();
break;
case 14: fork14();
break;
case 15: fork15();
break;
case 16: fork16();
break;
case 17: fork17();
break;
default:
printf("Unknown option %d\n", option);
break;
}
return 0;
}
主函数主要就是通过一个swith开关语句,用于分别调用不同的fork函数。
接下来fork0():
void fork0()
{
if (fork() == 0) {
printf("Hello from child\n");
}
else {
printf("Hello from parent\n");
}
}
这段代码运行结果如下:
我们可以看到输出两次Hellow。这是一个简单的fork()程序。所以我们直接在脑海里跑遍代码。其中,fork()创建一个进程,返回值为零时,即为子进程。子进程中只执行输出Hellow from child。
接下来fork1():
在这里插入代码片
oid fork1()
{
int x = 1;
pid_t pid = fork();
if (pid == 0) {
printf("Child has x = %d\n", ++x);
}
else {
printf("Parent has x = %d\n", --x);
}
printf("Bye from process %d with x = %d\n", getpid(), x);
}
运行结果:
这里fork创建进程并用getpid返回了任务的编号,当进行子进程时,子进程得到与父进程用户级虚拟地址空间相同的(但独立)的一份副本,包括代码,数据段等等,所以这时x=1然后执行++语句,然后如图所示。同时,我们可以看到子进程的任务编号为16788,父进程同理可得。
2,下面fork2
void fork2()
{
printf("L0\n");
fork();
printf("L1\n");
fork();
printf("Bye\n");
}
运行结果如下:
我们可以看到,出现了多个fork,这时我们接用进程图来理解运行结果。
然后我们用类似于遍历二叉树的顺序发现正如运行结果一样。
void fork3()
{
printf("L0\n");
fork();
printf("L1\n");
fork();
printf("L2\n");
fork();
printf("Bye\n");
}
这时的fork更加多了,子进程的个数也变成了很多,画出进程图如下:
然而惊讶的事情发生了,他没有按照便利二叉树的顺序输出,那我们再改动改动代码。
void fork3()
{
printf("L0\n");
fork();
printf("L1\n");
fork();
printf("L2\n");
if(fork()==0){
printf("childen,Bye\n");
}
else {
printf("father,Bye\n");
}
}
运行结果:
由此可见,只是因为Cpu在某个时间段实现另一个子进程。
4,
运行结果
fork4()
{
printf("L0\n");
if (fork() != 0) {
printf("L1\n");
if (fork() != 0) {
printf("L2\n");
}
}
printf("Bye\n");
}
5,
void fork5()
{
printf("L0\n");
if (fork() == 0) {
printf("L1\n");
if (fork() == 0) {
printf("L2\n");
}
}
printf("Bye\n");
}
6,
void cleanup(void) {
printf("Cleaning up\n");
}
void fork6()
{
atexit(cleanup);
fork();
exit(0);
}
在这个代码中,我们遇见了一个陌生的代码atexit,然后我们查阅一下其它的博客
发现atexit用来注册在程序正常终止时调用的函数。那么创建子进程时,同样也要调用cleanup,所以输出两个Cleaning up。
7,
void fork7()
{
if (fork() == 0) {
/* Child */
printf("Terminating Child, PID = %d\n", getpid());
exit(0);
} else {
printf("Running Parent, PID = %d\n", getpid());
while (1)
; /* Infinite loop */
}
}
这里的代码与上面的不同的地方在于,父进程调用时存在一个死循环所以不会正常退出,单子进程没有收到影响,说明cpu在处理两个进程时,将简单的子进程先跑完,再处理父进程。详见8。
8,
void fork8()
{
if (fork() == 0) {
/* Child */
printf("Running Child, PID = %d\n",getpid());
while (1)
; /* Infinite loop */
} else {
printf("Terminating Parent, PID = %d\n",
getpid());
exit(0);
}
}
这里,就是在7的基础上将死循环放在子进程,用ps命令发现代表子进程的任务编号还在运行。
9,
void fork9()
{
int child_status;
if (fork() == 0) {
printf("HC: hello from child\n");
exit(0);
} else {
printf("HP: hello from parent\n");
wait(&child_status);
printf("CT: child has terminated\n");
}
printf("Bye\n");
}
这里,我们看见出现了wait(),并且运行结果中父进程没有执行完,就转去执行子进程了,所以不难猜测,wait的作用就是让cpu先去执行等待的子进程,所以输出的结果顺序有一点差异。
10,
void fork10()
{
pid_t pid[N];
int i, child_status;
for (i = 0; i < N; i++)
if ((pid[i] = fork()) == 0) {
exit(100+i); /* Child */
}
for (i = 0; i < N; i++) { /* Parent */
pid_t wpid = wait(&child_status);
if (WIFEXITED(child_status))
printf("Child %d terminated with exit status %d\n",
wpid, WEXITSTATUS(child_status));
else
printf("Child %d terminate abnormally\n", wpid);
}
}
我们又遇到了wait,那么还是找专业的,所以都是返回子进程的任务编号。
WIFEXITED:如果子进程通过调用exit或者一个返回(return)正常终止,就返回真。在这个代码中返回的都是真。
WEXITSTATUS:返回一个正常终止的子进程的退出状态。然后,我们还会发现输出是倒着的,这个根据进程图也不难理解。
11,
void fork11()
{
pid_t pid[N];
int i;
int child_status;
for (i = 0; i < N; i++)
if ((pid[i] = fork()) == 0)
exit(100+i); /* Child */
for (i = N-1; i >= 0; i--) {
pid_t wpid = waitpid(pid[i], &child_status, 0);
if (WIFEXITED(child_status))
printf("Child %d terminated with exit status %d\n",
wpid, WEXITSTATUS(child_status));
else
printf("Child %d terminate abnormally\n", wpid);
}
}
这里我们需要明白wait与waitpid的区别
12,接下来一个面试题
#include "csapp.h"
int x=0;
/* $begin fork */
/* $begin wasidefork */
int Fork()
{
x++;printf("%d\n",x);
return fork();
}
int main(int argc, char *argv[])
{
Fork();
Fork()&&Fork()||Fork();
Fork();
printf("%d*\n",x);
}
/* $end fork */
/* $end wasidefork */
运行结果