前面我们学习了O(n)调度器的设计,以及它的核心算法。在这里复习下。

O(n)调度器核心:

O(n)调度器采用一个runqueue运行队列来管理所有可运行的进程,在主调度schedule函数中会选择一个优先级最高,也就是时间片最大的进程来运行,同时也会对喜欢睡眠的进程做一些补偿,去增加此类进程的时间片。当runqueue运行队列中无进程可选择时,则会对系统中所有的进程进行一次重新计算时间片的操作,同时也会对剩余时间片的进程做一次补偿。

O(n)调度器的缺陷:

  1. 时间复杂度是O(n)
  2. SMP系统扩展不好,访问runqueue需要加锁
  3. 实时进程不能及时调度
  4. CPU空转的现象存在
  5. 进程在各个CPU之间跳跃,性能影响

O(1)调度器的引入

基于O(n)调度器的种种问题,linux内核社区则在2.6内核版本引入了O(1)调度器,当然了引入的目的也正是要解决O(n)调度器面临的问题。我们这片文章以Linux2.6.2版本来学习,在Linux内核文档中有一篇关于O(1)调度器的目的,如何设计的,以及实现有一个详细的介绍:sched-design.txt文档,有兴趣的可以去阅读。

O(1)调度器的工作原理

  • 系统中的runqueue是一个PER_CPU变量,也就是说每一个CPU维护这一个runqueue,这样在SMP系统就可以有效的避免多个CPU去访问同一个runqueue。
  • 每一个runqueue运行队列维护两个链表。一个是active链表,表示运行的进程都挂载active链表中;一个是expired链表,表示所有时间片用完的进程都挂载expired链表中。
  • 为了解决O(n)中所有的进程都无序排列在runqueue中,O(1)算法中奖进程按照优先级排列,而且相同优先级的都挂在同等优先级的链表中
  • 同时提供了一个bitmap结构,用来存放那些优先级中有可以运行的进程。当每次pciknext的时候,只需要坚持bitmap,然后去对应的优先级列表中按照优先级策略选择进程。
  • 当acitve中无进程可运行时,说明系统中所有进程的时间片都已经耗光,这时候则只需要调整active和expired的指针即可。

从以上几点来看,可以看出O(1)的算法的改进都是针对O(n)算法存在的问题来修改的。

O(1)调度器涉及的数据结构

struct runqueue {spinlock_t lock;unsigned long nr_running, nr_switches, expired_timestamp,nr_uninterruptible, timestamp_last_tick;task_t *curr, *idle;struct mm_struct *prev_mm;prio_array_t *active, *expired, arrays[2];int best_expired_prio, prev_cpu_load[NR_CPUS];task_t *migration_thread;struct list_head migration_queue;atomic_t nr_iowait;
};static DEFINE_PER_CPU(struct runqueue, runqueues);

可以看到struct runqueue是一个PER_CPU的变量,则对应的是SMP系统中每一个CPU都维护一个struct runqueue结构。

/** These are the runqueue data structures:*/#define BITMAP_SIZE ((((MAX_PRIO+1+7)/8)+sizeof(long)-1)/sizeof(long))typedef struct runqueue runqueue_t;struct prio_array {int nr_active;unsigned long bitmap[BITMAP_SIZE];struct list_head queue[MAX_PRIO];
};

struct prio_array就代表的两个优先级数组,nr_active代表当前有多少进程处于active或者expried。bitmap是为了方便查找进程引入的,这样当寻找进程的时候只需要查询bitmap,而bitmap的大小是固定的,则算法的时间复杂度是O(1)

O(1)调度器的核心算法

O(1)的核心算法,我们可以直接看schedule函数

array = rq->active;
idx = sched_find_first_bit(array->bitmap);
queue = array->queue + idx;
next = list_entry(queue->next, task_t, run_list);

这三行就是算法的核心,首先去从runqueue的active队列中的bitmap找到一个下标,这个下标就是对应的优先级,然后获取到对应优先级的链表,然后从中获取一个next进程。后面的操作就是执行进程切换,调度了。

系统中无可运行进程时

当系统中无可运行进程时,也就是进程的时间片都耗光了,则需要重新给进程设置时间片

 array = rq->active;if (unlikely(!array->nr_active)) {/** Switch the active and expired arrays.*/rq->active = rq->expired;rq->expired = array;array = rq->active;rq->expired_timestamp = 0;rq->best_expired_prio = MAX_PRIO;}

操作也是相当的简单,只需要切换active和expried的指针即可。

O(1)调度器优先级的设置

进程的优先级分为静态优先级和动态优先级。普通优先级是进程创建时默认设置的优先级,动态优先级会在进程运行时经过动态的调整。

普通进程的静态优先级为static_prio

实时进程的静态优先级为rt_priority

O(1)调度器中所有进程的动态优先级为p->prio。

#define CURRENT_BONUS(p) \(NS_TO_JIFFIES((p)->sleep_avg) * MAX_BONUS / \MAX_SLEEP_AVG)static int effective_prio(task_t *p)
{int bonus, prio;if (rt_task(p))return p->prio;bonus = CURRENT_BONUS(p) - MAX_BONUS / 2;prio = p->static_prio - bonus;if (prio < MAX_RT_PRIO)prio = MAX_RT_PRIO;if (prio > MAX_PRIO-1)prio = MAX_PRIO-1;return prio;
}

在系统运行中,会跟踪此函数来重新计算进程的动态优先级。实时进程只需要返回对应的p->prio。而普通进程则就需要进行赏罚了。通过进程的睡眠时间sleep_avg来计算进程是否需要赏罚。当一个进程经常睡眠,则会增加它的优先级。当一个进程常占CPU,则需要惩罚,降低其优先级

时间片的更新

当系统的tick到来时,会走到schedule_tick函数中

 if (!--p->time_slice) {dequeue_task(p, rq->active);set_tsk_need_resched(p);p->prio = effective_prio(p);p->time_slice = task_timeslice(p);p->first_time_slice = 0;if (!rq->expired_timestamp)rq->expired_timestamp = jiffies;if (!TASK_INTERACTIVE(p) || EXPIRED_STARVING(rq)) {enqueue_task(p, rq->expired);if (p->static_prio < rq->best_expired_prio)rq->best_expired_prio = p->static_prio;} elseenqueue_task(p, rq->active);}

当时间片耗完时,则需要将进程从active队列中移除掉,需要设置需要重新调度的标志TIF_NEED_RESCHED。同时还需要重新计算此进程的优先级,时间片。而O(1)调度器算法比O(n)不是那么的粗暴,还需要判断是否是交互式进程,或者此进程是不是饥饿进程,如果是则将又添加到active队列中,否则添加到expried队列。

总结:

  • O(1)调度器的引入主要是为了解决O(n)调度器的不足
  • O(1)调度器在赏罚机制上比O(n)调度器考虑的因素比较多,不再时像O(1)那样直接考时间片的大小来调度
  • 但是O(n)和O(1)调度算法上核心还是通过判断一个进程的行为,比如爱睡眠来进程赏罚机制,爱睡眠来增大优先级,增大时间片的机制来获取更多的运行时间。
  • 如果去看O(1)调度器的实现,没有O(n)算法那么简单明了,O(1)中加了需要时间的判断,各种情况的考虑,导致代码的阅读性很差,读起来很费劲。
  • 当然了时代还是要前进的,O(n)和O(1)调度器是为CFS调度器出现地提供了很好的环境。

Linux O(1)调度器相关推荐

  1. 【Linux 内核】调度器 ② ( sched_class 调度类结构体源码 | 源码路径 linux-5.6.18\kernel\sched\sched.h )

    文章目录 一.调度器 二.sched_class 调度类结构体 一.调度器 上一篇博客 [Linux 内核]调度器 ( 调度器概念 | 调度器目的 | 调度器主要工作 | 调度器位置 | 进程优先级 ...

  2. 服务器io修改,更改 Linux I/O 调度器来改善服务器性能

    为了从 Linux 服务器榨取尽可能多的性能,请了解如何更改 I/O 调度器以满足你的需求. Linux I/O 调度器()控制内核提交读写请求给磁盘的方式.自从 2.6 内核以来,管理员已经能够更改 ...

  3. Linux进程调度 - 实时调度器 LoyenWang

    背景 Read the fucking source code! --By 鲁迅 A picture is worth a thousand words. --By 高尔基 说明: Kernel版本: ...

  4. Linux进程调度-deadline调度器

    Linux内核中定义了5个调度器类,分别对应5个调度器,调度优先级顺序由高到低依次为:stop_sched_class.dl_sched_class.rt_sched_class.fair_sched ...

  5. 【Linux 内核】调度器 ⑥ ( task_woken 函数 | set_cpus_allowed 函数 | rq_online 函数 | rq_offline 函数 )

    文章目录 一.task_woken 函数 ( 唤醒阻塞进程 ) 二.set_cpus_allowed 函数 ( 修改进程在 CPU 中的亲和力 ) 三.rq_online 函数 ( 启动执行队列 ) ...

  6. 【Linux 内核】调度器 ⑤ ( put_prev_task、set_next_task 函数 | select_task_rq 函数 | migrate_task_rq 函数 )

    文章目录 一.put_prev_task.set_next_task 函数 ( 进程放入执行队列 ) 二.select_task_rq 函数 ( 为进程选择 CPU ) 三.migrate_task_ ...

  7. 【Linux 内核】调度器 ④ ( sched_class 调度类结构体分析 | yield_task 函数 | heck_preempt_curr 函数 | task_struct 函数 )

    文章目录 一.yield_task 函数 ( 放弃 CPU 执行权限 ) 二.check_preempt_curr 函数 ( 检查进程是否可以被抢占 ) 三.task_struct 函数 ( 选择运行 ...

  8. 【Linux 内核】调度器 ③ ( sched_class 调度类结构体分析 | next 字段 | enqueue_task 函数 | dequeue_task 函数 )

    文章目录 一.next 字段 ( 指向链表中的下一个调度类 ) 二.enqueue_task 函数 ( 将进程加入执行队列 ) 三.dequeue_task 函数 ( 从执行队列中删除进程 ) Lin ...

  9. 【Linux 内核】调度器 ① ( 调度器概念 | 调度器目的 | 调度器主要工作 | 调度器位置 | 进程优先级 | 抢占式调度器 | Linux 进程状态 | Linux 内核进程状态 )

    文章目录 一.调度器 0.调度器概念 1.调度器目的 2.调度器主要工作 3.调度器位置 4.进程优先级 5.抢占式调度器 二.Linux 内核进程状态 API 简介 三.Linux 进程状态 一.调 ...

  10. (6)Linux进程调度-实时调度器

    目录 背景 1. 概述 2. 数据结构 3. 流程分析 3.1 运行时统计数据 3.2 组调度 3.3 带宽控制 3.4 调度器函数分析 3.4.1 pick_next_task_rt 3.4.2 e ...

最新文章

  1. MySQL 3.23 中文参考手册
  2. netstat/nmap/netcat用法
  3. Python基础概念_14_常见术语
  4. dtrace-stap-book
  5. SAP 电商云 Spartacus UI 和路由相关的 State 处理
  6. android shape 自定义,Android自定义shape的使用
  7. python中字符串乘法_python leetcode 字符串相乘实例详解
  8. 一文抽丝剥茧带你掌握复杂Gremlin查询的调试方法
  9. 几个常用的python脚本_几个很实用的python脚本
  10. c语言指针慕课,C语言-指针
  11. Linux下查看某个进程占用的CPU及内存
  12. CAN总线学习笔记(1)- CAN基础知识
  13. 运维监控系列(4)-Prometheus控制台功能详解
  14. 服务器cpu一直处于100%解决思路
  15. vscode远端编程 终极方案
  16. 几个炫酷且实用的CSS动画效果
  17. ICCV, ECCV, CVPR,IEEE的关系
  18. SAN存储的局限性相关介绍
  19. 什么是逻辑卷管理器lvm?lvm设备的管理
  20. 阿里资深技术专家总结:要怎样努力才可以成为公司主力架构师

热门文章

  1. Java中线程出现Exception in thread Thread-0 java.lang.IllegalMonitorStateException异常 解决方法...
  2. Excel 2007 底层实现方式
  3. 5.G - 湫湫系列故事——减肥记I
  4. 运用.net工厂编写数据库类
  5. java访问 mysql返回空格_JAVA连接数据库返回输出信息
  6. 未捕获异常string was not recognized_给你代码:PHP7中的异常与错误处理
  7. storm1.x支持主节点nimbus高可用 多master集群部署
  8. 稳压电源通过什么样的当时分类,怎么去分类
  9. ACM-ICPC 2018 沈阳赛区网络预赛 B Call of Accepted(表达式求值)
  10. 为什么开发移动端web不使用jQuery