一、函数wait、waitpid

一个进程在终止时会关闭所有文件描述符,释放在用户空间释放的内存,但它的PCB还保留着,内核在其中保存一些信息:如果是正常终止时则保存着退出状态,如果是异常终止则保存着导致该进程终止的信号是哪个,这个进程的父进程可以调用wait或waitpid获取这些信息,然后彻底清除这个进程,我们知道一个进程的退出状态可以在shell用特殊变量$?查看,因为shell是它的父进程,当它终止时shell调用wait或waitpid得到它的退出状态同时彻底清除这个进程。

1. wait函数原型:一次只能回收一个子进程

pid_t wait(int *status); 
  • 当进程终止时,操作系统隐式回收机制会:1. 关闭所有的文件描述符 2. 释放用户空间分配的内存。内核PCB仍存在,其中保存该进程的退出状态。(正常终止--------退出值;异常终止-------终止信号

2. 函数waitpid原型:
作用:同wait,但可指定pid进程清理,可以不阻塞( 一次只能回收一个子进程)

pid_t waitpid(pid_t pid, int *staloc, int options);

参数pid:

  • pid == -1:回收任一子进程
  • pid  >  0 :回收指定pid的进程
  • pid == 0 :回收与父进程同一个进程组的任一个子进程
  • pid < -1  :回收指定进程组内的任意子进程

参数 options:

  • 设置为WNOHANG:函数不阻塞;
  • 设置为0:函数阻塞。

3. 测试代码

#include <stdio.h>
#include <unistd.h>
#include<sys/wait.h>int main(int argc, const char* argv[])
{pid_t pid = fork();if (pid > 0) // 父进程{   printf("parent process, pid = %d, ppid = %d\n", getpid(), getppid());int status;pid_t wpid = wait(&status);if (WIFEXITED(status)) printf("exit value: %d", WEXITSTATUS(status));if (WIFSIGNALED(status)) printf("exit by signal: %d\n", WTERMSIG(status)); //是否被信号杀死printf(" die child pid = %d\n", wpid);}else if(pid == 0) {sleep(1);printf("child process, pid = %d, ppid = %d\n", getpid(), getppid());    }return 9;
}

输出结果:

二、孤儿进程、僵尸进程

  • 孤儿进程:一个父进程退出,而它的一个或多个子进程还在运行,那么那些子进程将成为孤儿进程。孤儿进程将被init进程(进程号为1)所收养,并由init进程对它们完成状态收集工作。
  • 僵尸进程:一个进程使用fork创建子进程,如果子进程退出,而父进程并没有调用wait或waitpid获取子进程的状态信息,那么子进程的进程描述符仍然保存在系统中。这种进程称之为僵死进程。

1. unix提供了一种机制可以保证只要父进程想知道子进程结束时的状态信息, 就可以得到。这种机制就是: 在每个进程退出的时候,内核释放该进程所有的资源,包括打开的文件,占用的内存等。 但是仍然为其保留一定的信息(包括进程号the process ID,退出状态the termination status of the process,运行时间the amount of CPU time taken by the process等)。直到父进程通过wait / waitpid来取时才释放。 但这样就导致了问题,如果进程不调用wait / waitpid的话, 那么保留的那段信息就不会释放,其进程号就会一直被占用,但是系统所能使用的进程号是有限的,如果大量的产生僵死进程,将因为没有可用的进程号而导致系统不能产生新的进程. 此即为僵尸进程的危害,应当避免。

2. 孤儿进程是没有父进程的进程,孤儿进程这个重任就落到了init进程身上,init进程就好像是一个民政局,专门负责处理孤儿进程的善后工作。每当出现一个孤儿进程的时候,内核就把孤 儿进程的父进程设置为init,而init进程会循环地wait()它的已经退出的子进程。这样,当一个孤儿进程凄凉地结束了其生命周期的时候,init进程就会代表党和政府出面处理它的一切善后工作。因此孤儿进程并不会有什么危害。

1. 僵尸进程测试

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>int main()
{pid_t pid;while (1){pid = fork();if (pid < 0){perror("fork error:");exit(1);}else if (pid == 0){printf("I am a child process.\nI am exiting.\n");exit(0); //子进程退出,成为僵尸进程}else{sleep(20); //父进程休眠20s继续创建子进程continue;}}return 0;
}

输出结果:

僵尸进程解决办法

1) . 通过信号机制

子进程退出时向父进程发送SIGCHILD信号,父进程处理SIGCHILD信号。在信号处理函数中调用wait进行处理僵尸进程。测试程序如下所示:

#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <stdlib.h>
#include <signal.h>
#include <sys/wait.h>static void sig_child(int signo)
{pid_t  pid;int    stat;while ((pid = waitpid(-1, &stat, WNOHANG)) > 0)  //处理僵尸进程printf("child %d terminated.\n", pid);
}int main()
{pid_t pid;signal(SIGCHLD, sig_child); //创建捕捉子进程退出信号pid = fork();if (pid < 0){perror("fork error:");exit(1);}else if (pid == 0){printf("I am child process,pid id %d.I am exiting.\n", getpid());exit(0);}printf("I am father process.I will sleep two seconds\n"); //等待子进程先退出sleep(2);system("ps -o pid,ppid,state,tty,command"); //输出进程信息printf("father process is exiting.\n");return 0;
}

输出结果:

2)fork两次

《Unix 环境高级编程》8.6节说的非常详细。原理是将子进程成为孤儿进程,从而其的父进程变为init进程,通过init进程可以处理僵尸进程。测试程序如下所示:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <sys/wait.h>int main()
{pid_t pid;pid = fork(); if (pid < 0){perror("fork error:");exit(1);}else if (pid == 0) //第一个子进程{printf("I am the first child process.  pid:%d\tppid:%d\n", getpid(), getppid());pid = fork();if (pid < 0){perror("fork error:");exit(1);}else if (pid > 0) //第一个子进程退出{printf("first procee is exited.\n");exit(0);}//第二个子进程//睡眠3s保证第一个子进程退出,这样第二个子进程的父亲就是init进程里sleep(3);printf("I am the second child process.  pid: %d\tppid:%d\n", getpid(), getppid());exit(0);}if (waitpid(pid, NULL, 0) != pid) //父进程处理第一个子进程退出{perror("waitepid error:");exit(1);}exit(0);return 0;
}

输出结果:

1. 孤儿进程与僵尸进程[总结]

二、exec函数族

1. 简介

  • 进程程序替换原理

fork创建子进程执行的是和父进程相同的程序(也有可能是某个分支),通常fork出的子进程是为了完成父进程所分配的任务,所以子进程通常会调用一种exec函数(六种中的任何一种)来执行另一个任务。当进程调用exec函数时,当前用户空间的代码和数据会被新程序所替换,该进程就会从新程序的启动历程开始执行。在这个过程中没有创建新进程,所以调用exec并没有改变进程的id。

  • 替换图解(图解)

(1). execl函数原型:

int execl(const char *path, const char *arg, ...);

分析:

  • path: 要执行的程序的绝对路径
  • 变参arg: 要执行的程序的需要的参数
  • 第一arg:占位
  • 后边的arg: 命令的参数
  • 参数写完之后: NULL
  • 一般执行自己写的程序
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(void)
{printf("entering main process---\n");if(execl("ls","ls","-l",NULL)<0)perror("excl error");return 0;
}

输出结果:

(2). execv函数原型:

int execv(const char *path, char *const argv[]);

分析:

  • path = /bin/ps
  • char* args[] = {"ps", "aux", NULL};
  • execv("/bin/ps", args);

(3). execlp函数原型

int execlp(const char *file, const char *arg, ...);

分析:

  • file: 执行的命令的名字
  • 第一arg:占位
  • 后边的arg: 命令的参数
  • 参数写完之后: NULL
  • 执行系统自带的程序
  • execlp执行自定义的程序: file参数绝对路径

(4). execvp函原型:

int execvp(const char *file, char *const argv[]);

(5). execle函数原型:

int execle(const char *path, const char *arg, ..., char *const envp[]);

分析:

  • path: 执行的程序的绝对路径  /home/itcast/a.out
  • arg: 执行的的程序的参数
  • envp: 用户自己指定的搜索目录, 替代PATH
  • char* env[] = {"/home/itcast", "/bin", NULL};
int execve(const char *path, char *const argv[], char *const envp[]);
函数名 参数格式 是否带路径 是否使用当前环境变量
execl 参数列表
execlp 参数列表
execle 参数列表
execv 参数数组
execvp 参数数组
execve 参数数组

7. 测试代码

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>int main()
{for (int i = 0; i < 8; ++i)printf(" parent i = %d\n", i);pid_t pid = fork();if (pid == 0){execlp("ps", "ps", "aux", NULL);perror("execlp");exit(1);}for (int i = 0; i < 3; ++i)printf("----------- i = %d\n", i);return 0;
}

参考资料

1. 操作系统重点知识汇总

函数wait、waitpid、孤儿进程、僵尸进程相关推荐

  1. linux系统编程学习_(2)进程控制-- fork函数、exec函数族、回收子进程--孤儿进程僵尸进程、wait函数

    linux系统编程学习_(2)进程控制-- fork函数.exec函数族.回收子进程–孤儿进程僵尸进程.wait函数 进程控制 fork()函数 创建一个子进程. pid_t fork(void); ...

  2. 守护进程/僵尸进程/孤儿进程

    一 守护进程 守护进程就是在后台运行,不与任何终端关联的进程,,一个守护进程的父进程是init进程,它是一个孤儿进程,没有控制终端,所以任何输出,无论是向标准输出设备stdout还是标准出错设备std ...

  3. 浅析三种特殊进程:孤儿进程,僵尸进程和守护进程

    其实有时想想linux内核的设计也蕴含着很多人生哲学,在linux中有这么几个特殊进程中,我们一开始见到它们的名字可能还会觉得很诧异,但在了解完了原理后,我们仔细想想,这样的命名也不无道理!下面我就给 ...

  4. 浅析三种特殊进程:孤儿进程,僵尸进程和守护进程.

    其实有时想想linux内核的设计也蕴含着很多人生哲学,在linux中有这么几个特殊进程中,我们一开始见到它们的名字可能还会觉得很诧异,但在了解完了原理后,我们仔细想想,这样的命名也不无道理!下面我就给 ...

  5. Linux中的defunct进程(僵尸进程)

    一.什么是defunct进程(僵尸进程)? 在 Linux 系统中,一个进程结束了,但是他的父进程没有等待(调用wait / waitpid)他,那么他将变成一个僵尸进程.当用ps命令观察进程的执行状 ...

  6. Kill杀死Linux中的defunct进程(僵尸进程)

    一.什么是defunct进程(僵尸进程)? 在 Linux 系统中,一个进程结束了,但是他的父进程没有等待(调用wait / waitpid)他,那么他将变成一个僵尸进程.当用ps命令观察进程的执行状 ...

  7. java defunct怎么杀掉_杀死Linux中的defunct进程(僵尸进程)的方法指南

    一.什么是defunct进程(僵尸进程)在 Linux 系统中,一个进程结束了,但是他的父进程没有等待(调用wait / waitpid)他,那么他将变成一个僵尸进程.当用ps命令观察进程的执行状态时 ...

  8. linux里面有mysql的僵尸进程_Linux中的defunct进程(僵尸进程)

    一.什么是defunct进程(僵尸进程)? 在 Linux 系统中,一个进程结束了,但是他的父进程没有等待(调用wait / waitpid)他,那么他将变成一个僵尸进程.当用ps命令观察进程的执行状 ...

  9. 【Linux】如何杀掉defunct进程-僵尸进程

    如何杀掉defunct进程-僵尸进程 defunct进程(僵尸进程) 什么是僵尸进程 杀死僵尸进程 1.重启服务器 2.杀死父进程 defunct进程(僵尸进程) 什么是僵尸进程 僵尸进程是一个早已死 ...

  10. Linux多进程编程之 孤儿进程僵尸进程+wait函数

    我们可否想过一个问题:使用fork()函数创建子进程,因为父进程和子进程的执行顺序是随机的 当父进程已经结束了,子进程还会继续存在并正常执行吗? 我们先看这个例子: guer1.c #include& ...

最新文章

  1. docker 安装oracle_阿里云使用Docker搭建Hadoop集群
  2. 脑洞大开!Adobe等新研究把「自拍」变「他拍」,魔幻修图效果感人
  3. 【android开发】Android防止内存溢出浅析
  4. 图像的膨胀与腐蚀、细化
  5. 状态压缩 DP AHU420
  6. ubuntu scp ssh 22: connection refused
  7. 文档数据库和关系数据库的区别
  8. 850pro测试软件,新极速霸主诞生 三星850 PRO首发评测
  9. VMware Workstation Pro 15安装和Win 10虚拟机安装
  10. TFT LCD液晶屏显示原理
  11. EXCEL根据两点经纬度计算距离
  12. Hello Shader之Hello Trangle
  13. 复旦大学计算机技术非全,复旦大学软件学院非全日制研究生专业介绍
  14. sql注入登陆(菜鸟级)
  15. leetcode125.验证回文串
  16. BiDi 算法详解及应用(一)
  17. idea 右下角不显示get
  18. WBS工作分解结构法,如何细分你的工作
  19. QQ账号测试用例思维导图
  20. 企业申请车辆蓄电池E-mark认证要提前准备些什么资料?

热门文章

  1. C# DataTable去除重复,极其简便、简单
  2. JS-取出字符串中重复次数最多的字符并输出
  3. 对c++primer 16.6.1的第4小节的代码说明
  4. c语言编译器怎样退出全屏,BOOX 应用软件怎样退出全屏模式?
  5. 西门子数控面板图解_学好四要点让你迅速成为数控机床“操作高手”
  6. tf 如何进行svd_Tensorflow快餐教程(6) - 矩阵分解
  7. linux开机启动roscore,树莓派ubuntuMate系统中开机自启动ROS的launch文件
  8. 163 coremail_Icoremail企业邮箱
  9. 图灵计算机模型意义,图灵机有什么意义_学习图灵机模型中遇到的问题
  10. Postgresql的HashJoin状态机流程图整理