个人主页MSTcheng · CSDN代码仓库MSTcheng · Gitee 精选专栏:《C语言》《数据结构》 《算法学习》《C由浅入深》《Linux操作系统》《Mysql数据库》 《白话大模型》座右铭路虽远行则将至事虽难做则必成上一篇文章中我们给大家介绍了笼统的Linux操作系统的状态和具体的Linux操作系统的状态。学完了Linux进程状态本篇文章就来谈谈进程优先级。文章目录一、进程优先级Priority1.1 优先级是什么 —— 对比权限2.2 为什么要有优先级1.3 Linux 下优先级怎么设计PRI 与 NI1.4 Linux 优先级的本质1.5 如何修改 nice 值改优先级1.6 优先级的范围以及为什么是有限的1.7 为什么变化范围是有限的关键为什么二、分时系统 vs 实时系统了解2.1 分时系统Time-Sharing2.2 实时系统Real-Time2.3 Linux 同时支持两者三、四大概念竞争、独立、并发、并行3.1 竞争性Competition3.2 独立性Independence3.3 并发Concurrency3.4 并行Parallelism四、进程切换上下文切换4.1 什么是上下文Context4.2 切换过程4.4 上下文保存到哪里4.5 时间片与切换五、补充进程切换的触发时机与机制《深入理解 Linux 内核》建议学完中断之后再回过头来看六、总结1~4部分一、进程优先级Priority1.1 优先级是什么 —— 对比权限重点区分这两个概念权限Permission解决能不能的问题。例你去坐飞机你想进入机长的驾驶舱去参观一下肯定不被允许——因为你是普通乘客所以不能进入。优先级Priority解决先还是后的问题。例食堂排队打饭(安装常理食堂应该为每一个学生都准备饭菜)你的脚慢一点就排到后面无非吃凉一点但总有你的饭——因为已经能了只差先后。定义进程优先级 在已经能得到某种资源的前提下得到该资源的先后顺序。。前提很关键优先级的前提是已经能了。2.2 为什么要有优先级想象一个场景一个学校学校有1万名学生不可能人手一个打饭窗口食堂的窗口注定比人的数量要少那么就注定要排队根本原因资源不足狼多肉少、僧多粥少→必须分配资源→ 必须确认先后→ 所以要有优先级。计算机中进程可以创建几十上百个但CPU 物理个数只有 1~2 颗核再多物理 CPU 也没几个网卡/显卡等外设更少 →资源被共享进程之间必然竞争。所以为了避免竞争后面会引入锁的概念。所以优先级 决定进程获得某种资源的先后顺序。调度队列的本质就是在确认优先级。1.3 Linux 下优先级怎么设计PRI 与 NI查看ps -l显示更详细的进程信息。ps常见选项再认识选项含义a显示一个终端的所有进程allx显示没有控制终端的进程如后台进程ax前后台都显示j显示任务相关PID、PPID、进程组、会话等u显示 CPU、内存等占用情况-l显示更详细的进程信息类似ls -lmstchengubuntu-dev:~/117/code/Process$ps-alF SUIDPIDPPIDC PRI NI ADDR SZ WCHAN TTY TIME CMD0S1000639927601060800-692hrtime pts/2 00:00:00 processTest4R100063995563993699800-3213- pts/0 00:00:00psps -al输出里两个关键字段UID这个进程是谁启动的在系统里人也是一串编号如 1000。这是归责依据——进程乱删数据一查 UID 就找到你。PRIpriority进程优先级可以看到它是一个数字。NInicenice 值优先级修正数下面会详细介绍。1.4 Linux 优先级的本质所以优先级的本质其实就是task_struct里的整型变量一个数字核心两个PRIpriority和NInice。规则数字越小优先级越高像考试名次名次越小越好数字越小越先被 CPU 调度。PRI 默认值是 80。非常重要通过上图可以得出 PRI 与 NI 的计算公式新优先级(PRI)80(固定起点) nice值(NI)nice 值是一个修正数字Linux 特有。nice 为正数→PRI 变大→ 优先级变低nice 为负数→PRI 变小→ 优先级变高。注意事项非常重要每次改优先级时起点old永远是 80不是上次的 PRI误以为本次 PRI90nice 改成 -10 → 以为新 PRI 90-10 80。实际结果PRI 70因为每次该nice值都从固定的 80 开始80 (-10) 70。所以优先级 80 起算且与上一次无关——每次改 nice都从 80 重新算。为什么从 80 起算因为简单。你只用改一个数nice就能达到调优先级的目的不用查旧 PRI、不用手算。1.5 如何修改 nice 值改优先级修改方式有 5 种nice命令、renice命令、top里改、系统调用setpriority/getpriority。除了上面那种直接renice命令修改还有一种使用top命令。top 操作流程运行top按rrenice输入目标PID输入新的nice 值回车即改。mstchengubuntu-dev:~/117/code/linux-code/process/processTest/Process$ps-alF SUIDPIDPPIDC PRI NI ADDR SZ WCHAN TTY TIME CMD0S100063992760106070-10-692hrtime pts/2 00:00:00 processTest 我给processTest这个进程nice值-10可以看到该进程的pri已经变成70了操作重要性结论一般来说优先级几乎不自己改由系统决定。继续回到上图中引出的那三个问题1.6 优先级的范围以及为什么是有限的先看范围通过实验验证把 nice 设成100→ 实际只到19被截断。把 nice 设成-100→ 实际只到-20被截断。结论nice 值范围-20 ~ 19共40 个取值。PRI 范围60 ~ 99 80 nice共40 个梯度。超过范围的输入会被夹到边界19 或 -20。为什么改优先级要 sudo / 普通用户改两次就被拒普通用户第一次改还行第二、三次就不让改了Permission denied。原因系统禁止高频改优先级会怀疑你是恶意程序影响整体调度。改用root超级用户就能改。结论优先级一般不许改尤其禁止高频改。1.7 为什么变化范围是有限的关键为什么因为我们用的是分时操作系统Time-Sharing OS。分时系统的核心特点给每个 进程分配时间片用相对公平、均衡的策略让 所有进程 在一段时间内 都能得到 CPU不是谁先来谁把任务全做完。为什么需要公平因为用户/互联网需求打游戏时不能等杀毒软件全部跑完才轮到你你要同时直播、画图、听歌、写代码一段时间内都要推进买火车票、刷抖音大家地位对等都要被公平对待。如果允许把优先级改到极端如 -1 万该进程会疯狂抢占 CPU其他进程长期得不到 CPU→ 这就是调度中的饥饿问题starvation。所以为了让优先级在可控范围内变化Linux 用 nice 值把变化限制在40 个梯度内。逻辑闭环既然是分时系统 → 那么必须保证公平调度 → 优先级变化必须有限 → Linux 选择 40 个优先级nice -20~19。静态优先级 vs 动态优先级nice 值决定静态优先级内核还会根据调度历史动态调整现代 CFS 调度器思路。二、分时系统 vs 实时系统了解2.1 分时系统Time-Sharing主流 OSWindows、Linux、手机、笔记本、服务器全是分时系统。特点同时可跑多个任务大家都没跑完但都在跑“我可以慢一点但每个人都要跑”。2.2 实时系统Real-Time与分时相对。“实时系统比分时系统更简单是死脑筋”任务来了必须立刻执行、且必须执行完才能换下一个可插队、有优先级但插进来的必须做完。典型场景工业控制、单片机如 FreeRTOS、RT-Thread/µC/OS 这类几千行的内核。例子机械臂给汽车喷漆——不能喷一半去装别的东西必须把这道工序做完。例子自动驾驶汽车你踩一脚刹车不是直接到刹车片而是先到车载系统行车电脑由 OS 判断后制动。刹车这个动作① 优先级必须拉满② 必须做完不能做一半。所以车载系统需要实时功能否则听歌优先于刹车会出大事。2.3 Linux 同时支持两者Linux 内核既有分时调度又有实时调度。通过条件编译 / 裁剪内核可以选择保留或裁掉实时/分时功能。我们平时用的 Linux 被编译成分时系统用于互联网应用三、四大概念竞争、独立、并发、并行这四个概念承上启下既总结前面的状态/优先级又给切换与调度做铺垫。3.1 竞争性Competition本质原因资源不足CPU、内存、外设永远不足进程数量永远比资源数量多 → 进程之间必然竞争。正因为存在竞争才需要优先级来确认谁先谁后。3.2 独立性Independence日常生活中的现象有时候抖音闪退不会让微信跟着退杀毒软件闪退不会让浏览器闪退。原因每个进程有自己独立的task_struct内核数据结构 独立的代码和数据。内核数据结构独立各有各的 PCBPID 不同。代码和数据独立你占你内存我占我内存互不影响。父子进程也具有独立性父子是两个进程各有独立的task_struct、独立 PID。代码共享但只读大家都不改所以不影响。数据一旦被改发生“写时拷贝”Copy-On-Write——这正是为了维护父子间的独立性防止一个进程改数据影响到另一个。写时拷贝在后面讲进程控制 的篇目会详细说明这也是fork返回值在父子里 “数值相同但内容不同” 的原因。3.3 并发Concurrency定义多个进程在单 CPU 下通过快速切换的方式在一段时间内都得以推进。这就好像以前老师叫我们背书每人只给有限时间背书背不完就下去轮一圈再继续——这样每个进程都被推进了一点。时间片计算示例10 个进程每个时间片 10ms →轮一圈 100ms→1 秒能轮 10 圈→ 每个进程 1 秒被调度10 次。我们在日常使用app时感受到很丝滑但其实底层是一段一段的卡顿因为人眼约 24 帧/秒就看不出断续时间片切换时系统里没有一个进程在跑因为正在切换/选下一个只是这个过程非常的快人感受不到。这也有点像静态数码管的显示硬件层面其实一直在闪但是人眼看到却是静止了因为频率太快了人眼感受不到注意并发 ≠ 并行。并发是交替执行宏观同时、微观交替并行是真正同时。单 CPU 只能并发多 CPU 才可能并行。3.4 并行Parallelism定义真正的同时运行。多 CPU多核/多物理 CPU下各 CPU 各有调度队列任一时刻都有多个进程同时在跑。并发看的是一段时间内都推进并行看的是某一时刻都在跑。现实笔记本/手机大多是单 CPU 多核多物理 CPU 功耗高手机续航受限所以并行场景较少。并发 vs 并行一句话并发一段时间内大家交替着推进宏观同时。并行某一时刻大家一起真同时跑。四、进程切换上下文切换4.1 什么是上下文Context进程在 CPU 上运行时各种寄存器里都保存着当前运行的临时数据函数返回值、PC 指针、通用寄存器、状态寄存器、各种标志位……。这些运行期间的寄存器临时数据就叫当前进程的上下文数据硬件上下文。4.2 切换过程单 CPU 最典型也最复杂切换概率最高进程A 时间片到了→把 A 的所有寄存器数据保存起来从运行队列重新选进程 B恢复 B 的寄存器数据B 从上次停下的地方继续运行不会从头再来。例拷贝 1G 数据拷了 500M 被切走下次回来应接着拷后 500M不是从头开始。核心结论寄存器这个 “硬件” 是共享的但寄存器里面的 “数据” 是进程私有的寄存器硬件只有一套/一份 →共享寄存器里的数据→ 每个进程一套 →私有走的时候带走回来的时候恢复。类比变量的左值/右值寄存器 变量空间左值共享寄存器内容 变量内容右值私有。-把寄存器保存这句话是错的——应该是把寄存器里的数据保存寄存器是硬件你拿袋子装不走它。4.4 上下文保存到哪里新式内核2.6 等严格说新式内核并不是“再拿一个指针指向别处”(老式内核)而是把进程切换要用的硬件上下文从 TSS 挪到了task_struct的thread成员里。这个成员的类型叫struct thread_struct线程结构体ip、sp 等寄存器上下文都保存在它里面。简化理解进程把自己的上下文保存在自己的task_structPCB里老内核如 Linux 0.11 就是task_struct里含一个tss_struct里面存eip/esp/ebp及各种段寄存器等。从硬件角度从 CPU寄存器保存到内存task_struct下次调度时再从内存恢复到 CPU。本篇文章中对于上下文的保存以及上下文恢复如何找到恢复前的位置有详细的说明想了解的可以去看这篇文章【Linux进程篇】Linux学习第十三弹——程序放进内存就是进程吗先把“进程”理解透4.5 时间片与切换分时系统每个进程都有合适的时间片时间片到了进程就从 CPU 上被剥离。“凭什么剥离怎么剥离” → 跟中断interrupt有关后面讲信号时再说。五、补充进程切换的触发时机与机制《深入理解 Linux 内核》建议学完中断之后再回过头来看① 什么情况会触发进程切换三类时机类别场景内核动作主动让出进程阻塞等 IO、等锁、sleep、exit()、sched_yield()主动调用schedule()时间片耗尽被动每个时钟节拍到来 →scheduler_tick()递减当前进程的time_slice减到 0置TIF_NEED_RESCHED标志表示该换人了被唤醒进程抢占被动try_to_wake_up()把睡眠/停止的进程置为TASK_RUNNING并插入本地 CPU 的运行队列被唤醒进程可能抢占本地 CPU 上正在运行的进程唤醒接口带sync标志可禁止这种抢占② 关键机制“想换≠马上换”——先打标志等调度点才切换内核发现该换进程时时间片耗尽、被抢占不会当场切换而是先给当前进程打上TIF_NEED_RESCHED标志。⭐⭐⭐书里原话“就像第四章’从中断和异常返回’一节中所描述的该标志强制调用schedule()函数”。也就是说真正切换发生在从中断或异常返回的路上——内核在那里检查到标志才强制调用schedule()选出新进程并完成上下文切换。这正好填上 9.5 的坑时钟中断里打标记中断返回时换人——这就跟中断interrupt有关。高频面试点“时间片用完进程会立刻被切换吗”不会。只是置了TIF_NEED_RESCHED标志等内核走到调度点中断/异常返回、系统调用返回等才真正切换。⚠️注意版本书基于 2.6 的 O(1) 调度器。注意时间片耗尽后并不是一律进过期队列——交互式进程会被放回活动队列只有非交互式或过期队列饿太久的才进过期队列但无论最后去哪TIF_NEED_RESCHED标志都会先打上——打标志和进哪个队列是两件事完整流程和时序见 9.7③。现代内核用 CFS但耗尽 → 打标志 → 调度点完成切换这条主轴不变。③ 实时进程什么时候被换下FIFO vs RR 对比SCHED_FIFO时钟节拍里scheduler_tick()什么都不做——FIFO 进程不会被优先级更低或相等的进程抢占所以只有两种情况会被换下主动让出、或被更高优先级的进程抢占。SCHED_RR时间片用完 → 重填时间片 被放到同优先级活动队列的尾部 置TIF_NEED_RESCHED→ 随后被同优先级的下一个实时进程取代如果有的话这就是时间片轮转。一句话什么时候被换下正是 FIFO 与 RR 的本质区别——FIFO 不轮转RR 在同优先级内按时间片轮转。六、总结1~4部分优先级资源不足 → 进程竞争 → 用优先级决定先后本质是task_struct里的数字PRI 80 NI数字越小越先被调度范围有限40 个梯度。分时 vs 实时分时公平分配时间片、大家交替推进实时必须立刻做完才换下一个Linux 两者都支持。四大概念竞争资源不足必然竞争、独立各进程有独立 PCB 与代码数据父子靠写时拷贝维护独立性、并发单 CPU 交替推进、并行多 CPU 真正同时跑。进程切换时间片到 → 保存 A 的寄存器数据 → 恢复 B 的寄存器数据 → B 从上次停下的地方继续上下文保存在各自的task_struct里。进程切换是在你脑子里形成一个抢占座位的印象——先记住时间片到了就换人、换人就是保存/恢复寄存器数据这个画面等到后面学了调度脑子里的进程才能真正跑起来本篇文章的内容就分享到这里下一篇文章再见