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_*、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 命令
a:显示⼀个终端所有的进程,包括其他用户的进程。

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号进程负责收养其子进程,以确保当子进程终止时,其资源能够被正确回收。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值