012 进程状态和优先级

012 进程状态和优先级
小米里的大麦进程状态和优先级
[!TIP]
一、进程状态分类
Linux 中的进程状态可以通过 ps
命令或者 top
命令来查看,常见的状态码有以下几种:
状态码 | 名称 | 含义说明 |
---|---|---|
R | 运行(Running) | 进程正在运行或处于可运行状态(等待 CPU 调度) |
S | 可中断睡眠(Sleeping) | 进程正在等待某个事件(如 I/O、信号等),可以被信号或外部事件唤醒 |
D | 不可中断睡眠(Uninterruptible Sleep) | 进程正等待 无法被信号唤醒 的事件(如磁盘 I/O),一般出现在设备驱动程序中,例如正在等待硬件操作 |
T | 停止(Stopped/Traced) | 进程已被暂停执行,例如收到了 SIGSTOP 信号,或者在被调试时被暂停。 |
Z | 僵尸(Zombie) | 子进程已结束/终止,但父进程未回收它的资源(PID 和退出状态仍占用系统资源),导致进程表里留有“尸体” |
X | 死亡(Dead) | 进程已彻底终止,且不会再存在于进程表中(非常短暂极少见,用户通常看不到) |
特殊状态说明
- 僵尸进程 (Z)(一种比较特殊的状态)
- 产生原因:当进程退出并且父进程(使用
wait()
系统调用,后面讲)没有读取到子进程退出的返回代码时就会产生僵死(尸)进程。僵死进程会以终止状态保持在进程表中,并且会一直在等待父进程读取退出状态代码。所以,只要子进程退出,父进程还在运行,但父进程没有读取子进程状态,子进程进入Z
状态。 - 危害:占用 PID,可能导致系统 PID 耗尽。
- 解决:杀死父进程(僵尸进程会由
init
进程接管并回收)。
- 产生原因:当进程退出并且父进程(使用
- 不可中断睡眠 (D)
- 常见场景:进程在执行关键系统调用(如写入磁盘)。
- 处理:通常需等待操作完成,强制终止可能导致数据损坏。
二、如何查看进程状态(进程的状态显示在 STAT
字段下)
1. 使用 ps
命令
ps
可以显示一次性快照信息:
1 | ps aux |
查看部分关键字段:
1 | ps -eo pid,ppid,stat,comm |
- PID:进程 ID。
- PPID:父进程 ID。
- STAT:进程状态(如
S
、R
、Z
等)。 - COMMAND:执行该进程的命令。
输出示例:
1 | PID PPID STAT COMMAND |
解释:
Ss
:systemd
是会话首领(s),当前处于可中断睡眠状态(S)。Ssl
:hostguard
是会话首领(s),处于可中断睡眠状态(S),且是多线程进程(l)。Ss+
:agetty
是会话首领(s),处于可中断睡眠状态(S),且是前台进程(+)。Z
:python
进程已变成僵尸进程(Z),等待父进程回收。
STAT
字段可能含有多个字符组合,如 Ss
、R+
,额外属性(从第二列字符开始):
字符 | 属性名称 | 含义说明 |
---|---|---|
s | 会话首领(Session Leader) | 该进程是会话的领导者,通常是终端启动的第一个进程。 |
+ | 前台进程组(Foreground) | 进程属于前台进程组,能接受来自终端的输入信号。 |
l | 多线程(Multithreaded) | 进程是多线程的(用 Linux 的线程实现)。 |
L | 内存锁定(Locked in Memory) | 进程有部分内存被锁定,无法被换出。 |
N | 低优先级(Low Priority) | 进程运行在低优先级(nice 值大于 0)。 |
< | 高优先级(High Priority) | 进程运行在实时优先级(nice 值小于 0)。 |
s | 会话首领(Session Leader) | 该进程是会话的领导者(通常是终端启动的第一个进程)。 |
n | 优先级降低(Reduced Priority) | 进程的优先级被降低(通过 nice 命令调整)。 |
** | 进程被克隆(Cloned Process) | 进程是通过 clone() 系统调用创建的,通常用于线程实现。 |
2. 使用 top
命令
top
命令动态刷新进程状态:
1 | top |
S
列表示进程状态,通常会看到R
、S
、Z
等状态。- 按
q
键退出。
3. 使用 proc
文件系统
每个进程在 /proc
下有一个专属目录(以 PID 命名),可以直接查看其状态:
1 | cat /proc/<PID>/status | grep State |
示例:
1 | cat /proc/1234/status | grep State |
三、进程状态的生命周期
一个典型的进程生命周期大致如下:
创建(Created): 用
fork()
创建进程,父进程复制自身的内存空间。就绪(Ready): 进程已准备好运行,等待 CPU 调度。
运行(Running): 进程正在被 CPU 执行。
阻塞/睡眠(Blocked/Sleeping): 进程等待外部事件(如 I/O)完成 (可中断睡眠和不可中断睡眠)。
- (挂起(Suspended):通常由用户或调试器主动暂停进程(如
CTRL + z
))
- (挂起(Suspended):通常由用户或调试器主动暂停进程(如
终止(Terminated): 进程执行完成或被强制终止。
僵尸(Zombie): 子进程结束后,父进程未回收其退出信息,导致子进程残留在进程表。
销毁(Dead): 僵尸进程被父进程回收后,彻底消失。
四、进程状态转换示意图
这部分比较抽象,了解部分即可。 典型的转换流程:新建 (New) → 就绪 (R) → 运行 (R) ↔ 阻塞 (S/D) → 终止 (Z/X),还有
1 | +---------------------+ +-----------------------+ |
进程的状态模型并没有一个单一的官方标准定义,而是根据不同的操作系统和理论模型有不同的实现。 不过,通常讨论的 三态、五态和七态模型 是基于操作系统的进程管理理论中的常见模型。
[!WARNING]
下面是从网络上找的相关图片,只是 偏向正确,因为没有具体标准定义!
实验 1:僵尸进程的创建与监控
1. 实验步骤
- 编写僵尸进程代码:父进程创建子进程后,子进程立即退出,但父进程不调用
wait()
去回收子进程的资源,从而让子进程变成僵尸进程。 - 编译与运行:运行代码后,使用
ps
命令在另一个终端监控进程状态。 - 观察现象:子进程退出后,父进程没有回收它,它的状态会变成
Z
(Zombie)。
2. 僵尸进程代码:
1 |
|
3. 运行步骤:
- 编译代码:
1 | gcc corpse.c -o corpse |
- 运行程序:
1 | ./corpse |
- 在另一个终端用
ps
命令观察:
1 | ps -eo pid,ppid,stat,cmd | grep 'Z' |
现象:你会发现子进程的
STAT
显示为Z
,说明它已变成僵尸进程。危害:
- PID 资源耗尽:僵尸进程本身不消耗资源,但它的 PID (进程标识符)不会被释放。如果系统产生大量僵尸进程,PID 会被耗尽,导致新进程无法创建!
- 内存泄漏:内核保留僵尸进程的退出状态和资源描述符,直到父进程回收。
解决办法:通过
kill
杀掉父进程,僵尸进程会被init
进程回收。
1 | kill -9 父进程PID # 终止父进程,子进程由 init 进程回收 |
实验 2:孤儿进程的创建与监控
1. 实验步骤:
- 编写孤儿进程代码:父进程创建子进程后,父进程主动退出,子进程被
init
进程接管。 - 编译与运行:运行代码后,用
ps
命令监控子进程的PPID
(父进程 ID)。 - 观察现象:父进程退出后,子进程的
PPID
变成1
(即init
进程)。
2. 孤儿进程代码:
1 |
|
3. 运行步骤:
- 编译代码:
1 | gcc orphan.c -o orphan |
- 运行程序:
1 | ./orphan |
- 在另一个终端用
ps
命令观察:
1 | ps -eo pid,ppid,stat,cmd | grep 子进程PID |
现象:你会发现子进程的
PPID
变成1
,说明它被init
进程接管,变成了孤儿进程。危害:孤儿进程一般不会直接危害系统,主要分下面几种情况(这就像一个“扫地机器人”:只有当进程“倒下”(退出)时,
init
才来打扫;如果进程一直乱跑或者不断“生孩子”,init
也束手无策。):自动回收机制:
- 当父进程退出后,孤儿进程会被
init
进程(PID = 1)收养。 init
进程会定期调用wait()
来回收那些已经结束的子进程,避免出现僵尸进程。
- 当父进程退出后,孤儿进程会被
陷入死循环的情况:
- 如果孤儿进程本身陷入死循环(比如
while (1) { sleep(1); }
),它不会退出,自然也不会被init
回收。 init
只能回收已经 退出的子进程。如果孤儿进程一直在运行,init
什么也做不了。
- 如果孤儿进程本身陷入死循环(比如
大量创建子进程的情况:
孤儿进程本身如果大量
fork()
创建新子进程,这些子进程同样会被它的父进程(即原孤儿进程)管理。如果原孤儿进程随后退出,那么它创建的子进程就会变成新的孤儿进程,
init
会接管它们。如果这种行为持续发生,系统的进程表(PID 资源)可能会被快速耗尽,导致系统无法创建新进程,从而影响稳定性。
解决办法:手动杀掉孤儿进程:
1 | kill -9 子进程PID |
4. 对比总结
[!NOTE]
- 孤儿进程的资源会被回收吗?
- 是的,
init
进程会主动回收孤儿进程。- 僵尸进程和孤儿进程哪个更危险?
- 僵尸进程,因为长期占用系统资源。
特性 | 僵尸进程 | 孤儿进程 |
---|---|---|
产生条件 | 子进程终止,父进程未调用 wait() |
父进程终止,子进程仍在运行 |
危害 | 占用 PID 和内核资源 | 一般无危害(主要由 init 进程自动回收) |
状态符号 | Z (Zombie) |
无特殊状态,PPID 变为 1 |
处理方式 | 终止父进程或修复父进程代码调用 wait() |
通常无需处理 |
系统影响 | 可能导致 PID 耗尽 | 无负面影响 |
五、进程的优先级
在 Linux 系统中,进程的优先级(Priority)决定了调度器(Scheduler)选择哪个进程优先运行。这里有两个关键概念:PRI(优先级) 和 NI(静态优先级/Nice 值)。
名称 | 全称 | 范围 | 作用 |
---|---|---|---|
PRI | Priority | 0~139 | 进程的实际优先级,值越小优先级越高 |
NI | Nice Value | -20~19 | 用户可调整的优先级修正值,影响 PRI,NI 是用户可调节的值,用来“建议”系统优先级,但最终还是由内核决定。 |
1. PRI(Priority,优先级):
- 定义:表示内核调度时分配给进程的优先级。数值越小,优先级越高。
- 范围:
- 内核视角:
PRI
范围0 ~ 139
是进程的 真实优先级,值越小优先级越高。 - 用户视角:实时进程的优先级是
0 ~ 99
(适合对实时性要求高的任务,如工业控制、音视频处理等),普通进程的优先级是100 ~ 139
(普通用户任务,如文本编辑器、浏览器等)。
- 内核视角:
- 决定因素:
PRI = 20 + NI
(普通进程),nice
值影响普通进程的优先级,但 不会直接影响实时进程的优先级。 - 查看命令:
1 | ps -eo pid,pri,ni,comm |
输出示例:
1 | PID PRI NI COMMAND |
或者:top
命令,在 top
界面里:
- 按 f 键进入字段选择界面,找到 PRI 和 NI,按空格键选中后回车返回主界面,即可看到优先级信息。
- PRI 和 NI 默认就会显示在列表中。
2. NI(Nice 值):
公式:
PRI (新) = PRI (默认) + NI
- 默认 PRI 通常为 80(不同系统可能不同),因此实际 PRI 范围是:
80 + (-20) = 60
(最高优先级) 到80 + 19 = 99
(最低优先级)。
- 默认 PRI 通常为 80(不同系统可能不同),因此实际 PRI 范围是:
定义:影响普通进程的优先级(PRI),表示“进程对 CPU 资源的友好程度”。数值越高,优先级越低,越“谦让”。当
nice
值为负值的时候,那么该程序将会优先级值将变小,即其优先级会变高,则其越快被执行。所以,调整进程优先级,在 Linux 下,就是调整进程 nice 值。范围:
-20
(最重要)到19
(最不重要),一共 40 个级别。默认值为0
。调整规则:NI 值越低,PRI 越小,进程优先级越高
- 普通用户只能 降低优先级(NI 值 ≥ 0)
- Root 用户可 提升优先级(NI 值 < 0)
关系:$PRI = 20 + NI$
NI = -20
→ PRI = 0 + 20 = 20(最高优先级)NI = 0
→ PRI = 0 + 20 = 20(默认值)NI = 19
→ PRI = 19 + 20 = 39(最低优先级)
调整 NI 值:
1 | # 启动时设置 nice 值 |
3. 注意事项
- 避免滥用高优先级:过多高优先级进程可能导致系统不稳定(如 GUI 无响应)。
- NI 值继承:子进程会继承父进程的 NI 值。
六、其他概念补充
[!IMPORTANT]
- 竞争性:进程争抢 CPU、内存等资源。
- 独立性:每个进程互不干扰,资源独立。
- 并行:多核 CPU 下的真正“同时运行”。
- 并发:单核 CPU 下的“快速切换”,让多个进程看似“同时进行”。
1. 竞争性(Competitiveness):
- 定义:由于系统中的进程数量众多,而 CPU 资源有限(甚至可能只有 1 个),所以各个进程需要竞争 CPU 使用权。为了高效完成任务,更合理竞争相关资源,便有了优先级。
- 引申含义:
- 系统通过调度算法来决定哪个进程先使用 CPU。
- 为了更合理地分配资源,引入了 优先级(Priority) 概念,优先级高的进程更有可能被调度运行。
- 竞争不局限于 CPU,进程还可能竞争内存、I/O 设备等资源。
2. 独立性(Independence):
- 定义:多进程运行时,每个进程都有自己独立的内存空间和资源,彼此不会直接影响。
- 特点:
- 每个进程的执行逻辑、变量、文件描述符等都是独立的。
- 若需要通信,通常使用 进程间通信 机制,例如:管道(Pipe)、共享内存、消息队列等。
- 意义:独立性保证了系统稳定性,即使某个进程崩溃,其他进程也能正常运行。
3. 并行(Parallelism):
- 定义:多个进程在多个 CPU 核心上同时运行,互不干扰,真正实现“同时进行”。
- 条件:需要多核 CPU 或多台机器(分布式系统)支持。
- 举例:在 4 核 CPU 上,4 个进程可以在每个核心上独立运行,互不干扰,实现真正的“并行”。
4. 并发(Concurrency):
- 定义:多个进程在一个CPU下采用进程切换的方式,在一段时间之内,让多个进程都得以推进,称之为并发。(在单核 CPU 上,多个进程通过频繁的“时间片切换”,让每个进程在宏观上看起来同时执行)
- 原理:CPU 每隔一段时间(时间片)切换到下一个进程执行,切换速度极快,人眼无法分辨,以为多个进程“同时”运行。
- 举例:在单核 CPU 上运行多个下载任务,CPU 不断在不同任务之间切换,让所有任务都能逐步完成。
[!NOTE]
进程切换
- 概念:在单 CPU 系统中,多个进程通过轮流使用 CPU 资源实现“并发”,这依赖于操作系统的“进程切换”。
- 实现方式:采用调度算法(如时间片轮转的调度算法),每个进程被分配固定的时间片。时间片用完后,系统暂停该进程,保存状态,并切换到下一个进程执行。
上下文保存
- 意义:确保进程被切换时,其运行状态(即“上下文”)被妥善保存,待下次恢复执行。上下文是指进程执行时的环境状态,包括寄存器的值、程序计数器的值等。
- 保存内容:
- 通用寄存器:如eax、ebx、ecx、edx等,用于存储操作数和计算结果。
- 栈指针和基指针:如ebp、esp,用于管理函数调用和局部变量的存储。
- 程序计数器(eip):记录当前进程正在执行指令的下一条指令的地址,决定了进程执行的流程。
- 状态寄存器(status):包含条件码等信息,用于判断指令执行后的状态。
- 保存时机:在进程被切换时,需要先保存当前进程的上下文,然后恢复下一个要执行进程的上下文。
寄存器的作用
- 通用寄存器:用于存储操作数和中间结果,提高数据访问速度,如在算术运算和数据处理指令中使用。
- 程序计数器(eip):指向进程下次执行的指令地址,是进程执行流程的关键。
- 栈指针和基指针:用于管理函数调用时的参数传递和局部变量存储,维护函数调用栈的结构。
- 状态寄存器:保存CPU的状态信息,如进位标志、零标志等,用于条件判断和跳转指令。
进程切换的具体步骤
- 保存当前进程上下文:暂停当前进程,将寄存器、程序计数器等保存到该进程的控制块(如
task_struct
)中。- 更新进程状态:将当前进程状态改为“就绪”或“等待”,并加入对应队列。
- 选择新进程:根据调度算法(如优先级调度)选择下一个要执行的进程。
- 恢复新进程上下文:从新进程的控制块恢复寄存器和程序计数器等信息。
- 切换至新进程:CPU 开始执行新进程的指令,完成切换。