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

Linux(四):进程状态

目录

  • 一、进程状态的本质:从抽象到具体
    • 1.1 抽象表现:PCB 中的状态字段
    • 1.2 具体实现:内核中的状态常量
  • 二、基础进程状态:运行、阻塞与挂起
    • 2.1 运行状态(Running)
    • 2.2 阻塞状态(Blocked)
      • 『信号量』:Semaphore
      • 就绪状态和阻塞状态的区别:
    • 2.3 挂起状态(Suspended)
  • 三、进程状态变化的本质与现象
    • 3.1 状态转换的底层逻辑
    • 3.2 系统卡顿的根源
  • 四、Linux 中的进程状态
    • 4.1 R(Running)
      • 『单核CPU 与 多核CPU』
    • 4.2 前台进程(+)与后台进程
    • 4.3 S(Sleeping)与 D(Disk Sleep)
      • 『信号』:Signal
      • Qn. Linux 进程 R状态:为何 “持续运行” 的进程常显 S/S + 态?
    • 4.4 T 状态(Stopped)与 t 状态(Tracing Stop)
    • 4.5 X 状态(Dead)与 Z 状态(Zombie)
      • 『僵尸状态的必要性』:退出状态码为何需保留?
    • 4.6 孤儿进程
  • 五、总结


摘要:运行状态,阻塞状态,僵尸状态,状态变化,前台进程与后台进程,S、D、T、t、X、Z状态,孤儿进程;信号量


一、进程状态的本质:从抽象到具体

进程状态并非凭空存在的概念,而是操作系统对进程运行阶段的量化描述。这种描述既是逻辑上的抽象分类,也是物理上的具体实现。

1.1 抽象表现:PCB 中的状态字段

进程控制块(Process Control Block,PCB)是操作系统管理进程的核心数据结构,而进程状态正是 PCB 中的一个关键字段,直观反映了进程当前的活动状态。无论是创建新进程、调度运行还是终止进程,OS的首要操作之一就是更新PCB 中的状态字段.

1.2 具体实现:内核中的状态常量

在 Linux 内核中,进程状态通过预定义的常量实现,这些常量由#define指令定义在include/linux/sched.h头文件中。它们本质上是整数常量,对应不同的进程状态:

// Linux内核中进程状态的部分定义(简化版)
#define TASK_RUNNING                0        // 运行/就绪状态
#define TASK_INTERRUPTIBLE        1        // 可中断睡眠(浅睡眠)
#define TASK_UNINTERRUPTIBLE        2        // 不可中断睡眠(深睡眠)
#define __TASK_STOPPED                4        // 停止状态
#define __TASK_TRACED                8        // 被跟踪状态
#define EXIT_ZOMBIE                16        // 僵尸状态
#define EXIT_DEAD                32        // 死亡状态

这些常量的数值本身并无实际意义,重要的是它们代表的逻辑状态。内核通过修改进程task_struct(Linux 中的 PCB 实现)中的state字段,来标记进程当前的状态。

例如,当进程等待键盘输入时,内核会将其state设为TASK_INTERRUPTIBLE


二、基础进程状态:运行、阻塞与挂起

所有OS都存在三种基础进程状态,它们构成了进程生命周期的核心流转环节。

2.1 运行状态(Running)

运行状态并非仅指进程正在 CPU 上执行,而是包含两种情况:

  • 正在 CPU 上运行(获得 CPU 时间片)
  • 处于就绪队列中,等待 CPU 调度(已具备运行条件,仅需 CPU 资源

类比:这就像排队买咖啡——正在窗口取餐的人(运行中)排在队伍里等待的人(就绪),都处于 “可以立即处理” 的状态,只是前者正在被服务,后者等待服务。

2.2 阻塞状态(Blocked)

当进程需要等待某类资源(如键盘输入、磁盘 I/O 完成、信号量)时,会进入阻塞状态。此时进程暂时放弃 CPU,直到等待的资源就绪后被内核唤醒。

『信号量』:Semaphore

信号量(Semaphore) 是控制多个进程访问共享资源的 “计数器”,进程需等待信号量变为可用状态(非 0)才能获取资源,否则进入阻塞——资源控制器
例子:比如多个进程共享一台打印机(共享资源),信号量初始值设为 1(表示仅允许 1 个进程使用)。当进程 A 正在使用打印机时,信号量变为 0;此时进程 B 想使用打印机,检查到信号量为 0,就会进入阻塞状态,等待进程 A 用完打印机并释放信号量(信号量变回 1)后,才会被唤醒继续执行。

就绪状态和阻塞状态的区别:

状态缺少的资源是否具备 “立即运行” 的潜力内核处理方式
就绪状态仅缺少 CPU 资源是(只要分配 CPU 就能运行)放在运行队列,等待调度器选中
阻塞状态缺少非 CPU 资源否(即使有 CPU 也无法运行)放在等待队列,不参与 CPU 调度

具体举例

  • 就绪状态:比如一个计算密集型程序(如循环求和),它不需要任何 I/O 资源,所有数据都在内存中。当它执行完一个时间片后,会被调度器暂停,回到就绪队列——此时它仍然满足所有运行条件,只是暂时没拿到 CPU,下次被选中就能立即继续执行。
  • 阻塞状态:比如一个读取文件的程序,当它执行到read()系统调用时,需要等待磁盘将数据加载到内存(非 CPU 资源未就绪)。此时即使给它 CPU,它也无法继续执行(因为需要的数据还没到),所以会进入阻塞状态。

类比:用 “工厂生产” 类比进程运行

  • 就绪状态:工人(进程)已准备好所有工具和原材料(非 CPU 资源就绪),只是在等待生产线(CPU)空闲,一上线就能立即开工。
  • 阻塞状态:工人(进程)缺少关键原材料(非 CPU 资源未就绪),即使生产线(CPU)空闲,也无法开工,只能等原材料到货(资源就绪)。

实例解析:以下 C 程序演示了进程进入阻塞状态的场景

#include <stdio.h>
int main() {char input[100];printf("请输入一段文字:");// 读取键盘输入时,进程会进入阻塞状态fgets(input, sizeof(input), stdin); printf("你输入的是:%s", input);return 0;
}

当程序执行到fgets函数时,由于需要等待用户键盘输入(硬件资源未就绪),进程会主动放弃 CPU,进入阻塞状态。此时内核会将其从运行队列移至对应设备的等待队列(如键盘等待队列)。资源未就绪意味着进程继续执行会产生错误或无效操作(比如读取不存在的输入),因此阻塞是一种 “主动等待” 的优化策略。

当用户输入完成(资源就绪),内核会将进程从阻塞状态唤醒,移回就绪队列,等待 CPU 调度。

进程内核调度器资源管理器(如I/O设备)初始状态:运行状态分配CPU时间片发起资源请求(如读取数据)资源未就绪,需等待主动放弃CPU(进入阻塞状态)收回CPU时间片,移入等待队列资源就绪(如数据读取完成)执行唤醒操作(移出等待队列)状态转换:阻塞状态 ->> 就绪状态移入就绪队列等待调度选中进程(分配CPU时间片)状态转换:就绪状态 ->> 运行状态继续执行后续指令进程内核调度器资源管理器(如I/O设备)

2.3 挂起状态(Suspended)

挂起状态是进程在内存不足时的 “应急状态”。当物理内存紧张时,操作系统会将部分进程的代码和数据从内存换出到 磁盘的 swap 分区,释放内存空间,此时进程处于挂起状态。

挂起状态可细分为就绪挂起阻塞挂起

  • 就绪挂起:进程原本处于就绪状态,因内存不足被换出到磁盘
  • 阻塞挂起:进程原本处于阻塞状态,因内存不足被换出到磁盘

为何就绪挂起较少被提及?
因为现代OS的内存管理机制更倾向于优先挂起阻塞进程——阻塞进程本身已无法运行,换出它们对系统响应速度的影响更小。而就绪进程随时可能被调度,频繁换出会导致严重的性能开销。

实例解析:以下代码模拟了可能导致阻塞挂起的场景

#include <stdio.h>
#include <stdlib.h>
int main() 
{// 模拟等待磁盘I/O(阻塞状态)printf("等待磁盘数据...\n");// 假设此处触发磁盘读取,进程进入阻塞状态sleep(1000); // 同时持续申请内存(增加内存压力)while(1) {void* ptr = malloc(1024 * 1024); // 每次申请1MB内存if (ptr == NULL) {printf("内存申请失败\n");break;}}return 0;
}

程序在等待磁盘 I/O(阻塞状态)的同时持续申请内存,当系统内存耗尽时,内核会将这个阻塞进程换出到 swap 分区,使其进入阻塞挂起状态。

swap 分区的设计权衡:swap 分区过大会导致进程频繁在内存与磁盘间切换(“颠簸” 现象),降低系统效率;过小则无法应对内存峰值,可能导致进程直接被终止。通常建议 swap 分区大小为物理内存的 1-2 倍(视具体场景调整)。

挂起会降低效率吗? 短期来看,挂起 - 唤醒过程涉及磁盘 I/O,确实有性能开销;但长期来看,挂起是解决内存不足的必要手段,能避免系统因内存耗尽而崩溃,本质是 “牺牲局部效率换取整体可用性”。


三、进程状态变化的本质与现象

3.1 状态转换的底层逻辑

进程状态变化的本质是两个核心操作的组合

  1. 修改task_struct中的state字段(更新状态标识)
  2. 将 PCB 从一个队列移至另一个队列(如从阻塞队列移至就绪队列)

例如,进程从阻塞到就绪的转换,内核会先将stateTASK_INTERRUPTIBLE改为TASK_RUNNING,再将其 PCB 从设备等待队列移除,加入 CPU 就绪队列。

3.2 系统卡顿的根源

使用电脑时的 “卡顿” 现象,本质是进程状态切换的延迟被用户感知。当系统内存不足导致频繁 swap、或大量进程竞争 CPU 时,进程状态切换的耗时会超过人类可接受的阈值(通常约 100ms),表现为操作响应迟缓、界面冻结等。


四、Linux 中的进程状态

Linux 在基础状态之上,定义了更精细的状态体系,通过psProcess Status)或top命令可查看进程状态(STAT 列)。

ps [选项] [PID]
  • 选项说明:
    • -p <PID>:指定进程 ID 查看单个进程(如ps -p 1234)。
    • -aux:显示所有进程的详细信息(包括用户、CPU、内存占用等),适合静态快照式查看。
    • -ef:显示进程间Parent-Child关系及完整命令行,与-aux类似但输出格式不同。
  • 输出解读(举例):
# 输入下面的命令:
ps -o pid,stat,cmd -p 30581  # 显式指定:仅显示 PID、状态、命令

输出结果:
在这里插入图片描述

//STAT 列:显示进程状态代码,例如:
R(Running)
S(Sleeping)
D(Disk Slepp)
T(Stooped)
t(Tracing Stop)
X(Dead)
Z(Zombie)

4.1 R(Running)

R 状态包含传统意义上的 “运行中”“就绪” 两种情况。例如,在多任务系统中,即使 CPU 只有 1 个核心,所有等待调度的就绪进程也会显示为 R 状态。

『单核CPU 与 多核CPU』

多核 CPU(Multi-Core CPU)是指在一块物理 CPU 芯片中,集成了多个独立的处理核心(Core)
的处理器。每个核心都拥有完整的运算单元、缓存和指令集,相当于把多个 “小 CPU” 整合到了一个芯片里,彼此可以独立并行地执行任务。

  • 单核 CPU:只有 1 个核心,同一时间只能真正执行 1 个任务(进程 / 线程),多任务是靠快速切换任务(时间分片)“假装” 同时进行的。
  • 多核 CPU:比如双核、四核、八核,每个核心都能独立干活。比如四核 CPU,理论上可以同时 “真正并行” 处理 4 个任务,不需要像单核那样频繁切换。

多核 CPU 的作用:在讨论进程的R状态(运行 / 就绪)时,多核 CPU 的意义很关键:

  • 单核 CPU 中,R状态里 “运行中” 的进程只能有 1 个(正在被 CPU 执行),其他R状态的进程都是 “就绪”(排队等 CPU 时间片)。
  • 多核 CPU 中,“运行中” 的进程数量可以等于核心数(比如 4 核 CPU,最多同时有 4 个进程在不同核心上真正执行),剩下的R状态进程才是 “就绪”(等某个核心空闲)。 这意味着多核 CPU
    能显著提升多任务效率:比如同时开浏览器、视频播放器、下载工具时,多核可以让这些进程的核心任务分配到不同核心,减少等待,更流畅。

举例:

  • 单核 CPU 处理 3 个R状态的进程:CPU 只能轮流给它们分配时间(比如每个进程执行 10 毫秒就切换),看起来 “同时运行”,但本质是串行切换。
  • 四核 CPU 处理 3 个R状态的进程:3 个核心可以分别 “同时” 执行这 3 个进程,不需要切换,真正并行,效率更高。

实例:在终端执行while true; do :; done创建一个无限循环进程,用ps aux | grep <pid>查看,其状态为 R(即使它可能在与其他进程交替使用 CPU)。

4.2 前台进程(+)与后台进程

这是从用户交互角度对进程的分类,与状态直接相关:

  • 前台进程:占据终端输入输出,执行时会阻塞其他命令(如直接执行./a.out)。可通过Ctrl+C发送SIGINT信号终止。+前台进程组标记+ 表示该进程属于 “前台进程组”(foreground process group),即进程与当前终端会话直接关联,会接收终端的输入(如键盘信号)。在这里插入图片描述

  • 后台进程:在命令后加&启动(如./a.out &),不阻塞终端,需通过kill <pid>终止。
    在这里插入图片描述

对应到图形化界面中:前台进程类似 “当前激活窗口”(如正在编辑的文档),后台进程类似 “后台运行的服务”(如音乐播放器在后台播放)。

4.3 S(Sleeping)与 D(Disk Sleep)

  • S 状态:可中断睡眠(睡眠),进程等待资源时进入,能被信号(如SIGKILL)唤醒并终止。例如,sleep 100命令创建的进程处于 S 状态,执行kill <pid>可立即终止。

当在终端输入sleep100时,这里的sleep是一个独立的可执行程序(通常位于/bin/sleep/usr/bin/sleep)。它是系统预装的工具,本身就是一个完整的程序,功能:接收时间参数,然后让自己的进程进入睡眠状态,等待指定时间后退出。

  • D 状态:不可中断睡眠(睡眠),专为磁盘 I/O 设计。当进程向磁盘写入关键数据时,内核会将其设为 D 状态,防止被意外终止导致数据损坏。此时kill命令无效,只能等待 I/O 完成或重启系统(🚫强行断电可以终止进程,但是有数据丢失的风险)。

在这里插入图片描述

『信号』:Signal

信号(Signal) 由信号源(如用户操作、硬件异常、其他进程等)产生,由操作系统内核运输,从信号源经内核传递到目标进程,传递 “某种事件发生” 的信息(如中断、错误、用户指令等)。

kill -l可查看所有信号(共 64 种),常用如SIGINT(2号,Ctrl+C触发)、SIGKILL(9 号,强制终止)、SIGSTOP(19 号,暂停进程)。kill <信号编号> <pid>用于发送信号,例如kill 9 1234强制终止 PID 为 1234 的进程。

Qn. Linux 进程 R状态:为何 “持续运行” 的进程常显 S/S + 态?

在 Linux 中,可中断睡眠态(S/S+)是绝大多数进程的日常状态——即便看似 “不间断执行” 的进程,也会因 CPU 调度机制频繁在 “运行态(R)” 与 “S/S + 态” 间切换。

#include <stdio.h>
#include <unistd.h>int main() {while (1) {  // 死循环,持续执行打印操作printf("This is a process: %d\n", getpid());sleep(1);}return 0;
}

编译并运行上述代码,通过ps命令(如ps aux | grep ./test)查看该进程状态,终端输出通常如下(状态列标为S+):

user    12345  0.5  0.0   4108   320 pts/0    S+   14:30   0:01 ./test
  • S+:表示进程为前台可中断睡眠态(后台运行时状态为S);
  • 即便程序看似 “持续打印”,ps捕捉到的状态仍以S/S+为主,而非运行态(R)。

原因:CPU 时间片轮转调度

内核会为每个就绪进程分配固定时长的 CPU 时间片(通常毫秒级,如 10ms);上述死循环程序仅在 “获得时间片” 时才进入运行态(R),执行打印指令;当时间片耗尽,内核会剥夺其 CPU 使用权,将其切换至可中断睡眠态(S/S+),并放入就绪队列等待下一轮时间片;由于时间片切换速度极快(远快于ps的采样频率),ps命令大概率捕捉到的是进程 “等待下一轮时间片” 的S/S+态,而非 “正在占用 CPU” 的R态。

Linux 进程的运行态(R)仅存在于 “占用 CPU 执行指令的极短时间内” ;绝大多数时候,进程要么因等待时间片处于S/S+态,要么因等待
I/O、信号等资源阻塞——这也是为何系统中S/S+态进程始终占比最高,而R态进程极少的核心原因。

4.4 T 状态(Stopped)与 t 状态(Tracing Stop)

  • T 状态:进程被暂停(如收到SIGSTOP信号),暂停期间不访问资源,可通过SIGCONT信号恢复运行。例如,执行kill -19 <pid>可将进程转为 T 状态,kill -18 <pid>恢复。
  • t 状态:进程被调试器跟踪(如 gdb 调试),遇到断点时进入。gdb 调试的底层流程是:
    1. 调试器通过fork创建Child进程(被调试程序)
    2. Child进程通过ptrace系统调用让调试器跟踪自己
    3. 执行到断点时,Child进程进入 t 状态,等待调试器指令
1.调用fork()系统调用
2.调用ptrace()系统调用,请求被跟踪
触发断点
等待调试器指令
调试器
Child进程(被调试程序)
执行到断点
Child进程进入 t 状态(被跟踪停止)

4.5 X 状态(Dead)与 Z 状态(Zombie)

  • X 状态:进程生命周期的终点,所有资源(代码、数据、PCB)均被释放,在ps中几乎不可见(存在时间极短)。
  • Z 状态僵尸状态,进程代码和数据已释放,但 PCB 被保留(存储退出状态码等信息),等待Parent进程读取。只有当Parent进程读取信息后,内核才会将 PCB 转为 X 状态并释放。

风险:若Parent进程始终不回收Child进程,僵尸进程会积累,导致 PCB 资源泄露(虽然单个 PCB 占用内存少,但大量积累会消耗系统资源)。

『僵尸状态的必要性』:退出状态码为何需保留?

  1. 为Parent进程提供 “Child进程执行结果” 的唯一反馈渠道
    Parent进程创建Child进程(如通过fork())的核心目的是让Child进程完成特定任务(如执行命令、处理子任务),而退出状态码是Child进程告知Parent进程 “任务是否完成、为何结束” 的标准化方式:

    • 若Child进程正常退出(如执行完ls命令),会返回0作为退出状态码,代表 “任务成功完成”;
    • 若Child进程异常终止(如因权限不足无法读写文件、触发除零错误),会返回非0值(如126代表 “权限不足”、139代表 “段错误”),Parent进程可通过该值定位失败原因。
  2. 保障进程生命周期的 “可追溯性” 与调试需求
    退出状态码不仅是 “结果反馈”,还是排查Child进程异常的关键依据:

    • 开发调试场景中,若Child进程意外崩溃(如运行时触发SIGSEGV信号),内核会将 “信号类型” 编码为退出状态码的一部分(如139对应SIGSEGV),Parent进程(或调试工具)可通过读取该值,快速定位Child进程是 “正常退出” 还是 “被信号杀死”、被哪种信号杀死;
    • 运维场景中,通过监控进程的退出状态码,可批量判断一批Child进程的运行健康度(如大量Child进程返回137,代表 “被SIGKILL强制终止”,可能是资源超限导致)。

4.6 孤儿进程

当Parent进程先于Child进程终止,Child进程会成为孤儿进程。此时 Linux 内核会将孤儿进程的Parent进程改为init进程(PID=1,或 systemd 等进程管理器),由其负责回收,避免孤儿进程成为僵尸进程后适中不被回收而造成僵尸进程累积。

五、总结

[图片]


END

http://www.dtcms.com/a/349738.html

相关文章:

  • 计算机毕业设计 java 血液中心服务系统 基于 Java 的血液管理平台Java 开发的血液服务系统
  • 如何清除 Docker 容器的日志 ?
  • LevelDB SSTable模块
  • mac设置鼠标滚轮方向
  • Elasticsearch脑裂紧急处理与预防
  • 数据挖掘5.3 PCA主成分分析降维
  • AI模型接入Web搜索功能实践与最佳API服务选型
  • 扭蛋机小程序系统开发:连接线上线下娱乐的新桥梁
  • 习题库小程序的设计与实现 计算机毕业设计源码27057
  • RAG检索增强生成
  • SuiHub 台北站正式发布,助力亚洲科技生态创新
  • [读论文]Hunyuan 3D 系列
  • 【Linux系统】1.Linux基础指令与权限
  • 8月25号打卡
  • 数据采集怎么做?质量、效率与合规该怎么平衡?
  • LLM学习:langchain架构——chain
  • 2025年03月 Python(二级)真题解析#中国电子学会#全国青少年软件编程等级考试
  • 【AGI使用教程】GPT-OSS 本地部署(2)
  • 【广告系列】流量优选
  • 第二章 设计模式故事会之策略模式:魔王城里的勇者传说
  • AcrelEMS-EDU在实践中的应用系列—“综合能源管理”
  • 2025年8月25日-8月31日(qtopengl+ue独立游戏)
  • 23种设计模式:模板方法模式与策略模式
  • vue 一键打包上传
  • 【车载开发系列】汽车零部件DV与PV试验的差异
  • 【QT/C++】实例理解类间的六大关系之组合关系(Composition)
  • 农业气象监测站:像敏锐的精灵,捕捉农业气象的每一丝变化
  • 18 继续学习
  • 【图像处理基石】基于Real-ESRGAN的实时图像超分辨率技术实现
  • 【GPT-5 与 GPT-4 的主要区别?】