Linux操作系统:进程的切换与调度
进程优先级
优先级概念
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(运行队列)
观察上图的队列结构,重点放在蓝色框框与红色框框
单独观察框内元素有: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)。
