目录
一,僵尸进程
1,僵尸进程
2,僵尸进程的危害
二,孤儿进程
1,孤儿进程
三,进程等待
1,进程等待的必要性
2,wait 方法
3,waitpid 方法
4,回收小结
一,僵尸进程
1,僵尸进程
僵死状态(Zombies)是一个比较特殊的状态。
当进程退出并且父进程(使用wait()系统调用,后面讲) 没有读取到子进程退出的返回代码时就会产生僵死(尸)进程 僵死进程会以终止状态保持在进程表中,并且会一直在等待父进程读取退出状态代码。
所以,只要子进程退出,父进程还在运行,但父进程没有读取子进程状态,子进程进入Z状态
来一个创建维持30秒的僵死进程例子:
#include <stdio.h>
#include <stdlib.h>
int main()
{
pid_t id = fork();
if (id < 0) {
perror("fork");
return 1;
}
else if (id > 0) { //parent
printf("parent[%d] is sleeping...\n", getpid());
sleep(30);
}
else {
printf("child[%d] is begin Z...\n", getpid());
sleep(5);
exit(EXIT_SUCCESS);
}
return 0;
}
当fork()函数返回值,父进程和子进程同时启动,当子进程运行结束退出进程时,父进程还在等待,此时子进程一直在等待父进程读取其退出状态代码,于是产生了僵尸状态;
僵尸状体会造成内存泄漏,我们要尽可能避免;
2,僵尸进程的危害
进程的退出状态必须被维持下去,因为他要告诉关心它的进程(父进程),你交给我的任务,我办的怎么样了。可父进程如果一直不读取,那子进程就一直处于Z状态?是的!
维护退出状态本身就是要用数据维护,也属于进程基本信息,所以保存在task_struct(PCB)中,换句话说,Z状态一直不退出,PCB一直都要维护?是的!
那一个父进程创建了很多子进程,就是不回收,是不是就会造成内存资源的浪费?是的!因为数据结构对象本身就要占用内存,想想C中定义一个结构体变量(对象),是要在内存的某个位置进行开辟空间!
内存泄漏?是的!
二,孤儿进程
1,孤儿进程
父进程如果提前退出,那么子进程后退出,进入Z之后,那该如何处理呢?
父进程先退出,子进程就称之为“孤儿进程”
孤儿进程被1号init进程领养,当然要有init进程回收喽。
我们写一个进程来感受一下:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main()
{
pid_t id = fork();
if (id < 0) {
perror("fork");
return 1;
}
else if (id == 0) {//child
printf("I am child, pid : %d\n", getpid());
sleep(10);
}
else {//parent
printf("I am parent, pid: %d\n", getpid());
sleep(3);
exit(0);
}
return 0;
}
fork()函数返回后,子进程和父进程同时运行,当父进程执行结束并且退出后,子进程还在运行,此时子进程沦为孤儿!
一般孤儿进程最后会被1号init进程领养,不会造成内存泄漏;
三,进程等待
1,进程等待的必要性
之前讲过,子进程退出,父进程如果不管不顾,就可能造成‘僵尸进程’的问题,进而造成内存泄漏。
另外,进程一旦变成僵尸状态,那就刀枪不入,“杀人不眨眼”的kill -9 也无能为力,因为谁也没有办法杀死一个已经死去的进程。
最后,父进程派给子进程的任务完成的如何,我们需要知道。如子进程运行完成,结果对还是不对, 或者是否正常退出。
父进程通过进程等待的方式,回收子进程资源,获取子进程退出信息
2,wait 方法
pid_t wait(int*status);
返回值: 成功返回被等待进程pid,失败返回-1。
参数: 输出型参数,获取子进程退出状态,不关心则可以设置成为NULL
#include <stdio. h>
#include <unistd.h>
#include <stdlib.h>
#include<sys/types.h>
#include<sys / wait.h>
int main()
{
pid_t id = fork();
if (id < 0) {
perror("fork");
return 1;
}
else if (id == 0) {
//parent
printf("child[%d] is sleeping...\n", getpid());
sleep(5);
exit(3);
}
int status = 0;
pid_t rid = wait(&status);
if (rid > 0)
{
printf("parent[%d] is begin Z...\n", rid);
sleep(10);
}
else
{
printf("进程失败:rid:%d\n", rid);
}
return 0;
}
这样子 wait 会自动及时回收子进程,不会形成僵尸,不会造成内存泄漏;
3,waitpid 方法
pid_ t waitpid(pid_t pid, int *status, int options);
返回值: 当正常返回的时候waitpid返回收集到的子进程的进程ID;
如果设置了选项WNOHANG,而调用中waitpid发现没有已退出的子进程可收集,则返回0;
如果调用中出错,则返回-1,这时errno会被设置成相应的值以指示错误所在;
参数:
pid: Pid=-1,等待任一个子进程。与wait等效。 Pid>0.等待其进程ID与pid相等的子进程。 status: WIFEXITED(status):
若为正常终止子进程返回的状态,则为真。
(查看进程是否是正常退出) WEXITSTATUS(status): 若WIFEXITED非零,提取子进程退出码。(查看进程的退出码) options: WNOHANG:
若pid指定的子进程没有结束,则waitpid()函数返回0,不予以等待。若正常结束,则返回该子进 程的ID。
#include <stdio. h>
#include <unistd.h>
#include <stdlib.h>
#include<sys/types.h>
#include<sys / wait.h>
int main()
{
pid_t id = fork();
if (id < 0) {
perror("fork");
return 1;
}
else if (id == 0) {
//parent
printf("child[%d] is sleeping...\n", getpid());
sleep(5);
exit(3);
}
int status = 0;
pid_ t rid = waitpid(-1,&status,0);
if (rid > 0)
{
printf("parent[%d] is begin Z...\n", rid);
sleep(10);
}
else
{
printf("进程失败:rid:%d\n", rid);
}
return 0;
}
4,回收小结
如果子进程已经退出,调用wait/waitpid时,wait/waitpid 会立即返回,并且释放资源,获得子进程退出信息。
如果在任意时刻调用 wait/waitpid,子进程存在且正常运行,则进程可能阻塞。
如果不存在该子进程,则立即出错返回。