进程优先级

优先级概念        

         CPU资源稀缺。进程优先级是操作系统调度资源的重要依据,决定了多个进程竞争CPU时的执行顺序。高优先级进程通常能更快获得CPU资源,低优先级进程则可能被延迟处理。其核心作用体现在以下几个方面:

  • 资源分配效率
    操作系统通过优先级动态调整CPU时间片的分配。例如,实时任务(如视频解码)需要高优先级以确保流畅性,而后台任务(如日志记录)可设为低优先级,避免抢占关键资源。

  • 系统响应性

    交互式应用(如用户界面)通常被赋予较高优先级,确保用户操作得到即时响应。低优先级任务(如批量数据处理)则会在系统空闲时执行,减少对用户体验的影响。

  • 负载均衡

    在多任务环境中,优先级机制帮助平衡系统负载。突发的高优先级任务(如硬件中断)可临时抢占资源,处理完成后恢复正常调度,避免资源长期被单一进程独占。

PRI和NI

在Linux命令行输入ps -l命令会显示:

我们很容易注意到其中的几个重要信息,有下:

  • UID:代表执行者的身份
  • PID:代表这个进程的代号
  • PPID:代表这个进程是由哪个进程发展衍生而来的,亦即父进程的代号
  • PRI:代表这个进程可被执行的优先级,其值越小越早被执行
  • NI:代表这个进程的nice值(nice取值范围:-20 ~ 19)

PRI和NI的关系是:PRI(new)= PRI(old)+ nice。所以,在Linux系统下,调整进程的优先级,就是调整进程的nice值。

注意:

  • 响到进程的优先级变化。进程的nice值不是进程的优先级,他们不是一个概念,但是进程nice值会影响到进程优先级的变化
  • 可以理解nice是进程优先级的修正数据。

查看进程优先级

使用 ps -l 命令可以查看当前用户的进程及其优先级(NI 列表示 nice 值)。例如:

ps -l

启动时设置优先级

通过 nice 命令启动新进程时直接指定优先级。优先级范围为 -20(最高)到 19(最低),普通用户只能调低优先级(即设置正值)。例如以优先级 10 运行命令:

nice -n 10 command

修改运行中进程的优先级

使用 renice 命令调整已运行进程的优先级。需指定进程 ID(PID)和目标优先级。例如将 PID 为 1234 的进程优先级改为 5:

renice -n 5 -p 1234

注意事项

  • 只有 root 用户可设置负优先级(提高优先级)。
  • 普通用户仅能调低自身进程的优先级。
  • 优先级数值越大,进程获得的 CPU 时间越少。

进程的特性和时间片解释

进程的特性

  • 竞争性: 系统进程数目众多,而CPU资源只有少量,甚至1个,所以进程之间是具有竞争属性的。为了高效完成任务,更合理竞争相关资源,便具有了优先级。
  • 独立性: 多进程运行,需要独享各种资源,多进程运行期间互不干扰。
  • 并行: 多个进程在多个CPU下分别,同时进行运行,这称之为并行。
  • 并发: 多个进程在一个CPU下采用进程切换的方式,在一段时间之内,让多个进程都得以推进,称之为并发。

什么是时间片

在操作系统中,总会有多个进程等待被CPU调度,这就是并发。每一个进程在调度队列里如果都想被轮到被CPU调度的话,那么每一个进程都会在CPU中以单位时间运行,过了时间就轮到下一个进程被调度,自己则回到队列末尾接着等待被调度。我们称这个单位时间时间片

分时操作系统:按照进程的时间片快速轮转的调度进程,可以达到人类感官上几近“同时”完成多个任务。

进程的切换

在上面我们谈到了什么是时间片,因为有了时间片的约束,才让进程得以并发执行。那么当一个进程的时间片用完,这个老进程就会从CPU身上剥离下来,接着放入队列中的下一个进程。我们称这个行为叫做 进程的切换。简单地说,就是切换CPU上的进程。

但以上是很笼统的理解。要真正理解他的工作细节,需要明白一个CPU中都有一个寄存器,而寄存器是用来存放进程留下的临时数据的,我们把在CPU内部寄存器的和进程相关的所有内容叫做 硬件上下文。比如说进程A进入CPU时,会在寄存器中存下进程A产生的临时数据,那么当进程A从CPU上剥离的时候也要让进程A带走他的临时数据,否则就会被后来的进程B的硬件上下文给覆盖掉,那么等下次又轮到进程A调度的时候,他的数据就会丢失。

进程的调度

一个CPU拥有一个Runqueue(运行队列)

Linux2.6内核中进程调度队列的数据结构

观察上图的队列结构,重点放在蓝色框框红色框框

单独观察框内元素有:nr_active、bitmap[5]、queue[140]

这三个元素的具体作用会在【CPU的调度算法原理】中清楚说明。

CPU的调度算法原理

这里讲的是进程在如何在CPU中被调度的,他的原理是什么。Runqueue运行队列是Linux系统内核中非常重要的一个组成部分。它是用于管理所有的进程和线程的队列,使它们能够按照特定的优先级被调度执行。                                                   

优先级队列一共有140个元素(注意这里的优先级队列是嵌套在runqueue中的queue[140],这里不要搞混了)。在CPU调度的时候,就是要排查queue中所有不为空位置,也就是没有进程需要被调度。

为了更高效的查找队列的每个位置上是否为空(无进程),因此就有了bitmap[5](位图),他的数据类型是unsighed,所以他的总大小为160个bit位(5 x 32 = 160),但我们只用140个bit位 ,这个位图 就跟二进制序列一样,例如:011100010....  1就代表队列不为空,0就代表为空。   所以我们就把遍历队列的工作简化为了对位图的操作。通过位图 就可以每跨32位找一次,提升了效率。 

                                                                                                                

在系统中进程的优先级(pri)大小范围是 [ 60,99],也就是40个优先级顺序位置,如上图所示,runqueue的结构里有一个大小140位的queue。系统会根据这140个优先级不同,将进程列入到不同的队列当中,每一个队列标记着一个优先级,                                           

如右图所示,后40个bit位,从100位到139位,从上到下遍历所有的队列是否为空,不为空,CPU就调度此处队列链起来的进程。               进程PCB的双链表结构 比较特殊,可以点击链接进行了解。                      虽然优先级的范围是60 --> 99,而右侧图片中红色数组优先级区域的范围是100 --> 139,但不影响pri+40映射到数组的下标,这其实用了一个开散列的哈希表。                                   注意:每一个数组的节点对应的是一个队列头节点,而这每一个队列里都可以有多个进程。在相同的优先级的进程当中才用FIFO算法调度。

每一个节点通过这个双链表将进程PCB连起来。

补充:优先级队列的前100位是针对实时进程的,而后40位是管普通进程的

接着观察runqueue的结构图,可以看见有红色区域和蓝色区域分别有两个140队列:

两个队列的分工:

  • 活跃队列:放时间片未用完的就绪进程;CPU只在这里选进程运行。
  • 过期队列:放时间片已用完的进程;新就绪的普通进程也放这里。

红色数组蓝色数组又嵌套在一个arrays[2]的结构体数组中,再有两个指针分别是:

  • active:指向第一组队列(活跃),就是图片中蓝色区域的
  • expired:指向第二组队列(过期),即红色

为什么这样设计呢?因为CPU只看活跃队列,按优先级进程。当时间片用完,那么这时中需要将active* 和 expired*指向的内容swap,也就是交换一下,那么CPU就可以调度原来的过期队列了,轮流反复。这样的设计就可以避免低优先级队列得不到调度的问题(饥饿问题)!!!

在我们包裹着优先级队列的array数组当中还有一个nr_active调度器,也叫计数器。用来统计当前array数组内所有队列中就绪进程的总数量,如果nr_active为0,那么就直接交换active*和expired*。

以上差不多就是时间片+优先级轮转调度算法,时间复杂度为O(1)。

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐