目录
进程的退出
Linux进程的退出分为正常退出和异常退出两种:
正常退出
a.在main()函数中执行return
b.调用exit()函数,标准c库
c.调用_exit()函数或者_Exit(),属于系统调用
d.进程最后一个线程返回
e.最后一个线程调用pthread_exit
异常退出
a.调用abort函数
b.进程收到某个信号,而该信号使程序终止。如ctrl+c
c.最后一个线程对取消(cancellation)请求做出响应
不管进程如何终止,最后都会执行内核中的同一段代码。这段代码为相应进程关闭所有打开描述符,释放它所使用的存储器等。对上述任意一种终止情形,我们都希望终止进程能够通知其父进程它是如何终止的。对于三个终止函数(exit、_exit和_Exit),实现这一点的方法是,将其退出状态(exit status)作为参数传送给函数。在异常终止情况下,内核(不是进程本身)产生一个指示其异常终止原因的终止状态(termination status)。在任意一种情况下,该终止进程的父进程都能用wait或waitpid函数取得其终止状态。
僵尸进程和孤儿进程
两者简介
在linux中,正常情况下,子进程是通过父进程创建的,子进程再创建新的进程。子进程的结束和父进程的运行是一个异步过程,即父进程永远无法预测子进程到底什么时候结束。 当一个子进程完成它的工作终止之后,它的父进程需要调用wait()或者waitpid()系统调用取得子进程的终止状态。
孤儿进程:一个父进程退出,而它的一个或多个子进程还在运行,那么那些子进程将成为孤儿进程。孤儿进程将被init进程(进程号为1)所收养,并由init进程对它们完成状态收集工作。
僵尸进程:一个进程使用fork创建子进程,如果子进程退出,而父进程并没有调用wait或waitpid获取子进程的状态信息,那么子进程的进程描述符仍然保存在系统中。这种进程称之为僵死进程。
简单来说就是子先走父不收尸就是僵尸,父早于子先走就是孤儿。我们在后面wait函数用代码深入了解僵尸进程和孤儿进程
问题及其危害
在每个进程退出的时候,内核释放该进程所有的资源,包括打开的文件,占用的内存等。 但是仍然为其保留一定的信息(包括进程号the process ID,退出状态the termination status of the process,运行时间the amount of CPU time taken by the process等)。直到父进程通过wait / waitpid来取时才释放。 但这样就导致了问题,如果进程不调用wait / waitpid的话, 那么保留的那段信息就不会释放,其进程号就会一直被占用,但是系统所能使用的进程号是有限的,如果大量的产生僵死进程,将因为没有可用的进程号而导致系统不能产生新的进程. 此即为僵尸进程的危害。
孤儿进程是没有父进程的进程,每当出现一个孤儿进程的时候,内核就把孤儿进程的父进程设置为init,而init进程会循环地wait()它的已经退出的子进程。因此孤儿进程并不会有什么危害。
附:任何一个子进程(init除外)在exit()之后,并非马上就消失掉,而是留下一个称为僵尸进程(Zombie)的数据结构,等待父进程处理。这是每个子进程在结束时都要经过的阶段。如果子进程在exit()之后,父进程没有来得及处理,这时用ps命令就能看到子进程的状态是“Z”。如果父进程能及时处理,可能用ps命令就来不及看到子进程的僵尸状态,但这并不等于子进程不经过僵尸状态。如果父进程在子进程结束之前退出,则子进程将由init接管。init将会以父进程的身份对僵尸状态的子进程进行处理。
wait和waitpid函数
函数简介
wait函数一般用在父进程中等待回收子进程的资源,而防止僵尸进程的产生。
当父进程调用wait()函数时:
a. 如果其所有子进程都还在运行,则阻塞;
b.只要有一个进程终止,wait就会返回。也就是说只要wait接收到一个SIGCHLD信号,wait()就会返回。对于两个或多个子进程的情况,需要多次调用。
c.如果一个子进程已经终止,正等待父进程获取其终止状态,则取得该子进程的终止状态立即返回;
d.如果它没有任何子进程,则立即出错返回;
当父进程调用waitpid时:
waitpid()的作用和wait()一样,但它并不一定要等待第一个终止的子进程,它还有若干选项,如可提供一个非阻塞版本的wait()功能等。
函数头文件、参数以及返回值
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *status);
pid_t waitpid(pid_t pid, int *status, int options);
wait()与fork()配套出现,如果在fork()之前调用wait(), wait返回-1,正常情况下,应返回销毁的子进程的pid。参数stauts用来保存被收集进程退出时的状态,它是一个指向int类型的指针,如果我们对这个子进程如何结束的不在意,只想把这个僵尸进程消灭掉,就把这个参数置为NULL。如果status的值不是NULL,wait把子进程的退出状态取出并存入其中,这是一个整数值(int)它指出了子进程是正常退出还是非正常结束,以及正常结束的返回值,或被哪个信号结束等信息。使用macro来获取这些信息。
waitpid函数参数:
pid:
pid==-1 等待任何一个子进程,此时waitpid的作用与wait相同
pid >0 等待进程ID与pid值相同的子进程
pid==0 等待与调用者进程组ID相同的任意子进程
pid<-1 等待进程组ID与pid绝对值相等的任意子进程
option:
WNOHANG waitpid将不阻塞如果指定的pid并未结束
WUNTRACED 如果子进程进入暂停执行情况则马上返回,但结束状态不予以理会。
wait函数与waitpid函数区别
从本质上讲,wait和waitpid的作用是完全相同的,但是waitpid多了两个参数,从而使我们的编程能更加的灵活。
waitpid有wait没有的三个功能:
(1)waitpid能等待一个特定的子进程,而wait只能等待任意的子进程,
(2)系统一旦调用wait函数就会阻塞父进程来等待,直到等到子进程的退出才停止阻塞,而waitpid提供了一种非阻塞方式的等待,也就是第三个参数,当第三个参数设置为WNOHANG,当子进程没有结束,直接返回0,不等待
(3)waitpid支持作业控制,提供用于检查wait和waitpid返回状态的宏这两个函数返回的子进程的状态都保存在status指针中
wait函数实战
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
int main()
{
pid_t pid;
int cnt = 0;
pid = fork();
if(pid > 0)
{
wait(NULL);//等待子进程结束
while(1)
{
printf("cnt = %d\n",cnt);
printf("this is father print = %d\n",getpid());
sleep(1);
}
}
else if(pid == 0)
{
while(1)
{
printf("this is child print,child pid = %d\n",getpid());
sleep(1);
cnt++;
if(cnt == 5)
{
exit(0);//结束子进程
}
}
}
return 0;
}
编译运行可以看到调用wait后,父进程等待子进程推出之后执行
修改部分代码,给wait传参,用于接收进程退出时的状态,并打印出值
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
int main()
{
pid_t pid;
int cnt = 0;
int status = 10;
pid = fork();
if(pid > 0)
{
wait(&status);
printf("child quit,child status = %d\n",WEXITSTATUS(status));
while(1)
{
printf("cnt = %d\n",cnt);
printf("this is father print = %d\n",getpid());
sleep(1);
}
}
else if(pid == 0)
{
while(1)
{
printf("this is child print,child pid = %d\n",getpid());
sleep(1);
cnt++;
if(cnt == 5)
{
exit(3);
}
}
}
return 0;
}
编译运行
孤儿进程代码实现
让父进程打印父进程pid之后退出,子进程继续执行
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
int main()
{
pid_t pid;
int cnt = 0;
int status = 10;
pid = fork();
if(pid > 0)
{
printf("this is father print, pid = %d\n",getpid());
}
else if(pid == 0)
{
while(1)
{
printf("this is child print,child pid = %d,my father pid = %d\n",getpid(),getppid());
sleep(1);
cnt++;
if(cnt == 6)
{
exit(3);
}
}
}
return 0;
}
编译运行发现,在父进程退出之前,子进程getppid获取父进程pid与父进程一致,在父进程退出后子进程getppid获取父进程pid为init的1,这时孤儿进程被init进程收留
编程实现僵尸进程
让子进程先退出,父进程不处理
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
int main()
{
pid_t pid;
pid = fork();
if(pid > 0)
{
sleep(30);//30秒的时间足够通过子进程pid搜索子进程状态
}
else if(pid == 0)
{
printf("Child process exit!!!\n");
printf("this is father print = %d\n",getpid());//打印子进程pid
sleep(2);
}
return 0;
}
编译运行后 ps -aux | grep xxxx搜索子进程状态,发现是僵尸进程(Z)