文章目录
- 前言
- 一、操作系统学科下的进程状态
-
- [1.1 运行状态](#1.1 运行状态)
- [1.2 阻塞态](#1.2 阻塞态)
- [1.3 挂起态](#1.3 挂起态)
- 二、Linux系统下的进程状态
-
- [1.1 运行态观察](#1.1 运行态观察)
- [1.2 S阻塞、浅度睡眠状态](#1.2 S阻塞、浅度睡眠状态)
- [1.3 D深度睡眠状态](#1.3 D深度睡眠状态)
- [1.4 S停止状态](#1.4 S停止状态)
- [1.5 X死亡状态](#1.5 X死亡状态)
- [1.6 Z(zombie)-僵尸进程(重要)](#1.6 Z(zombie)-僵尸进程(重要))
- 三、进程优先级
-
- [3.1 概念](#3.1 概念)
- [3.2 Linux如何设计优先级调度呢?](#3.2 Linux如何设计优先级调度呢?)
- [3.3 操作系统如何根据优先级展开调度呢?](#3.3 操作系统如何根据优先级展开调度呢?)
- [3.4 那么切换进程后再次回来的时候如何定位?](#3.4 那么切换进程后再次回来的时候如何定位?)
- 总结
前言
在Linux系统下,系统通过PCB的方式管理进程(struct_task),在进程还未获得CPU资源时处于等待就绪状态,排队依次被CPU执行。在本文中我们将会了解到进程还有哪些状态,以及Linux系统下的实现方式是什么样的。
一、操作系统学科下的进程状态
1.1 运行状态
处于运行队列中的进程,进程获得CPU资源并正在执行指令的状态。在单核系统中,任何时刻只有一个进程处于运行态。
- 时间片轮转机制:
现代操作系统通过时间片(Timeslice)实现多任务并发。每个进程获得CPU的时间单位为毫秒级(通常5-100ms),当时间片耗尽时,进程会被强制放回运行队列尾部重新排队。 - 并发与并行:
在单核CPU中,通过快速切换时间片实现并发执行;多核CPU则能实现真正的并行执行。例如4核CPU可同时有4个进程处于运行态。 - 运行队列管理:
所有就绪态的进程PCB(Process ControlBlock)存储在运行队列(Runqueue)中,调度器根据优先级选择下一个执行的进程。
1.2 阻塞态
进程因等待外部事件(如I/O完成、资源就绪)而暂停执行的状态。此时进程不占用CPU,主动释放资源进入阻塞队列
在c语言学习阶段,scanf这种等待输入情况就是我们遇到的阻塞状态。
1.3 挂起态
挂起态是特殊的阻塞状态,当系统内存不足时,内核会将部分进程的映像交换到磁盘交换区(Swap)。该状态特点:
- 进程的代码和数据被移出物理内存
- 仅保留PCB在内存中
- 需要换入(Swap in)才能恢复执行
二、Linux系统下的进程状态
操作系统的理论与实现并不一样,不同的操作系统有着不同的实现方式。
c
/*
* The task state array is a strange "bitmap" of
* reasons to sleep. Thus "running" is zero, and
* you can test for combinations of others with
* simple bit tests.
*/
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 */
};
1.1 运行态观察
c
// running.c
int main() {
while(1); // 无限循环
}

1.2 S阻塞、浅度睡眠状态
阻塞的本质就是在等待某种资源就绪,
典型场景:vim编辑scanf,scanf等待时,进程进入了S+阻塞状态,在等待键盘的输入准备,当输入完毕按下回车的一瞬间,进程变为R运行状态运行完毕。实际上,有很多进程都是阻塞状态,都需要等待诸如键盘、鼠标或磁盘等外设是否准备好资源。
1.3 D深度睡眠状态
典型场景:等待磁盘写入,进程不能被系统杀死。不响应任何请求
1.4 S停止状态
进程被信号暂停(如Ctrl+Z)
1.5 X死亡状态
这个状态只是一个返回状态,你不会在任务列表里看到这个状态。
1.6 Z(zombie)-僵尸进程(重要)
父进程不回收子进程,子进程运行完了会进入僵尸进程状态,导致内存泄漏。在父子关系进程中,如果父进程先退出,父进程会被改成1号进程(被操作系统领养),子进程成为了孤儿进程,被系统领养。因为孤儿进程也要被回收,只能托孤给操作系统,等孤儿进程结束,会释放内存。
2案例2:僵尸进程生成
c
// zombie.c
#include <stdlib.h>
#include <unistd.h>
int main() {
pid_t pid = fork();
if (pid == 0) { // 子进程
exit(0); // 立即退出
} else { // 父进程
sleep(60); // 不回收子进程
}
return 0;
}

僵尸进程的危害:
进程的退出状态必须被维持下去,因为他要告诉关心它的进程(父进程),你交给我的任务,我办的怎么样了。可父进程如果一直不读取,那子进程就一直处于Z状态?是的!
维护退出状态本身就是要用数据维护,也属于进程基本信息,所以保存在task_struct(PCB)中,换句话说,Z状态一直不退出,PCB一直都要维护?是的! 那一个父进程创建了很多子进程,就是不回收,是不是就会造成内存资源的浪费?是的!因为数据结构对象本身就要占用内存,想想C中定义一个结构体变量(对象),是要在内存的某个位置进行开辟空 间!
内存泄漏?是的!
三、进程优先级
3.1 概念
进程优先级概念:在对于资源的访问时,规定了谁先访问,谁后访问
文件权限:有没有权限访问
如果一个进程长时间得不到CPU资源,进程的代码长时间无法得到推进,导致进程的饥饿问题(windows下的未响应)。因此,操作系统需要保证进程之间的良性竞争需要确认进程的优先级。
3.2 Linux如何设计优先级调度呢?
- 查看优先级
bash
ps -al | grep -1 && grep myproc

PRI优先级:最终优先级值,范围60-99(值越小优先级越高)
NI(nice值 ):进程优先级的修正数据。该数据意味着进程优先级是可以被修改的,实际上不能任意或过多的修改进程优先级 ,在Linux中,nice值是有范围的[-20~19]
对应了四十个级别的优先级,所以优先级最高最低位(60,99)
。
计算公式: PRI(new) = PRI(old) + NI
- 调整优先级
bash
$ nice -n -5 ./myprogram # 启动时设置NI为-5
$ renice -10 -p 1234 # 修改PID为1234的进程 NI值为-10
3.3 操作系统如何根据优先级展开调度呢?
在Linux中,每个进程都有一个优先级,Linux使用一个包含40个不同级别的位图来管理进程的优先级。该位图称为"nice"值位图,它用于确定进程在被调度时的优先级。在Linux中,nice值的范围通常为-20到+19,其中-20表示最高优先级,+19表示最低优先级。
每个进程的nice值位图中的每一个位代表一个特定的优先级级别,如果某个位为1,则表示该进程对应的优先级级别可用,如果为0,则表示该优先级不可用。通过设置nice值,可以调整进程在系统中的优先级,以便进行合理的资源分配和任务调度。
位图:Linux内核O(1)调度算法。
其他概念竞争性:系统进程数目众多,而CPU资源只有少量,甚至1个,所以进程之间是具有竞争属性的。为了高效完成任务,更合理竞争相关资源,便具有了优先级
独立性:多进程运行,需要独享各种资源,多进程运行期间互不干扰 并行: 多个进程在多个CPU下分别,同时进行运行,这称之为并行
并发:多个进程在一个CPU下采用进程切换的方式,在一段时间之内,让多个进程都得以推进,称之为并发;(进程切换 + 时间片限制 = 基于进程切换时间片轮转的调度算法)
但高优先级被轮换下次又将进入第一位执行,那这样是不是也耽误了其他进程的执行?(维护两批队列,执行队列和等待队列,高优先级进程被切换至等待队列,这样一来就避免了高优先级进程插队的现象)
3.4 那么切换进程后再次回来的时候如何定位?
CPU内部存在大量的寄存器,寄存器堆具有临时保存进程相关数据的能力,存储的是进程的上下文数据,进程在从CPU上离开的时候,要将自己的上下文数据保存 起来,以便再次执行的时候恢复到上一次的执行位置。在往后可能涉及中断等知识,这里不再叙述了。
当进程切换发生时,CPU需要完成以下操作:
1.保存当前进程的寄存器状态到PCB
2.加载新进程的寄存器状态
3.更新内存管理单元(MMU)的页表
4.刷新TLB缓存
为什么函数的返回值会被外部拿到呢?通过寄存器拿到
系统如何得知我们的进程当前执行到哪一行呢?
程序计数器 (PC指针 / EIP):记录当前进程正在执行的指令的下一行
总结
通过本文的学习,读者应该能够全面理解Linux进程状态转换机制,并掌握基本的进程管理技巧。
👍 感谢各位大佬观看。如果本文有帮助,请点赞收藏支持~