经典linux把进程分为5个状态,运行态,停止态,等待态(两种),僵死态。
还有一种划分法: 就绪态,运行态,挂起态,停止态。
如果想要父进程一次创建多个子进程,应该使用如下代码:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main()
{
pid_t pid;
int i;
for(i=0;i<4;i++)
{
pid=fork();
if (pid==-1){
perror("fork");
exit(1);
}
else if(pid==0)
break;
}
if (i<4)
{
printf("i am %d child,pid=%u\n",i+1,getpid());
}
return 0;
}
如果想创建4个子进程,直接写fork的话,实际上会创建出2的4次方-1个,因为每一个子进程在循环时也在调用fork,创建出了孙进程。
所以必须屏蔽掉子进程本身的创建,核心在于如果子进程运行(pid=0)推出循环。
运行结果:
再详细梳理一下代码的执行流程,在if语句中添加
else if(pid==0)
break;
else
{
printf("parant\n");
}
执行结果:
应该是正在执行一次父进程,再执行一次子进程的交替过程。
这个结果不一定能保证准确,取决于调度,为了尽量让它按照顺序走,可以添加sleep语句(也不是100%)。
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main()
{
pid_t pid;
int i;
for(i=0;i<4;i++)
{
pid=fork();
if (pid==-1){
perror("fork");
exit(1);
}
else if(pid==0)
break;
else
{
printf("parant\n");
}
}
if (i<4)
{
// sleep(i);
printf("i am %d child,pid=%u\n",i+1,getpid());
}
else
{
sleep(3);
printf("parant\n");
}
return 0;
}
这样基本能保证父进程是最后一个结束的。
当代码有了微小变化后,其执行顺序就发生了变化:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main()
{
pid_t pid;
int i;
for(i=0;i<4;i++)
{
pid=fork();
if (pid==-1){
perror("fork");
exit(1);
}
else if(pid==0)
break;
else
{
printf("parant is runnig\n");
}
}
if (i<4)
{
// sleep(i);
printf("i am %d child\n,pid=%d",i+1,getpid());
}
else
{
sleep(3);
printf("parant end\n");
}
return 0;
}
执行结果:
我们可以对子进程也使用sleep来控制它们的执行顺序,以达到相应子进程先结束,父进程最后结束。
把对子进程和父进程的处理方法,写在for循环函数之外,让架构更加清晰。
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main()
{
pid_t pid;
int i;
for(i=0;i<4;i++)
{
pid=fork();
if (pid==-1){
perror("fork");
exit(1);
}
else if(pid==0)
break;
}
if (i<4)
{
sleep(i);
printf("i am %d child,pid=%d\n",i+1,getpid());
}
else
{
sleep(i);
printf("parant end\n");
}
return 0;
}
孤儿进程:父进程先于子进程结束,内核会把这个子进程当作孤儿进程,并由init进程“领养”,由它来对子进程进行回收。
僵尸进程:子进程终止,父进程尚未回收,子进程会在内核中残留pcb,变成僵尸进程。僵尸进程无法由kill语句直接杀死,但可以通过kill杀死僵尸进程的父进程,然后僵尸进程转换为孤儿进程,并由init进程“领养”来回收。
进程回收:
一个进程在终止时会关闭所有文件描述符,释放在用户空间分配的内存,但它的pcb还保留着。如果是异常终止则pcb能保留终止的信号,以便查询,这个进程的父进程可以调用wait或waitpid函数获取这些信息,然后彻底清除掉这个子进程。
wait函数有3个功能:
①阻塞等待子进程退出。
②回收子进程残留资源。
③获取子进程结束状态(退出原因)。
对以上代码稍加修改,让父进程一直循环不结束(不回收),而子进程都已经结束,于是产生了僵尸进程。
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main()
{
pid_t pid;
int i;
for(i=0;i<4;i++)
{
pid=fork();
if (pid==-1){
perror("fork");
exit(1);
}
else if(pid==0)
break;
}
if (i<4)
{
sleep(i);
printf("i am %d child,pid=%d\n",i+1,getpid());
}
else
{
sleep(i);
while(1);
//printf("parant end\n");
}
return 0;
}
调用一次wait只能回收一个子进程。调用成功返回子进程的id,错误返回-1.
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
int main()
{
pid_t pid;
int i;
for(i=0;i<4;i++)
{
pid=fork();
if (pid==-1){
perror("fork");
exit(1);
}
else if(pid==0)
break;
}
if (i<4)
{
sleep(i);
printf("i am %d child,pid=%d\n",i+1,getpid());
}
else
{
while(wait(NULL));
while(1)
{
};
//printf("parant end\n");
}
return 0;
}
利用 while(wait(NULL)) 就可以完成多个子进程的回收,不会产生僵尸进程。
同时wait是阻塞父进程,那么sleep也没有必要了,能保证父进程最后结束。
waitpid函数功能更强大,pid_t waitpid(pid_t pid, int *status, int options);
第一个参数想回收哪一个子进程,如果参数是>0,那么就是指定的子进程ID;
如果参数=-1,回收任意子进程,相当于wait;
如果参数=0,回收当前调用waitpid一个组的所有子进程;
如果参数<-1,回收制定进程组内的任意子进程;
简易的轮询写法:
do
{
//
//每隔2秒钟去做
sleep(2);
}while()
无论是wait还是waitpid,参数status都保存了子进程终止时的信息,利用4个宏,就能分析该信息。
WIFEXITED()如果真,再调用WEXITSTATUS()获取子进程的退出状态,说白了就是找exit(n)里面那个小n的值。
WIFSIGNALED()如果真,再调用WTERMSIG()获取子进程终止的信号编号。
掌握execlp p–path 通常用来执行系统可执行程序调用
execl 通常用来执行用户自定义可执行程序调用
相当于c语言在windows下的system。