🦄 个人主页: 小米里的大麦-CSDN博客
🎏 所属专栏: Linux_小米里的大麦的博客-CSDN博客
🎁 GitHub主页: 小米里的大麦的 GitHub
⚙️ 操作环境: Visual Studio 2022
文章目录
- 进程状态和优先级
- 一、进程状态分类
- 特殊状态说明
- 二、如何查看进程状态(进程的状态显示在 `STAT` 字段下)
- 1. 使用 `ps` 命令
- 2. 使用 `top` 命令
- 3. 使用 `proc` 文件系统
- 三、进程状态的生命周期
- 四、进程状态转换示意图
- 实验 1:僵尸进程的创建与监控
- 1. 实验步骤
- 2. 僵尸进程代码:
- 3. 运行步骤:
- 实验 2:孤儿进程的创建与监控
- 1. 实验步骤:
- 2. 孤儿进程代码:
- 3. 运行步骤:
- 4. 对比总结
- 五、进程的优先级
- 1. PRI(Priority,优先级):
- 2. NI(Nice 值):
- 3. 注意事项
- 六、其他概念补充
- 1. 竞争性(Competitiveness):
- 2. 独立性(Independence):
- 3. 并行(Parallelism):
- 4. 并发(Concurrency):
- 进程切换
- 上下文保存
- 寄存器的作用
- 进程切换的具体步骤
- 共勉
进程状态和优先级
[!TIP]
相关推荐视频 | B站
一、进程状态分类
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
可以显示一次性快照信息:
ps aux
查看部分关键字段:
ps -eo pid,ppid,stat,comm
- PID:进程 ID。
- PPID:父进程 ID。
- STAT:进程状态(如
S
、R
、Z
等)。 - COMMAND:执行该进程的命令。
输出示例:
PID PPID STAT COMMAND1 0 Ss systemd2 0 S kthreadd123 1 R bash456 123 S vim789 456 Z python945 1 Ssl hostguard1254 1 Ss+ agetty
解释:
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
命令动态刷新进程状态:
top
S
列表示进程状态,通常会看到R
、S
、Z
等状态。- 按
q
键退出。
3. 使用 proc
文件系统
每个进程在 /proc
下有一个专属目录(以 PID 命名),可以直接查看其状态:
cat /proc/<PID>/status | grep State
示例:
cat /proc/1234/status | grep State
State: S (sleeping)
三、进程状态的生命周期
一个典型的进程生命周期大致如下:
-
创建(Created): 用
fork()
创建进程,父进程复制自身的内存空间。 -
就绪(Ready): 进程已准备好运行,等待 CPU 调度。
-
运行(Running): 进程正在被 CPU 执行。
-
阻塞/睡眠(Blocked/Sleeping): 进程等待外部事件(如 I/O)完成 (可中断睡眠和不可中断睡眠)。
- (挂起(Suspended):通常由用户或调试器主动暂停进程(如
CTRL + z
))
- (挂起(Suspended):通常由用户或调试器主动暂停进程(如
-
终止(Terminated): 进程执行完成或被强制终止。
-
僵尸(Zombie): 子进程结束后,父进程未回收其退出信息,导致子进程残留在进程表。
-
销毁(Dead): 僵尸进程被父进程回收后,彻底消失。
四、进程状态转换示意图
这部分比较抽象,了解部分即可。 典型的转换流程:新建 (New) → 就绪 ® → 运行 ® ↔ 阻塞 (S/D) → 终止 (Z/X),还有
+---------------------+ +-----------------------+| 创建 (New) | | 不可中断睡眠 (D) |+---------------------+ +-----------------------+↓ ↑+---------------------+ 等待事件完成 || 可运行/运行中 (R) | ←--------------→ | 可中断睡眠 (S) |+---------------------+ 调度器分配 CPU +---------------------+| ↑ ↑ || | | 等待硬件操作 | 挂起信号↓ | ↓ ↓+---------------------+ +---------------------+| 停止 (T) | ← SIGSTOP/SIGTSTP | 终止/僵尸 (Z) |+---------------------+ +---------------------+| || SIGCONT ↓↓ +---------------------++---------------------+ | 回收 (X) || 可运行/运行中 (R) | → 父进程回收 → +---------------------++---------------------+
进程的状态模型并没有一个单一的官方标准定义,而是根据不同的操作系统和理论模型有不同的实现。 不过,通常讨论的 三态、五态和七态模型 是基于操作系统的进程管理理论中的常见模型。
[!WARNING]
下面是从网络上找的相关图片,只是 偏向正确,因为没有具体标准定义!
实验 1:僵尸进程的创建与监控
1. 实验步骤
- 编写僵尸进程代码:父进程创建子进程后,子进程立即退出,但父进程不调用
wait()
去回收子进程的资源,从而让子进程变成僵尸进程。 - 编译与运行:运行代码后,使用
ps
命令在另一个终端监控进程状态。 - 观察现象:子进程退出后,父进程没有回收它,它的状态会变成
Z
(Zombie)。
2. 僵尸进程代码:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>int main()
{pid_t pid = fork(); // 创建子进程if (pid < 0){perror("fork失败");exit(1);}else if (pid == 0) // 子进程{printf("子进程(PID:%d)正在运行...\n", getpid());sleep(5); // 子进程运行5秒后退出printf("子进程(PID:%d)即将退出...\n", getpid());exit(0);}else // 父进程{printf("父进程(PID:%d)正在运行...\n", getpid());sleep(30); // 父进程等待30秒(不调用wait(),导致子进程成为僵尸进程)printf("父进程结束。\n");}return 0;
}
3. 运行步骤:
- 编译代码:
gcc corpse.c -o corpse
- 运行程序:
./corpse
- 在另一个终端用
ps
命令观察:
ps -eo pid,ppid,stat,cmd | grep 'Z'
-
现象:你会发现子进程的
STAT
显示为Z
,说明它已变成僵尸进程。 -
危害:
- PID 资源耗尽:僵尸进程本身不消耗资源,但它的 PID (进程标识符)不会被释放。如果系统产生大量僵尸进程,PID 会被耗尽,导致新进程无法创建!
- 内存泄漏:内核保留僵尸进程的退出状态和资源描述符,直到父进程回收。
-
解决办法:通过
kill
杀掉父进程,僵尸进程会被init
进程回收。
kill -9 父进程PID # 终止父进程,子进程由 init 进程回收
实验 2:孤儿进程的创建与监控
1. 实验步骤:
- 编写孤儿进程代码:父进程创建子进程后,父进程主动退出,子进程被
init
进程接管。 - 编译与运行:运行代码后,用
ps
命令监控子进程的PPID
(父进程 ID)。 - 观察现象:父进程退出后,子进程的
PPID
变成1
(即init
进程)。
2. 孤儿进程代码:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>int main()
{pid_t pid = fork(); // 创建子进程if (pid < 0){perror("fork 失败");exit(1);}else if (pid == 0) // 子进程逻辑{sleep(1); // 确保父进程先退出printf("子进程 PID: %d, PPID: %d\n", getpid(), getppid());while (1){sleep(10); // 保持子进程存活,方便观察}}else // 父进程逻辑{printf("父进程 PID: %d 创建了子进程 PID: %d,然后退出\n", getpid(), pid);exit(0); // 父进程主动退出,产生孤儿进程}return 0;
}
3. 运行步骤:
- 编译代码:
gcc orphan.c -o orphan
- 运行程序:
./orphan
- 在另一个终端用
ps
命令观察:
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 资源)可能会被快速耗尽,导致系统无法创建新进程,从而影响稳定性。
-
-
-
解决办法:手动杀掉孤儿进程:
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
值影响普通进程的优先级,但 不会直接影响实时进程的优先级。 - 查看命令:
ps -eo pid,pri,ni,comm
输出示例:
PID PRI NI COMMAND1 80 0 systemd1234 90 10 python5678 70 -5 nginx
或者: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)
-
关系: P R I = 20 + N I PRI = 20 + NI PRI=20+NI
NI = -20
→ PRI = 0 + 20 = 20(最高优先级)NI = 0
→ PRI = 0 + 20 = 20(默认值)NI = 19
→ PRI = 19 + 20 = 39(最低优先级)
-
调整 NI 值:
# 启动时设置 nice 值
nice -n 10 ./可执行程序# 运行中调整 nice 值
renice -5 1234 # 把 PID 为 1234 的进程 NI 调整到 -5
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 开始执行新进程的指令,完成切换。
共勉