当前位置: 首页 > news >正文

【Linux】Linux进程状态和僵尸进程:一篇看懂“进程在忙啥”

前言:欢迎各位光临本博客,这里小编带你直接手撕Make/Makefile (自动化构建),文章并不复杂,愿诸君耐其心性,忘却杂尘,道有所长!!!!

解释_chmod_命令_(3).gif

**🔥个人主页:IF’Maxue-CSDN博客

🎬作者简介:C++研发方向学习者

📖**个人专栏:
《C语言》
《C++深度学习》
《Linux》
《数据结构》
《数学建模》
**

⭐️人生格言:生活是默默的坚持,毅力是永久的享受。不破不立,远方请直行!

文章目录

    • 一、先搞懂:进程状态藏在哪?
    • 二、课本里的核心逻辑:3句话说透
    • 三、基础三态:运行、阻塞、挂起(大白话版)
      • 1. 运行态:“在工位上,要么干活要么等活”
      • 2. 阻塞态:“等材料,暂时离开工位”
      • 3. 挂起态:“工位不够,去临时仓库待着”
    • 四、内核小知识:用“链表”管进程
    • 五、Linux实际进程状态:逐个拆,附代码
      • 1. R状态:运行态(Running)
      • 2. S状态:可中断休眠(浅睡眠)
      • 3. T状态:暂停态(Stopped)
      • 4. t状态:断点态(Trace Stopped)
      • 5. D状态:不可中断休眠(深睡眠)
      • 6. Z状态:僵尸态(Zombie)
      • 7. X状态:死亡态(Dead)
    • 六、孤儿进程:爹跑了,谁管孩子?
      • 为啥要领养?
    • 七、总结:3个核心记牢

一、先搞懂:进程状态藏在哪?

进程不是“黑盒子”,它的所有信息——比如在干啥、代码存在哪、有啥权限——都记在一个叫task_struct的“档案本”里。而进程状态,就是这个档案本里的一个整数,像员工的“工作状态标签”,直接告诉操作系统“这进程现在能干活不”。

image.png

二、课本里的核心逻辑:3句话说透

不管啥操作系统,进程状态的底层逻辑都逃不开这3点(对应你放的课本截图):
image.png

  1. 进程不只有“跑”和“不跑”,有好几种状态;
  2. 状态能互相转——比如“等键盘输入”时会从“能跑”变“等着”,输入完又变回去;
  3. 只有“运行状态”的进程,才真正拿着CPU干活。

三、基础三态:运行、阻塞、挂起(大白话版)

咱们用“工厂干活”的例子理解,比硬记定义简单:

1. 运行态:“在工位上,要么干活要么等活”

操作系统里,每个CPU都有一个“调度队列”(像工厂的“待岗工位”),里面放的全是task_struct(进程档案本)。
只要进程在这个队列里,状态就是运行态(Running) ——不管是正在被CPU“叫去干活”,还是排队等CPU,都算运行态。

调度时,CPU会按规则(比如“先进先出”FIFO)从队列里挑一个档案本,找到对应的代码和数据,让它跑起来。
image.png

2. 阻塞态:“等材料,暂时离开工位”

比如你写了个带scanf的程序——运行后不敲键盘,进程就“卡住了”。这不是它偷懒,是它在等“键盘输入”这个“材料”,这就是阻塞。

操作系统怎么管这种情况?它会给每个硬件(键盘、磁盘、网卡)建个“设备档案”(struct device),档案里专门留了个“等待队列”——所有等这个硬件的进程,都会被移到这个队列里。
简单说:阻塞 = 进程档案本从“调度队列”挪到“硬件等待队列”,直到“材料”备好(比如你敲了键盘),再挪回调度队列。
image.png
image.png
image.png

3. 挂起态:“工位不够,去临时仓库待着”

如果内存实在不够(工厂工位满了),操作系统会把进程的“代码和数据”挪到磁盘(临时仓库),只留task_struct(档案本)在内存——这叫“阻塞挂起”(本来就在等材料,现在连工具都收起来了)。
要是内存还不够,连调度队列里的进程也会被挪去磁盘,叫“运行挂起”(本来在等活,现在先去仓库)。

不过不用记这么细——Linux里不细分挂起态,咱们重点看实际能用的状态就行。
image.png
image.png
image.png

四、内核小知识:用“链表”管进程

操作系统不是瞎管进程的,靠的是“链表”这种数据结构。Linux用的是list_head链表,特点很实用:

  • 每个task_struct里可以放多个list_head(像一个员工有多个“身份标签”);
  • 这样一个进程能同时属于多个链表——比如既在“进程组链表”,又在“调度队列链表”;
  • 想通过list_head找到完整的task_struct?靠“地址偏移”——知道list_head在档案本里的位置,就能算出整个档案本的地址。

简单说:进程状态切换,本质就是list_head在不同链表间“挪位置”,无非是增删查改的操作。
image.png
image.png
image.png
image.png
image.png
image.png

五、Linux实际进程状态:逐个拆,附代码

Linux的进程状态存在task_state_array里,咱们逐个讲,每个都给代码例子,跟着做就能看懂。

1. R状态:运行态(Running)

  • 特点:在调度队列里,要么正在跑,要么等CPU;不能被kill打断(想停它得等它自己出队列)。
  • 为啥有时查不到?比如printf这种操作太快,进程瞬间切到其他状态,得用“死循环不做IO”才能稳定抓到。

代码例子(抓R状态):

#include <stdio.h>
int main() {while(1); // 死循环,不做任何IO,一直待在调度队列return 0;
}
  • 操作步骤:
    1. 编译:gcc test.c -o test
    2. 后台运行(不占终端):./test &
    3. 查看状态:ps aux | grep test,会看到状态是R

image.png
image.png
image.png
image.png
(注:状态后的+表示“前台进程”,后台进程没有+

2. S状态:可中断休眠(浅睡眠)

  • 特点:等资源(键盘、文件),处于“浅睡”;能被kill命令打断(比如等输入时,kill一下就退出)。
  • 最常见的阻塞态,比如scanf等输入、读文件时都算S状态。

代码例子(抓S状态):

#include <stdio.h>
int main() {int a;scanf("%d", &a); // 等键盘输入,进程阻塞,状态变Sprintf("%d\n", a);return 0;
}
  • 操作步骤:
    1. 运行:./test,不输入任何内容;
    2. 另开终端查状态:ps aux | grep test,状态是S
    3. 测试kill:kill 进程号,进程会直接退出(S状态能被打断)。

image.png
image.png

3. T状态:暂停态(Stopped)

  • 特点:进程被“暂停”,既不在调度队列也不在等待队列;得用命令恢复kill -18)。
  • 触发方式:按ctrl+z暂停前台进程,或用kill -19手动暂停。

操作例子

  1. 运行上面的scanf程序:./test
  2. ctrl+z,终端提示“已暂停”;
  3. 查状态:ps aux | grep test,状态是T
  4. 恢复:kill -18 进程号,进程继续等输入;
  5. 再暂停:kill -19 进程号,变回T。

image.png

4. t状态:断点态(Trace Stopped)

  • 特点:只有调试时会出现!进程在断点处停下,比如gdb设断点后运行。

操作例子

  1. 用gdb调试:gdb ./test
  2. 设断点:b main(在main函数开头停);
  3. 运行:r
  4. 另开终端查状态:ps aux | grep test,状态是t

image.png
image.png

5. D状态:不可中断休眠(深睡眠)

  • 特点:等“关键资源”(比如磁盘IO),处于“深睡”;kill -9都杀不掉(怕打断磁盘操作导致数据损坏)。
  • 常见场景:用dd命令拷贝大文件时。

操作例子(抓D状态):

  1. 执行磁盘读写命令:dd if=/dev/zero of=/tmp/test bs=1G count=10(往/tmp写10G文件);
  2. 另开终端查状态:ps aux | grep dd,状态是D
  3. 试杀:kill -9 进程号,进程纹丝不动,直到磁盘操作完成才退出。

image.png
image.png

6. Z状态:僵尸态(Zombie)

  • 特点:子进程退出了,但父进程没“要它的退出信息”(比如退出码),只剩task_struct(档案本)在内存;不能被调度,也杀不掉(因为进程已经死了,只剩空壳)。
  • 风险:僵尸进程占内存,父进程一直不管就会“内存泄露”(尤其是开机就跑的常驻进程)。

代码例子(造僵尸进程):

#include <stdio.h>
#include <unistd.h>
int main() {pid_t pid = fork(); // 创建子进程if (pid == 0) {// 子进程:直接退出,没被回收printf("子进程PID:%d\n", getpid());return 0;} else if (pid > 0) {// 父进程:死循环,不回收子进程while(1) sleep(1); }return 0;
}
  • 操作步骤:
    1. 运行:./test
    2. 查状态:ps aux | grep test,会看到子进程状态是Z
    3. 解决办法:让父进程调用wait()回收,或杀掉父进程(子进程会被领养)。

image.png
(注:slab是Linux内核的内存分配机制,专门管理像task_struct这样的小对象,僵尸进程的task_struct就存在这里,不回收会占 slab 内存)
image.png

7. X状态:死亡态(Dead)

  • 特点:进程彻底退出,所有资源(代码、数据、task_struct)被OS回收;看不到这个状态(因为瞬间就没了),只是理论上的状态。

六、孤儿进程:爹跑了,谁管孩子?

如果父进程先退出,子进程没人管,就成了“孤儿进程”——这时候Linux会让1号进程领养它:

  • 新内核(比如Ubuntu、CentOS 7+):1号进程是systemd
  • 老内核:1号进程是init

为啥要领养?

怕孤儿进程退出后没人回收,变成僵尸进程,导致内存泄露。1号进程会负责“要它的退出信息”,相当于“福利院”。

代码例子(造孤儿进程):

#include <stdio.h>
#include <unistd.h>
int main() {pid_t pid = fork();if (pid == 0) {// 子进程:睡10秒,等父进程先退出printf("子进程PID:%d,当前父进程PID:%d\n", getpid(), getppid());sleep(10); printf("子进程现在父进程PID:%d\n", getppid()); // 会变成1return 0;} else if (pid > 0) {// 父进程:马上退出,不管子进程printf("父进程退出,PID:%d\n", getpid());return 0;}return 0;
}
  • 操作步骤:
    1. 运行:./test
    2. 父进程瞬间退出,子进程一开始的父进程是终端(比如bash);
    3. 10秒后,子进程的父进程变成1(被1号进程领养);
    4. 孤儿进程会变成后台进程,终端看不到它的输出(除非重定向)。

image.png
image.png
image.png
image.png
image.png
image.png

七、总结:3个核心记牢

  1. 进程状态本质:task_struct在不同链表间挪位置(调度队列、等待队列);
  2. Linux重点状态:R(跑)、S(浅睡等资源)、D(深睡杀不掉)、Z(僵尸要回收)、T(暂停);
  3. 孤儿/僵尸处理:孤儿找1号进程领养,僵尸靠父进程wait()回收,避免内存泄露。

文章转载自:

http://StgLJPgj.kwksj.cn
http://cZ7tBtjL.kwksj.cn
http://F3oLuPzN.kwksj.cn
http://2xw0cjbj.kwksj.cn
http://6M1ypTPM.kwksj.cn
http://dalgutxg.kwksj.cn
http://NY5sW1qj.kwksj.cn
http://EphCbt7M.kwksj.cn
http://98VOviRm.kwksj.cn
http://JhTRRr5d.kwksj.cn
http://UgtOhsrf.kwksj.cn
http://CyZvg8dR.kwksj.cn
http://uPy5UDBb.kwksj.cn
http://789qrfLW.kwksj.cn
http://xpog9yA8.kwksj.cn
http://KKFRQb4P.kwksj.cn
http://iwjt0KnJ.kwksj.cn
http://jmevj80V.kwksj.cn
http://cuKRhIr9.kwksj.cn
http://J6SP0umj.kwksj.cn
http://4bRzSBYa.kwksj.cn
http://u0Y9y0kr.kwksj.cn
http://scWG3zTH.kwksj.cn
http://aXq2rOBE.kwksj.cn
http://Ny4NNqq4.kwksj.cn
http://r84wkYse.kwksj.cn
http://mqIrFqIt.kwksj.cn
http://Yk9sJcDz.kwksj.cn
http://z5UyGdCB.kwksj.cn
http://tn4hmsVJ.kwksj.cn
http://www.dtcms.com/a/368297.html

相关文章:

  • 理解UE4中C++17的...符号及enable_if_t的用法及SFINAE思想
  • 某头部能源集团“数据治理”到“数智应用”跃迁案例剖析
  • 阿里云服务器配置ssl-docker nginx
  • 2025年COR SCI2区,基于近似细胞分解的能源高效无人机路径规划问题用于地质灾害监测,深度解析+性能实测
  • 实战案例:数字孪生+可视化大屏,如何高效管理智慧能源园区?
  • 容器的定义及工作原理
  • 【Python - 类库 - BeautifulSoup】(01)“BeautifulSoup“使用示例
  • 神经网络之深入理解偏置
  • 三、神经网络
  • 仓颉编程语言青少年基础教程:布尔类型、元组类型
  • UC Berkeley 开源大世界模型(LWM):多模态大模型领域世界模型技术新进展
  • 一次由CellStyle.hashCode值不一致引发的HashMap.get返回null问题排查
  • 【Java鱼皮】智能协同云图库项目梳理
  • 固定资产报废在BPM或OA中审批,再通过接口传到SAP
  • Redis-持久化
  • 寻找AI——初识3D建模AI
  • Playwright MCP Server - FAQ
  • Linux系统TCP/IP网络参数优化
  • 多模联邦查询网关:ABP + Trino/Presto 聚合跨源数据
  • 基于单片机智能家居环境检测系统/室内环境检测设计
  • 23种设计模式-模板方法模式
  • 容器学习day05_k8s(二)
  • ES04-批量写入
  • 大数据毕业设计推荐:基于Spark的零售时尚精品店销售数据分析系统【Hadoop+python+spark】
  • 企业数字安全双保险:终端安全与数据防泄漏如何构筑全方位防护体系
  • 信息系统安全保护措施文件方案
  • 【C++】 list 容器模拟实现解析
  • 鹿客发布旗舰新品AI智能锁V6 Max,打造AI家庭安全领域新标杆
  • 【GEOS-Chem 输入数据】使用 AWS CLI 访问 GEOS-Chem 数据
  • 23种设计模式——原型模式 (Prototype Pattern)详解