在操作系统中,进程(或线程)可以处于多种状态,其中 运行、阻塞、挂起是最核心的三种状态。它们描述了进程在不同阶段的资源占用情况和调度行为。
运行状态:进程正在 CPU 上执行指令。单核 CPU 同一时间只能有一个进程处于运行状态(多核 CPU 可并行运行多个进程)。
触发条件:进程被 调度器选中,从就绪队列分配到 CPU 时间片。
特点:占用 CPU 资源 ,执行代码。时间片用完后,可能被剥夺 CPU,回到 就绪状态。
示例:
// 一个运行中的进程示例(计算密集型任务)
while (1) {
int result = 1 + 1; // 占用 CPU 计算
}
时间片概念:操作系统分配给每个可运行进程的一小段CPU时间。
为什么需要时间片?
原因:在早期的操作系统中,一个进程一旦开始运行,就会一直占用CPU,直到它主动放弃(比如需要等待输入/输出操作完成)。这会导致几个严重问题:
(1)如果一个进程正在进行一个长时间的计算,其他所有进程都无法得到响应,整个系统会显得"卡死"。(2)无法实现多任务
时间片的引入,正是为了实现抢占式多任务。
调度器:负责时间片的管理与分配
工作流程:在时间片轮转调度机制中,调度器首先从就绪队列(即所有已准备好运行的进程列表)中选出首个进程,为其分配一个固定的时间片(例如 10 毫秒),随后让 CPU 启动该进程的执行;与此同时,硬件计时器(时钟中断)开始同步倒计时,实时监控进程的运行时长。当时间片消耗完毕时,计时器会立即发出时钟中断信号,CPU 接收到该信号后,会暂停当前正在执行的进程,并完整保存其当前状态(包括寄存器数据、程序计数器指向位置等关键信息),确保后续可准确恢复执行。此时操作系统的调度器重新获取系统控制权,它会再次检查就绪队列,根据预设的调度算法(可能综合考虑进程优先级、剩余运行时间等因素)选择下一个待运行的进程,接着执行进程切换操作 ------ 恢复所选进程之前保存的现场信息,使其从暂停处继续运行,同时重启硬件计时器开始新的倒计时。整个过程会如此循环往复,让就绪队列中的所有进程得以被公平地轮流分配 CPU 资源,实现多进程的并发执行。
调度队列:调度队列是操作系统中用来管理和排序等待使用CPU的进程的数据结构。它维护着一个"就绪"状态的进程列表,操作系统调度器根据特定的算法从这个队列中选取下一个要运行的进程。可以想象一下在一个银行中,客户就是进程,柜员就是CPU,然后取号机和等待区就是调度队列,叫号的规则就是调度算法.
阻塞状态:进程 暂时无法继续执行,正在等待某个事件(如 I/O 完成、信号量释放等)。不占用 CPU,但仍在内存中。
触发条件:进程请求的资源不可用(如读取磁盘、等待网络数据)。主动调用阻塞式系统调用(如 read()
, sleep()
)
特点:主动调用阻塞式系统调用(如 read()
, sleep()
)事件完成后,进程转为 就绪状态,等待被调度。
示例:
// 一个阻塞的进程示例(等待用户输入)
char buffer[100];
read(STDIN_FILENO, buffer, sizeof(buffer)); // 阻塞,直到用户输入
挂起状态:进程被 强制移出内存,存储到磁盘(交换空间/Swap),以释放内存资源。挂起的进程可能是 阻塞挂起或 就绪挂起。
触发条件:系统内存不足,内核将不活跃的进程挂起。用户或管理员手动挂起进程(如 Ctrl+Z
发送 SIGTSTP
信号)。
特点:不占用内存和 CPU,但保留 PCB(进程控制块)信息。恢复时需要重新加载到内存(可能引发延迟)。
示例:
# 手动挂起一个进程(Shell 命令)
$ sleep 100 &
[1] 12345 # 进程 PID
$ kill -TSTP 12345 # 挂起进程
$ kill -CONT 12345 # 恢复进程
Linux状态:
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 */
};
状态字母 | 数值 | 名称 | 描述 |
---|---|---|---|
R | 0 | Running | 运行中 或 可运行。进程正在 CPU 上执行,或者已经在运行队列中准备就绪,一旦获得 CPU 时间片就能立即运行。 |
S | 1 | Sleeping | 可中断睡眠。进程正在等待某个事件的完成,比如等待用户输入、等待网络数据或等待一个信号。在这种状态下,进程可以被信号(如 Ctrl+C 产生的中断信号)唤醒。这是最常见的睡眠状态。 |
D | 2 | Disk Sleep | 不可中断睡眠。进程通常正在等待 I/O 操作(如磁盘读写)的完成。关键点:处于此状态的进程不能被信号中断或杀死(即使是 kill -9 )。这是为了防止在磁盘进行关键操作时进程被意外终止,导致数据不一致。常见于密集的磁盘读写操作。 |
T | 4 | Stopped | 停止。进程的执行已被暂停("挂起")。通常是由于收到一个信号(例如 SIGSTOP 信号,由按 Ctrl+Z 触发),或者被调试器(如 gdb)在断点处暂停。可以通过 SIGCONT 信号让其继续运行。 |
t | 8 | Tracing Stop | 跟踪停止。与 T 状态类似,但特指进程正在被调试器跟踪时暂停(例如,单步执行时)。 |
X | 16 | Dead | 死亡。进程已经终止,其资源已被系统回收。这个状态只是一个瞬间,你几乎不可能在 ps 或 top 等工具中看到它。 |
Z | 32 | Zombie | 僵尸。进程已经执行完毕并终止,但其父进程还没有调用 wait() 或 waitpid() 来读取它的退出状态。僵尸进程已经释放了大部分资源,但仍在进程表中保留着一个条目(包含进程号、退出状态等信息)。如果父进程异常退出,其僵尸子进程会被 init 进程(PID 1)收养并清理。僵尸进程无法被杀死,因为它已经死了。 |
僵尸进程危害:僵尸进程本身几乎不消耗资源,但其存在会占用系统宝贵的进程号(PID),当PID被耗尽时,系统将无法创建任何新进程,导致严重故障。

孤儿进程:当一个父进程先于其子进程结束时,这个子进程就变成了孤儿进程。
例:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main()
{
pid_t id = fork();
if(id < 0){
perror("fork");
return 1;
}
else if(id == 0){//child
printf("I am child, pid : %d\n", getpid());
sleep(10);
}else{//parent
printf("I am parent, pid: %d\n", getpid());
sleep(3);
exit(0);
}
return 0;
}
Linux 系统为了解决孤儿进程的问题,设计了一个机制:Init 进程(PID 1)会自动成为所有孤儿进程的新父进程(被称作"领养")。Init 进程会负责清理这些孤儿进程(当它们退出时调用 wait()
),从而防止它们变成僵尸进程。
下图表示孤儿进程产生和被init进程领养的过程:

进程状态查看
命令:
ps aux / ps axj
进程优先级:cpu资源分配的先后顺序,就是指进程的优先权.优先权高的进程有优先执行权利。配置进程优先权对多任务环境的linux很有用,可以改善系统性能。 还可以把进程运行到指定的CPU上,这样一来,把不重要的进程安排到某个CPU,可以大大改善系统整体性能。
查看系统进程:
在linux或者unix系统中,用ps --l命令则会类似输出以下几个内容:

这些信息分别表示:
UID : 代表执行者的身份
PID : 代表这个进程的代号
PPID :代表这个进程是由哪个进程发展衍生而来的,亦即父进程的代号
PRI :代表这个进程可被执行的优先级,其值越小越早被执行
NI :代表这个进程的nice值
查看nice值:使用ps或top指令
ps -eo pid,ni,comm --sort=-ni | head
输出中的 NI
列就是 Nice 值。
使用 top
:在 top
命令界面中:NI
列显示 Nice 值。按 r
键可以实时调整一个运行中进程的 Nice 值。
PRI和NI:PRI即进程的优先级,或者通俗点说就是程序被CPU执行的先后顺序,这个值越小进程的优先级别越高,NI就是nice值,其表示进程可被执行的优先级的修正数值,PRI值越小越快被执行,那么加入nice值后,将会使得PRI变为:PRI(new)=PRI(old)+nice 这样,当nice值为负值的时候,那么该程序将会优先级值将变小,即其优先级会变高,则其越快被执行 所以,调整进程优先级,在Linux下,就是调整进程nice值 nice其取值范围是-20至19,一共40个级别。
其他概念:
(1)竞争性: 系统进程数目众多,而CPU资源只有少量,甚至1个,所以进程之间是具有竞争属性的。为了高 效完成任务,更合理竞争相关资源,便具有了优先级
(2)独立性: 多进程运行,需要独享各种资源,多进程运行期间互不干扰
(3)并行: 多个进程在多个CPU下分别,同时进行运行,这称之为并行
(4)并发: 多个进程在一个CPU下采用进程切换的方式,在一段时间之内,让多个进程都得以推进,称之为 并发
普通优先级和实时优先级的区别

特性 | 普通优先级 (SCHED_OTHER/NORMAL) | 实时优先级 (SCHED_FIFO/RR) |
---|---|---|
调度策略 | SCHED_OTHER 或 SCHED_NORMAL |
SCHED_FIFO , SCHED_RR , SCHED_DEADLINE |
优先级表示 | Nice 值 (-20 到 +19) | 实时优先级 (1 到 99) |
优先级高低 | 值越小,优先级越高 (-20最高) | 值越大,优先级越高 (99最高) |
调度器 | 完全公平调度器 (CFS) | 实时调度器 |
核心特性 | 公平性:所有进程公平分享CPU时间 | 确定性:高优先级进程立即运行,可预测 |
抢占规则 | 优先级高的获得更多CPU时间,但不会饿死低优先级进程 | 绝对抢占:高优先级进程立即抢占低优先级进程 |
时间片 | 动态变化,基于优先级和负载 | 固定时间片或无限 |
权限要求 | 任何用户可降低优先级,root可提高 | 需要root权限(因可能导致系统锁死) |
风险性 | 低 | 高(设计不当可导致系统无响应) |
Linux2.6核心设计结构
Linux2.6内核进程调度队列:实现一个时间复杂度为 O(1) 的调度器,无论系统中有多少可运行进程,调度决策都能在恒定时间内完成。

运行队列:
每个 CPU 都有一个自己的运行队列结构,包含两个主要的优先级数组:
struct runqueue {
prio_array_t *active; // 指向活动数组
prio_array_t *expired; // 指向过期数组
// ... 其他字段
};
优先级数组:
#define MAX_PRIO 140 // 2.6内核中的总优先级数
struct prio_array {
int nr_active; // 队列中的总进程数
unsigned long bitmap[BITMAP_SIZE]; // 优先级位图
struct list_head queue[MAX_PRIO]; // 140个优先级队列
};
普通优先级:100~139(我们都是普通的优先级,想想nice值的取值范围,可与之对应!)
实时优先级:0~99(不关心)
活动队列:
时间片还没有结束的所有进程都按照优先级放在该队列
nr_active: 总共有多少个运行状态的进程
queue[140]: 一个元素就是一个进程队列,相同优先级的进程按照FIFO规则进行排队调度,所以,数组下 标就是优先级!
从该结构中,选择一个最合适的进程,过程是怎么的呢? 1. 从0下表开始遍历queue[140] 2. 找到第一个非空队列,该队列必定为优先级最高的队列 3. 拿到选中队列的第一个进程,开始运行,调度完成! 4. 遍历queue[140]时间复杂度是常数!但还是太低效了!
bitmap[5]:一共140个优先级,一共140个进程队列,为了提高查找非空队列的效率,就可以用5*32个 比特位表示队列是否为空,这样,便可以大大提高查找效率!
过期队列:过期队列和活动队列结构一模一样,过期队列上放置的进程,都是时间片耗尽的进程,当活动队列上的进程都被处理完毕之后,对过期队列的进程进行时间片重新计算
active指针和expired指针:active指针永远指向活动队列,expired指针永远指向过期队列,可是活动队列上的进程会越来越少,过期队列上的进程会越来越多,因为进程时间片到期时一直都存在的。 没关系,在合适的时候,只要能够交换active指针和expired指针的内容,就相当于有具有了一批新的活动进程!