《内核源代码情景分析》-CPU调度阅读笔记

  • linux 是抢占式的 (依赖时钟中断)
  • 时间片 timeslice
  • 【动态时间片计算】
  • 【可配置的策略 policy】
  • linux 的公平调度,没有采用时间片实现公平??
  • o(1)调度程序
  • 静态时间算法
  • 每个CPU的调度队列
  • 交互进程
  • rotating staircase ??
  • rotating staircase deadline scheduler 公平队列调度
  • 进程的类型: IO密集 计算密集
  • 调度算法需要在 IO响应时间t 与 吞吐量 p 之前寻找权衡点。
  • QPS = 并发量 / 平均响应时间
  • 对于给定的机器, QPS 往往是固定的,那么 并发量越大,响应时间也越大。
  • 或者说,IO响应时间短,那么花费在切换上下文的时间片就会比较多,整体的吞吐量就比较差。(就好比,餐厅员工摸鱼的人多了,同一时间能服务的客人数目就少了)
  • Unix / Linux 都倾向于 IO 密集型服务。
  • nice 值,系统提供给用户影响调度的配置项。
  • linux 采用两种优先级配置项,除了 nice ,还有实时优先级 prio
  • 实时优先级 prio
  • 时间片消耗计数器
  • 抢占时机 与 CFS
  • 【调度器模块】
  • 进程可以选【调度算法类】
  • 调度器类也有优先级。
  • unix 中 不通 nice 值的 时间片大小不一样
  • o(1) 调度算法中, 假如 nice = 0 为 100ms, nice = 1 为 95 ms, nice = 18 为 10 ms, 19 为 5 ms 。
  • o(1) 调度算法跟我们工资一样,穷人涨薪都是翻倍涨的,所以穷人薪资百分比差值大,富人涨幅就比较小。
  • 优先级 可能是一种后门,可以命令 CPU 做一些无用功
  • CFS 算法提供了上下文切换频率,避免cpu独占好几毫秒,导致延迟高达好几ms。
  • CFS 算法提高了切换频率,为了避免“切换时间 等于 做功的时间”,约定做功时间最小值为 1ms 。(如果分配的时间片小于 1 ms 就没必要给他分配CPU了)
  • CFS的对时间片的分配,不再基于 优先级 0 , 而是基于进程间的相对差值。(比如 优先级 10, 15 对于 20ms, 分别获得 15ms 、5 ms)
  • 影响进程吞吐量的两个因素: 1.进程本身优先级, 2. CPU的上下文切换频率。(内因外因)
  • 动态时间片,【指标】虚拟运行时间 vruntime 计数器, 【行为】CFS 的 平均 vruntime
  • 静态时间片,【指标】固定运行时间,【行为】FIFO 派发固定时间片。
  • NICE 值 可以影响 CFS 类
  • 优先级 PRIO 可以影响 FIFO 类 RR 类
  • 调度有关的系统调用有 sched_setscheduler(), nice(), sched_setparam(), sched_setaffinity()
  • sched_setaffinity 设置亲和度。
  • sched_setparam 选择实时调度器时,设置优先级。
  • nice 选择CFS调度器时,设置 nice 时间片权重。
  • O(1)与 CFS 的区别, 时间片的比例,上下文切换的频率(响应性)
  • CFS 的响应性更合理,时间片大小也更合理。
  • 控制好实时调度类的时间片大小,避免把CPU资源独吞了。
  • 【硬实时进程任务】,任务必须在指定的时限内完成。(时效性,即任务最好不被中断,准时交付执行结果)
  • 【软实时进程任务】,具有优先执行权的普通进程。
  • 【普通进程】通过nice 调配的普通进程(CFS)
    1. 避免无事可做的进程占用CPU,区分【等待队列】、【就绪队列】
    1. linux 有多种调度类,实时的调度类无任务时,才会调度普通任务。
    1. 当进程变为就绪可选择是否抢占当前CPU。
  • 进程列表是n个元素的数组, O(1) 调度器算法的时间复杂度做到了跟 n 无关,所以叫 O(1)
  • CFS改进 O(1), 更合理的CPU切换频率,更合理的几何加权时间片计算,新增【调度实体】的概念。
  • 中断可以抢夺系统调用时间(通过中断程序进行上下文切换)
  • 系统调用无法被其他进程抢夺(包括其他进程的系统调用)
  • 中断程序无法被其他中断抢夺。
  • 内核抢占(kernel preemption)允许内核态被其他进程抢夺(不开启该功能则不允许打断内核态)。
  • unsigned long nvcsw, nivcsw; /* 上下文切换计数 */
  • 内核线程是直接由内核本身启动的进程。
  • 内核进程无法访问用户态内存空间
  • 红黑树是内核的 标准数据结构
  • 一个进程只能设置一个调度器
  • normal_priority表示基于进程的静态优先级和调度策略计算出的动态优先级
  • 静态优先级是进程启动时分配的优先级。它可以用nice 和sched_setscheduler系统调用修改。
  • sched_entity 可以实现组调度的功能,组调度是调度实体的自我分配(类似 线程或者协程的概念)
  • 如果不 打算用nice降低进程的静态优先级,可以考虑设置 policy 为 SCHED_BATCH和SCHED_IDLE
  • policy 是比 nice 更加柔和的干预调度算法的手段。
  • 在【编译时】已经建立调度类:没有在运行时动态增加新调度器类的机制。
  • 【用户层应用程序】无法直接与【调度类】交互。
  • 它们只知道上文定义的常量SCHED_xyz。在这些常量和 可用的调度类之间提供适当的映射,这是内核的工作。
  • 各个CPU都有自身的就绪队列
  • 各个活动进程只出现在一个就绪队列中。(在多个CPU上同时运行一个进程是不可能的)
  • 每个【cpu】 通过 【全局调度器】访问 【调度类】的 【子就绪队列】。
  • 【调度类】 给每个cpu都安排了一个【子就绪队列】。
  • 为idle线程,在无其他可运行进程时执行
  • 系统的所有就绪队列都在runqueues数组中,该数组的每个元素分别对应于系统中的一个CPU。 在单处理器系统中
  • 虚拟时钟上流逝的时间数量由vruntime统计
  • 更高的rt_priority值 表示更高的实时优先级。
  • 优先级值 与 优先级
  • 动态优先级 (task_struct->prio)、普通优先级(task_struct->normal_prio)和静态优先级(task_struct-> static_prio),则是 值越低, 权越大
  • rt_policy 为 RR 和 FIFO 为实时进程
  • static const int prio_to_weight[40] 权重值表,CFS的精华之一
  • 进程每降低一个nice值,则多获得10%的CPU时间,每升高一个nice值,则放 弃10%的CPU时间。为执行该策略,内核将优先级转换为权重值。
  • 周期性调度器在scheduler_tick, 有2个任务: 1. 统计进程的时间片。2.执行调度类的周期性任务。
  • CFS 不是基于时间片调度,而是基于 vruntime 调度。
  • 如果当前进程应该被重新调度,那么调度器类方法会在task_struct中设置TIF_NEED_RESCHED 标志,以表示该请求,而内核会在接下来的适当时机完成该请求。
  • TIF_NEED_RESCHED
  • 惰性TLB (延迟切换用户态)和 惰性FPU技术 (延迟切换浮点寄存器) 都用到了懒汉模式,减少CPU上下文切换的成本,但是代码复杂度变高了,而且有泄漏数据风险 。
  • 内核抢占是低延迟应用的福音,也是并发问题的潘多拉魔盒。
  • 亲和性和调度域的问题,跟调度作业就近机房调度问题与 就近主机调度问题一类问题。
  • kernel/sched_fair.c
  • static const struct sched_class fair_sched_class {}
  • kernel/sched.c struct cfs_rq {}
  • rb_leftmost 总是设置为指向树最左边的结点,即最需要被调度的进程。
  • 通常只对红黑树最左边的结点感兴趣,因为这可以减少搜索树花费的平均 时间。
  • 完全公平调度算法依赖于虚拟时钟,
  • 虚拟时钟
  • 数据结构中任何地方都没找到虚拟时钟
  • 根据现 存的实际时钟
  • 进程的负荷权重
  • __uodate_curr() 更新进程的物理运行时间 sum_exec_runtime 和虚拟运行时间 vruntime
  • __uodate_curr()
  • rq_of是一个辅助函数,用于确定与CFS就绪队列相关的struct rq实例
  • 辅助函数
  • CFS就绪队列
  • struct rq实例
  • delta_exec = (unsigned long)(now -curr->exec_start); 计算工作的时间差,即计算进程使用CPU的时长。
  • 忽略舍入和溢出检查 unlikely()
  • Curr->load.weight 越大,vruntime 增幅越小。
  • min_vruntime是单调递增的
  • 进程进入睡眠,则其vruntime保持不变。
  • 睡眠进程醒来后,在红黑树中的位置会更靠左。
  • 内核有一个固有的概念,称之为良好的调度延迟
  • 良好的调度延迟
  • 即保证每个可运行的进程都应该至少运行一次 的某个时间间隔。
  • 良好的调度延迟,默认值为20 000 000纳秒或20毫秒
  • /proc/sys/kernel/sched_latency_ ns
  • sysctl_sched_latency
  • 控制参数sched_nr_latency,控制在一个延迟周 期中处理的最大活动进程数目。
  • 控制参数 sched_nr_latency
  • 进程的数目超出该上限,则延迟周期也成比例地线性扩展。(进程越多,能够得到保证越差)
  • 进程越多,能够得到保证越差
  • 调度延迟 ,避免了CFS的调度饥饿。
  • /proc/sys/kernel/sched_min_granularity_ns 间接控制延迟周期
  • 延迟时间间隔 sched_vslice 也是按照 vruntime 一样的权重比例算出来的。
  • sched_vslice 是每个进程保底能享用CPU的时间(类似低保)
  • static const struct sched_class fair_sched_class = {
  • .next = &idle_sched_class,
  • .enqueue_task = enqueue_task_fair,
  • .dequeue_task = dequeue_task_fair,
  • .yield_task = yield_task_fair,
  • .check_preempt_curr = check_preempt_wakeup,
  • .pick_next_task = pick_next_task_fair,
  • .put_prev_task = put_prev_task_fair,
  • .set_curr_task = set_curr_task_fair,
  • .task_tick = task_tick_fair,
  • .task_new = task_new_fair,
  • };
  • 全局调度器
  • 全局调度器,其实是一个接类型
  • 全局调度器包括:任务队列,出让CPU方法,挑选下一个任务,周期性调度,新建任务。
  • check_preempt_curr
  • 调用likely()或unlikely()告诉编译器这个条件很有可能或者不太有可能发生,好让编译器对这个条件判断进行正确地优化。
  • 分支预测优化
  • __builtin_expect是GCC提供的內建函数,用于给GCC提供分支预测优化信息。
  • CPU无一例外的都引入了流水线技术,用于加快指令的执行,提高CPU的性能
  • 换句话说,就是CPU在处理当前指令的同时,会先取出后面的多条指令进行预处理。
  • I486拥有五级流水线。分别是:取指(Fetch),译码(D1, main decode),转址(D2, translate),执行(EX, execute),写回(WB)。
  • 对于进程来说,虚拟时钟 就跟 虚拟地址一样 ,在读写的时候需要根据权重换算程物理时钟。
  • 从而实现每个进程的虚拟时钟周期是一样 的,但物理时钟不一样。
  • 比如:计算每个进程的最小保证允许时间 (进程的CPU低保)
  • kernel/sched_fair.c
  • gran = sysctl_sched_wakeup_granularity;
  • if (unlikely(se->load.weight != NICE_0_LOAD))
  • gran = calc_delta_fair(gran, &se->load);
  • 当在try_to_wake_up和wake_up_new_task中唤醒进程时 (中断唤醒,或者当前进程唤醒),会判断被唤醒的进程是否是实时进程,如果是的话,就会 resched_task(curr); 抢占当前进程。
  • 实时调度器类的实现比完全公平调度器简单。大约只需要250行代码,而CFS则需要1100行!
  • 核心调度器的就绪队列也包含了用于实时进程的子就绪队列,是一个嵌入的struct rt_rq实例。
  • 每个sched_class 只包含了对 全局调度器接口的实现方法,而针对每个CPU核心的就绪队列,则放在核心调度器 struct rq {} 中。 (kernel/sched.c)
  • 核心调度器 struct rq {}
  • RT scheduler / CFS调度器 / 全局调度器 / 核心调度器
  • 全局调度器的实时调度器接口 const struct sched_class rt_sched_class {}
  • 核心调度器 struct rq {}
  • 周期性调度器在 scheduler_tick 中实现。如果系统正在活动中,内核会按照频率HZ自动调用该 函数。如果没有进程在等待调度,那么在计算机电力供应不足的情况下,也可以关闭该调度器以减少 电能消耗。
  • 周期性调度器 scheduler_tick
  • scheduler_tick 位于 当前进程的调度类中。
  • 调度类 如:实时调度器 / CFS调度器
  • TIF_NEED_RESCHED + schedule 是主调度器的入口。
  • 核心调度器(core scheduler)
  • 通用调度器(generic scheduler)
  • 核心调度器 与 通用调度器 是一回事。
  • 核心调度器 是一个分配器,与其他两个组件交互。
  • 核心调度器 = 周期性调度器 + 主调度器(由特定调度时机触发,比如中断)
  • 调度类,包括了RT / CFS / IDLE , 他们实现了 调度类 sched_class 接口,里面包含了 周期性调度器函数接口 + 主调度器函数接口
  • 核心调度器 struct rq {},包含了多个调度类的 就绪队列
  • 核心调度器 可以理解为 是 全局调度器。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
本PDF电子书包含上下两册,共1576页,带目录,高清非扫描版本。 作者: 毛德操 胡希明 丛书名: Linux内核源代码情景分析 出版社:浙江大学出版社 目录 第1章 预备知识 1.1 Linux内核简介. 1.2 Intel X86 CPU系列的寻址方式 1.3 i386的页式内存管理机制 1.4 Linux内核源代码中的C语言代码 1.5 Linux内核源代码中的汇编语言代码 第2章 存储管理 2.1 Linux内存管理的基本框架 2.2 地址映射的全过程 2.3 几个重要的数据结构和函数 2.4 越界访问 2.5 用户堆栈的扩展 2.6 物理页面的使用和周转 2.7 物理页面的分配 2.8 页面的定期换出 2.9 页面的换入 2.10 内核缓冲区的管理 2.11 外部设备存储空间的地址映射 2.12 系统调用brk() 2.13 系统调用mmap() 第3章 中断、异常和系统调用 3.1 X86 CPU对中断的硬件支持 3.2 中断向量表IDT的初始化 3.3 中断请求队列的初始化 3.4 中断的响应和服务 3.5 软中断与Bottom Half 3.6 页面异常的进入和返回 3.7 时钟中断 3.8 系统调用 3.9 系统调用号与跳转表 第4章 进程与进程调度 4.1 进程四要素 4.2 进程三部曲:创建、执行与消亡 4.3 系统调用fork()、vfork()与clone() 4.4 系统调用execve() 4.5 系统调用exit()与wait4() 4.6 进程的调度与切换 4.7 强制性调度 4.8 系统调用nanosleep()和pause() 4.9 内核中的互斥操作 第5章 文件系统 5.1 概述 5.2 从路径名到目标节点 5.3 访问权限与文件安全性 5.4 文件系统的安装和拆卸 5.5 文件的打开与关闭 5.6 文件的写与读 5.7 其他文件操作 5.8 特殊文件系统/proc 第6章 传统的Unix进程间通信 6.1 概述 6.2 管道和系统调用pipe() 6.3 命名管道 6.4 信号 6.5 系统调用ptrace()和进程跟踪 6.6 报文传递 6.7 共享内存 6.8 信号量 第7章基于socket的进程间通信 7.1系统调用socket() 7.2函数sys—socket()——创建插口 7.3函数sys—bind()——指定插口地址 7.4函数sys—listen()——设定server插口 7.5函数sys—accept()——接受连接请求 7.6函数sys—connect()——请求连接 7.7报文的接收与发送 7.8插口的关闭 7.9其他 第8章设备驱动 8.1概述 8.2系统调用mknod() 8.3可安装模块 8.4PCI总线 8.5块设备的驱动 8.6字符设备驱动概述 8.7终端设备与汉字信息处理 8.8控制台的驱动 8.9通用串行外部总线USB 8.10系统调用select()以及异步输入/输出 8.11设备文件系统devfs 第9章多处理器SMP系统结构 9.1概述 9.2SMP结构中的互斥问题 9.3高速缓存与内存的一致性 9.4SMP结构中的中断机制 9.5SMP结构中的进程调度 9.6SMP系统的引导 第10章系统引导和初始化 10.1系统引导过程概述 10.2系统初始化(第一阶段) 10.3系统初始化(第二阶段) 10.4系统初始化(第三阶段) 10.5系统的关闭和重引导

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值