父进程等待子进程退出
我们创建子进程的目的,说白了就是让子进程为我们干活,干完活后会正常退出(exit),也有可能没干完异常退出(abort,或ctrl+c)。因此我们需要等待子进程退出,并收集退出状态。子进程退出状态不被收集会编程僵尸进程。
c
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
int main(){
//pid_t getpid(void);
pid_t pid;
int cnt = 0;
pid = vfork();
if(pid > 0){
while(1){
printf("%d\n",cnt);
printf("this is father process,father pid is:%d\n",getpid());
sleep(1);
}
}else if(pid == 0){
while(1){
printf("this is son process,son pid is:%d\n",getpid());
sleep(1);
cnt++;
if(cnt == 3){
exit(0);
}
}
}
return 0;
}
在这段代码中,我们只是调用exit(0)退出进程,而没有收集退出状态,因此这个进程会变为僵尸进程,我们可以通过查看进程状态来观察:
this is son process,son pid is:14192
this is son process,son pid is:14192
this is son process,son pid is:14192
3
this is father process,father pid is:14191
3
this is father process,father pid is:14191
CLC 14191 0.0 0.0 4160 428 pts/0 S+ 10:56 0:00 ./myPro
CLC 14192 0.0 0.0 0 0 pts/0 Z+ 10:56 0:00 [myPro]
CLC 14194 0.0 0.0 13588 940 pts/4 S+ 10:56 0:00 grep --color=auto myPro
父进程id为14191 状态为S+说明正在运行,而子进程id为14192,状态为Z+说明是僵尸进程。
wait函数
因此我们需要调用wait函数来收集退出状态,防止子进程变为僵尸进程:
#include <sys/types.h>
#include <sys/wait.h>
*pid_t wait(int status); //参数是一个指针,是一个地址
在父进程中调用wait函数,会让子进程先退出后父进程在开始运行。
status参数
NULL
当我们不关心子进程的退出码时,直接wait(NULL);即可
在父进程中加入wait(NULL);运行后结果:
this is son process,son pid is:14259
this is son process,son pid is:14259
this is son process,son pid is:14259
3
this is father process,father pid is:14258
3
this is father process,father pid is:14258
子进程的pid为14259,父进程pid为14258,之后去检查子进程的运行状态:
CLC 14258 0.0 0.0 4160 428 pts/0 S+ 11:08 0:00 ./myPro
CLC 14261 0.0 0.0 13588 936 pts/4 S+ 11:08 0:00 grep --color=auto myPro
可以看到子进程已经退出,不是僵尸进程了。
非空
子进程的退出状态放在它所指向的地址中。
WIFEXITED(status)
此时我们需要调用特定的宏去解析,如果为正常终止子进程返回的状态,则为真(1).对于这种情况可执行WEXITSTATUS(status),取子进程传送给exit,_exit或_Exit参数的低8位。
c
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
int main(){
//pid_t getpid(void);
pid_t pid;
int cnt = 0;
int status = 10;
pid = fork();
if(pid > 0){
wait(&status);
printf("child process quit,WIFEXITED=%d,WEXITSTATUS=%d\n",WIFEXITED(status),WEXITSTATUS(status));
while(1){
printf("this is father process,father pid is:%d\n",getpid());
sleep(1);
}
}else if(pid == 0){
while(1){
printf("this is son process,son pid is:%d\n",getpid());
sleep(1);
cnt++;
if(cnt == 3){
exit(3);
}
}
}
return 0;
}
运行结果:
this is son process,son pid is:14392
this is son process,son pid is:14392
this is son process,son pid is:14392
child process quit,WIFEXITED=1,WEXITSTATUS=3
this is father process,father pid is:14391
WIFSIGNALED(status)
若为异常终止子进程返回的状态,则为真(接到一个不捕捉的信号)。对于这种情况,可执行WTERMSIG(status),取使进程终止的信号编号。
waitpid
wait与waitpid的区别:wait使调用者阻塞,waitpid有一个选项,可以使调用者不阻塞
*pid_t waitpid(pid_t pid, int status, int options);
pid_t pid
pid == -1 等待任一子进程。就这一方面而言,waitpid和wait等效
pid > 0 等待其进程id与pid相等的子进程
通常使用pid > 0在父进程中,由fork创建的子进程的pid号就等于父进程fork的返回值
pid == 0 等待其组id等于调用进程组id的任一子进程
pid < -1 等待其组id等于pid绝对值的任一子进程
int options常量参数
WCONTINUED :若实现支持作业控制,那么由pid指定的任一子进程在暂停后已经继续,但其状态尚未报告,则返回其状态。
WNOHANG :若由pid指定的子进程并不是立即可用的,则waitpid不阻塞,此时返回值为0
WUNTRACED:若某实现支持作业控制,而由pid指定的任一子进程已处于暂停状态,并且其状态自暂停以来还未报告过,则返回其状态。
使用wait时会阻塞父进程,先运行子进程。如果我们希望不阻塞父进程,可以将options改为WNOHANG:
c
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
int main(){
//pid_t getpid(void);
pid_t pid;
int cnt = 0;
int status = 10;
pid = fork();
if(pid > 0){
while(1){
//wait(&status);
waitpid(pid,&status,WNOHANG);
printf("child process quit,WIFEXITED=%d,WEXITSTATUS=%d\n",WIFEXITED(status),WEXITSTATUS(status));
printf("this is father process,father pid is:%d\n",getpid());
sleep(1);
}
}else if(pid == 0){
while(1){
printf("this is son process,son pid is:%d\n",getpid());
sleep(1);
cnt++;
if(cnt == 3){
exit(3);
}
}
}
return 0;
}
运行结果:
this is father process,father pid is:14535
this is son process,son pid is:14536
this is father process,father pid is:14535
this is son process,son pid is:14536
两个进程同时运行,不会发生阻塞的情况。
但是此时我们查看进程运行状态可以发现,虽然我们调用waitpid读取的子进程退出状态,但是子进程仍然是Z+僵尸进程。因此就目前的学习情况来说,还是使用wait来收集退出状态比较好,在未来waitpid会有他自己的应用。
孤儿进程
父进程如果不等待子进程退出,在子进程之前就结束了自己的生命,此时子进程叫做孤儿进程。
linux为了避免系统存在过多孤儿进程,init进程(进程id=1)收留孤儿进程,变成孤儿进程的父进程。
我们可以使父进程比子进程先结束,让父进程只执行一段printf函数而子进程执行三次,打印观察子进程的父进程id(通过getppid()读取父进程的pid)的变化:
c
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
int main(){
//pid_t getpid(void);
pid_t pid;
int cnt = 0;
int status = 10;
pid = fork();
if(pid > 0){
printf("this is father process,father pid is:%d\n",getpid());
}else if(pid == 0){
while(1){
printf("this is son process,son pid is:%d,my father pid is:%d\n",getpid(),getppid());
sleep(1);
cnt++;
if(cnt == 3) exit(0);
}
}
return 0;
}
运行结果:
this is father process,father pid is:15583
this is son process,son pid is:15584,my father pid is:15583
this is son process,son pid is:15584,my father pid is:1
this is son process,son pid is:15584,my father pid is:1
可以看到父进程的pid为15583,最开始子进程的父进程pid还是15583,此时父进程还没有结束,之后父进程比子进程先结束了,那么子进程就变成了孤儿进程,被init进程收留,init进程的pid为1.