linux进程状态的多面人生:从奔跑到沉睡,从新生到孤独

    在 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 命令
a:显示⼀个终端所有的进程,包括其他用户的进程。
x:显示没有控制终端的进程,例如后台运行的守护进程。
j:显示进程归属的进程组ID、会话ID、父进程ID,以及与作业控制相关的信息
u:以用户为中心的格式显示进程信息,提供进程的详细信息,如用户、CPU和内存使用情况等

5. Z(zombie)-僵尸进程:死而未息的灵魂

僵死状态(Zombies)是⼀个比较特殊的状态。当进程退出并且父进程(使用wait()系统调用)没有读取到子进程退出的返回代码时就会产生僵死(尸)进程。
僵死进程会以终止状态保持在进程表中,并且会⼀直在等待父进程读取退出状态代码。
所以,只要子进程退出,父进程还在运行,但父进程没有读取子进程状态,子进程进入Z状态

如果父进程一直不管、不回收、不获取子进程的提出信息,那么Z会一直存在

5. 僵尸进程危害

- 进程的退出状态必须被维持下去,因为他要告诉关心它的进程(父进程),你交给我的任务,我
办的怎么样了。可父进程如果⼀直不读取,那子进程就⼀直处于Z状态?是的!
- 维护退出状态本身就是要用数据维护,也属于进程基本信息,所以保存在task_struct(PCB)中,
换句话说,Z状态⼀直不退出,PCB⼀直都要维护?是的!
- 那⼀个父进程创建了很多子进程,就是不回收,是不是就会造成内存资源的浪费?是的!因为数
据结构对象本身就要占用内存,想想C中定义⼀个结构体变量(对象),是要在内存的某个位置
进行开辟空间!
- 内存泄漏

6. 孤儿进程:无人认领的孩子

父进程如果提前退出,子进程被1号进程领养,这个贝领养的进程就称之为“孤儿进程”,同时该进程会变成后台进程。

那么1号进程是什么呢?

在Linux中,1号进程通常是init进程(在现代Linux系统中是systemd)。这是所有其他进程的祖先,它在系统引导时由内核创建,并负责启动和管理系统中的其他进程。

- 在Linux启动时,内核完成自检和设备初始化后,会启动1号进程(initsystemd)。

- 作为所有进程的父进程,1号进程负责收养其子进程,以确保当子进程终止时(比如崩溃或正常结束),其资源能够被正确回收。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值