Linux fork()进程树的两种实现方法
刚完成操作系统的实验一,其中附加题是使用fork()实现一颗满二叉树形态的进程树,觉得好玩,做完之后就记录一下。
1. 暴力的做法
最简单粗暴的做法,当然就是直接把层数写死啦。例如要创建三层的进程树,就父进程fork()两次,然后在子进程里又fork()两次,这样就有三层了,且最后fork()出来的那层就不再继续生成,直接打印出“我的PID是xxx,我爸是xxx”就可以跟它say goodbye了。
下面贴一下拙略的代码:
#include <unistd.h>
#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>
int main(){
pid_t pid[2];// 记录生成的两个子进程的PID
printf("Root pid is %d.\n",getpid());
for( int i=0;i<2;++i ){
pid[i] = fork();
if( pid[i]==0 ){// 子进程
printf("My parent is %d, my pid is %d.\n",getppid(),getpid());
for( int j=0;j<2;++j ){// 跟外面那个for差不多(很暴力)
pid[j] = fork();
if( pid[j]==0 ){// 第三层了,可以结束了
printf("My parent is %d, my pid is %d.\n",getppid(),getpid());
printf("Process %d had exited.\n",getpid());
exit(0);
}else if( pid[j]!=-1 ){// 父进程
printf("Parent process %d create child process %d.\n",getpid(),pid[j]);
}else{
printf("Error: when %d fork()!\n",getpid());
}
}
// waitpid,给孩子收尸,免得孩子变成僵尸进程
for( int j=0;j<2;++j ){
waitpid(pid[j],NULL,0);
}
printf("Process %d had exited.\n",getpid());
exit(0);
}else if( pid[i]!=-1 ){
printf("Parent process %d create child process %d.\n",getpid(),pid[i]);
}else{
printf("Error: when %d fork()!\n",getpid());
}
}
// waitpid,给孩子收尸,免得孩子变成僵尸进程
for( int i=0;i<2;++i ){
waitpid(pid[i],NULL,0);
}
printf("Process %d had exited.\n",getpid());
exit(0);
}
怎么样,是不是真的很暴力。
代码中之所以可以每一层都是用pid[]这个数组去存子进程的pid,是因为使用fork()之后,子进程会复制父进程的代码、变量的值(只是数值而已,不是同一块内存)等,并从fork()语句下面开始执行。此时,子进程的数据跟父进程的数据在内存里已经不是同一块地址了,也就是说,在子进程中改变变量的值,不会对父进程产生影响。(不考虑实现了共享内存之类的东西哈)
上面代码有个很骚的地方,就是如果要求实现100层的进程树的话,那就要写99个for,骚气十足。(内心OS:鬼会有这么无聊的要求哦。。。)但是呢,作为一个准程序员,还是要想一波优化的啦,所以就想出了下面的这种写法。
2. 不写死层数的做法
刚才也说了嘛,fork()之后,子进程会复制父进程的数据,所以我们可以利用这一点,来实现终止条件的控制。
首先,可以用一个变量例如叫做layer(本英语渣每次取变量名都要搜一波英语单词怎么拼)来表示当前已经搞定几层了。因为一开始的进程可以看作是第一层,树的根节点嘛,所以layer初始赋值为1。之后在循环中,每fork()一层子进程,就在该进程中将当前的层数变量layer加一。由于fork()之后,子进程会复制父进程的数据,故此时在子进程中,layer的值与父进程是相同的。例如,根节点fork()出来的两个子进程的layer变量都是2,很符合要求。在子进程中判断layer是否已达到目标层数,如果是,则打印信息说一下byebye,使用exit(0)自杀就行了。如果不是,则修改循环的下标,继续从头开始fork()子进程。最后,在循环结束后,调用waitpid()收尸,防止子进程变成僵尸进程,结束程序。
感觉需要看一波代码,才能知道上面那堆东西在说什么Orz
代码如下:
#include <unistd.h>
#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>
int main(){
pid_t pid[2];
int layer = 1;
const int targetLayer = 3;// 目标层数
printf("Root pid is %d.\n",getpid());
for( int i=0;i<2; ){// 这里没有++i哦
if( i==0 ) layer++;// 条件控制,使得整个循环中layer只增加1
pid[i] = fork();
if( pid[i]==0 ){// 子进程
printf("My parent is %d, my pid is %d.\n",getppid(),getpid());
if( layer>=targetLayer ){// 层数够了,结束
printf("Process %d had exited.\n",getpid());
exit(0);
}else{
i = 0;// 从头开始循环,以便fork两次
}
}else if( pid[i]!=-1 ){// 父进程
printf("Parent process %d create child process %d.\n",getpid(),pid[i++]);//下标+1
}else{
printf("Error: when %d fork()!\n",getpid());
}
}
for( int i=0;i<2;++i ){
waitpid(pid[i],NULL,0);
}
printf("Process %d had exited.\n",getpid());
exit(0);
}
上面的代码只需要修改targetLayer的值即可控制要弄多少层。懒得每次都输入,就直接写个常量了
怎么样,对比之下是不是显得代码精炼了很多咧→_→
第一次在网上贴代码,有点瑟瑟发抖。如果诸位大佬发现有bug,欢迎指出一起探讨~~