Linux进程优先级的处理--Linux进程的管理与调度(二十二)

日期 内核版本号 架构 作者 GitHub CSDN
2016-06-14 Linux-4.6 X86 & arm gatieme LinuxDeviceDrivers Linux进程管理与调度

1 前景回想


1.1 进程调度


内存中保存了对每一个进程的唯一描写叙述, 并通过若干结构与其它进程连接起来.

调度器面对的情形就是这样, 其任务是在程序之间共享CPU时间, 创造并行执行的错觉, 该任务分为两个不同的部分, 当中一个涉及调度策略, 另外一个涉及上下文切换.

内核必须提供一种方法, 在各个进程之间尽可能公平地共享CPU时间, 而同一时候又要考虑不同的任务优先级.

调度器的一个重要目标是有效地分配 CPU 时间片。同一时候提供非常好的用户体验。调度器还须要面对一些互相冲突的目标,比如既要为关键实时任务最小化响应时间, 又要最大限度地提高 CPU 的总体利用率.

调度器的一般原理是, 按所需分配的计算能力, 向系统中每一个进程提供最大的公正性, 或者从另外一个角度上说, 他试图确保没有进程被亏待.

1.2 进程的分类


linux把进程区分为实时进程和非实时进程, 当中非实时进程进一步划分为交互式进程和批处理进程

类型 描写叙述 演示样例
交互式进程(interactive process) 此类进程常常与用户进行交互, 因此须要花费非常多时间等待键盘和鼠标操作. 当接受了用户的输入后, 进程必须非常快被唤醒, 否则用户会感觉系统反应迟钝 shell, 文本编辑程序和图形应用程序
批处理进程(batch process) 此类进程不必与用户交互, 因此常常在后台执行. 因为这种进程不必非常快相应, 因此常受到调度程序的怠慢 程序语言的编译程序, 数据库搜索引擎以及科学计算
实时进程(real-time process) 这些进程由非常强的调度须要, 这种进程绝不会被低优先级的进程堵塞. 而且他们的响应时间要尽可能的短 视频音频应用程序, 机器人控制程序以及从物理传感器上收集数据的程序

在linux中, 调度算法能够明白的确认全部实时进程的身份, 可是没办法区分交互式程序和批处理程序, linux2.6的调度程序实现了基于进程过去行为的启示式算法, 以确定进程应该被当做交互式进程还是批处理进程. 当然与批处理进程相比, 调度程序有偏爱交互式进程的倾向

1.3 不同进程採用不同的调度策略


根据进程的不同分类Linux採用不同的调度策略.

对于实时进程,採用FIFO, Round Robin或者Earliest Deadline First (EDF)最早截止期限优先调度算法|的调度策略.

对于普通进程,则须要区分交互式和批处理式的不同。传统Linux调度器提高交互式应用的优先级。使得它们能更快地被调度。而CFS和RSDL等新的调度器的核心思想是”全然公平”。

这个设计理念不仅大大简化了调度器的代码复杂度。还对各种调度需求的提供了更完美的支持.

注意Linux通过将进程和线程调度视为一个,同一时候包括二者。

进程能够看做是单个线程,可是进程能够包括共享一定资源(代码和/或数据)的多个线程。

因此进程调度也包括了线程调度的功能.

眼下非实时进程的调度策略比較简单, 因为实时进程值仅仅要求尽可能快的被响应, 基于优先级, 每一个进程根据它重要程度的不同被赋予不同的优先级,调度器在每次调度时, 总选择优先级最高的进程開始执行. 低优先级不可能抢占高优先级, 因此FIFO或者Round Robin的调度策略就可以满足实时进程调度的需求.

可是普通进程的调度策略就比較麻烦了, 因为普通进程不能简单的仅仅看优先级, 必须公平的占有CPU, 否则非常easy出现进程饥饿, 这种情况下用户会感觉操作系统非常卡, 响应总是非常慢。因此在linux调度器的发展历程中经过了多次重大变动, linux总是希望寻找一个最接近于完美的调度策略来公平高速的调度进程.

1.4 linux调度器的演变


一開始的调度器是复杂度为O(n)的始调度算法(实际上每次会遍历全部任务。所以复杂度为O(n)), 这个算法的缺点是当内核中有非常多任务时,调度器本身就会耗费不少时间。所以,从linux2.5開始引入赫赫有名的O(1)调度器

然而。linux是集全球非常多程序猿的聪明才智而发展起来的超级内核。没有最好,仅仅有更好,在O(1)调度器风光了没几天就又被还有一个更优秀的调度器代替了。它就是CFS调度器Completely Fair Scheduler. 这个也是在2.6内核中引入的。详细为2.6.23。即从此版本号開始,内核使用CFS作为它的默认调度器,O(1)调度器被抛弃了, 事实上CFS的发展也是经历了非常多阶段,最早期的楼梯算法(SD), 后来逐步对SD算法进行改进出RSDL(Rotating Staircase Deadline Scheduler), 这个算法已经是”全然公平”的雏形了, 直至CFS是终于被内核採纳的调度器, 它从RSDL/SD中吸取了全然公平的思想,不再跟踪进程的睡眠时间,也不再企图区分交互式进程。

它将全部的进程都统一对待。这就是公平的含义。CFS的算法和实现都相当简单,众多的測试表明其性能也非常优越

字段 版本号
O(n)的始调度算法 linux-0.11~2.4
O(1)调度器 linux-2.5
CFS调度器 linux-2.6~至今

1.5 Linux的调度器组成


2个调度器

能够用两种方法来激活调度

  • 一种是直接的, 比方进程打算睡眠或出于其它原因放弃CPU

  • 还有一种是通过周期性的机制, 以固定的频率执行, 不时的检測是否有必要

因此当前linux的调度程序由两个调度器组成:主调度器周期性调度器(两者又统称为通用调度器(generic scheduler)核心调度器(core scheduler))

而且每一个调度器包括两个内容:调度框架(事实上质就是两个函数框架)及调度器类

6种调度策略

linux内核眼下实现了6中调度策略(即调度算法), 用于对不同类型的进程进行调度, 或者支持某些特殊的功能

  • SCHED_NORMAL和SCHED_BATCH调度普通的非实时进程

  • SCHED_FIFO和SCHED_RR和SCHED_DEADLINE则採用不同的调度策略调度实时进程

  • SCHED_IDLE则在系统空暇时调用idle进程.

5个调度器类

而根据其调度策略的不同实现了5个调度器类, 一个调度器类能够用一种种或者多种调度策略调度某一类进程, 也能够用于特殊情况或者调度特殊功能的进程.

其所属进程的优先级顺序为

stop_sched_class -> dl_sched_class -> rt_sched_class -> fair_sched_class -> idle_sched_class

3个调度实体

调度器不限于调度进程, 还能够调度更大的实体, 比方实现组调度.

这种一般性要求调度器不直接操作进程, 而是处理可调度实体, 因此须要一个通用的数据结构描写叙述这个调度实体,即seched_entity结构, 事实上际上就代表了一个调度对象。能够为一个进程,也能够为一个进程组.

linux中针对当前可调度的实时和非实时进程, 定义了类型为seched_entity的3个调度实体

  • sched_dl_entity 採用EDF算法调度的实时调度实体

  • sched_rt_entity 採用Roound-Robin或者FIFO算法调度的实时调度实体 rt_sched_class

  • sched_entity 採用CFS算法调度的普通非实时进程的调度实体

调度器总体框架

每一个进程都属于某个调度器类(由字段task_struct->sched_class标识), 由调度器类採用进程相应的调度策略调度(由task_struct->policy )进行调度, task_struct也存储了其相应的调度实体标识

linux实现了6种调度策略, 根据其调度策略的不同实现了5个调度器类, 一个调度器类能够用一种或者多种调度策略调度某一类进程, 也能够用于特殊情况或者调度特殊功能的进程.

调度器类 调度策略 调度策略相应的调度算法 调度实体 调度实体相应的调度对象
stop_sched_class 特殊情况, 发生在cpu_stop_cpu_callback 进行cpu之间任务迁移migration或者HOTPLUG_CPU的情况下关闭任务
dl_sched_class SCHED_DEADLINE Earliest-Deadline-First最早截至时间有限算法 sched_dl_entity 採用DEF最早截至时间有限算法调度实时进程
rt_sched_class SCHED_RR

SCHED_FIFO
Roound-Robin时间片轮转算法

FIFO先进先出算法
sched_rt_entity 採用Roound-Robin或者FIFO算法调度的实时调度实体
fair_sched_class SCHED_NORMAL

SCHED_BATCH
CFS全然公平懂调度算法 sched_entity 採用CFS算法普通非实时进程
idle_sched_class SCHED_IDLE 特殊进程, 用于cpu空暇时调度空暇进程idle

2 linux优先级的表示


2.1 优先级的内核表示


linux优先级概述

在用户空间通过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——99 实时进程
100——139 非实时进程


内核的优先级标度

内核的优先级表示

内核表示优先级的全部信息基本都放在include/linux/sched/prio.h中, 当中定义了一些表示优先级的宏和函数.

优先级数值通过宏来定义, 例如以下所看到的,

当中MAX_NICE和MIN_NICE定义了nice的最大最小值

而MAX_RT_PRIO指定了实时进程的最大优先级, 而MAX_PRIO则是普通进程的最大优先级数值

/*  http://lxr.free-electrons.com/source/include/linux/sched/prio.h?v=4.6#L4 */
#define MAX_NICE        19
#define MIN_NICE        -20
#define NICE_WIDTH      (MAX_NICE - MIN_NICE + 1)

/* 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)
描写叙述
MIN_NICE -20 相应于优先级100, 能够使用NICE_TO_PRIO和PRIO_TO_NICE转换
MAX_NICE 19 相应于优先级139, 能够使用NICE_TO_PRIO和PRIO_TO_NICE转换
NICE_WIDTH 40 nice值得范围宽度, 即[-20, 19]共40个数字的宽度
MAX_RT_PRIO, MAX_USER_RT_PRIO 100 实时进程的最大优先级
MAX_PRIO 140 普通进程的最大优先级
DEFAULT_PRIO 120 进程的默认优先级, 相应于nice=0
MAX_DL_PRIO 0 使用EDF最早截止时间优先调度算法的实时进程最大的优先级

而内核提供了一组宏将优先级在各种不同的表示形之间转移

//  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))

还有一些nice值和rlimit值之间相互转换的函数nice_to_rlimit和rlimit_to_nice, 这在nice系统调用进行检查的时候非常实用, 他们定义在include/linux/sched/prio.h, L47中, 例如以下所看到的

/*
 * Convert nice value [19,-20] to rlimit style value [1,40].
 */
static inline long nice_to_rlimit(long nice)
{
    return (MAX_NICE - nice + 1);
}

/*
 * Convert rlimit style value [1,40] to nice value [-20, 19].
 */
static inline long rlimit_to_nice(long prio)
{
    return (MAX_NICE - prio + 1);
}

DEF最早截至时间优先实时调度算法的优先级描写叙述

此外新版本号的内核还引入了EDF实时调度算法, 它的优先级比RT进程和NORMAL/BATCH进程的优先级都要高, 关于EDF的优先级的设置信息都早内核头文件include/linux/sched/deadline.h

因此内核将MAX_DL_PRIO设置为0, 能够參见内核文件include/linux/sched/deadline.h

#define MAX_DL_PRIO             0

此外也提供了一些EDF优先级处理所需的函数, 例如以下所看到的, 能够參见内核文件include/linux/sched/deadline.h

static inline int dl_prio(int prio)
{
    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;
}

2.2 进程的优先级表示


struct task_struct
{
    /* 进程优先级
     * prio: 动态优先级,范围为100~139,与静态优先级和补偿(bonus)有关
     * static_prio: 静态优先级。static_prio = 100 + nice + 20 (nice值为-20~19,所以static_prio值为100~139)
     * normal_prio: 没有受优先级继承影响的常规优先级,详细见normal_prio函数,跟属于什么类型的进程有关
     */
    int prio, static_prio, normal_prio;
    /* 实时进程优先级 */
    unsigned int rt_priority;
}

动态优先级 静态优先级 实时优先级

当中task_struct採用了三个成员表示进程的优先级:prio和normal_prio表示动态优先级, static_prio表示进程的静态优先级.

为什么表示动态优先级须要两个值prio和normal_prio

调度器会考虑的优先级则保存在prio. 因为在某些情况下内核须要临时提高进程的优先级, 因此须要用prio表示. 因为这些改变不是持久的, 因此静态优先级static_prio和普通优先级normal_prio不受影响.

此外还用了一个字段rt_priority保存了实时进程的优先级

字段 描写叙述
static_prio 用于保存静态优先级, 是进程启动时分配的优先级, 。能够通过nice和sched_setscheduler系统调用来进行改动, 否则在进程执行期间会一直保持恒定
rt_priority 用于保存实时优先级
normal_prio 表示基于进程的静态优先级static_prio和调度策略计算出的优先级. 因此即使普通进程和实时进程具有同样的静态优先级, 其普通优先级也是不同的, 进程分叉(fork)时, 子进程会继承父进程的普通优先级
prio 保存进程的动态优先级

实时进程的优先级用实时优先级rt_priority来表示

3 进程优先级的计算


前面说了task_struct中的几个优先级的字段

静态优先级 实时优先级 普通优先级 动态优先级
static_prio rt_priority normal_prio prio

可是这些优先级是怎样关联的呢, 动态优先级prio又是怎样计算的呢?

3.1 normal_prio函数设置普通优先级normal_prio



静态优先级static_prio(普通进程)和实时优先级rt_priority(实时进程)是计算的起点

因此他们也是进程创建的时候设定好的, 我们通过nice改动的就是普通进程的静态优先级static_prio

首先通过静态优先级static_prio计算出普通优先级normal_prio, 该工作能够由nromal_prio来完毕, 该函数定义在kernel/sched/core.c#L861

/*
 * __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.

3.1.1 辅助函数task_has_dl_policy和task_has_rt_policy


定义在kernel/sched/sched.h#L117

其本质事实上就是传入task->policy调度策略字段看其值等于SCHED_NORMAL, SCHED_BATCH, SCHED_IDLE, SCHED_FIFO, SCHED_RR, SCHED_DEADLINE中的哪个, 从而确定其所属的调度类, 进一步就确定了其进程类型

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);
}

3.1.2 关于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的值,所以越大。优先级越高。

所以用户在使用实时进程或线程。在改动优先级时,就会有“优先级值越大,优先级越高的说法”,也是对的。

3.1.3 为什么须要__normal_prio函数


我们肯定会奇怪, 为什么添加了一个__normal_prio函数做了这么简单的工作, 这个事实上是有历史原因的: 在早期的O(1)调度器中, 普通优先级的计算涉及相当多技巧性地工作, 必须检測交互式进程并提高其优先级, 而必须”惩处”非交互进程, 以便是得系统获得更好的交互体验. 这须要非常多启示式的计算, 他们可能完毕的非常好, 也可能不工作

3.2 effective_prio函数设置动态优先级prio



能够通过函数effective_prio用静态优先级static_prio计算动态优先级prio, 即·

p->prio = effective_prio(p);

该函数定义在kernel/sched/core.c, line 861

/*
 * 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不变

3.2.1 为什么effective_prio使用优先级数值检測实时进程


t_prio会检測普通优先级是否在实时范围内, 即是否小于MAX_RT_PRIO.參见include/linux/sched/rt.h#L6

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)时.

3.3 设置prio的时机


  • 在新进程用wake_up_new_task唤醒时, 或者使用nice系统调用改变其静态优先级时, 则会通过effective_prio的方法设置p->prio

wake_up_new_task(), 计算此进程的优先级和其它调度參数。将新的进程添加到进程调度队列并设此进程为可被调度的。以后这个进程能够被进程调度模块调度执行。

  • 进程创建时copy_process通过调用sched_fork来初始化和设置调度器的过程中会设置子进程的优先级

3.4 nice系统调用的实现


nice系统调用是的内核实现是sys_nice, 其定义在kernel/sched/core.c#L7498

它在通过一系列检測后, 通过set_user_nice函数。 其定义在kernel/sched/core.c#L3497

关于其详细实现我们会在另外一篇博客里面详细讲

3.5 fork时优先级的继承


在进程分叉处子进程时, 子进程的静态优先级继承自父进程. 子进程的动态优先级p->prio则被设置为父进程的普通优先级, 这确保了实时相互排斥量引起的优先级提高不会传递到子进程.

能够參照sched_fork函数, 在进程复制的过程中copy_process通过调用sched_fork来设置子进程优先级, 參见sched_fork函数

/*
 * 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;
    }
    /*  ......  */
}

4 总结



task_struct採用了四个成员表示进程的优先级:prio和normal_prio表示动态优先级, static_prio表示进程的静态优先级. 同一时候还用了rt_priority表示实时进程的优先级

字段 描写叙述
static_prio 用于保存静态优先级, 是进程启动时分配的优先级, ,能够通过nice和sched_setscheduler系统调用来进行改动, 否则在进程执行期间会一直保持恒定
prio 进程的动态优先级, 这个有显示才是调度器重点考虑的进程优先级
normal_prio 普通进程的静态优先级static_prio和调度策略计算出的优先级. 因此即使普通进程和实时进程具有同样的静态优先级, 其普通优先级也是不同的, 进程分叉(fork)时, 子进程会继承父进程的普通优先级, 能够通过normal_prio来计算(非实时进程用static_prIo计算, 实时进程用rt_priority计算)
rt_priority 实时进程的静态优先级

调度器会考虑的优先级则保存在prio. 因为在某些情况下内核须要临时提高进程的优先级, 因此须要用prio表示. 因为这些改变不是持久的, 因此静态优先级static_prio和普通优先级normal_prio不受影响.
此外还用了一个字段rt_priority保存了实时进程的优先级静态优先级static_prio(普通进程)和实时优先级rt_priority(实时进程)是计算的起点, 通过他们计算进程的普通优先级normal_prio和动态优先级prio.


内核通过normal_prIo函数计算普通优先级normal_prio
通过effective_prio函数计算动态优先级prio

參考

进程调度之sys_nice()系统调用

linux调度器源代码研究 - 概述(一)

深入 Linux 的进程优先级

posted @ 2018-03-30 09:21  zhchoutai  阅读(1069)  评论(0编辑  收藏  举报