前言:欢迎各位光临本博客,这里小编带你直接手撕Make/Makefile (自动化构建),文章并不复杂,愿诸君耐其心性,忘却杂尘,道有所长!!!!
**🔥个人主页:IF’Maxue-CSDN博客
🎬作者简介:C++研发方向学习者
📖**个人专栏:
《C语言》
《C++深度学习》
《Linux》
《数据结构》
《数学建模》**⭐️人生格言:生活是默默的坚持,毅力是永久的享受。不破不立,远方请直行!
文章目录
- 一、先搞懂:进程状态藏在哪?
- 二、课本里的核心逻辑:3句话说透
- 三、基础三态:运行、阻塞、挂起(大白话版)
- 1. 运行态:“在工位上,要么干活要么等活”
- 2. 阻塞态:“等材料,暂时离开工位”
- 3. 挂起态:“工位不够,去临时仓库待着”
- 四、内核小知识:用“链表”管进程
- 五、Linux实际进程状态:逐个拆,附代码
- 1. R状态:运行态(Running)
- 2. S状态:可中断休眠(浅睡眠)
- 3. T状态:暂停态(Stopped)
- 4. t状态:断点态(Trace Stopped)
- 5. D状态:不可中断休眠(深睡眠)
- 6. Z状态:僵尸态(Zombie)
- 7. X状态:死亡态(Dead)
- 六、孤儿进程:爹跑了,谁管孩子?
- 为啥要领养?
- 七、总结:3个核心记牢
一、先搞懂:进程状态藏在哪?
进程不是“黑盒子”,它的所有信息——比如在干啥、代码存在哪、有啥权限——都记在一个叫task_struct
的“档案本”里。而进程状态,就是这个档案本里的一个整数,像员工的“工作状态标签”,直接告诉操作系统“这进程现在能干活不”。
二、课本里的核心逻辑:3句话说透
不管啥操作系统,进程状态的底层逻辑都逃不开这3点(对应你放的课本截图):
- 进程不只有“跑”和“不跑”,有好几种状态;
- 状态能互相转——比如“等键盘输入”时会从“能跑”变“等着”,输入完又变回去;
- 只有“运行状态”的进程,才真正拿着CPU干活。
三、基础三态:运行、阻塞、挂起(大白话版)
咱们用“工厂干活”的例子理解,比硬记定义简单:
1. 运行态:“在工位上,要么干活要么等活”
操作系统里,每个CPU都有一个“调度队列”(像工厂的“待岗工位”),里面放的全是task_struct
(进程档案本)。
只要进程在这个队列里,状态就是运行态(Running) ——不管是正在被CPU“叫去干活”,还是排队等CPU,都算运行态。
调度时,CPU会按规则(比如“先进先出”FIFO)从队列里挑一个档案本,找到对应的代码和数据,让它跑起来。
2. 阻塞态:“等材料,暂时离开工位”
比如你写了个带scanf
的程序——运行后不敲键盘,进程就“卡住了”。这不是它偷懒,是它在等“键盘输入”这个“材料”,这就是阻塞。
操作系统怎么管这种情况?它会给每个硬件(键盘、磁盘、网卡)建个“设备档案”(struct device
),档案里专门留了个“等待队列”——所有等这个硬件的进程,都会被移到这个队列里。
简单说:阻塞 = 进程档案本从“调度队列”挪到“硬件等待队列”,直到“材料”备好(比如你敲了键盘),再挪回调度队列。
3. 挂起态:“工位不够,去临时仓库待着”
如果内存实在不够(工厂工位满了),操作系统会把进程的“代码和数据”挪到磁盘(临时仓库),只留task_struct
(档案本)在内存——这叫“阻塞挂起”(本来就在等材料,现在连工具都收起来了)。
要是内存还不够,连调度队列里的进程也会被挪去磁盘,叫“运行挂起”(本来在等活,现在先去仓库)。
不过不用记这么细——Linux里不细分挂起态,咱们重点看实际能用的状态就行。
四、内核小知识:用“链表”管进程
操作系统不是瞎管进程的,靠的是“链表”这种数据结构。Linux用的是list_head
链表,特点很实用:
- 每个
task_struct
里可以放多个list_head
(像一个员工有多个“身份标签”); - 这样一个进程能同时属于多个链表——比如既在“进程组链表”,又在“调度队列链表”;
- 想通过
list_head
找到完整的task_struct
?靠“地址偏移”——知道list_head
在档案本里的位置,就能算出整个档案本的地址。
简单说:进程状态切换,本质就是list_head
在不同链表间“挪位置”,无非是增删查改的操作。
五、Linux实际进程状态:逐个拆,附代码
Linux的进程状态存在task_state_array
里,咱们逐个讲,每个都给代码例子,跟着做就能看懂。
1. R状态:运行态(Running)
- 特点:在调度队列里,要么正在跑,要么等CPU;不能被kill打断(想停它得等它自己出队列)。
- 为啥有时查不到?比如
printf
这种操作太快,进程瞬间切到其他状态,得用“死循环不做IO”才能稳定抓到。
代码例子(抓R状态):
#include <stdio.h>
int main() {while(1); // 死循环,不做任何IO,一直待在调度队列return 0;
}
- 操作步骤:
- 编译:
gcc test.c -o test
; - 后台运行(不占终端):
./test &
; - 查看状态:
ps aux | grep test
,会看到状态是R
。
- 编译:
(注:状态后的+
表示“前台进程”,后台进程没有+
)
2. S状态:可中断休眠(浅睡眠)
- 特点:等资源(键盘、文件),处于“浅睡”;能被kill命令打断(比如等输入时,kill一下就退出)。
- 最常见的阻塞态,比如
scanf
等输入、读文件时都算S状态。
代码例子(抓S状态):
#include <stdio.h>
int main() {int a;scanf("%d", &a); // 等键盘输入,进程阻塞,状态变Sprintf("%d\n", a);return 0;
}
- 操作步骤:
- 运行:
./test
,不输入任何内容; - 另开终端查状态:
ps aux | grep test
,状态是S
; - 测试kill:
kill 进程号
,进程会直接退出(S状态能被打断)。
- 运行:
3. T状态:暂停态(Stopped)
- 特点:进程被“暂停”,既不在调度队列也不在等待队列;得用命令恢复(
kill -18
)。 - 触发方式:按
ctrl+z
暂停前台进程,或用kill -19
手动暂停。
操作例子:
- 运行上面的
scanf
程序:./test
; - 按
ctrl+z
,终端提示“已暂停”; - 查状态:
ps aux | grep test
,状态是T
; - 恢复:
kill -18 进程号
,进程继续等输入; - 再暂停:
kill -19 进程号
,变回T。
4. t状态:断点态(Trace Stopped)
- 特点:只有调试时会出现!进程在断点处停下,比如gdb设断点后运行。
操作例子:
- 用gdb调试:
gdb ./test
; - 设断点:
b main
(在main函数开头停); - 运行:
r
; - 另开终端查状态:
ps aux | grep test
,状态是t
。
5. D状态:不可中断休眠(深睡眠)
- 特点:等“关键资源”(比如磁盘IO),处于“深睡”;kill -9都杀不掉(怕打断磁盘操作导致数据损坏)。
- 常见场景:用
dd
命令拷贝大文件时。
操作例子(抓D状态):
- 执行磁盘读写命令:
dd if=/dev/zero of=/tmp/test bs=1G count=10
(往/tmp写10G文件); - 另开终端查状态:
ps aux | grep dd
,状态是D
; - 试杀:
kill -9 进程号
,进程纹丝不动,直到磁盘操作完成才退出。
6. Z状态:僵尸态(Zombie)
- 特点:子进程退出了,但父进程没“要它的退出信息”(比如退出码),只剩
task_struct
(档案本)在内存;不能被调度,也杀不掉(因为进程已经死了,只剩空壳)。 - 风险:僵尸进程占内存,父进程一直不管就会“内存泄露”(尤其是开机就跑的常驻进程)。
代码例子(造僵尸进程):
#include <stdio.h>
#include <unistd.h>
int main() {pid_t pid = fork(); // 创建子进程if (pid == 0) {// 子进程:直接退出,没被回收printf("子进程PID:%d\n", getpid());return 0;} else if (pid > 0) {// 父进程:死循环,不回收子进程while(1) sleep(1); }return 0;
}
- 操作步骤:
- 运行:
./test
; - 查状态:
ps aux | grep test
,会看到子进程状态是Z
; - 解决办法:让父进程调用
wait()
回收,或杀掉父进程(子进程会被领养)。
- 运行:
(注:slab
是Linux内核的内存分配机制,专门管理像task_struct
这样的小对象,僵尸进程的task_struct
就存在这里,不回收会占 slab 内存)
7. X状态:死亡态(Dead)
- 特点:进程彻底退出,所有资源(代码、数据、
task_struct
)被OS回收;看不到这个状态(因为瞬间就没了),只是理论上的状态。
六、孤儿进程:爹跑了,谁管孩子?
如果父进程先退出,子进程没人管,就成了“孤儿进程”——这时候Linux会让1号进程领养它:
- 新内核(比如Ubuntu、CentOS 7+):1号进程是
systemd
; - 老内核:1号进程是
init
。
为啥要领养?
怕孤儿进程退出后没人回收,变成僵尸进程,导致内存泄露。1号进程会负责“要它的退出信息”,相当于“福利院”。
代码例子(造孤儿进程):
#include <stdio.h>
#include <unistd.h>
int main() {pid_t pid = fork();if (pid == 0) {// 子进程:睡10秒,等父进程先退出printf("子进程PID:%d,当前父进程PID:%d\n", getpid(), getppid());sleep(10); printf("子进程现在父进程PID:%d\n", getppid()); // 会变成1return 0;} else if (pid > 0) {// 父进程:马上退出,不管子进程printf("父进程退出,PID:%d\n", getpid());return 0;}return 0;
}
- 操作步骤:
- 运行:
./test
; - 父进程瞬间退出,子进程一开始的父进程是终端(比如
bash
); - 10秒后,子进程的父进程变成1(被1号进程领养);
- 孤儿进程会变成后台进程,终端看不到它的输出(除非重定向)。
- 运行:
七、总结:3个核心记牢
- 进程状态本质:
task_struct
在不同链表间挪位置(调度队列、等待队列); - Linux重点状态:R(跑)、S(浅睡等资源)、D(深睡杀不掉)、Z(僵尸要回收)、T(暂停);
- 孤儿/僵尸处理:孤儿找1号进程领养,僵尸靠父进程
wait()
回收,避免内存泄露。