你不好奇 CPU 是如何执行任务的?( 四 )


  • SCHED_RR:对于相同优先级的任务 , 轮流着运行 , 每个任务都有一定的时间片 , 当用完时间片的任务会被放到队列尾部 , 以保证相同优先级任务的公平性 , 但是高优先级的任务依然可以抢占低优先级的任务;
  • 而 Fair 调度类是应用于普通任务 , 都是由 CFS 调度器管理的 , 分为两种调度策略:
    • SCHED_NORMAL:普通任务使用的调度策略;
    • SCHED_BATCH:后台任务的调度策略 , 不和终端进行交互 , 因此在不影响其他需要交互的任务 , 可以适当降低它的优先级 。
     
    完全公平调度我们平日里遇到的基本都是普通任务 , 对于普通任务来说 , 公平性最重要 , 在 Linux 里面 , 实现了一个基于 CFS 的调度算法 , 也就是完全公平调度(Completely Fair Scheduling) 。
    这个算法的理念是想让分配给每个任务的 CPU 时间是一样 , 于是它为每个任务安排一个虚拟运行时间 vruntime , 如果一个任务在运行 , 其运行的越久 , 该任务的 vruntime 自然就会越大 , 而没有被运行的任务 , vruntime 是不会变化的 。
    那么 , 在 CFS 算法调度的时候 , 会优先选择 vruntime 少的任务 , 以保证每个任务的公平性 。
    这就好比 , 让你把一桶的奶茶平均分到 10 杯奶茶杯里 , 你看着哪杯奶茶少 , 就多倒一些;哪个多了 , 就先不倒 , 这样经过多轮操作 , 虽然不能保证每杯奶茶完全一样多 , 但至少是公平的 。
    当然 , 上面提到的例子没有考虑到优先级的问题 , 虽然是普通任务 , 但是普通任务之间还是有优先级区分的 , 所以在计算虚拟运行时间 vruntime 还要考虑普通任务的权重值 , 注意权重值并不是优先级的值 , 内核中会有一个 nice 级别与权重值的转换表 , nice 级别越低的权重值就越大 , 至于 nice 值是什么 , 我们后面会提到 。
    于是就有了以下这个公式:
    你不好奇 CPU 是如何执行任务的?

    文章插图
    你可以不用管 NICE_0_LOAD 是什么 , 你就认为它是一个常量 , 那么在「同样的实际运行时间」里 , 高权重任务的 vruntime 比低权重任务的 vruntime 少 , 你可能会奇怪为什么是少的?你还记得 CFS 调度吗 , 它是会优先选择 vruntime 少的任务进行调度 , 所以高权重的任务就会被优先调度了 , 于是高权重的获得的实际运行时间自然就多了 。
     
    CPU 运行队列一个系统通常都会运行着很多任务 , 多任务的数量基本都是远超 CPU 核心数量 , 因此这时候就需要排队 。
    事实上 , 每个 CPU 都有自己的运行队列(Run Queue, rq) , 用于描述在此 CPU 上所运行的所有进程 , 其队列包含三个运行队列 , Deadline 运行队列 dl_rq、实时任务运行队列 rt_rq 和 CFS 运行队列 csf_rq , 其中 csf_rq 是用红黑树来描述的 , 按 vruntime 大小来排序的 , 最左侧的叶子节点 , 就是下次会被调度的任务 。
    你不好奇 CPU 是如何执行任务的?

    文章插图
    这几种调度类是有优先级的 , 优先级如下:Deadline > Realtime > Fair , 这意味着 Linux 选择下一个任务执行的时候 , 会按照此优先级顺序进行选择 , 也就是说先从 dl_rq 里选择任务 , 然后从 rt_rq 里选择任务 , 最后从 csf_rq 里选择任务 。因此 , 实时任务总是会比普通任务优先被执行 。
     
    调整优先级如果我们启动任务的时候 , 没有特意去指定优先级的话 , 默认情况下都是普通任务 , 普通任务的调度类是 Fail , 由 CFS 调度器来进行管理 。CFS 调度器的目的是实现任务运行的公平性 , 也就是保障每个任务的运行的时间是差不多的 。
    如果你想让某个普通任务有更多的执行时间 , 可以调整任务的 nice 值 , 从而让优先级高一些的任务执行更多时间 。nice 的值能设置的范围是 -20~19 ,  值越低 , 表明优先级越高 , 因此 -20 是最高优先级 , 19 则是最低优先级 , 默认优先级是 0 。


    推荐阅读