- nice值:
- 取值范围是-20至19;进程创建时,默认从父进程继承该值;一般是0;
- nice命令:创建 给定nice值的进程;
- renice命令:可以动态修改正在运行的进程;
- 越nice的进程,越可以容忍别的进程占用更多的CPU资源;值越大,权重越低;
- nice值,称为静态优先级,一般不改变;
- 对应priority的100-139;
- priority值:
- 称为动态优先级;
- 范围0-140;其中0-99属于实时进程的优先级;
- 新的进程默认优先级: #define DEFAULT_PRIO (MAX_RT_PRIO + NICE_WIDTH/2) 等于 120;即nice值为0对应的优先级;
- 实时进程的调度算法:
- 调度算法更加简单,调度延时更低;
- 要求中断的延时更小;
- 调度器和调度策略:
- 用户可以指定创建的进程使用哪种调度策略;
- 系统中同时运行多个调度策略;
- 只有当实时进程全部退出,才会运行非实时进程;
- O1调度:
- 时间片轮转,每个进程分配时间片,轮流运行;
- 问题:是否是每个CPU都有一个队列。还是所有的任务共享一个任务队列?
- CFS调度:
- runtime:进程的实际运行时间;
- nice:决定了任务的运行权重;
- 最小运行时间:sched_min_granularity_ns(默认0.75ms)
- 调度延迟:
- 当进程数小于8时,sysctl_sched_latency 默认为6ms;
- 当进程数大于8时,sysctl_sched_latency = 进程数 * sched_min_granularity_ns;
- vruntime:用于给任务排序,下次调度应该选择哪个任务?即选择运行时间(vruntime)最小的进程,使用rbtree管理;
- rbtree:vrutntime最小的进程,位于最左侧叶子节点;里面都是ready的任务;
- rbtree任务插入:(任务创建、任务唤醒)
- rbtree任务删除:
- vruntime溢出风险:无符号值比较,只需要比较相对值,可以使用类似于的time_after原理来解决溢出风险,实现溢出后的大小正确比较;
- 如何保证任务不会来回频繁切换:
- 权重设计原则:nice值差1, CPU的占用差别10%左右;
- 每个CPU都有一个运行队列,避免进程调度时对其他CPU的干扰;(相比于O1的调度算法的优点)
- 负载均衡:
- 因为每个CPU都有一个运行队列,那么会存在负载不均衡的问题;
- 解决:CFS定期检查各个CPU的运行队列,并使之均衡;
- 线程从CPU1迁移到CPU2,vruntime需要变化:?
- 实际任务的运行时间:
- 与系统tick周期有关系,当tick周期是4ms,频率为250hz, 那么任务最少运行时间为4ms而不是sched_min_granularity_ns;
- 问题:
- 如何使用top等命令工具查看系统中的各个任务优先级和nice值?
- 负载均衡的周期? 多久检查一次?在哪里实现的?内核线程?
1、Linux的CFS调度器并没有直接分配时间片到进程,而是将处理器的使用比例划分给进程。在大多数操作系统中,是否将一个进程立刻投入运行完全取决于:进程优先级和是否有时间片。CFS调度器抢占时机取决于:可运行程序消耗了多少处理器使用比。
2、调度器类:每个调度器都有一个优先级,都有0个或若干个可执行进程。基础调度器会按照优先级遍历调度器类,选择一个有可执行进程的最高优先级的类,再从此类中选择可执行进程。
3、CFS调度器原理:允许每个进程允许一段时间,循环轮转,选择运行时间最少进程作为下一个进程,而不再分配时间片。CFS在所有可运行进程总数基础上计算一个进程应该运行多久。不依靠nice值来计算时间片,而是使用nice值作为可以获得处理器运行比的权重。
vruntime变量存放进程的虚拟运行时间,该运行时间经过所有可运行进程总数的标准化。
选择具有最小vruntime的任务运行。cfs_rq按照红黑树组织,vruntime的最小的进程的sched_entity被放在最左侧的叶子节点上。并且最左侧节点存放在cfs_rq中的rb_leftmost字段中。
4、调度器入口:schedule()函数:找一个调度器类,在该类的运行队列中寻找下一个可运行进程。
5、抢占:每个进程有一个need_resched标志。
用户抢占:从系统调用返回用户空间;从中断处理程序返回用户空间。
内核抢占:只要没有持有锁,内核就可以进行抢占。为支持内核抢占,加入preempt_count标志,在thread_info结构体中。
内核抢占时机:中断返回内核时候,会检查need_resched和preempt_count标志;如果need_resched被设置,并且preempt_count=0。那么可以安全的抢占,如果preempt_count=1,表示不能抢占,内核持有锁,等内核释放锁的时候会重新将preempt_count=0,然后检查need_resched是否被设置,如果是,可以抢占。
归纳:中断返回内核,且在返回用户空间之前;内核代码再一次具有可抢占性;内核中显示的调用schedule();内核中的任务阻塞,也会调用schedule()。
睡眠/休眠: