1.进程状态
Linux 中的进程主要有以下几种状态:
- 运行:正在使用 CPU 或准备使用 CPU。
- 阻塞:进程处于等待某一条件满足的状态。
- 挂起:被暂停运行,通常由信号或用户操作引起。
- 僵尸:进程已结束,但未被回收。
- 孤儿:父进程已经终止,进程由 `init` 进程接管。

2. 进程的linux内核源代码
Linux 内核中通过一个数组 task_state_array[] 定义了进程的多种状态。以下是源码的具体实现内容:
/*
*The task state array is a strange "bitmap" of
*reasons to sleep. Thus "running" is zero, and
*you can test for combinations of others with
*simple bit tests.
*/
static const char *const task_state_array[] = {
"R (running)", /*0 */
"S (sleeping)", /*1 */
"D (disk sleep)", /*2 */
"T (stopped)", /*4 */
"t (tracing stop)", /*8 */
"X (dead)", /*16 */
"Z (zombie)", /*32 */
};
task_state_array[] 是一个状态描述的字符串数组。每个元素对应进程的某种状态。
进程状态的内核常量定义:
在内核中,不同状态的常量值由 TASK_*、EXIT_* 系列宏定义:
#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内的一个整数。
运行时状态切换:
内核会根据事件触发对进程状态进行切换:
- 运行(R):进程处于调度队列,等待 CPU 调度。
- 睡眠(S/D):进程等待资源或事件发生,可被中断或不可被中断。
- 停止(t/T):进程响应信号(如 SIGSTOP)而暂停。
- 僵尸(Z):进程已退出,但父进程尚未回收其资源。
状态切换过程:
1. 当进程进入 CPU 调度队列时,状态设置为 TASK_RUNNING。
2. 当进程调用阻塞操作(如 scanf 或 I/O)时,状态切换为 TASK_INTERRUPTIBLE (浅睡眠)或 TASK_UNINTERRUPTIBLE(深睡眠)。
3. 进程收到信号后,可能被置为 TASK_STOPPED 或 TASK_TRACED。
4. 进程结束时,状态设置为 EXIT_ZOMBIE,等待父进程回收资源。
(1)R (Running) - 运行状态
描述:
- 进程处于运行状态时,表示它正在使用 CPU 或在运行队列中等待被调度使用 CPU。
- 是进程最活跃的状态,进程只有在该状态下才会真正执行指令。
关键点:
- 状态值:TASK_RUNNING = 0
- 进程的调度优先级决定它在运行队列中的顺序。
- 如果有多核 CPU,多个进程可以同时处于运行状态(各占一个 CPU)。
(2)S (Sleeping) / D (Disk Sleep) - 睡眠状态
S (Sleeping) - 可中断睡眠状态
描述:
- 进程处于睡眠状态时,表示它正在等待某个事件(如 I/O 操作完成或信号到达)。
- 可中断表示进程可以被信号唤醒,并重新进入运行状态。
关键点:
- 状态值:TASK_INTERRUPTIBLE = 1
- 最常见的进程状态,许多 I/O 密集型任务会频繁进入此状态。
sleeping就是阻塞状态,浅睡眠(可中断睡眠)



D (Disk Sleep) - 不可中断睡眠状态
描述:
- 不可中断的睡眠状态表示进程正在等待一个特定事件(通常是硬件相关任务),无法被信号打断。
- 常用于访问慢速外部设备的 I/O 操作,例如磁盘访问。
关键点:
- 状态值:TASK_UNINTERRUPTIBLE = 2
- 通常是短暂的状态,如果长时间停留,可能意味着硬件问题或系统瓶颈。
(3)t (Tracing Stop) - 被跟踪的停止状态
描述:
- 进程被调试器(如 gdb)跟踪时进入此状态。
- 这是一个特殊的停止状态,便于调试器操作。
关键点:
- 状态值:TASK_TRACED = 8
在第八行打上一个断点


(4)T (Stopped) - 停止状态
描述:
- 表示进程因接收到 SIGSTOP 等信号而暂停运行。
- 用户可以通过发送 SIGCONT 信号让进程恢复运行。
关键点:
- 状态值 TASK_STOPPED = 4
- 常用于调试目的,允许用户在进程暂停时检查其状态或内容。

运行程序,使用ctrl + z将程序挂起放到后台


(5)X (Dead) - 死亡状态
描述:
- 进程已结束运行,并即将被彻底清理出系统。
- 这是一个极为短暂的状态,在用户层面几乎无法观察到。
关键点:
- 状态值:TASK_DEAD = 16
- 内核会立即回收其所有资源并清除进程信息。
(6)Z (Zombie) - 僵尸状态
描述:
- 进程已终止运行,但父进程尚未回收其退出信息。
- 僵尸进程会占用系统的进程表资源,但不会消耗其他资源。
关键点:
- 状态值:EXIT_ZOMBIE = 32
- 过多的僵尸进程可能导致系统资源耗尽,影响性能。
(7)补充
使用kill管理进程

kill -18:sigcont继续执行
kill -19:sigstop停止运行
3.运行、阻塞和挂起
在 Linux 系统中,进程的生命周期涉及多个状态,其中 阻塞、运行 和 挂起 是三个关键的状态。理解它们的本质和差异,可以帮助我们更好地管理和优化进程。
3.1.运行:进程的巅峰时刻
(1)什么是运行状态
运行状态 (Running) 是 Linux 进程生命周期中最活跃的状态。处于该状态的进程正在使用 CPU 执行指令,或者已经准备好执行,只需等待 CPU 调度。
运行:进程处于调度队列中,进程的状态就是running。
(2)单核与多核系统
- 在单核系统中,每次仅有一个进程处于运行状态。
- 在多核系统中,多个 CPU 核心可以同时运行不同的进程。
(3)运行状态的三种触发条件
1. 系统调度:当进程准备好运行并且内核调度器为其分配了 CPU,它将进入运行状态。
2. 等待结束:当一个睡眠或阻塞的进程被唤醒(如 I/O 完成、信号到达),它会重新进入运行队列,等待调度。
3. 手动恢复:暂停的进程接收到 SIGCONT 信号后,可能重新进入运行状态。
一个CPU,一个调度队列,操作系统专门为CPU设计了一个队列。
调度算法之一:FIFO(先进先出)

3.2.阻塞:进程的等待之旅
(1)什么是阻塞状态?
阻塞状态表示一个进程因等待某种资源或事件而暂停运行,此时它无法继续执行指令,也不会占用 CPU。只有当等待的条件被满足(如 I/O 操作完成、信号到达),进程才会从阻塞状态恢复到可运行状态。
阻塞:等待某种资源或设备就绪。(例如键盘、显示器、磁盘......)
(2)阻塞分为两种类型
1. 可中断阻塞 (Interruptible Blocking)
- 进程因等待某些条件(如用户输入或 I/O 完成)进入休眠状态,但在此期间可以通过信号唤醒。
- 状态值:TASK_INTERRUPTIBLE,显示为 S (sleeping)。
2. 不可中断阻塞 (Uninterruptible Blocking)
- 进程进入休眠状态以等待某些关键条件(如硬件响应或文件系统访问),无法被信号中断。
- 状态值:TASK_UNINTERRUPTIBLE,显示为 D (disk sleep)。
(3)阻塞状态的进程调度机制
运行到阻塞:
- 当进程调用阻塞系统调用(如 I/O 请求)或尝试访问不可用资源时,内核会将进程从 TASK_RUNNING 转为 TASK_INTERRUPTIBLE 或 TASK_UNINTERRUPTIBLE 状态,并将其从运行队列中移除。
阻塞到运行:
- 当等待的事件发生(如 I/O 完成或信号到达),内核会将进程重新加入运行队列,并将状态改为 TASK_RUNNING,等待调度器分配 CPU。
OS管理系统中的各种硬件设备->先描述,再组织。与管理进程大致相同,都是用struct结构体先描述,使用直接将进程、硬件设备联系起来,转变为对链表的增删查改。
如果调度队列中的进程在运行时遇到了阻塞,例如c语言代码中使用了scanf,那么该进程就会被链入阻塞队列,scanf读取键盘的数据后才会被重新链入调度队列,变为运行状态。
3.3.挂起:进程的暂停时光
进程挂起状态指进程暂停执行、暂不占用 CPU 的状态。按触发条件与表现形式,可分为 运行挂起和 阻塞挂起 两类;此时进程的代码和数据会被换出至磁盘的 swap 交换分区(外设),必要时再换入内存。
3.3.1.运行挂起 (Stopped Suspension)
运行挂起状态指处于就绪队列中的进程被暂停执行,此时其资源与上下文仍被保留,但进程会被换出至磁盘的 swap 分区;当被换入内存后,进程可重新进入就绪状态等待调度执行。
(1)触发条件
接收到以下信号时,进程会进入运行挂起状态:
- SIGSTOP:强制暂停进程,无法被捕获或忽略。
- SIGTSTP(终端停止信号):通常由用户通过按下 Ctrl+Z 触发,暂停进程。
- SIGTTIN / SIGTTOU(终端输入/输出信号):进程尝试访问终端时被挂起(常用于后台进程)。
(2)内核实现
- 在内核中,运行挂起状态对应于 TASK_STOPPED,显示为 T
(3)特性
- 暂停执行:挂起进程不会占用 CPU。
- 状态保留:进程的内存、文件描述符等资源不会被释放。
- 可恢复:通过向进程发送 SIGCONT 信号,恢复挂起状态的进程,使其重新进入运行状态。
3.3.2. 阻塞挂起 (Blocked Suspension)
阻塞挂起状态是指一个已经处于阻塞状态的进程(等待资源或事件)被进一步暂停。此时,进程不仅在等待条件满足,还被人为挂起,需手动恢复。
进程阻塞挂起:代码和数据换出磁盘上的swap交换分区
(1)触发条件
1.阻塞状态的进程接收到 SIGSTOP 信号时,转为阻塞挂起状态。
2.外部手动干预暂停了阻塞状态的进程。
(2)内核实现
- 阻塞挂起状态在内核中是 TASK_STOPPED 的一种变形,其表现为阻塞进程被暂停并无法立即响应等待的事件。
(3)特性
- 双重暂停:进程既处于资源等待状态(如 I/O 完成),又被手动暂停。
- 资源满足不自动唤醒:即使资源条件满足,进程也不会自动恢复。
- 恢复依赖外部:外部信号 (SIGCONT) 才能解除挂起状态。
示例
1.一个阻塞进程(如等待文件 I/O)接收到 SIGSTOP:
kill -SIGSTOP <PID>
2.恢复进程后,它会重新进入阻塞状态,继续等待资源:
kill -SIGCONT <PID>
4.进程状态查看
ps aux / ps axj 命令
x:显示没有控制终端的进程,例如后台运行的守护进程。
j:显示进程归属的进程组ID、会话ID、父进程ID,以及与作业控制相关的信息
u:以用户为中心的格式显示进程信息,提供进程的详细信息,如用户、CPU和内存使用情况等
5. Z(zombie)-僵尸进程:死而未息的灵魂
僵死状态(Zombies)是一个比较特殊的状态。当进程退出并且父进程(使用wait()系统调用)没有读取到子进程退出的返回代码时就会产生僵死(尸)进程。
父进程读取返回代码后的核心工作:
(1)回收内核资源,清除僵尸状态
子进程退出后,虽然代码、数据等用户空间资源已释放,但内核中仍保留其 PCB,此时进程处于 “僵尸态”。父进程调用 wait() 读取退出码后,内核会立即回收该子进程的 PCB,彻底清除其在系统中的痕迹,僵尸进程随之消失。
(2)处理子进程的退出结果(可选)
父进程拿到子进程的退出码(如正常退出的 0、异常退出的非零值)后,可根据业务逻辑做后续处理。例如:
- 判断子进程是否正常退出,若异常退出则记录日志(如 “子进程因内存错误退出,退出码 139”)。
- 若子进程是任务型进程(如处理单个文件),父进程可根据退出结果决定是否启动新的子进程接替任务。
僵死进程会以终止状态保持在进程表中,并且会一直在等待父进程读取退出状态代码。
所以,只要子进程退出,父进程还在运行,但父进程没有读取子进程状态,子进程进入Z状态


如果父进程一直不管、不回收、不获取子进程的提出信息,那么Z会一直存在。
僵尸进程危害:
- 进程的退出状态必须被维持下去,因为它要告诉关心它的进程(父进程),你交给我的任务,我办的怎么样了。可父进程如果一直不读取,那子进程就一直处于Z状态?是的!
- 维护退出状态本身就是要用数据维护,也属于进程基本信息,所以保存在 PCB 中, 换句话说,Z状态一直不退出,PCB一直都要维护?是的!
- 那一个父进程创建了很多子进程,就是不回收,是不是就会造成内存资源的浪费?是的!因为数 据结构对象本身就要占用内存,想想C中定义一个结构体变量(对象),是要在内存的某个位置 进行开辟空间!
- 内存泄漏
6.孤儿进程:无人认领的孩子
父进程如果提前退出,子进程被1号进程领养,这个被领养的进程就称之为“孤儿进程”,同时该进程会变成后台进程。



那么1号进程是什么呢?
在Linux中,1号进程通常是 init 进程(在现代Linux系统中是systemd)。这是所有其它进程的祖先,它在系统引导时由内核创建,并负责启动和管理系统中的其他进程。
- 在Linux启动时,内核完成自检和设备初始化后,会启动1号进程(init或systemd)。
- 作为所有进程的父进程,1号进程负责收养其子进程,以确保当子进程终止时,其资源能够被正确回收。


3617

被折叠的 条评论
为什么被折叠?



