十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Linux:进程切换与调度

Linux:进程切换与调度 一、进程优先级1.1 什么是进程优先级进程优先级是指进程获得CPU资源先后顺序的数值数值越小优先级越高。优先度越高的进程有优先执行的权利而配置进程优先度对执行多任务环境的Linux系统有明显的帮助可以改善系统的性能。1.2 PRI值和NI值当我们用ps -l查看系统进程时我们很容易发现其中几个重要的属性信息其中有关于我们进程优先级的属性是PRI值和NI值PRI值PRI还是比较好理解的就是该进程的优先级通俗点来说就是程序被CPU执行的先后顺序该值越小进程的优先级越高。NI值NI值就是我们所说的nice值表示进程可被执行的优先级的修正数值。PRI值越小的越优先被执行而有了NI值后我们PRI新 PRI旧 NI。当NI值为正时PRI值变大优先级变低NI值为负时PRI值变小优先级变高。那我们将NI值无限调小或调大岂不是能将某进程的优先级变得极高或极低答案当然是不可能的系统规定我们的NI值的设定范围为[-2019]当我们设定的NI值小于-20或大于19时系统自动将NI值变得-20或19不会超出所规定的范围。另外一般系统是不允许更改PRI值默认80的只能更改NI值来间接更改PRI值优先级的更改也是不支持高频修改的一段时间是不能多次更改NI值。二、进程切换2.1 并行与并发进程具有独立性和竞争性那么如何将有限的CPU资源分配给多个独立并竞争的进程这里系统有两种调度方式并行与并发。并行多个进程在多个CPU下分别同时的进行运行。并发多个进程在一个CPU中采用进程轮换的方式在一段时间内让多个进程都得以推进。并行指多个 CPU 核心同时运行多个进程此处不展开。并发是在单个 CPU 上通过进程轮换使用 CPU 实现宏观上同时运行的效果进程之间的轮换依赖上下文切换完成。上下文切换其本质是任务切换核心是 CPU 寄存器现场的切换。当多任务内核决定调度另一个任务运行时会保存当前正在运行任务的上下文即 CPU 寄存器中的所有内容。这些现场信息保存在该任务对应PCB中的内核栈中。保存完成后从下一个任务的内核栈中恢复它的上下文把数据重新载入 CPU 寄存器随后开始运行新任务这个过程就是上下文切换。寄存器是共享的而寄存器里的数据本质上是进程所私有的即是上下文数据。2.2 进程的组织在内核中task_struct进程描述符内部嵌入会多个链表节点结构体包含前后指针。依靠这些独立的链表节点同一个task_struct可以同时挂载到多个不同数据结构的链表当中。struct task_struct { **********//很多进程的属性 struct link node;//内嵌节点结构体 } struct link { struct link *prev;//前驱 struct link *next;//后继 }通俗理解就是 task_struct 是一个人 每一个 link 是这个人身上不同的挂钩 一个挂钩挂班级名单链表另一个挂钩挂运动会名单链表。人只有一个但可以同时在多个名单里。那为什么要如此设计呢首先进程在系统中会拥有多种属性不同内核模块需要按不同维度遍历进程调度器需要遍历就绪队列而内核管理模块需要遍历全部进程分开的链表节点可以满足不同模块的访问需求。 其次进程本体task_struct只需要保留一份不需要复制进程信息节省内存修改进程属性时只需要修改唯一的这份保证数据一致性。 最后进程状态发生变化时仅需要摘下或挂载对应的链表节点例如进程从就绪变为阻塞只需操作就绪队列对应的节点其他链表上的节点不受影响操作灵活且开销小。三、进程调度3.1 Linux调度器基础架构首先我们要了解prio_array_t也就是活跃队列、过期队列这两个进程数组内部包含三个核心成员nr_active、bitmap[5]和queue[140]。struct prio_array_t { nr_active; bitmap[5]; queue[140]; }第一queue[140]这是一个链表数组。下标 0~99 对应实时进程下标 100~139 对应普通进程。进程的优先级本质就是数组下标。调度普通进程时从下标 100 向 139 的顺序依次执行对应队列里的进程。数组的每一个元素都是一条对应优先级的就绪进程链表。找到目标优先级队列后队列内部按照 FIFO 的规则调度进程。第二bitmap[5]即位图数组。如果每次查找就绪进程都遍历全部 140 条链表开销会很大。于是把 140 个队列按每 32 个分为一组用位图标记队列内存在就绪进程则对应 bit 置 1无进程则置 0。调度时通过位运算快速定位到存在就绪进程的队列效率远高于线性遍历。第三nr_active用来记录当前这个prio_array_t内就绪进程的总数量。当nr_active为 0 时代表当前队列没有就绪进程可以直接跳过后续判断避免无效计算。我们将优先级链表等属性封装进prio_array_t结构体中并创建数组array[2]。数组的两个元素分别代表活跃进程队列array[0]和过时进程队列array[1]再通过*active和*expired两个指针分别指向它们。这里就有一个很疑惑的点了为什么要多余分成活跃队列和过时队列明明都是相同的构成。我们设想一种场景如果所有进程优先级都是 61但持续不断有优先级为 60 的新进程就绪那么优先级 61 的进程会持续被抢占永远得不到 CPU 执行。为了解决这类饥饿问题O (1) 调度器引入了活跃队列与过时队列的设计。在 Linux O (1) 调度器里runqueue 包含 prioity_struct_t array [2]分为活跃队列与过时队列调度器给进程分配不同时间片对活跃队列内进程分时调度进程时间片耗尽就移入过时队列当活跃队列全部进程时间片用完时交换两个队列指针以此避免进程饥饿。我们所说多个进程宏观上“同时运行”依靠的是 CPU 快速切换活跃队列中的进程产生并发效果并不是依靠频繁交换活跃、过时队列指针队列交换只是一轮调度周期结束时用来重新启用过期进程的机制。3.2 新旧调度方法比较老版 Linux O (1) 调度器基于时间片与双队列模型依靠active、expired队列切换解决进程饥饿使用bitmap和优先级链表做到 O (1) 查找就绪进程进程优先级决定时间片大小高优先级进程能获得更长的 CPU 运行时间。新版 CFS 完全公平调度器抛弃了时间片与 active/expired 双队列结构改用红黑树维护进程虚拟运行时间 vruntime始终挑选 vruntime 最小的进程运行nice 值不再控制时间片而是调整进程权重追求对所有进程的 CPU 使用占比公平。四、总结本文围绕 Linux 进程调度展开主要梳理了三个核心部分进程优先级、进程切换与进程调度。在进程优先级部分我们明确了 PRI 值代表进程优先级数值越小优先级越高NI 值nice 值用于修正优先级其取值范围被系统限制在 [-20, 19]且一般只能通过修改 NI 值来间接调整 PRI 值。在进程切换部分我们区分了并行与并发的概念并行依赖多个 CPU 核心同时运行多个进程并发则是在单个 CPU 上通过进程轮换实现宏观上的同时运行。进程轮换依赖上下文切换完成其核心是 CPU 寄存器现场的保存与恢复现场信息保存在进程 PCB 的内核栈中。此外进程通过 task_struct 内嵌多个链表节点可同时挂载到不同链表满足不同内核模块按不同维度遍历进程的需求。在进程调度部分我们介绍了 Linux O (1) 调度器的核心架构包括 queue[140] 优先级链表数组、bitmap[5] 位图加速查找以及 nr_active 就绪进程计数并通过活跃队列与过时队列的双队列设计解决进程饥饿问题。最后对比了新旧调度方法老版 O (1) 调度器基于时间片与双队列新版 CFS 调度器则改用红黑树维护 vruntime追求对所有进程的 CPU 使用占比公平。
返回列表