🎁个人主页:工藤新一¹
🔍系列专栏:C++面向对象(类和对象篇)
🌟心中的天空之城,终会照亮我前方的路
🎉欢迎大家点赞👍评论📝收藏⭐文章
文章目录
进程优先级(Process Priority)
一、回顾:进程状态
1. 状态是变量:进程状态在代码上是一个可修改的整型变量(
task_struct->state
),不同的值代表不同的状态宏2. 状态决定队列:这个状态值直接决定了进程应该位于哪个队列(运行队列 or 各种等待队列)。进程在哪里,就决定了它的行为
3. 队列决定命运:
- 在运行队列 -> 有资格被CPU调度 -> 表现为“运行”或“就绪”
- 在等待队列 -> 不会被CPU调度 -> 表现为“阻塞”、“睡眠”、“卡住”
- 被交换到磁盘 -> 既是阻塞,又不在内存 -> 表现为“挂起
a. 本质:task_struct
中的状态标志(state
字段)
- 这是状态的“源代码视角”或“静态视角”。在 Linux 内核中,每个进程/线程都是一个
task_struct
结构体(称为进程描述符)。其中有一个关键的成员变量,比如volatile long state;
,它是一个整数。 - 这个整数的值不是任意的,它由内核预先定义好的一系列宏来决定,例如:
TASK_RUNNING
: 可运行状态。TASK_INTERRUPTIBLE
: 可中断的睡眠状态(一种阻塞)。TASK_UNINTERRUPTIBLE
: 不可中断的睡眠状态(另一种阻塞,通常发生在等待硬件I/O时)。__TASK_STOPPED
: 暂停状态(如收到SIGSTOP
信号)。TASK_DEAD
/EXIT_ZOMBIE
: 退出状态。
- 您的结论完全正确:修改这个
state
变量的值,就改变了内核对这个进程状态的“官方认定”。
b. 行为:队列决定调度(运行 vs. 阻塞)
- 这是状态的“内核调度视角”或“动态视角”。内核的调度器(Scheduler)不关心
state
这个数字本身,它只关心队列。调度器的核心工作就是从运行队列(Runqueue) 里选择一个最合适的进程来运行。 - 运行(RUNNING):当一个进程的
state
为TASK_RUNNING
时,它有资格被调度。此时,它一定存在于某个CPU的运行队列中(或者正在某个CPU上执行)。调度器只从这个队列里选人。 - 阻塞/睡眠(BLOCKED/SLEEPING):当一个进程需要等待某个事件(如
scanf
等待键盘输入、read
等待磁盘数据、sleep
等待超时)时,内核会:- 将其
state
设置为TASK_INTERRUPTIBLE
或TASK_UNINTERRUPTIBLE
。 - 将其从运行队列中移除。
- 将其加入到另一个等待队列(Wait Queue) 中,这个等待队列通常与它正在等待的资源(如键盘、硬盘的某个块)相关联。
- 将其
- 此时,因为这个进程已经不在运行队列里了,调度器根本看不到它,所以它绝对没有机会获得CPU,这就是它“卡住了”的原因。
c. 状态转换的完整流程
让我们用 scanf
的例子来串联这两个视角:
- 进程运行:进程在CPU上执行,
state = TASK_RUNNING
,位于运行队列。 - 调用
scanf
:进程执行到scanf
系统调用,代码进入内核态。 - 内核检查资源:内核发现键盘缓冲区中没有数据(用户还没输入)。
- 状态改变与队列操作:
- 内核将进程的
state
从TASK_RUNNING
修改为TASK_INTERRUPTIBLE
。 - 内核将进程的
task_struct
从运行队列中摘下。 - 内核将进程的
task_struct
加入到“键盘输入等待队列”中。
- 内核将进程的
- 调度切换:内核调用调度器,切换到另一个就绪的进程运行。
- 事件发生:用户按下键盘,触发硬件中断。键盘中断处理程序工作,将按键数据放入缓冲区。
- 唤醒进程:中断处理程序调用
wake_up
函数,它会找到“键盘输入等待队列”中的进程,并:- 将其
state
修改回TASK_RUNNING
。 - 将其 从等待队列中摘下。
- 将其 重新放回运行队列。
- 将其
- 再次被调度:在未来的某个时刻,调度器再次选中这个进程,它就从
scanf
系统调用中返回,继续执行,并读取到输入数据。
d. 关于“挂起(Suspended)”
您还提到了“挂起”,这是一个很好的延伸。挂起通常不是Linux内核原生的一个状态宏,而是一种行为,通常发生在内存压力很大时。
- 行为:内核需要腾出物理内存(RAM)。它会选择一个处于阻塞状态(
TASK_INTERRUPTIBLE
或TASK_UNINTERRUPTIBLE
)的进程,将其占用的内存数据交换(Swap Out)到磁盘上。此时,这个进程除了在等待队列里,它的代码和数据都不在内存中了。 - 状态:它的
state
可能仍然是TASK_INTERRUPTIBLE
,但它多了一个“被换出”的属性。当它等待的事件到来(被唤醒)时,内核首先需要把它的内存数据从磁盘换回(Swap In)到内存,然后才能将其加入运行队列,这个过程会有明显的延迟。
二、进程优先级
2.1基本概念
进程优先级 是一个决定进程如何被 CPU
调度器对待的关键属性,理解优先级的核心是:在多个可运行(RUNNABLE)的进程竞争 CPU 时,优先级高的进程有更大的机会被调度器选中执行
抽象化的理解:我们车站排队,排队的本质是确认优先级,确认了优先级,我们就能知道什么时候可以上车。
优先级的本质是:衡量得到某种资源的先后顺序。同样地,对进程来讲:优先级是进程得到 CPU资源的先后顺序
- 优先级:能得到资源(先后问题)
- 权限:是否得到某种资源
2.2查看系统进程
我们很容易注意到其中的⼏个重要信息,有下:
• UID:执⾏者的⾝份(user id)
• PID:进程的代号
• PPID:进程是由哪个进程发展衍⽣⽽来的,亦即⽗进程的代号
• PRI:进程可被执⾏的优先级(程序被CPU执行的先后顺序),其值越⼩越早被执⾏(默认80,且始终为默认值)
• NI:进程的nice值,进程优先级的修正数据(默认0)
进程的真实优先级PRI(new) == PRI(old默认) + NI
2.3 PRI vs NI
- 需要强调⼀点的是,进程的nice值不是进程的优先级,他们不是⼀个概念,但是进程nice值会影 响到进程的优先级变化
- 可以理解nice值是进程优先级的修正修正数据
- 所以,调整进程优先级,在Linux下,就是调整进程nice值,nice其取值范围:[-20, 19],⼀共40个级别。Linux进程优先级(PRI):[60, 99]
2.4 查看进程优先级的命令
⽤ top 指令更改已存在进程的nice:
• top
• 进⼊top后按“r”‒>输⼊进程PID‒>输⼊nice值
注意:
• 其他调整优先级的命令:nice,renice
• 系统函数:
三、 优先级存在的意义
“进程优先级”的存在绝非偶然,它是解决计算机系统中资源竞争与任务多样性之间矛盾的根本性方案
“我们”为何在火车站要排队?进程又为何在 CPU
上排队呢?
目标资源稀缺,导致要通过优先级确认谁先谁后的问题!
3.1 应对任务的重要性差异(关键型任务优先)
不是所有任务都生而平等。系统必须能够区分哪些任务关乎全局,哪些任务只是锦上添花。
- 例子:
- 高优先级:键盘输入处理。当你按下
Ctrl+C
试图终止一个失控的程序时,系统必须立即响应这个中断,否则用户会失去对机器的控制。如果键盘处理程序和后台的文件压缩任务平等竞争CPU,用户可能会感觉系统“卡死”了。 - 低优先级:后台文件备份、软件更新、病毒扫描。这些任务很重要,但不需要立即完成。它们可以悄无声息地在系统空闲时利用资源,而不影响你的正常工作。
- 高优先级:键盘输入处理。当你按下
- 目的:确保关键任务总能得到及时响应,维持系统的可用性和响应性。
3.2 满足任务的时效性要求(实时性任务优先)
某些任务有严格的时间期限(Deadline),错过期限会导致结果错误或完全失效。
- 例子:
- 视频播放:解码器必须在下一帧需要显示之前完成解码工作,否则视频就会卡顿。
- 工业控制:机器人传感器数据处理必须在几毫秒内完成,并发出控制指令,否则可能导致生产事故。
- 音频处理:声音缓冲区必须被及时填充,否则会产生刺耳的爆破音。
- 目的:通过赋予实时进程最高的优先级,保证时间敏感型任务能够抢占CPU,按时完成。这就是实时优先级(1-99)存在的意义。
3.3 提升整体系统效率和用户体验(CPU空闲时间利用)
系统资源(尤其是CPU)经常会出现短暂的闲置状态。优先级机制允许系统**“见缝插针**”地利用这些碎片时间。
- 例子:你在编辑文档时,每次思考或停顿的瞬间,CPU利用率都会骤降。一个低优先级的进程(如索引服务的文件索引器)就可以在这个时候被调度运行,进行一些计算。
- 目的:最大化资源利用率。让低优先级任务填充高优先级任务之间的空闲时间,从而提升系统的整体吞吐量,同时还不会让用户感觉到任何卡顿。
3.4 实现资源的合理分配与公平性(防止饿死)
如果没有优先级,所有进程完全平等地分享CPU时间(即“轮转”调度),对于一些需要大量计算的后台任务来说是公平的,但对于需要交互的用户来说却是灾难性的。你的每次点击可能都要等一个时间片轮完才能被响应。
优先级机制引入了一种受控的不公平:
- 交互式进程(如桌面、浏览器、IDE)被赋予较高优先级,从而获得更快的响应速度,提升用户体验。
- 批处理进程(如编译大型项目、科学计算)被赋予较低优先级,它们可以充分利用系统空闲资源,但不会拖慢前台工作。
同时,调度器(如CFS)会保证即使是最低优先级的进程(Nice +19)也能最终获得一定的CPU时间,防止其被完全“饿死(Starvation)”。
即,优先级设立不合理[大量等级差过大进程],会导致优先级低的进程长时间得不到 CPU资源,进而导致:进程饥饿
3.5 匹配任务的资源需求特征(I/O消耗型 vs. CPU消耗型)
进程通常分为两类:
- I/O消耗型进程:大部分时间在等待I/O操作(如磁盘读写、网络请求)。例如,Web服务器、文本编辑器。它们需要的是在I/O完成时能被快速响应,以便发起下一个请求。因此,它们应该被赋予较高优先级。
- CPU消耗型进程:大部分时间在进行数学计算。例如,视频编码、数据建模。它们一旦获得CPU就会长时间占用。它们应该被赋予较低优先级,以免影响系统的交互性。
优先级机制帮助调度器更好地识别和区分这两种类型的进程,并采取不同的调度策略。
一个生动的比喻
你可以把CPU想象成一个医院的急诊室。
- 实时优先级进程:是生命垂危、需要立即抢救的病人(如心脏骤停)。他们拥有最高优先级,一来就必须立刻处理,所有医生都要围上来。
- 高优先级进程(Nice值低):是突发急症的病人(如高烧、严重外伤)。他们需要很快被医生接诊。
- 普通优先级进程(Nice=0):是普通急诊病人(如感冒、肠胃炎)。按挂号顺序排队等候。
- 低优先级进程(Nice值高):是来做体检或打疫苗的人。他们不紧急,可以在所有急诊病人都处理完后,或者夜深人静医院空闲时再来。
如果没有这个优先级分诊系统,让所有病人(进程)完全平等地排队,那么生命垂危的病人可能还没排到就去世了(系统无响应)。而有了优先级,医院(操作系统)就能最大限度地拯救生命(保证关键任务),同时又能高效地处理所有病人(提升整体吞吐量),并确保每个人最终都能得到治疗(防止饿死)。
因此,进程优先级是现代操作系统中一项至关重要的机制,它优雅地平衡了效率、响应性、公平性和资源利用率这多个相互冲突的目标。
🌟 各位看官好,我是工藤新一¹呀~
🌈 愿各位心中所想,终有所致!