在 Linux 的世界里,进程是系统的核心元素。就像人类的生命一样,进程也经历不同的阶段:从忙碌的奔跑到沉寂的等待,从活跃的状态到遗留的痕迹。理解这些状态的本质,能够帮助我们更好地掌控系统的运行状态,解决潜在问题。
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_*` 系列宏定义:
#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分区。换入使进程重新成为运行状态
触发条件
- 接收到以下信号时,进程会进入运行挂起状态:
- `SIGSTOP`:强制暂停进程,无法被捕获或忽略。
- `SIGTSTP`:通常由用户通过按下 `Ctrl+Z` 触发,暂停进程。
- `SIGTTIN` / `SIGTTOU`:进程尝试访问终端时被挂起(常用于后台进程)。
内核实现
- 在内核中,运行挂起状态对应于 `TASK_STOPPED`,显示为 `T`:
特性
- 暂停执行:挂起进程不会占用 CPU。
- 状态保留:进程的内存、文件描述符等资源不会被释放。
- 可恢复:通过向进程发送 `SIGCONT` 信号,恢复挂起状态的进程,使其重新进入运行状态。
3.3.2. 阻塞挂起 (Blocked Suspension)
阻塞挂起状态是指一个已经处于阻塞状态的进程(等待资源或事件)被进一步暂停。此时,进程不仅在等待条件满足,还被人为挂起,需手动恢复。
进程阻塞挂起:代码和数据换出磁盘上的swap交换分区
触发条件
1. 阻塞状态的进程接收到 `SIGSTOP` 信号时,转为阻塞挂起状态。
2. 外部手动干预暂停了阻塞状态的进程。
内核实现
- 阻塞挂起状态在内核中是 `TASK_STOPPED` 的一种变形,其表现为阻塞进程被暂停并无法立即响应等待的事件。
特性
- 双重暂停:进程既处于资源等待状态(如 I/O 完成),又被手动暂停。
- 不可被唤醒:即使资源条件满足,进程也不会自动恢复,需通过 `SIGCONT` 信号解除挂起。
- 恢复依赖外部:外部信号 (`SIGCONT`) 才能解除挂起状态。
示例
1. 一个阻塞进程(如等待文件 I/O)接收到 `SIGSTOP`:
kill -SIGSTOP <PID>
2. 恢复进程后,它会重新进入阻塞状态,继续等待资源:
kill -SIGCONT <PID>
4. 进程状态查看
ps aux / ps axj 命令
5. Z(zombie)-僵尸进程:死而未息的灵魂
如果父进程一直不管、不回收、不获取子进程的提出信息,那么Z会一直存在
5. 僵尸进程危害
6. 孤儿进程:无人认领的孩子


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