孤儿进程:
父进程先死了,子进程被init进程领养,即子进程的父进程pid变成了1
这个子进程就叫孤儿进程
僵尸进程:
子进程死了,但是父进程没有回收子进程的资源(子进程的资源必须由父进程回收,操作系统不会自动回收)
任何进程都会存在僵尸进程阶段,只不过有些及时被回收了
因为僵尸进程是已经死掉的进程,因此用kill没办法回收僵尸进程
子进程回收
一个进程终止时关闭所有文件描述符,释放在用户空间分配的内存,但它的PCB还保留的,内核在其中保留一些信息:
如果是正常终止,则保留着退出状态
如果是异常终止,则保存导致该进程终止的信号是哪个
终止进程的父进程可以调用wait或waitpid获取这些信息,然后彻底清除掉这个进程
wait函数
作用:
1 阻塞等待,即在父进程中调用的wait会一直等待子进程死亡然后再进行回收,子进程不死亡就不执行wait下边的语句
2 回收子进程资源, 释放PCB
3 查看死亡原因,即获取status
pid_t wait(int *status)
status: 是一个传出参数,即定义一个整型变量,然后把该整型变量传进wait函数中就行,然后操作系统会给该形参复制,从而获取进程的结束状态(正常死亡还是异常死亡,怎么死亡)
返回值:如果成功,返回已经终止的子进程的pid,如果失败返回-1
int main(){
pid_t pid = fork();
if(pid == 0){
cout<<"孩子"<<endl;
sleep(2);
}
else if(pid > 0){
cout<<"父进程"<<endl;
int status; // 整型数,用来接收子进程终止状态
pid_t wpid = wait(&status); // 如果子进程不死,则父进程会停留再这一行语句阻塞等待,如果不看终止状态的话,直接传入NULL就行,不用特地传个整型数
if(WIFEXITED(status)){
cout<<"退出状态"<<WEXITEDSTATUS(status)<<endl;
}
if(WIFSIGNALED(status)){
cout<<"孩子被几号信号杀死?"<<WTERMSIG(status)<<endl;
}
cout<<"孩子die"<<endl;
while(1){
sleep(1);
}
}
return 0;
}
子进程死亡原因可以把status传入几个宏来获取
正常死亡:WIFEXITED(status) == true, 代表正常死亡,WEXITSTATUS(status)得到退出状态(退出状态一般指的是子进程的返回值,比如return 101, 那就是101,exit(544),那就是544)
非正常死亡:WIFSIGNALED(status) == true, 代表非正常死亡,WTERMSIG得到是第几号信号将其杀死的
waitpid函数
pid_t waitpid(pie_t pid, int *status, int options)
返回值:
参数pid:
>0 回收指定ID的子进程
-1 回收任意子进程,这个时候waitpid和wait的作用完全一样,就是当有一个子进程终止了以后去回收它
0 回收进程组ID与当前父进程的进程组ID相同的子进程,这说明了一个问题,虽然一般情况下子进程的进程组ID等于父进程的进程组ID,但是子进程的进程组ID也是可以不等于其父进程的进程组ID的,这种情况下就不能用pid=0来回收该进程了,所以还是用pid=-1比较好,默认情况下,所有子进程的进程组ID都是其父进程ID
-1 回收指定进程组内的任何子进程,这个进程组的组ID就是pid的绝对值,这意味着有多个子进程其组ID相同,但是与父进程的组ID不同,但血浓于水,父进程还是要回收他们
options:
可以为0或者WNOHANG
0: 跟wait一样,waitpid会发生阻塞等待
WNOHANG: 如果没有子进程退出,会立刻返回,即没有阻塞等待
wait和waitpid的一个区别是wait没得选,只要有子进程退出就得去处理,而waitpid可以选择要去处理哪种类型子进程,是否阻塞等待处理
因为waitpid是没有阻塞等到,如果当前没有需要回收得进程,则会马上退出,所以这里用了个while循环来回收死掉了子进程
回收多个子进程
wait和waitpid一次都是只能回收一个子进程,如果要回收多个子进程,可能就要用一个循环依次回收了
现在给出waitpid回收逻辑
if(i == 5){
// 父进程的逻辑
while(1){
pid_t wpid = waitpid(-1, NULL, WNOHANG);
if(wpid == -1){
// 说明已经没有子进程
break;
}
else if(wpid > 0){
cout<<"回收子进程ID"<<wpid<<endl;
}
}
}