diff --git a/study/kernel/01-process/05-schedule/06-load_weight/README.md b/study/kernel/01-process/05-schedule/06-load_weight/README.md index cfb9e22..87816c3 100644 --- a/study/kernel/01-process/05-schedule/06-load_weight/README.md +++ b/study/kernel/01-process/05-schedule/06-load_weight/README.md @@ -1,4 +1,4 @@ -Linux进程优先级的处理 +Linux进程负荷权重 ======= @@ -21,8 +21,6 @@ Linux进程优先级的处理 内核必须提供一种方法, 在各个进程之间尽可能公平地共享CPU时间, 而同时又要考虑不同的任务优先级. -调度器的一个重要目标是有效地分配 CPU 时间片,同时提供很好的用户体验。调度器还需要面对一些互相冲突的目标,例如既要为关键实时任务最小化响应时间, 又要最大限度地提高 CPU 的总体利用率. - 调度器的一般原理是, 按所需分配的计算能力, 向系统中每个进程提供最大的公正性, 或者从另外一个角度上说, 他试图确保没有进程被亏待. @@ -47,12 +45,6 @@ linux把进程区分为实时进程和非实时进程, 其中非实时进程进 对于实时进程,采用FIFO, Round Robin或者Earliest Deadline First (EDF)最早截止期限优先调度算法|的调度策略. -对于普通进程,则需要区分交互式和批处理式的不同。传统Linux调度器提高交互式应用的优先级,使得它们能更快地被调度。而CFS和RSDL等新的调度器的核心思想是"完全公平"。这个设计理念不仅大大简化了调度器的代码复杂度,还对各种调度需求的提供了更完美的支持. - -注意Linux通过将进程和线程调度视为一个,同时包含二者。进程可以看做是单个线程,但是进程可以包含共享一定资源(代码和/或数据)的多个线程。因此进程调度也包含了线程调度的功能. - -目前非实时进程的调度策略比较简单, 因为实时进程值只要求尽可能快的被响应, 基于优先级, 每个进程根据它重要程度的不同被赋予不同的优先级,调度器在每次调度时, 总选择优先级最高的进程开始执行. 低优先级不可能抢占高优先级, 因此FIFO或者Round Robin的调度策略即可满足实时进程调度的需求. - 但是普通进程的调度策略就比较麻烦了, 因为普通进程不能简单的只看优先级, 必须公平的占有CPU, 否则很容易出现进程饥饿, 这种情况下用户会感觉操作系统很卡, 响应总是很慢,因此在linux调度器的发展历程中经过了多次重大变动, linux总是希望寻找一个最接近于完美的调度策略来公平快速的调度进程. @@ -94,50 +86,17 @@ linux把进程区分为实时进程和非实时进程, 其中非实时进程进 当前的内核支持两种调度器类(sched_setscheduler系统调用可修改进程的策略):CFS(公平)、RT(实时);5种调度策略:SCHED_NORAML(最常见的策略)、SCHED_BATCH(除了不能抢占外与常规任务一样,允许任务运行更长时间,更好地使用高速缓存,适合于成批处理的工作)、SCHED_IDLE(它甚至比nice 19还有弱,为了避免优先级反转使用)和SCHED_RR(循环调度,拥有时间片,结束后放在队列末)、SCHED_FIFO(没有时间片,可以运行任意长的时间);其中前面三种策略使用的是cfs调度器类,后面两种使用rt调度器类。 -**2个调度器类** -当前的内核支持2种调度器类(sched_setscheduler系统调用可修改进程的策略):**CFS(公平调度器)**、**RT(实时调度器)** - -**5种调度策略** - -| 字段 | 描述 | 所在调度器类 | -| ------------- |:-------------:|:-------------:| -| SCHED_NORMAL | (也叫SCHED_OTHER)用于普通进程,通过CFS调度器实现。SCHED_BATCH用于非交互的处理器消耗型进程。SCHED_IDLE是在系统负载很低时使用 | CFS | -| SCHED_BATCH | SCHED_NORMAL普通进程策略的分化版本。采用分时策略,根据动态优先级(可用nice()API设置),分配CPU运算资源。注意:这类进程比上述两类实时进程优先级低,换言之,在有实时进程存在时,实时进程优先调度。但针对吞吐量优化, 除了不能抢占外与常规任务一样,允许任务运行更长时间,更好地使用高速缓存,适合于成批处理的工作 | CFS | -| SCHED_IDLE | 优先级最低,在系统空闲时才跑这类进程(如利用闲散计算机资源跑地外文明搜索,蛋白质结构分析等任务,是此调度策略的适用者)| CFS | -| SCHED_FIFO | 先入先出调度算法(实时调度策略),相同优先级的任务先到先服务,高优先级的任务可以抢占低优先级的任务 | RT | -| SCHED_RR | 轮流调度算法(实时调度策略),后者提供 Roound-Robin 语义,采用时间片,相同优先级的任务当用完时间片会被放到队列尾部,以保证公平性,同样,高优先级的任务可以抢占低优先级的任务。不同要求的实时任务可以根据需要用sched_setscheduler()API 设置策略 | RT | -| SCHED_DEADLINE | 新支持的实时进程调度策略,针对突发型计算,且对延迟和完成时间高度敏感的任务适用。基于Earliest Deadline First (EDF)最早截止期限优先调度算法| - -其中前面三种策略使用的是cfs调度器类,后面两种使用rt调度器类。 - -另外,对于调度框架及调度器类,它们都有自己管理的运行队列,调度框架只识别rq(其实它也不能算是运行队列),而对于cfs调度器类它的运行队列则是cfs_rq(内部使用红黑树组织调度实体),实时rt的运行队列则为rt_rq(内部使用优先级bitmap+双向链表组织调度实体) - - -本质上, 通用调度器(核心调度器)是一个分配器,与其他两个组件交互. - -* 调度器用于判断接下来运行哪个进程. - 内核支持不同的调度策略(完全公平调度, 实时调度, 在无事可做的时候调度空闲进程,即0号进程也叫swapper进程,idle进程), 调度类使得能够以模块化的方法实现这些侧露额, 即一个类的代码不需要与其他类的代码交互 - 当调度器被调用时, 他会查询调度器类, 得知接下来运行哪个进程 - -* 在选中将要运行的进程之后, 必须执行底层的任务切换. - 这需要与CPU的紧密交互. 每个进程刚好属于某一调度类, 各个调度类负责管理所属的进程. 通用调度器自身不涉及进程管理, 其工作都委托给调度器类. - - -#优先级的内核表示 +##优先级的内核表示 ------- ->在用户空间通过nice命令设置进程的静态优先级, 这在内部会调用nice系统调用, 进程的nice值在-20~+19之间. 值越低优先级越高. -> ->setpriority系统调用也可以用来设置进程的优先级. 它不仅能够修改单个线程的优先级, 还能修改进程组中所有进程的优先级, 或者通过制定UID来修改特定用户的所有进程的优先级 - 内核使用一些简单的数值范围0~139表示内部优先级, 数值越低, 优先级越高。 从0~99的范围专供实时进程使用, nice的值[-20,19]则映射到范围100~139 ->linux2.6内核将任务优先级进行了一个划分, 实时优先级范围是0到MAX_RT_PRIO-1(即99),而普通进程的静态优先级范围是从MAX_RT_PRIO到MAX_PRIO-1(即100到139)。 +>实时优先级范围是0到MAX_RT_PRIO-1(即99),而普通进程的静态优先级范围是从MAX_RT_PRIO到MAX_PRIO-1(即100到139)。 | 优先级范围 | 描述 | | ------------- |:-------------:| @@ -145,78 +104,8 @@ linux把进程区分为实时进程和非实时进程, 其中非实时进程进 | 100——139 | 非实时进程 | -优先级数值通过宏来定义, 如下所示, 其中MAX_RT_PRIO指定了实时进程的最大优先级, 而MAX_PRIO则是普通进程的最大优先级数值 -```c -/* http://lxr.free-electrons.com/source/include/linux/sched/prio.h?v=4.6#L21 */ -#define MAX_USER_RT_PRIO 100 -#define MAX_RT_PRIO MAX_USER_RT_PRIO - -/* http://lxr.free-electrons.com/source/include/linux/sched/prio.h?v=4.6#L24 */ -#define MAX_PRIO (MAX_RT_PRIO + 40) -#define DEFAULT_PRIO (MAX_RT_PRIO + 20) -``` - -此外新版本的内核还引入了EDF实时调度算法, 它的优先级比RT进程和NORMAL/BATCH进程的优先级都要高, 因此内核将MAX_DL_PRIO设置为0, 可以参见内核文件[include/linux/sched/deadline.h](http://lxr.free-electrons.com/source/include/linux/sched/deadline.h) - -```c -#define MAX_DL_PRIO 0 -```` - -| 宏 | 值 | 描述 | -| ------------- |:-------------:| -| MAX_RT_PRIO, MAX_USER_RT_PRIO | 100 | 实时进程的最大优先级 | -| MAX_PRIO | 140 | 普通进程的最大优先级 | -| DEFAULT_PRIO | 120 | 进程的默认优先级, 对应于nice=0 | -| MAX_DL_PRIO | 0 | 使用EDF最早截止时间优先调度算法的实时进程最大的优先级 | - -而内核提供了一组宏将优先级在各种不同的表示形之间转移 - -```c -// http://lxr.free-electrons.com/source/include/linux/sched/prio.h?v=4.6#L27 -/* - * Convert user-nice values [ -20 ... 0 ... 19 ] - * to static priority [ MAX_RT_PRIO..MAX_PRIO-1 ], - * and back. - */ -#define NICE_TO_PRIO(nice) ((nice) + DEFAULT_PRIO) -#define PRIO_TO_NICE(prio) ((prio) - DEFAULT_PRIO) - -/* - * 'User priority' is the nice value converted to something we - * can work with better when scaling various scheduler parameters, - * it's a [ 0 ... 39 ] range. - */ -#define USER_PRIO(p) ((p)-MAX_RT_PRIO) -#define TASK_USER_PRIO(p) USER_PRIO((p)->static_prio) -#define MAX_USER_PRIO (USER_PRIO(MAX_PRIO)) -``` - -此外也提供了一些EDF调度算法的函数, 如下所示 - -```c -/* 判断一个优先级数值是不是EDF调度算法的优先级 */ -static inline int dl_prio(int prio) -{ - /* 数组小于MAX_DL_PRIO的优先级就是EDF算法的优先级 */ - if (unlikely(prio < MAX_DL_PRIO)) - return 1; - return 0; -} - -static inline int dl_task(struct task_struct *p) -{ - return dl_prio(p->prio); -} - -static inline bool dl_time_before(u64 a, u64 b) -{ - return (s64)(a - b) < 0; -} -``` - - -#进程的优先级表示 +##进程的优先级表示 ------- @@ -234,18 +123,14 @@ struct task_struct } ``` - **动态优先级 静态优先级 实时优先级** 其中task_struct采用了三个成员表示进程的优先级:prio和normal_prio表示动态优先级, static_prio表示进程的静态优先级. ->为什么表示动态优先级需要两个值prio和normal_prio -> ->调度器会考虑的优先级则保存在prio. 由于在某些情况下内核需要暂时提高进程的优先级, 因此需要用prio表示. 由于这些改变不是持久的, 因此静态优先级static_prio和普通优先级normal_prio不受影响. - 此外还用了一个字段rt_priority保存了实时进程的优先级 + | 字段 | 描述 | | ------------- |:-------------:| | static_prio | 用于保存静态优先级, 是进程启动时分配的优先级, ,可以通过nice和sched_setscheduler系统调用来进行修改, 否则在进程运行期间会一直保持恒定 | @@ -257,292 +142,6 @@ struct task_struct 实时进程的优先级用实时优先级rt_priority来表示 -#进程优先级的计算 -------- - -前面说了task_struct中的几个优先级的字段 - -| 静态优先级 | 普通优先级 | 动态优先级 | 实时优先级 | -| ------------- |:-------------:|:-------------:|:-------------:| -| static_prio | normal_prio | prio | rt_priority | - -但是这些优先级是如何关联的呢, 动态优先级prio又是如何计算的呢? - - - -##normal_prio设置普通优先级normal_prio -------- - -静态优先级static_prio(普通进程)和实时优先级rt_priority(实时进程)是计算的起点 - -因此他们也是进程创建的时候设定好的, 我们通过nice修改的就是静态优先级static_prio - -首先通过静态优先级static_prio计算出普通优先级normal_prio, 改工作可以由nromal_prio来完成, 该函数定义在[kernel/sched/core.c#L861](http://lxr.free-electrons.com/source/kernel/sched/core.c#L861) - -```c -/* - * __normal_prio - return the priority that is based on the static prio - * 普通进程(非实时进程)的普通优先级normal_prio就是静态优先级static_prio - */ -static inline int __normal_prio(struct task_struct *p) -{ - return p->static_prio; -} - -/* - * Calculate the expected normal priority: i.e. priority - * without taking RT-inheritance into account. Might be - * boosted by interactivity modifiers. Changes upon fork, - * setprio syscalls, and whenever the interactivity - * estimator recalculates. - */ -static inline int normal_prio(struct task_struct *p) -{ - int prio; - - if (task_has_dl_policy(p)) /* EDF调度的实时进程 */ - prio = MAX_DL_PRIO-1; - else if (task_has_rt_policy(p)) /* 普通实时进程的优先级 */ - prio = MAX_RT_PRIO-1 - p->rt_priority; - else /* 普通进程的优先级 */ - prio = __normal_prio(p); - return prio; -} -``` - -| 进程类型 | 调度器 | 普通优先级normal_prio | -| ------------- |:-------------:|:-------------:| -| EDF实时进程 | EDF | MAX_DL_PRIO-1 = -1 | -| 普通实时进程 | RT | MAX_RT_PRIO-1 - p->rt_priority = 99 - rt_priority | -| 普通进程 | CFS | __normal_prio(p) = static_prio | - - -普通优先级normal_prio需要根据普通进程和实时进程进行不同的计算, 其中__normal_prio适用于普通进程, 直接将普通优先级normal_prio设置为静态优先级static_prio. 而实时进程的普通优先级计算依据其实时优先级rt_priority. - -###辅助函数task_has_dl_policy和task_has_rt_policy -------- - -定义在[kernel/sched/sched.h#L117](http://lxr.free-electrons.com/source/kernel/sched/sched.h?v=4.6#L117) 中 - -其本质其实就是传入task->policy调度策略字段看其值等于SCHED_NORMAL, SCHED_BATCH, SCHED_IDLE, SCHED_FIFO, SCHED_RR, SCHED_DEADLINE中的哪个, 从而确定其所属的调度类, 进一步就确定了其进程类型 -```c -static inline int idle_policy(int policy) -{ - return policy == SCHED_IDLE; -} -static inline int fair_policy(int policy) -{ - return policy == SCHED_NORMAL || policy == SCHED_BATCH; -} - -static inline int rt_policy(int policy) -{ - return policy == SCHED_FIFO || policy == SCHED_RR; -} - -static inline int dl_policy(int policy) -{ - return policy == SCHED_DEADLINE; -} -static inline bool valid_policy(int policy) -{ - return idle_policy(policy) || fair_policy(policy) || - rt_policy(policy) || dl_policy(policy); -} - -static inline int task_has_rt_policy(struct task_struct *p) -{ - return rt_policy(p->policy); -} - -static inline int task_has_dl_policy(struct task_struct *p) -{ - return dl_policy(p->policy); -} -``` - - -###关于rt_priority数值越大, 实时进程优先级越高的问题 --------- - -我们前面提到了数值越小, 优先级越高, 但是此处我们会发现rt_priority的值越大, 其普通优先级越小, 从而优先级越高. - -因此网上出现了一种说法, 优先级越高?这又是怎么回事?难道有一种说法错了吗? - -实际的原因是这样的,对于一个实时进程,他有两个参数来表明优先级——prio 和 rt_priority, - -prio才是调度所用的最终优先级数值,这个值越小,优先级越高; - -而rt_priority 被称作实时进程优先级,他要经过转化——prio=MAX_RT_PRIO - 1- p->rt_priority; - -MAX_RT_PRIO = 100, ;这样意味着rt_priority值越大,优先级越高; - -而内核提供的修改优先级的函数,是修改rt_priority的值,所以越大,优先级越高。 - - 所以用户在使用实时进程或线程,在修改优先级时,就会有“优先级值越大,优先级越高的说法”,也是对的。 - -###为什么需要__normal_prio函数 -------- - -我们肯定会奇怪, 为什么增加了一个__normal_prio函数做了这么简单的工作, 这个其实是有历史原因的: 在早期的$O(1)$调度器中, 普通优先级的计算涉及相当多技巧性地工作, 必须检测交互式进程并提高其优先级, 而必须"惩罚"非交互进程, 以便是得系统获得更好的交互体验. 这需要很多启发式的计算, 他们可能完成的很好, 也可能不工作 - -##effective_prio设置动态优先级prio -------- - -可以通过函数effective_prio用静态优先级static_prio计算动态优先级, 即· - -```c -p->prio = effective_prio(p); -``` - -该函数定义在[kernel/sched/core.c, line 861](http://lxr.free-electrons.com/source/kernel/sched/core.c#L861) - -```c -/* - * Calculate the current priority, i.e. the priority - * taken into account by the scheduler. This value might - * be boosted by RT tasks, or might be boosted by - * interactivity modifiers. Will be RT if the task got - * RT-boosted. If not then it returns p->normal_prio. - */ -static int effective_prio(struct task_struct *p) -{ - p->normal_prio = normal_prio(p); - /* - * If we are RT tasks or we were boosted to RT priority, - * keep the priority unchanged. Otherwise, update priority - * to the normal priority: - */ - if (!rt_prio(p->prio)) - return p->normal_prio; - return p->prio; -} -``` - -我们会发现函数首先effective_prio设置了普通优先级, 显然我们用effective_prio同时设置了两个优先级(普通优先级normal_prio和动态优先级prio) - -因此计算动态优先级的流程如下 - -* 设置进程的普通优先级(实时进程99-rt_priority, 普通进程为static_priority) - -* 计算进程的动态优先级(实时进程则维持动态优先级的prio不变, 普通进程的动态优先级即为其普通优先级) - -最后, 我们综述一下在针对不同类型进程的计算结果 - -| 进程类型 | 实时优先级rt_priority | 静态优先级static_prio | 普通优先级normal_prio | 动态优先级prio | -| ------- |:-------:|:-------:|:-------:| -| EDF调度的实时进程 | rt_priority | 不使用 | MAX_DL_PRIO-1 | 维持原prio不变 | -| RT算法调度的实时进程 | rt_priority | 不使用 | MAX_RT_PRIO-1-rt_priority | 维持原prio不变 | -| 普通进程 | 不使用 | static_prio | static_prio | static_prio | -| 优先级提高的普通进程 | 不使用 | static_prio(改变) | static_prio | 维持原prio不变 | - -###为什么effective_prio使用优先级数值检测实时进程 -------- - -t_prio会检测普通优先级是否在实时范围内, 即是否小于MAX_RT_PRIO.参见[include/linux/sched/rt.h#L6](http://lxr.free-electrons.com/source/include/linux/sched/rt.h#L6) - -```c -static inline int rt_prio(int prio) -{ - if (unlikely(prio < MAX_RT_PRIO)) - return 1; - return 0; -} -``` -而前面我们在normal_prio的时候, 则通过task_has_rt_policy来判断其policy属性来确定 -``` -policy == SCHED_FIFO || policy == SCHED_RR; -``` -那么为什么effective_prio重检测实时进程是rt_prio基于优先级数值, 而非task_has_rt_policy或者rt_policy? - -对于临时提高至实时优先级的非实时进程来说, 这个是必要的, 这种情况可能发生在是哦那个实时互斥量(RT-Mutex)时. - - -##设置prio的时机 -------- - - -* 在新进程用wake_up_new_task唤醒时, 或者使用nice系统调用改变其静态优先级时, 则会通过effective_prio的方法设置p->prio - ->wake_up_new_task(), 计算此进程的优先级和其他调度参数,将新的进程加入到进程调度队列并设此进程为可被调度的,以后这个进程可以被进程调度模块调度执行。 - -* 进程创建时copy_process通过调用sched_fork来初始化和设置调度器的过程中会设置子进程的优先级 - -##nice系统调用的实现 -------- - -nice系统调用是的内核实现是sys_nice, 其定义在[kernel/sched/core.c#L7498](http://lxr.free-electrons.com/source/kernel/sched/core.c?v=4.6#L7498), - -它在通过一系列检测后, 通过[set_user_nice函数](http://lxr.free-electrons.com/source/kernel/sched/core.c?v=4.6#L3497), 其定义在[kernel/sched/core.c#L3497](http://lxr.free-electrons.com/source/kernel/sched/core.c?v=4.6#L3497) - -关于其具体实现我们会在另外一篇博客里面详细讲 - - -##fork时优先级的继承 -------- - - -在进程分叉处子进程时, 子进程的静态优先级继承自父进程. 子进程的动态优先级p->prio则被设置为父进程的普通优先级, 这确保了实时互斥量引起的优先级提高不会传递到子进程. - -可以参照sched_fork函数, 在进程复制的过程中copy_process通过调用sched_fork来设置子进程优先级, 参见[sched_fork函数](http://lxr.free-electrons.com/source/kernel/sched/core.c#L2236) - - -```c -/* - * fork()/clone()-time setup: - */ -int sched_fork(unsigned long clone_flags, struct task_struct *p) -{ - /* ...... */ - /* - * Make sure we do not leak PI boosting priority to the child. - * 子进程的动态优先级被设置为父进程普通优先级 - */ - p->prio = current->normal_prio; - - /* - * Revert to default priority/policy on fork if requested. - * sched_reset_on_fork标识用于判断是否恢复默认的优先级或调度策略 - - */ - if (unlikely(p->sched_reset_on_fork)) /* 如果要恢复默认的调度策略, 即SCHED_NORMAL */ - { - /* 首先是设置静态优先级static_prio - * 由于要恢复默认的调度策略 - * 对于父进程是实时进程的情况, 静态优先级就设置为DEFAULT_PRIO - * - * 对于父进程是非实时进程的情况, 要保证子进程优先级不小于DEFAULT_PRIO - * 父进程nice < 0即static_prio < 的重新设置为DEFAULT_PRIO的重新设置为DEFAULT_PRIO - * 父进程nice > 0的时候, 则什么也没做 - * */ - if (task_has_dl_policy(p) || task_has_rt_policy(p)) - { - p->policy = SCHED_NORMAL; /* 普通进程调度策略 */ - p->static_prio = NICE_TO_PRIO(0); /* 静态优先级为nice = 0 即DEFAULT_PRIO*/ - p->rt_priority = 0; /* 实时优先级为0 */ - } - else if (PRIO_TO_NICE(p->static_prio) < 0) /* */ - p->static_prio = NICE_TO_PRIO(0); /* */ - - /* 接着就通过__normal_prio设置其普通优先级和动态优先级 - * 这里做了一个优化, 因为用sched_reset_on_fork标识设置恢复默认调度策略后 - * 创建的子进程是是SCHED_NORMAL的非实时进程 - * 因此就不需要绕一大圈用effective_prio设置normal_prio和prio了 - * 直接用__normal_prio设置就可 */ - p->prio = p->normal_prio = __normal_prio(p); /* 设置*/ - - /* 设置负荷权重 */ - set_load_weight(p); - - /* - * We don't need the reset flag anymore after the fork. It has - * fulfilled its duty: - */ - p->sched_reset_on_fork = 0; - } - /* ...... */ -} -``` http://blog.sina.com.cn/s/blog_9ca3f6e70102wkwp.html http://lxr.free-electrons.com/source/kernel/sched/core.c#L3527 diff --git a/study/kernel/01-process/05-schedule/06-load_weight/images/level.jpg b/study/kernel/01-process/05-schedule/06-load_weight/images/level.jpg new file mode 100644 index 0000000..6fccf32 Binary files /dev/null and b/study/kernel/01-process/05-schedule/06-load_weight/images/level.jpg differ diff --git a/study/kernel/01-process/05-schedule/06-load_weight/images/priority.jpg b/study/kernel/01-process/05-schedule/06-load_weight/images/priority.jpg new file mode 100644 index 0000000..057e03c Binary files /dev/null and b/study/kernel/01-process/05-schedule/06-load_weight/images/priority.jpg differ