linux进程调度器概述(一)

   进程分为实时进程和普通进程,实时进程主要为音频、视频等相关的进程;普通进程主要为:交互式进程(文档编辑器)和批处理进程(编译器)。

     linux调度器分为周期性调度器和主调度器;周期性调度器在schedule_tick中实现,主调度器在schedule中实现。

    每个cpu都会产生tick event,在tick event中调用schedule_tick,完成周期性调度器的任务:维护和更新rq的clock/clock_task、判断当前进程时间片是否用完以及判断是否有更高优先级的进程抢占当前进程。如果当前进程的时间片用完或者有更高优先级的进程抢占当前进程,那么就将当前进程标记为TIF_NEED_RESCHED。

    当有进程要放弃cpu的时候,会调用主调度器:schedule,主调度器将当前进程从cpu上移除,并放入进程队列,然后从标记为TIF_NEED_RESCHED的进程中选择一个进程放入cpu执行。主调度器中根据stop->dl->rt->fair->idle的调度类顺序,选择一个进程,放入cpu执行。

 /*每个cpu都有自己的rq结构,其用于描述在此cpu上所运行的所有进程,其包括一个实时进程队列、CFS运行队列、dl运行队列
  *在调度的时候,调度器首先会先去实时进程的队列找是否有实时进程需要运行,如果没有才会去CFS运行队列找是否有进程需要
  *运行。
  *实时进程的优先级比普通进程优先级高,不仅体现在prio优先级上,还体现在调度器的设计上
  */
struct rq {
/* runqueue lock: */
raw_spinlock_t lock;


/*
* nr_running and cpu_load should be in the same cacheline because
* remote CPUs use both these fields when doing load calculation.
*/
/*此cpu上总共就绪的进程数,包括cfs,rt和正在运行的*/
unsigned int nr_running;//有多少个运行就绪的进程
#ifdef CONFIG_NUMA_BALANCING
unsigned int nr_numa_running;
unsigned int nr_preferred_running;
#endif
#define CPU_LOAD_IDX_MAX 5
/* 根据CPU历史情况计算的负载,cpu_load[0]一直等于load.weight,当达到负载平衡时,cpu_load[1]和cpu_load[2]都应该等于load.weight */
unsigned long cpu_load[CPU_LOAD_IDX_MAX];
/* 最后一次更新 cpu_load 的时间(jiffies值) */
unsigned long last_load_update_tick;
#ifdef CONFIG_NO_HZ_COMMON
u64 nohz_stamp;
unsigned long nohz_flags;
#endif
#ifdef CONFIG_NO_HZ_FULL
unsigned long last_sched_tick;
#endif
/* 是否需要更新rq的运行时间 */
int skip_clock_update;


/* capture load from *all* tasks on this cpu: */
/* CPU负载,该CPU上所有可运行进程的load之和,nr_running更新时这个值也必须更新 */
struct load_weight load;
unsigned long nr_load_updates;
u64 nr_switches;//进行上下文切换的次数,只有proc会使用这个
/*一种调度类使用一种或者多种调度策略
*只在cpu的就绪队列rq和cfs调度器的就绪队列cfs_rq上需要保存需要保存负荷权重信息,rt_rq/dl_rq不需要保存负荷权重信息
*/
struct cfs_rq cfs;//cfs调度类
struct rt_rq rt;//rt调度类
struct dl_rq dl;//deadline调度类


#ifdef CONFIG_FAIR_GROUP_SCHED
/* list of leaf cfs_rq on this cpu: */
struct list_head leaf_cfs_rq_list;


struct sched_avg avg;
#endif /* CONFIG_FAIR_GROUP_SCHED */


/*
* This is part of a global counter where only the total sum
* over all CPUs matters. A task can increase this counter on
* one CPU and if it got migrated afterwards it may decrease
* it on another CPU. Always updated under the runqueue lock:
*/
/*曾经处于队列但现在处于TASK_UNINTERRRUPTIBLE状态的进程数量*/
unsigned long nr_uninterruptible;
/*curr:运行队列中哪个进程在运行
*idle:当前cpu上idle进程的指针,idle进程用于当CPU没事做的时候调用,它什么都不执行
*stop进程指针
*/
struct task_struct *curr, *idle, *stop;
unsigned long next_balance;
struct mm_struct *prev_mm;
/*运行队列的时钟,每个tick都会更新,将sched clock在一个tick的时间间隔累加计算出,clock和clock_task的区别是什么?*/
/*初始化为0,从cpu的tick建立起来开始,在每个tick里面维护更新*/
u64 clock;//记录物理时间
u64 clock_task;//记录task运行的物理时间
/*运行队列中正在等待IO操作的进程数*/
atomic_t nr_iowait;


#ifdef CONFIG_SMP
struct root_domain *rd;
struct sched_domain *sd;


unsigned long cpu_capacity;


unsigned char idle_balance;//当前cpu的rq中没有进程可供调度,
/* For active balancing */
int post_schedule;
int active_balance;//将其他一些进程迁移到其他运行队列中,需要设置该标志
int push_cpu;
struct cpu_stop_work active_balance_work;
/* cpu of this runqueue: */
int cpu;//当前运行队列所在的cpu
int online;


struct list_head cfs_tasks;


u64 rt_avg;
/*该运行队列存活时间*/
u64 age_stamp;
u64 idle_stamp;
u64 avg_idle;


/* This is used to determine avg_idle's max value */
u64 max_idle_balance_cost;
#endif


#ifdef CONFIG_IRQ_TIME_ACCOUNTING
u64 prev_irq_time;
#endif
#ifdef CONFIG_PARAVIRT
u64 prev_steal_time;
#endif
#ifdef CONFIG_PARAVIRT_TIME_ACCOUNTING
u64 prev_steal_time_rq;
#endif


/* calc_load related fields */
/*用于负载均衡*/
unsigned long calc_load_update;
long calc_load_active;


#ifdef CONFIG_SCHED_HRTICK
#ifdef CONFIG_SMP
int hrtick_csd_pending;
struct call_single_data hrtick_csd;
#endif
/*调度使用的高精度定时器*/
struct hrtimer hrtick_timer;
#endif


#ifdef CONFIG_SCHEDSTATS
/* latency stats */
struct sched_info rq_sched_info;
unsigned long long rq_cpu_time;
/* could above be rq->cfs_rq.exec_clock + rq->rt_rq.rt_runtime ? */


/* sys_sched_yield() stats */
unsigned int yld_count;


/* schedule() stats */
unsigned int sched_count;
unsigned int sched_goidle;


/* try_to_wake_up() stats */
unsigned int ttwu_count;
unsigned int ttwu_local;
#endif


#ifdef CONFIG_SMP
struct llist_head wake_list;
#endif

};

/* CFS-related fields in a runqueue */

/*完全公平的调度器运行队列,内部使用红黑树组织调度体*/
/*CFS调度的运行队列,每个cpu的rq会包含一个cfs_rq,而每个组调度的sched_entity也会有自己的一个cfs_rq队列*/
struct cfs_rq {
/*CFS运行队列中所有进程的总负载(累计负荷权重)*/
struct load_weight load;
/*
*nr_running:cfs_rq中可运行进程的数目(就绪队列的进程数)
*h_nr_running:只对进程组有效,其下所有进程组中cfs_rq的nr_running之和
*/
unsigned int nr_running, h_nr_running;


u64 exec_clock;
/*当前CFS队列上最小运行时间,单调递增
*两种情况下更新该值:
*1、更新当前运行任务的累积运行时间时
*2、当任务从队列删除,如任务睡眠或退出,这时候会查看剩下的任务的vruntime是否大于min_vruntime,如果是则更新该值
*如果新进程的vruntime初始值为0,那么根据CFS调度方法,新进程将长时间占用CPU,这是不合理的,因此引入min_vruntime,这样就可以防止vruntime倒退
*/
u64 min_vruntime;
#ifndef CONFIG_64BIT
u64 min_vruntime_copy;
#endif
/*红黑树的root和最左节点*/
struct rb_root tasks_timeline;
struct rb_node *rb_leftmost;


/*
* 'curr' points to currently running entity on this cfs_rq.
* It is set to NULL otherwise (i.e when none are currently running).
*/
/*curr当前正在运行的sched_entity
  *next:表示有些进程急需运行,即使不遵从CFS调度也必须运行它,调度时会检查是否next需要调度,有就调度next
  *skip:略过的进程
  *last:最后一个执行wakeup的sched_entity
  */
struct sched_entity *curr, *next, *last, *skip;


#ifdef CONFIG_SCHED_DEBUG
unsigned int nr_spread_over;
#endif


#ifdef CONFIG_SMP
/*
* CFS Load tracking
* Under CFS, load is tracked on a per-entity basis and aggregated up.
* This allows for the description of both thread and group usage (in
* the FAIR_GROUP_SCHED case).
*/
unsigned long runnable_load_avg, blocked_load_avg;
atomic64_t decay_counter;
u64 last_decay;
atomic_long_t removed_load;


#ifdef CONFIG_FAIR_GROUP_SCHED
/* Required to track per-cpu representation of a task_group */
u32 tg_runnable_contrib;
unsigned long tg_load_contrib;


/*
*   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;
u64 runtime_expires;
s64 runtime_remaining;


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

};


/*调度实体,调度的一个单位,可以表示进程调度也可以表示组调度
 *所有的sched_entity以vruntime为key(实际上是以vruntime-min_vruntime为key,是为了防止溢出,反正结果是一样的)插入到红黑树中,同时缓存树的最左侧节点,也就是vruntime最小的节点,这样可以迅速选中vruntime最小的进程。
 *注意只有等待CPU的就绪态进程在这棵树上,睡眠进程和正在运行的进程都不在树上。
 */
struct sched_entity {
/* 权重,在数组prio_to_weight[]包含优先级转权重的数值 
*通过优先级转换而成,是vruntime计算的关键
*单个进程的权重
*/
struct load_weight load; /* for load-balancing */
/* 实体在红黑树对应的结点信息,rb中的节点和调度实体之间是一一对应关系,可以认为调度实体就是rb的一个节点 */
struct rb_node run_node;//红黑树节点
/* 实体所在的进程组 */
struct list_head group_node;
/* 实体是否处于红黑树运行队列中 */
unsigned int on_rq;
/*当前进程开始获得CPU时的时间戳,进程调度进入cpu时进行设置的*/
u64 exec_start;
/*记录进程在cpu上执行的总时间*/
u64 sum_exec_runtime;
/*进程的虚拟时间,这个虚拟时间就是组织rq红黑树的key*/
/* 虚拟运行时间,在时间中断或者任务状态发生改变时会更新
* 其会不停增长,增长速度与load权重成反比,load越高,增长速度越慢,就越可能处于红黑树最左边被调度
* 每次时钟中断都会修改其值
* 具体见calc_delta_fair()函数
*一次调度间隔的虚拟运行时间 = 实际运行时间*(NICE_0_LOAD/权重)
*/
u64 vruntime;
/*当进程从CPU 挪下,当前的sum_exec_runtime移动到prev_sum_exec_runtime 在进程抢占的时候会用到,但是并不会清除sum_exec_runtime,该值仍然是持续增长的 */
/* 进程从CPU切换出去时的sum_exec_runtime值 */
u64 prev_sum_exec_runtime;
/* 此调度实体中进程移到其他CPU组的数量 */
u64 nr_migrations;


#ifdef CONFIG_SCHEDSTATS
struct sched_statistics statistics;
#endif


#ifdef CONFIG_FAIR_GROUP_SCHED
/* 代表此进程组的深度,每个进程组都比其parent调度组深度大1 */
int depth;
struct sched_entity *parent;//调度实体构成链表
/* rq on which this entity is (to be) queued: */
struct cfs_rq *cfs_rq;
/* rq "owned" by this entity/group: */
/* 实体的红黑树运行队列,如果为NULL表明其是一个进程,若非NULL表明其是调度组 */
struct cfs_rq *my_q;


#endif


#ifdef CONFIG_SMP
/* Per-entity load-tracking */
struct sched_avg avg;
#endif
};

猜你喜欢

转载自blog.csdn.net/qq_38712943/article/details/79924301