在Linux系统中,每个进程都有自己的生命周期。

Linux进程状态

​   在Linux中,大多数进程都是被fork()出来的,进程被fork创建后,则会进入就绪态,进入就绪态的进程等待CPU资源,一旦进程获取到了CPU,该进程就进入到运行态,若是进程需要等待资源或IO,则进入就绪态。一下这张图大家应该不陌生。

​   但是,在Linux中的进程,我们其实可以用下面这张图详细描述:


关于僵尸进程

​   僵死态(僵尸进程):处于僵死态的进程的task_struct还没消失,但是该进程所依赖的资源都内核被回收了

​   僵尸进程留下来的目的是为了让其父进程调用Linux中的一个API:wait4来等待子进程完全结束。在Linux操作系统中,一个进程的task_struct是在其父进程等待其结束时才会消失。因为这样的话在子进程死亡时父进程可以做一些善后工作:比如通过死亡子进程的task_struct了解子进程的死因。

​   另一方面,进程的task_struct结构中有很多统计信息,比如CPU使用时间等,让父进程来料理后事可以将这些信息并入父进程的统计信息而不至于丢失;另一方面,也是更重要的一方面,无论如何系统必须得有一个当前进程,在中断以及异常的服务程序中要用到当前进程的系统空间堆栈。如果在下一个进程投入运行之前,就把当前进程的系统空间回收,这样就存在一个空档,如果恰巧此时有中断发生就会造成问题。

​   僵尸进程是一个非常短的临界的情况:一个进程结束了,但父进程还没来得及调用wait4函数,该进程就是一个僵尸进程。一旦父进程调用wait4,该进程的task_struct才会消失。

​   有三个问题值得注意:

  1. 如果父进程在子进程退出之前退出呢?子进程退出时该把报丧信号发给谁?这种情况下将由init进程“领养”父进程的所有子进程。
  2. 如果子进程已经终止了,但父进程没有调用wait函数获取它的终止状态又如何?内核为每个终止进程保存了一定量的信息,包括子进程的ID、进程终止状态以及进程使用的CPU时间总量,可以理解为子进程虽已去世,但还留着“尸体”等着父进程“收尸”。尸体要保留到父进程调用wait函数来收尸为止,在此之前,该子进程便成为一个僵尸进程(zombile)。
  3. 如果被init进程“领养”的进程终止了,系统中岂不会有大量的僵尸进程?不用担心,init进程被设计成“无论何时,只要有一个子进程终止,init就会调用wait函数来为之收尸”,从而防止了在系统中有很多僵尸进程。

  下图为wait4的原语:

​   关于进程退出的详细介绍,可参考此篇博文:https://my.oschina.net/u/3857782/blog/1857551

  以下为wait_task_zombie函数中的代码段:

static int wait_task_zombie(struct wait_opts *wo, struct task_struct *p)
{int status;....if (unlikely(wo->wo_flag & WNOWAIT)) {int exit_code = p->exit_code;int why;....;if ((exit_code & 0x7f) == 0) {why = CLD_EXITED;status = exit_code >> 8;} else {why = (exit_code & 0x80) ? CLD_DUMPED : CLD_KILLED;status = exit_code & 0x7f;}....}....
}

​ 其中的exit_code即为子进程退出原因的相关信息。
  

举个例子

  我们使用的用例程序如下,我们可以通过控制#if 0那段while循环的代码来决定是否让父进程给子进程“处理后事”。

#include <stdio.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <unistd.h>int main(void)
{pid_t pid,wait_pid;int status;pid = fork();if (pid==-1)    {perror("Cannot create new process");exit(1);} else  if (pid==0) {printf("child process id: %ld\n", (long) getpid());pause();_exit(0);} else {#if 0 /* define 1 to make child process always a zomie */printf("ppid:%d\n", getpid());while(1);
#endifdo {wait_pid=waitpid(pid, &status, WUNTRACED | WCONTINUED);if (wait_pid == -1) {perror("cannot using waitpid function");exit(1);}if (WIFEXITED(status))printf("child process exites, status=%d\n", WEXITSTATUS(status));if(WIFSIGNALED(status))printf("child process is killed by signal %d\n", WTERMSIG(status));if (WIFSTOPPED(status))printf("child process is stopped by signal %d\n", WSTOPSIG(status));if (WIFCONTINUED(status))printf("child process resume running....\n");} while (!WIFEXITED(status) && !WIFSIGNALED(status));exit(0);}
}

​   该测试程序中的waitpid函数的作用就是等待子进程死亡并处理task_struct,函数参数中的pid即子进程的pid,而status参数则是保存子进程的退出原因。

​   当程序运行时,父进程输出子进程的pid,并等待子进程结束,当我们使用kill命令杀掉子进程后,父进程将输出子进程的“死亡原因”:

  当我们用kill发送信号干掉子进程时,父进程将收到通知,并能获取到子进程的死因:

​   若子进程已经死亡,且父进程一直不做清理(执行wait),那么子进程将一直处于僵尸进程状态。例如如果我们把上述代码中的while循环打开之后,父进程进入死循环且不等待子进程结束:

​   此时父进程打印出了子进程的pid,我们使用kill -2 1307结束子进程后,子进程的状态将会变成僵尸进程,我们可通过ps aux查看,此处子进程的STAT变成了Z+,表示已经变成了僵尸进程:

​   悲哀的是,僵尸进程已经死亡,我们再怎么kill这具尸体还是会在,除非我们杀掉子进程的父进程,或是父进程执行wait,僵尸进程的task_struct才会被清理。

​   也许有人会问:若父进程创建了多个子进程都变成了僵尸进程,那么这种情况不就白白耗费了很多资源吗?注意:**进程从死亡时到变成僵尸进程,所耗费的资源将全部由内核释放。**此处仅仅保留了该进程的task_struct来描述该僵尸进程的一些信息,其占用的资源都已被内核回收。所以这里需要纠正一个误区:内存泄露并不是指一个进程申请的内存在进程结束后未得到释放导致内存被消耗,而是进程在一直运行的过程中已经使用完了的内存未正确释放且丢失指针,从而导致该进程所耗费的内存不断增多。

​   在进程结束之后,进程的所有内存都将被释放,包括堆上的内存泄露的内存。原因是,当进程结束时,GDT、LDT和页目录都被操作系统更改,逻辑内存全部消失,可能物理内存的内容还在但是逻辑内存已经从LDT和GDT删除,页目录表全部销毁,所以内存会被全部收回。(若没有该机制,面对各种牛鬼蛇神的应用Linux怎么可能健壮运行那么多年)

防止僵尸进程

  当我们只fork()一次后,存在父进程和子进程。这时有两种方法来避免产生僵尸进程:

  • 父进程调用waitpid()等函数来接收子进程退出状态。
  • 父进程先结束,子进程则自动托管到Init进程(pid = 1)。

目前先考虑子进程先于父进程结束的情况:

  • 若父进程未处理子进程退出状态,在父进程退出前,子进程一直处于僵尸进程状态。
  • 若父进程调用waitpid()(这里使用阻塞调用确保子进程先于父进程结束)来等待子进程结束,将会使父进程在调用waitpid()后进入睡眠状态,只有子进程结束父进程的waitpid()才会返回。 如果存在子进程结束,但父进程还未执行到waitpid()的情况,那么这段时期子进程也将处于僵尸进程状态。

  由此,可以看出父进程与子进程有父子关系,除非保证父进程先于子进程结束或者保证父进程在子进程结束前执行waitpid(),子进程均有机会成为僵尸进程。那么如何使父进程更方便地创建不会成为僵尸进程的子进程呢?这就要用两次fork()了。

  父进程一次fork()后产生一个子进程随后立即执行waitpid(子进程pid, NULL, 0)来等待子进程结束,然后子进程fork()后产生孙子进程随后立即exit(0)。这样子进程顺利终止(父进程仅仅给子进程收尸,并不需要子进程的返回值),然后父进程继续执行。这时的孙子进程由于失去了它的父进程(即是父进程的子进程),将被转交给Init进程托管。于是父进程与孙子进程无继承关系了,它们的父进程均为Init,Init进程在其子进程结束时会自动收尸,这样也就不会产生僵尸进程了。

进程的暂停态

  进程在运行时不睡眠,人为让进程停止而不死亡时,进程将进入暂停态,比如发送STOP信号。

  发送STOP信号一般在两种情况下发生:

  • 键盘输入ctrl+z时,为了job control(JC)
  • gdb attatchdebug时,

停止态不占用CPU资源,需要再次唤醒该进程时,需要发送CONTINUE信号,使得进程再次进入就绪态。

举个例子

​ 若有一个程序如下所示:

#include <stdio.h>int main()
{int i = 0;while(1){volatile int j, k;for (i = 0; i < 1000000; i++);printf("hello %d\n", j++);printf("world %d\n", j++);}return 0;
}

  我们可以使用ctrl + zfg命令不断暂停和恢复该进程到前台,如下图所示:

  注:在Linux系统中存在cpulimit这么一个控制工具,它能限制一个进程的cpu使用率,大致的方法就是不断让进程进入暂停态然后再唤醒。

例如cpulimit -l 10 -p 12296表示将pid为12296的进程的CPU使用率限制在10%。

深度睡眠及浅度睡眠描述

  • 深睡眠:必须等到资源到来才会醒

  • 浅睡眠:可以被资源或者是信号唤醒

注:当一个进程处于深睡眠时,是不会响应任何信号的(包括kill -9)。深度睡眠为内核调用的结果。

reference

https://www.cnblogs.com/codingmylife/archive/2010/11/10/1874235.html

https://my.oschina.net/u/3857782/blog/1857551

Linux进程(二):生命周期相关推荐

  1. UNIX 进程揭秘--进程的生命周期

    探索运行在 UNIX 操作系统下的进程的生命周期 Sean A. Walberg (sean@ertw.com), 高级网络工程师 2007 年 7 月 16 日 研究进程的生命周期,以便您能将所看到 ...

  2. 【Microsoft Azure 的1024种玩法】六.使用Azure Cloud Shell对Linux VirtualMachines 进行生命周期管理...

    [文章简介] Azure Cloud Shell 是一个用于管理 Azure 资源的.可通过浏览器访问的交互式经验证 shell. 它使用户能够灵活选择最适合自己工作方式的 shell 体验,本篇文章 ...

  3. (四)进程的生命周期——起源

    操作系统:linux 处理器:arm 内核版本:4.x 目录: 0号进程 1号进程.2号进程 提到进程生命周期就不得不说一说进程的起源:进程是怎么来的?第一个进程是谁? 0号进程 实际上计算机中第一个 ...

  4. linux 进程(二) --- 进程的创建及相关api

    一.进程的创建fork()函数 由fork创建的新进程被称为子进程(child process).该函数被调用一次,但返回两次.两次返回的区别是子进程的返回值是0,而父进程的返回值则是 新子进程的进程 ...

  5. Linux 进程(二) 进程地址空间

    上一节我们提到过父子进程的一个概念:父子进程代码共享,数据各自开辟空间. 因为子进程从父进程的PCB中拷贝了数据,所以它的代码.数据以及运行的位置,都与父进程一模一样.但是为什么这个代码是无法修改的? ...

  6. (五)进程的生命周期——诞生:fork、vfork、clone、内核线程(待续)

    操作系统:linux 处理器:arm 内核版本:4.x fork.vfork.clone cow _do_fork copy_process 内核线程 自然界中的每一个生命都需要经历出生.成长.死亡, ...

  7. 修改Jenkins启动衍生进程的生命周期

    先介绍下场景: 在Jenkins中新建了一个Job,假设你在一些列Build Step之前/之后,启动了一个进程,打个比方说启动一个Jboss进程.等到Build完成,你去Console Output ...

  8. 【Linux进程、线程、任务调度】一 Linux进程生命周期 僵尸进程的含义 停止状态与作业控制 内存泄漏的真实含义 task_struct以及task_struct之间的关系

    学习交流加(可免费帮忙下载CSDN资源): 个人微信: liu1126137994 学习交流资源分享qq群1(已满): 962535112 学习交流资源分享qq群2: 780902027 文章目录 1 ...

  9. 【Linux 内核】进程管理 ( Linux 中进程的 CPU 资源调度 | 进程生命周期 | 创建状态 | 就绪状态 | 执行状态 | 阻塞状态 | 终止状态 | 进程生命周期之间的转换 )

    文章目录 一.Linux 中进程的 CPU 资源调度 二.进程生命周期 三.进程生命周期之间的转换 一.Linux 中进程的 CPU 资源调度 Linux 操作系统 是 多任务系统 , 可以 同时运行 ...

  10. 操作系统原理:进程与线程、进程生命周期、线程的类型

    一.进程定义 进程可以看成程序的执行过程,可以展示在当前时刻的执行状态.它是程序在一个数据集合上的一次动态执行的过程.这个数据集合通常包含存放可执行代码的代码段,存放初始化全局变量和初始化静态局部变量 ...

最新文章

  1. 【匹配算法】渐进一致采样 PROSAC(PROgressive SAmple Consensus)
  2. java中bufferendwriter_Java IO系列(三)Writer
  3. etcd v3 集群——简单配置
  4. 重新同步多线程集成测试
  5. 短视频、直播平台第三方SDK接入教程
  6. export default 打包_贵阳【打包扣】价格
  7. python shell怎么打开测试,python脚本第一篇,运行时间测试
  8. java关闭按钮代码_Java高手看看如何实现关闭按钮
  9. Kubernetes详解(十四)——Pod对象生命周期
  10. 中国纺织行业前景动态分析与投资战略研究报告2022-2028年
  11. matlab 稀疏编码,稀疏编码怎样进行图像的特征提取
  12. Excel+Word批量发邮件的方法
  13. 台式计算机关机后自行重启,台式电脑关机后自动重启该怎么解决
  14. c 语言识别图片中的文字,Tesseract OCR图片识别为文字
  15. 版权符号模糊解决办法
  16. mac 查看端口使用情况
  17. 一键搞定身份证复印 多功能应用全面满足工作组需求
  18. 百度APP移动研发平台及DevOps实践
  19. python怎么播放视频教程_python怎样播放视频?
  20. android 图片气泡,关于实现微信聊天气泡里显示图片解决方案

热门文章

  1. 爬虫笔记——东方财富科创板数据爬取(selenium方法)
  2. 031--python--打印机票页面
  3. 脸上不同位置长痘痘的原因
  4. 重新振作起来,继续战斗
  5. 移动机器人全覆盖路径规划及仿真(三.地图分割)
  6. 图形处理之网格平滑vtkSmoothPolyDataFilter
  7. 评救市后中国股市新乱象泛起谣言
  8. 测绘技能大赛-无人机航测虚拟仿真(内业部分)
  9. 个人独资企业,核定征收;怎么申请?
  10. 天耀18期 – 03.Java基本语法【作业】.