Day8-进程调度器

调度器

1.调度:就是按照某种调度的算法设计,从进程的就绪队列当中选取进程分配CPU,主要是协调对CPU等相关资源使用. 进程调度目的:最大限度利用CPU时间。

调度器

Linux内核中用来安排调度进程执行的模块称为调度器(Scheduler), 他可以切换进程状态,比如:执行、可中断睡眠、不可中断睡眠、退出、暂停等。

调度器相当于CPU中央处理器的管理员,主要负责完成两件事情:

  • 选择某些就绪进程执行
  • 打断某些执行的进程让他们变为就绪状态

2、如果调度器支持就绪状态切换至执行状态,同时支持执行状态切换到就绪状态,称该调度器为抢占式调度器。

3. 调度类sched_class结构体分析

// 调度类sched_class结构体如下:
struct sched_class {
    /* 操作系统当中有多个调度类,按照调度优先级排成一个链表 */
    const struct sched_class *next;

#ifdef CONFIG_UCLAMP_TASK
    int uclamp_enabled;
#endif
    // 将进程加入到执行队列当中,即将调度实体(进程)存放到红黑树当中,并对nr_running变量自动加1
    void (*enqueue_task) (struct rq *rq, struct task_struct *p, int flags);
    // 从执行队列当中删除进程,并对nr_running变量自动减1
    void (*dequeue_task) (struct rq *rq, struct task_struct *p, int flags);
    // 放弃CPU执行权限,实际次函数执行方式为先出队,后入队,在这种情况下他直接把调度实体存放在红黑树最右端
    void (*yield_task)   (struct rq *rq);
    bool (*yield_to_task)(struct rq *rq, struct task_struct *p, bool preempt);
    // 专门检查当前进程是否可被新进程抢占
    void (*check_preempt_curr)(struct rq *rq, struct task_struct *p, int flags);
    // 选择下一个要运行的进程
    struct task_struct *(*pick_next_task)(struct rq *rq);
    // 将进程放回到运行队列当中
    void (*put_prev_task)(struct rq *rq, struct task_struct *p);
    void (*set_next_task)(struct rq *rq, struct task_struct *p, bool first);

#ifdef CONFIG_SMP
    int (*balance)(struct rq *rq, struct task_struct *prev, struct rq_flags *rf);
    // 位进程选择一个合适的CPU
    int  (*select_task_rq)(struct task_struct *p, int task_cpu, int sd_flag, int flags);
    // 迁移任务到另一个CPU
    void (*migrate_task_rq)(struct task_struct *p, int new_cpu);
    // 专门用于唤醒进程
    void (*task_woken)(struct rq *this_rq, struct task_struct *task);
    //修改进程在CPU的亲和力
    void (*set_cpus_allowed)(struct task_struct *p,
                 const struct cpumask *newmask);
    // 启动或者禁止运行队列
    void (*rq_online)(struct rq *rq);
    void (*rq_offline)(struct rq *rq);
#endif
    void (*task_tick)(struct rq *rq, struct task_struct *p, int queued);
    void (*task_fork)(struct task_struct *p);
    void (*task_dead)(struct task_struct *p);

    /*
     * The switched_from() call is allowed to drop rq->lock, therefore we
     * cannot assume the switched_from/switched_to pair is serliazed by
     * rq->lock. They are however serialized by p->pi_lock.
     */
    void (*switched_from)(struct rq *this_rq, struct task_struct *task);
    void (*switched_to)  (struct rq *this_rq, struct task_struct *task);
    void (*prio_changed) (struct rq *this_rq, struct task_struct *task,
                  int oldprio);

    unsigned int (*get_rr_interval)(struct rq *rq,
                    struct task_struct *task);

    void (*update_curr)(struct rq *rq);

4、调度器类可分为: stop_sched_class, dll_sched_class, rt_sched_class, fair_sched_class以及idle_sched_class

优先级从高到低依次为:停机调度类,限期调度类,实时调度类,公平调度类,空闲调度类。

停机调度类:优先级最高的调度类,停机进程是优先级最高的进程,可以抢占所有其他进程,其他进程不可能抢占停机进程。

限期调度类:最早使用的优先算法,使用红黑树把进程按照绝对截止期限从小到大排序,每次调度时选择绝对截止期限最小的进程。

实时调度类:为每个调度优先级维护一个队列。

公平调度类:使用完全公平调度算法。完全公平调度算法引入虚拟运行时间的相关概念:

虚拟运行时间=实际运行时间*nice对应的权重/进程的权重

空闲调度类: 每个CPU上有一个空闲线程,即0号线程。空闲调度类优先级别最低,仅当CPU没有其他进程可以调度的时候,才会调度空闲线程。

优先级

tasks_struct结构体中采用三个成员标识进程的优先级:prio和normal_prio标识动态优先级,static_prio表示进程的静态优先级。

内核将任务优先级划分,实时优先级的范围是0到MAX_TR_PRIO-1(即99), 而普通进程的静态优先级范围是MAX_RT_PRIO到MAX_PRIO-1(即100到139)。

5、进程优先级

Linux内核优先级源码如下:

进程分类

实时进程(Real-Time Process):优先级高、需要立即被执行的进程

普通进程(Normal Processs): 优先级低、更长执行时间的进程

进程的优先级是一个0-139的整数直接表示。数字越小,优先级越高,其中优先级0-99留给实时进程,100-139留给普通进程。

6、内核调度策略: Linux内核提供一些调度策略供用户应用程序来选择调度器。Linux内核调度策略源码分析如下:

SCHED_NORMAL: 普通进程调度策略,使task选择CFS调度器来调度运行

SCHED_FIFO: 实时进程调度策略,先进先出调度,没有时间片,没有更高优先级的状态下,只有等待主动让出CPU

SCHED_RR: 实时进程调度策略,采用时间片轮转方法,进程使用完时间片之后加入优先级对应运行队列的尾部,把CPU让给同等优先级的其他进程。

SCHED_BATCH:普通进程调度策略,批量处理,使task选择CFS调度器来调度运行

SCHED_IDEL: 普通进程调度策略,使task以最低优先级选择CFS调度器来调度运行

SCHED_DEADLINE: 限期进程调度策略,使task选择Deadline调度器来调度运行

备注:其中stop调度器和IDLE-task调度器,仅适用于内核,用户无法进行选择。

CFS调度器

CFS调度器基本原理

完全公平调度算法体现在对待每个进程都是公平的,让每个进程都运行一段相同的时间片,这就是基于时间片轮询调度算法。

CFS定义一种新调度的模型,他给cfs_rq(cfs的run queue)中的每一个进程都设置一个虚拟时钟-virtual runtie(vruntime)。如果一个进程得以执行,随着执行时间的不断增长,其vruntime也将不断增大,没有得到执行的进程vruntime将保持不变。

备注:进程描述符task_struct结构中,有几个成员与调度相关,具体成员: prio, normal_prio, static_prio, rt_priorty

7、实际运行时间

CFS是Completely Fair Scheduler简称,完全公平调度器。在实际当中,必然会有进程优先级高或者进程优先级低,CFS调度器引入权重,使用权重代表进程的优先级,各个进程按照权重比例分配CPU时间,

假设有2个进程X和Y,X权重为1024 Y的权重为2048.

X获得CPU时间的比例为:1024/(1024+2048) = 33.33%

Y获得CPU时间的比例为:2048/(1024+2048) = 66.67%

在引入权重之后分配给进程的时间计算公式如下:

实际运行时间=调度器*进程权重/所有进程权重之和

8、虚拟运行时间

虚拟运行时间=实际运行时间*NICE_0_LOAD/进程权重=(调度周期*进程权重/所有进程权重之和)*NICE_0_LOAD/进程权重=调度周期*1024/所有进程总权重

在一个调度周期里面,所有进程的虚拟运行时间是相同的,所以在进程调度时,只需要找到虚拟运行时间最小的进程调度运行即可。

9、调度子系统的各个组件模块

主调度器: 通过调度schedule()函数来完成进程的选择和切换

周期性调度器:根据频率自动调用scheduler_tick函数,作用根据进程运行时间触发调度

上下文切换: 主要做两件事情(切换地址空间,切换寄存器和栈空间)

10、CFS调度器类

CFS调度器类为fair_sched_class, CFS调度器和一些特定的函数关联起来。

enqueue_task_fair: 当任务进入可运行状态时,用此函数将调度实体存放到红黑树,完成入队操作

dequeue_task_fair: 当任务退出可运行状态时,用此函数将调度实体从红黑树移除,完成出队操作

11、CFS调度器就绪队列

调度管理是各个调度器的职责,CFS的顶级调度就队列struct cfs_rq

源码如下:

/* CFS-related fields in a runqueue */
struct cfs_rq {
    struct load_weight  load;
    unsigned long       runnable_weight;
    unsigned int        nr_running;
    unsigned int        h_nr_running;      /* SCHED_{NORMAL,BATCH,IDLE} */
    unsigned int        idle_h_nr_running; /* SCHED_IDLE */

    u64         exec_clock;
    u64         min_vruntime;
#ifndef CONFIG_64BIT
    u64         min_vruntime_copy;
#endif

    struct rb_root_cached   tasks_timeline;

    /*
     * 'curr' points to currently running entity on this cfs_rq.
     * It is set to NULL otherwise (i.e when none are currently running).
     */
    // sched_entity可被内核调度的实体
    struct sched_entity *curr;
    struct sched_entity *next;
    struct sched_entity *last;
    struct sched_entity *skip;

#ifdef  CONFIG_SCHED_DEBUG
    unsigned int        nr_spread_over;
#endif

#ifdef CONFIG_SMP
    /*
     * CFS load tracking
     */
    struct sched_avg    avg;
#ifndef CONFIG_64BIT
    u64         load_last_update_time_copy;
#endif
    struct {
        raw_spinlock_t  lock ____cacheline_aligned;
        int     nr;
        unsigned long   load_avg;
        unsigned long   util_avg;
        unsigned long   runnable_sum;
    } removed;

#ifdef CONFIG_FAIR_GROUP_SCHED
    unsigned long       tg_load_avg_contrib;
    long            propagate;
    long            prop_runnable_sum;

    /*
     *   h_load = weight * f(tg)
     *
     * Where f(tg) is the recursive weight fraction assigned to
     * this group.
     */
    unsigned long       h_load;
    u64         last_h_load_update;
    struct sched_entity *h_load_next;
#endif /* CONFIG_FAIR_GROUP_SCHED */
#endif /* CONFIG_SMP */

#ifdef CONFIG_FAIR_GROUP_SCHED
    struct rq       *rq;    /* CPU runqueue to which this cfs_rq is attached */

    /*
     * leaf cfs_rqs are those that hold tasks (lowest schedulable entity in
     * a hierarchy). Non-leaf lrqs hold other higher schedulable entities
     * (like users, containers etc.)
     *
     * leaf_cfs_rq_list ties together list of leaf cfs_rq's in a CPU.
     * This list is used during load balance.
     */
    int         on_list;
    struct list_head    leaf_cfs_rq_list;
    struct task_group   *tg;    /* group that "owns" this runqueue */

#ifdef CONFIG_CFS_BANDWIDTH
    int         runtime_enabled;
    s64         runtime_remaining;

    u64         throttled_clock;
    u64         throttled_clock_task;
    u64         throttled_clock_task_time;
    int         throttled;
    int         throttle_count;
    struct list_head    throttled_list;
#endif /* CONFIG_CFS_BANDWIDTH */
#endif /* CONFIG_FAIR_GROUP_SCHED */
};

cfs_rq: 跟踪就绪队列信息以及管理就绪态调度实体,并维护一棵按照虚拟实践排序的红黑树

struct rb_root_cached tasks_timeline;

tasks_timeline -> rb_root是红黑树的根, tasks_timeline->rb_leftmost指向红黑树最左边的调度实体,即虚拟时间最小的调度实体

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值