进程状态
1.进程状态
运行,阻塞,挂起
运行状态

阻塞状态
我们先来看一个现象


当我们运行到这里的时候进程就卡住了,进程再等待我们的输入
这个就是进程的阻塞的状态,

为了管理硬件,要做到先描述,在组织
管理硬件

阻塞等待键盘地输入
pcb会被放到键盘地等待队列里面,知道等到键盘地输入完成后又回到运行队列里面

总述
-
进程正常运行阶段 程序刚启动时,操作系统把 CPU 分配给这个进程,进程处于运行态(Running),依次执行定义变量的代码。
-
触发阻塞的关键:scanf 等待 IO 输入 当 CPU 执行到
scanf库函数时,程序需要等待外设(键盘)的数据,当前没有用户输入,外设没有就绪数据。 操作系统会执行以下操作:
- 立刻剥夺当前进程的 CPU 使用权,不再给它分配时间片;
- 将该进程的 PCB(进程控制块)移动到阻塞队列(等待队列);
- 进程进入阻塞状态(Blocked / Waiting,也叫等待态)。
- 阻塞状态的特征
- 进程主动放弃 CPU,不参与 CPU 调度排队;
- 哪怕 CPU 空闲,操作系统也不会唤醒、执行这个进程;
- 进程会一直卡在阻塞队列,直到等待的事件发生(用户在键盘输入数字并回车)。
- 输入数字后的状态切换 当我们在终端输入数字、按下回车,键盘外设完成数据输入,触发中断:
- 操作系统感知到等待的 IO 事件完成;
- 将该进程从阻塞队列移出,放入就绪队列(Ready);
- 进程变为就绪态,等待操作系统调度分配 CPU;
- 再次拿到 CPU 后,
scanf读取输入的值存入变量 a,执行return 0,进程正常终止。
进程是什么状态就是看进程在哪个队列里面,在运行队列里面就是运行状态,在阻塞队列里面就是阻塞状态
挂起状态
我们如果一直打开和使用进程,后来内存空间不够了,然后依然有进程需要进行,此时操作系统会怎么办?
这时候会有两种挂起状态
阻塞挂起
将再看等待键盘等就绪的进程,一直占用内存,但是没有处于运行,此时内存不够用时,操作系统会将这个进程挂起,将这个空间的数据交换到磁盘的swap分区里面,但是描述者个进程的task_struct不会清除,键盘等就绪的时候,swap分区里面的数据又会重新加载到内存,进程继续运行

二、核心流程(scanf阻塞代码案例)
进程执行scanf等待键盘输入,进入阻塞态,PCB 留在内存,代码数据暂存内存;
系统物理内存耗尽,内核筛选长时间无访问的冷页(这个阻塞进程的数据);
执行swap out:将进程代码、数据拷贝到磁盘 swap 分区;
释放这部分物理内存,供前台运行的程序使用;
当用户敲击键盘唤醒该阻塞进程时,执行swap in:从 swap 分区把代码 / 数据读回内存,进程恢复运行。
当阻塞挂起之后内存还是不够用的时候,就会有运行挂起
运行挂起
运行挂起(Suspended / Hang) 是指程序或线程在执行过程中暂停运行、不再响应用户操作、也不继续执行后续代码的状态。它既不是正常退出,也不是立即崩溃,而是"卡住了"。

将还没有执行到的进程的代码和数据都swap out到swap分区,要运行了在swap in到内存里面
如果还是不够的,那么操作系统会杀掉一些程序
了解swap
一、Swap "以时间换空间" 的核心逻辑
物理内存容量有限,能同时驻留的进程数据有限。 操作系统借助 Swap 分区(磁盘),把暂时不活跃进程的代码、数据换出到低速磁盘,腾出物理内存给当前活跃进程执行:
- 空间层面收益:逻辑上系统可同时管理远超物理内存容量的进程,实现多进程并发调度;
- 时间层面代价 :磁盘 IO 读写速度比物理内存慢几百~上千倍,每一次换出
swap out、换入swap in都要消耗大量 IO 耗时; 这就是牺牲运行时间,换取可用内存空间的设计思路。
二、过多 Swap 带来的两大负面影响
1. 大量 IO 操作,整机运行速度严重下降
- 频繁换出、换入会持续占用磁盘 IO 带宽,CPU 大量空闲等待磁盘读写(IO 等待),CPU 利用率看似低,但所有程序卡顿、响应延迟极高,这种现象称为内存抖动(Thrashing);
- 举例:你的阻塞进程被换到 Swap,键盘输入唤醒它后,不能立刻执行代码,必须先从磁盘把数据读回内存,多出一段磁盘加载等待时间。
2. 产生两类内存碎片
(1)物理内存碎片(内部 / 外部碎片)
内核频繁回收、释放内存页给 Swap:
- 不断分配、释放内存,物理内存中出现大量不连续的空闲小页框(外部碎片),明明总空闲内存充足,却无法分配连续大块内存;
- 进程换入时占用零散内存页,页内未填满的闲置空间形成内部碎片,内存利用率降低。
(2)Swap 磁盘碎片
反复将进程数据写入、擦除 Swap 分区,磁盘上的数据块变得零散;后续换入时需要多次寻道读取碎片数据,进一步拉长 IO 耗时,恶性循环拖慢系统。
在Linux系统理解进程状态
linux中进程状态的表示

R状态
R:running状态
我们先来看一个现象


为什么state状态是s??
理论上无限循环疯狂打印,应该持续占用 CPU,状态是 R,但查到是 S,根源是标准输出缓冲区。
当我们改成这样就是R状态,运行状态


S状态
S:修眠状态,可被中断状态,也叫做浅度休眠


这里的休眠就是我们之前说的阻塞状态
我们再来看一下+是什么意思
前后台进程
stat + 前台进程
前台进程我们可以使用ctrl +c将其终止

也可以使用信号

bash
kill -9 pid

stat 后台进程

bash
./my_schedule.exe &

此时状态就是R

后台进程不能使用ctrl + c将其终止
但是也可以用信号将其终止
bash
kill -9 pid

前后台进程的最主要区别是前台进程可以从键盘获取信息,而后台进程则不可以
D状态
D:休眠状态,是一个不可被中断的状态(disk sleep)

如果现在内存不够,操作系统将这个进程杀了,那么磁盘到底有没有写入成功是不知道,会让数据损失
所以D状态就是让这个进程不能被杀,保证了数据的安全
T状态
T:被强行叫停,必须手动下令才肯继续跑。
1.ctrl +z 让进程进入T状态



2.后台读取终端输入被系统自动暂停

我们让后台进程获取键盘输入,而我们之前说过后台进程不能获取键盘输入

,此时操作系统会直接将这个进程杀掉

3.用信号将进程stop

bash
kill -19 pid

恢复进程
cpp
kill -18 pid
| 对比项 | S 状态 | T 状态 |
|---|---|---|
| 运行状态 | 进程在后台等待事件,逻辑仍存活 | 进程彻底冻结,代码不再执行 |
| 如何产生 | sleep()、scanf、等待锁、IO 就绪 |
1. 前台按 Ctrl+Z2. kill -STOP PID3. 后台读取终端输入被系统自动暂停 |
| 自动恢复 | 等待的事件完成自动唤醒(sleep 到点、输入回车) | 不会自动恢复 ,必须手动执行 bg / fg / kill -CONT PID |
| 信号响应 | 能响应 kill、Ctrl+C 终止 |
接收终止信号也不会退出,只会保持暂停;仅 CONT 能解冻 |
| 前后台标识 | 前台 S+ / 后台 S |
统一 T,无 +(暂停后脱离前台运行) |
| CPU 占用 | 几乎 0,休眠等待 | 完全 0,彻底停止调度 |
t状态
t:进程被调试器(gdb/strace/lldb) 打断点暂停,和普通手动暂停 T 是两类状态。

gdb进行调试

- PID=10329:是 gdb 调试器进程,
S+前台可中断睡眠,等待键盘的输入; - PID=10435:是被调试的程序,STAT=t
当一个进程要被杀掉之前,要记录他的信息,
X状态
X;进程已经完成退出逻辑,内核正在彻底销毁该进程的所有内核资源,处于最后的资源回收阶段。
- 瞬时性,几乎无法观测
X 是过渡状态,无任何等待逻辑,内核不会主动暂停清理流程;
清理耗时通常几微秒,而你手动 ps、top、循环监控都是毫秒级采样;
人眼和普通脚本永远抓不到,只有 CPU 极度卡死、内核调度阻塞时才有极低概率捕捉。
- X 状态进程没有任何可运行资源
处于 X 的进程:
用户空间内存、文件、线程、信号全部提前释放完毕;
仅剩内核正在销毁的 task_struct,销毁结束立刻彻底消失;
不占用 CPU、不占用用户内存,只占用极短时间的内核操作。
Z状态(僵尸)
Z:状态就是僵尸进程
由于子进程结束,父进程没有对其进行回收

子进程运行完后,父进程还在运行但是没有对进程进行回收

当子进程还没有运行结束时,大部分时间都是在S状态,少部分时间将结果刷新到显示器上,R状态

当子进程结束时,状态变成Z状态,僵尸状态,等待进程将他回收,defunct就是无效的意思,实际是进程的代码和数据已经从内存上清除了,但是task_struct还在内存里面

我们来思考几个问题

对于第一个问题,我们可以来思考一下main函数,都会有一个return 0;这到底是给谁进行return,就是操作系统,告诉他进程是怎么结束的,是异常结束,还是正常结束.那么对于进程而言就是进程异常终止时收到的信号编号。
对于第二个问题,进程退出信息存放在子进程自身的内核结构体
task_struct中,进程退出时是先将代码和数据进行释放,然后会依然保存进程的task_struct来存放自己的退出信息对于第三个问题,就是将内存里有关这个进程的部分全部清除,避免内存泄漏
对于第四个问题,就是由该进程的父进程进行回收,会有相应的系统调用接口.
如果父进程不会收子进程,子进程就会一直处于Z状态,一直占用就会导致内存泄漏
孤儿进程
孤儿进程就是子进程还没有结束,父进程先结束了,子进程就变成了孤儿进程



一开始父子进程都在跑



后来父进程结束了,子进程还没有结束,子进程的ppid变成了1,被系统一号进程 systemd(init)领养。为什么呢???
父进程提前退出后,子进程没人管理了,内核必须做两件事:
- 重新分配养父(PID=1 systemd) 1 号进程是系统初始化进程,生命周期从开机到关机永久存活,专门负责兜底回收所有孤儿。
- 等孤儿进程将来退出时,自动回收 孤儿结束后,1 号进程会自动调用 wait,读取它的退出信息、销毁
task_struct,避免孤儿死后变成僵尸。


后来我们在屏幕上按ctrl + c不能将子进程杀死,要用kill才能将子进程杀死,也就是说明孤儿进程,操作系统会自动将他变成后台进程

