快捷搜索:

您的位置:澳门新葡4473网站 > 项目 > Linux内核——进程管理之CFS调度器(基于版本4.

Linux内核——进程管理之CFS调度器(基于版本4.

发布时间:2019-10-16 03:45编辑:项目浏览(127)

    《奔跑吧linux内核》3.2笔记,不足之处还望大家商量指正

    经过调治所选用到的数据结构:

    提出阅读博文理解linux cfs调度器

    1.就绪队列

      进程大约能够分为交互式进度批管理进度实时进度。对于区别的历程采取分裂的调整攻略,近年来Linux内核中暗许达成了4种调节攻略,分别是deadline、realtime、CFS和idle,分别适用struct sched_class来定义调节类。

    基本为每三个cpu成立一个经过就绪队列,该队列上的经过均由该cpu实施,代码如下(kernel/sched/core.c)。

      4种调解类经过next指针串联在同步,客商空间程序能够运用调治攻略API函数(sched_setscheduler())来设定客商进度的调整战略。

    1 DEFINE_PER_CPU_SHARED_ALIGNED(struct rq, runqueues);
    

    主题素材一:请简述对经过调整器的知情,早起Linux内核调节器(包涵O(N)和O(1))调解器是什么行事的?

    概念了三个struct rq结构体数组,每一种数组成分是三个就绪队列,对应叁个cpu。上边看下struct rq结构体(kernel/sched/sched.h):

      调治器是按一定的主意对进程张开调节的一种体制,要求为种种普通进程尽恐怕公平地分享CPU时间。

    图片 1图片 2

      O(N)调节器发表与壹玖玖叁年,从就绪队列中相比较全数进度的优先级,然后采取一个参天优先级的进度作为下二个调节进度。每叁个进度有四个稳住时间片,当进度时间片使用完今后,调整器会选取下一个调节进程,当有着进程都运作二次后再重新分配时间片。那一个调整器选取下八个调解进度前需求遍历整个就绪队列,开支O(N)时间。

      1 struct rq {
      2     /* runqueue lock: */
      3     raw_spinlock_t lock;
      4 
      5     /*
      6      * nr_running and cpu_load should be in the same cacheline because
      7      * remote CPUs use both these fields when doing load calculation.
      8      */
      9     unsigned int nr_running;
     10 #ifdef CONFIG_NUMA_BALANCING
     11     unsigned int nr_numa_running;
     12     unsigned int nr_preferred_running;
     13 #endif
     14     #define CPU_LOAD_IDX_MAX 5
     15     unsigned long cpu_load[CPU_LOAD_IDX_MAX];
     16     unsigned long last_load_update_tick;
     17 #ifdef CONFIG_NO_HZ_COMMON
     18     u64 nohz_stamp;
     19     unsigned long nohz_flags;
     20 #endif
     21 #ifdef CONFIG_NO_HZ_FULL
     22     unsigned long last_sched_tick;
     23 #endif
     24     int skip_clock_update;
     25 
     26     /* capture load from *all* tasks on this cpu: */
     27     struct load_weight load;
     28     unsigned long nr_load_updates;
     29     u64 nr_switches;
     30 
     31     struct cfs_rq cfs;
     32     struct rt_rq rt;
     33     struct dl_rq dl;
     34 
     35 #ifdef CONFIG_FAIR_GROUP_SCHED
     36     /* list of leaf cfs_rq on this cpu: */
     37     struct list_head leaf_cfs_rq_list;
     38 
     39     struct sched_avg avg;
     40 #endif /* CONFIG_FAIR_GROUP_SCHED */
     41 
     42     /*
     43      * This is part of a global counter where only the total sum
     44      * over all CPUs matters. A task can increase this counter on
     45      * one CPU and if it got migrated afterwards it may decrease
     46      * it on another CPU. Always updated under the runqueue lock:
     47      */
     48     unsigned long nr_uninterruptible;
     49 
     50     struct task_struct *curr, *idle, *stop;
     51     unsigned long next_balance;
     52     struct mm_struct *prev_mm;
     53 
     54     u64 clock;
     55     u64 clock_task;
     56 
     57     atomic_t nr_iowait;
     58 
     59 #ifdef CONFIG_SMP
     60     struct root_domain *rd;
     61     struct sched_domain *sd;
     62 
     63     unsigned long cpu_capacity;
     64 
     65     unsigned char idle_balance;
     66     /* For active balancing */
     67     int post_schedule;
     68     int active_balance;
     69     int push_cpu;
     70     struct cpu_stop_work active_balance_work;
     71     /* cpu of this runqueue: */
     72     int cpu;
     73     int online;
     74 
     75     struct list_head cfs_tasks;
     76 
     77     u64 rt_avg;
     78     u64 age_stamp;
     79     u64 idle_stamp;
     80     u64 avg_idle;
     81 
     82     /* This is used to determine avg_idle's max value */
     83     u64 max_idle_balance_cost;
     84 #endif
     85 
     86 #ifdef CONFIG_IRQ_TIME_ACCOUNTING
     87     u64 prev_irq_time;
     88 #endif
     89 #ifdef CONFIG_PARAVIRT
     90     u64 prev_steal_time;
     91 #endif
     92 #ifdef CONFIG_PARAVIRT_TIME_ACCOUNTING
     93     u64 prev_steal_time_rq;
     94 #endif
     95 
     96     /* calc_load related fields */
     97     unsigned long calc_load_update;
     98     long calc_load_active;
     99 
    100 #ifdef CONFIG_SCHED_HRTICK
    101 #ifdef CONFIG_SMP
    102     int hrtick_csd_pending;
    103     struct call_single_data hrtick_csd;
    104 #endif
    105     struct hrtimer hrtick_timer;
    106 #endif
    107 
    108 #ifdef CONFIG_SCHEDSTATS
    109     /* latency stats */
    110     struct sched_info rq_sched_info;
    111     unsigned long long rq_cpu_time;
    112     /* could above be rq->cfs_rq.exec_clock + rq->rt_rq.rt_runtime ? */
    113 
    114     /* sys_sched_yield() stats */
    115     unsigned int yld_count;
    116 
    117     /* schedule() stats */
    118     unsigned int sched_count;
    119     unsigned int sched_goidle;
    120 
    121     /* try_to_wake_up() stats */
    122     unsigned int ttwu_count;
    123     unsigned int ttwu_local;
    124 #endif
    125 
    126 #ifdef CONFIG_SMP
    127     struct llist_head wake_list;
    128 #endif
    129 };
    

      O(1)调整器用于Linux2.6.23水源在此以前,它为每一种CPU维护一组经过优先级队列,各类优先级三个行列,那样在甄选下一个历程时,只要查询优先级队列相应的位图就能够见道哪些队列中有就绪进程,查询时间常数为O(1)。

    struct rq

    标题二:请简述进度优先级、nice和权重之间的关系。

    该结构体是本地cpu全数进度组成的服服帖帖队列,在linux内核中,进度被分为平时进度和实时进度,那二种进程的调节战略是区别的,因而在31-32行能够见见rq结构体中又内嵌了struct cfs_rq cfs和struct rt_rq rt七个子就绪队列,分别来公司普通进度和实时进程(普通进度将动用完全公平级调动度计策cfs,而实时进程将选用实时调节战略),第33行struct dl_rq dl调整空闲进度,暂时不商量。所以,假如大家钻探的是普普通通进度的调节,需求关爱的正是struct cfs_rq cfs队列;假诺商量的是实时进度,就只关怀struct rt_rq rt队列。

      内核使用0~139的数值表示经过的优先级,数值越低优先级越高。优先级0~99给实时进度使用,100~139给普通进度使用。在顾客空间由叁个理念的变量nice值映射到平凡进度的优先级(100~139)。

    1.1常备进度的就绪队列struct cfs_rq(kernel/sched/sched.h)

      nice值从-20~19,进度私下认可的nice值为0。那足以明白为四十多个阶段,nice值越高,则先行级越低,反之亦然。(nice每变化1,则附和的进程获得CPU的年月就改成一成)。

    图片 3图片 4

      权重消息即为调整实体的权重,为了总结方便,内核约定nice值为0的权重值为1024,别的的nice值对应相应的权重值可以通过查表的主意来获取,表即为prio_to_weight。

     1 /* CFS-related fields in a runqueue */
     2 struct cfs_rq {
     3     struct load_weight load;
     4     unsigned int nr_running, h_nr_running;
     5 
     6     u64 exec_clock;
     7     u64 min_vruntime;
     8 #ifndef CONFIG_64BIT
     9     u64 min_vruntime_copy;
    10 #endif
    11 
    12     struct rb_root tasks_timeline;
    13     struct rb_node *rb_leftmost;
    14 
    15     /*
    16      * 'curr' points to currently running entity on this cfs_rq.
    17      * It is set to NULL otherwise (i.e when none are currently running).
    18      */
    19     struct sched_entity *curr, *next, *last, *skip;
    20 
    21 #ifdef    CONFIG_SCHED_DEBUG
    22     unsigned int nr_spread_over;
    23 #endif
    24 
    25 #ifdef CONFIG_SMP
    26     /*
    27      * CFS Load tracking
    28      * Under CFS, load is tracked on a per-entity basis and aggregated up.
    29      * This allows for the description of both thread and group usage (in
    30      * the FAIR_GROUP_SCHED case).
    31      */
    32     unsigned long runnable_load_avg, blocked_load_avg;
    33     atomic64_t decay_counter;
    34     u64 last_decay;
    35     atomic_long_t removed_load;
    36 
    37 #ifdef CONFIG_FAIR_GROUP_SCHED
    38     /* Required to track per-cpu representation of a task_group */
    39     u32 tg_runnable_contrib;
    40     unsigned long tg_load_contrib;
    41 
    42     /*
    43      *   h_load = weight * f(tg)
    44      *
    45      * Where f(tg) is the recursive weight fraction assigned to
    46      * this group.
    47      */
    48     unsigned long h_load;
    49     u64 last_h_load_update;
    50     struct sched_entity *h_load_next;
    51 #endif /* CONFIG_FAIR_GROUP_SCHED */
    52 #endif /* CONFIG_SMP */
    53 
    54 #ifdef CONFIG_FAIR_GROUP_SCHED
    55     struct rq *rq;    /* cpu runqueue to which this cfs_rq is attached */
    56 
    57     /*
    58      * leaf cfs_rqs are those that hold tasks (lowest schedulable entity in
    59      * a hierarchy). Non-leaf lrqs hold other higher schedulable entities
    60      * (like users, containers etc.)
    61      *
    62      * leaf_cfs_rq_list ties together list of leaf cfs_rq's in a cpu. This
    63      * list is used during load balance.
    64      */
    65     int on_list;
    66     struct list_head leaf_cfs_rq_list;
    67     struct task_group *tg;    /* group that "owns" this runqueue */
    68 
    69 #ifdef CONFIG_CFS_BANDWIDTH
    70     int runtime_enabled;
    71     u64 runtime_expires;
    72     s64 runtime_remaining;
    73 
    74     u64 throttled_clock, throttled_clock_task;
    75     u64 throttled_clock_task_time;
    76     int throttled, throttle_count;
    77     struct list_head throttled_list;
    78 #endif /* CONFIG_CFS_BANDWIDTH */
    79 #endif /* CONFIG_FAIR_GROUP_SCHED */
    80 };
    

    主题材料三:请简述CFS调整器是哪些做事的。

    struct cfs_rq

      CFS调整器丢掉以前固按期间片和长久调节周期的算法,选拔进度权重值的比例来量化和计量实际运作时刻。引入设想时钟的概念,各类进程的设想时间是实在运维时刻绝对nice值为0的权重的比例值。进度遵照各自分裂的速率比在大意石英钟节拍内发展。nice值小的进度,优先级高且权重大,其虚拟挂钟比实际石英钟跑得慢,可是足以获得相当多的运作时刻;反之,nice值大的长河,优先级低,权重也低,其虚拟石英钟比实际石英钟跑得快,获得少之甚少的运维时刻。CFS调整器总是挑肥拣瘦设想石英钟跑得慢的历程,类似三个各类手动变速箱,nice值为0的长河是准则齿轮,别的各类进程在不一样变速比下互相追赶,进而实现公正公道。

    cfs_rq就绪队列是以红黑树的花样来公司调解实体。第12行tasks_timeline成员正是红黑树的树根。第13行rb_leftmost指向了红黑树最左侧的左孩子(下一个可调治的实业)。第19行curr指向当下正运营的实体,next指向将被提示的经过,last指向唤醒next进度的历程,next和last用法前面会提到。第55行rq指向了该cfs_rq就绪队列所属的rq队列。

    主题素材四:CFS调节器中的vruntime是怎么着总括的?

    1.2实时进度的就绪队列struct rt_rq(kernel/sched/sched.h)

      vruntime=(delta_exec*nice_0_weight)/weight。其中,delta_exec为实际运作时刻,nice_0_weight为nice为0的权重值,weight表示该进度的权重值。在update_curr()函数中,实现了该值的乘除,此时,为了总计高效,将总结办法改为了乘法和活动vruntime=(delta_exec*nice_0_weight*inv_weight)>>shift,其中inv_weight=2^32/weight,是开始的一段时期总计好存放在prio_to_wmult中的。

    图片 5图片 6

    标题五:vruntime是何时更新的?

     1 /* Real-Time classes' related field in a runqueue: */
     2 struct rt_rq {
     3     struct rt_prio_array active;
     4     unsigned int rt_nr_running;
     5 #if defined CONFIG_SMP || defined CONFIG_RT_GROUP_SCHED
     6     struct {
     7         int curr; /* highest queued rt task prio */
     8 #ifdef CONFIG_SMP
     9         int next; /* next highest */
    10 #endif
    11     } highest_prio;
    12 #endif
    13 #ifdef CONFIG_SMP
    14     unsigned long rt_nr_migratory;
    15     unsigned long rt_nr_total;
    16     int overloaded;
    17     struct plist_head pushable_tasks;
    18 #endif
    19     int rt_queued;
    20 
    21     int rt_throttled;
    22     u64 rt_time;
    23     u64 rt_runtime;
    24     /* Nests inside the rq lock: */
    25     raw_spinlock_t rt_runtime_lock;
    26 
    27 #ifdef CONFIG_RT_GROUP_SCHED
    28     unsigned long rt_nr_boosted;
    29 
    30     struct rq *rq;
    31     struct task_group *tg;
    32 #endif
    33 };
    

      创设新进度,到场就绪队列,调治tick等都会更新当前vruntime值。

    struct rt_rq

    问题六:CFS调节器中的min_vruntime有啥效益?

    2.调治实体(include/linux/sched.h)

      min_vruntime在CFS就绪队列数据结构中,单步递增,用于追踪该就绪队列红黑树中型小型小的的vruntime。

    2.1平凡进度的调节实体sched_entity

    标题七:CFS调整器对新创立的进度和刚唤醒的进程有啥照望?

     1 struct sched_entity {
     2     struct load_weight    load;        /* for load-balancing */
     3     struct rb_node        run_node;
     4     struct list_head    group_node;
     5     unsigned int        on_rq;
     6 
     7     u64            exec_start;
     8     u64            sum_exec_runtime;
     9     u64            vruntime;
    10     u64            prev_sum_exec_runtime;
    11 
    12     u64            nr_migrations;
    13 
    14 #ifdef CONFIG_SCHEDSTATS
    15     struct sched_statistics statistics;
    16 #endif
    17 
    18 #ifdef CONFIG_FAIR_GROUP_SCHED
    19     int            depth;
    20     struct sched_entity    *parent;
    21     /* rq on which this entity is (to be) queued: */
    22     struct cfs_rq        *cfs_rq;
    23     /* rq "owned" by this entity/group: */
    24     struct cfs_rq        *my_q;
    25 #endif
    26 
    27 #ifdef CONFIG_SMP
    28     /* Per-entity load-tracking */
    29     struct sched_avg    avg;
    30 #endif
    31 };
    

      对于睡眠进程,其vruntime在睡觉之间不进步,在晋升后一旦还用原来的vruntime值,交易会开报复性满载运营,所以要改进vruntime,具体在enqueue_entity()函数中,计算公式为vruntime+=min_vruntime,vruntime=MAX(vruntime, min_vruntime-sysctl_sched_lantency/2)。

    各种进程描述符中均包括八个该结构体变量,内核使用该结构体来将常常进度协会到应用完全公平级调动度计谋的就绪队列中(struct rq中的cfs队列中,上边提到过),该结构体有四个成效,一是带有有经过调节的新闻(例如进程的运作时刻,睡眠时间等等,调解程序仿照效法那几个消息决定是还是不是调治进程),二是使用该结构体来公司过程,第3行的struct rb_node类型结构体变量run_node是红黑树节点,因而struct sched_entity调整实体将被协会成红黑树的样式,同有的时候候代表平时进程也被公司成红黑树的格局。第18-25行是和组调治有关的成员,需求开启组调整。第20行parent从名称想到所满含的意义指向了日前实体的上拔尖实体,前边再介绍。第22行的cfs_rq指向了该调节实体所在的妥贴队列。第24行my_q指向了本实体具备的妥当队列(调解组),该调解组(饱含组员实体)属于下一个品级,和本实体不在同三个品级,该调整组中负有成员实体的parent域指向了本实体,那就表明了上边包车型地铁parent,此外,第19行depth代表了此行列(调节组)的吃水,各类调整组都比其parent调节组深度大1。内核信任my_q域实现组调解。

      对于新创造的进程,要求加上叁个调治周期的杜撰是时刻(sched_vslice())。首先在task_fork_fair()函数中,place_entity()扩展了调整周期的设想时间,约等于查办,se->vruntime=sched_vslice()。接着新进度在拉长到就绪队列时,wake_up_new_task()->activate_task()->enqueue_entity()函数里,se->vruntime+=cfs_rq->min_vruntime。

    2.2实时进度的调解实体 sched_rt_entity

    难点八:如何总结普通进程的平分负载load_avg_contrib?runnable_avg_sum和runnable_avg_period分别代表了什么意思?

     1 struct sched_rt_entity {
     2     struct list_head run_list;
     3     unsigned long timeout;
     4     unsigned long watchdog_stamp;
     5     unsigned int time_slice;
     6 
     7     struct sched_rt_entity *back;
     8 #ifdef CONFIG_RT_GROUP_SCHED
     9     struct sched_rt_entity    *parent;
    10     /* rq on which this entity is (to be) queued: */
    11     struct rt_rq        *rt_rq;
    12     /* rq "owned" by this entity/group: */
    13     struct rt_rq        *my_q;
    14 #endif
    15 };
    

      load_avg_contrib=runnable_avg_sum*weight/runnable_avg_period。

    该结构体和上个结构体是近似的,只可是用来公司实时过程,实时进度被集体到struct rq中的rt队列中,下面有涉及。每个进程描述符中也含有叁个该结构体。该结构体中尚无饱含struct rb_node类型结构体变量,而在第1行出现了struct list_head类型结构体变量run_list,因而能够看看实时进度的服服帖帖队列是双向链表情势,并非红黑数的花样。

      runnable_avg_sum为调整实体的可运维处境下总衰减累积时间。

    3.调度类(kernel/sched/sched.h)

      runnable_avg_period记录的是上一遍立异时的总周期数(三个周期是1024us),即调治实体在调整器中的总衰减累计时间。

     1 struct sched_class {
     2     const struct sched_class *next;
     3 
     4     void (*enqueue_task) (struct rq *rq, struct task_struct *p, int flags);
     5     void (*dequeue_task) (struct rq *rq, struct task_struct *p, int flags);
     6     void (*yield_task) (struct rq *rq);
     7     bool (*yield_to_task) (struct rq *rq, struct task_struct *p, bool preempt);
     8 
     9     void (*check_preempt_curr) (struct rq *rq, struct task_struct *p, int flags);
    10 
    11     /*
    12      * It is the responsibility of the pick_next_task() method that will
    13      * return the next task to call put_prev_task() on the @prev task or
    14      * something equivalent.
    15      *
    16      * May return RETRY_TASK when it finds a higher prio class has runnable
    17      * tasks.
    18      */
    19     struct task_struct * (*pick_next_task) (struct rq *rq,
    20                         struct task_struct *prev);
    21     void (*put_prev_task) (struct rq *rq, struct task_struct *p);
    22 
    23 #ifdef CONFIG_SMP
    24     int  (*select_task_rq)(struct task_struct *p, int task_cpu, int sd_flag, int flags);
    25     void (*migrate_task_rq)(struct task_struct *p, int next_cpu);
    26 
    27     void (*post_schedule) (struct rq *this_rq);
    28     void (*task_waking) (struct task_struct *task);
    29     void (*task_woken) (struct rq *this_rq, struct task_struct *task);
    30 
    31     void (*set_cpus_allowed)(struct task_struct *p,
    32                  const struct cpumask *newmask);
    33 
    34     void (*rq_online)(struct rq *rq);
    35     void (*rq_offline)(struct rq *rq);
    36 #endif
    37 
    38     void (*set_curr_task) (struct rq *rq);
    39     void (*task_tick) (struct rq *rq, struct task_struct *p, int queued);
    40     void (*task_fork) (struct task_struct *p);
    41     void (*task_dead) (struct task_struct *p);
    42 
    43     void (*switched_from) (struct rq *this_rq, struct task_struct *task);
    44     void (*switched_to) (struct rq *this_rq, struct task_struct *task);
    45     void (*prio_changed) (struct rq *this_rq, struct task_struct *task,
    46                  int oldprio);
    47 
    48     unsigned int (*get_rr_interval) (struct rq *rq,
    49                      struct task_struct *task);
    50 
    51 #ifdef CONFIG_FAIR_GROUP_SCHED
    52     void (*task_move_group) (struct task_struct *p, int on_rq);
    53 #endif
    54 };
    

      runnable_avg_sum越接近runnable_avg_period,则平均负载越大,表示该调节实体一向在挤占CPU。

    水源证明了一个调治类sched_class的结构体类型,用来贯彻分裂的调治战略,能够观察该结构体成员都以函数指针,这么些指针指向的函数就是调治计谋的实际完成,全部和进程调治有关的函数都直接也许直接调用了那一个分子函数,来贯彻进度调治。另外,每一个进度描述符中都包罗二个对准该组织体类型的指针sched_class,指向了所运用的调解类。上面我们看下完全公平级调动度计策类的概念和先河化(kernel/sched/fair.c)。

    标题九:内核代码中定义了多少个表,请分别讲出它们的含义,比如prio_to_weight、prio_to_wmult、runnable_avg_yN_inv、runnable_avg_yN_sum。

    1 const struct sched_class fair_sched_class;
    

      prio_to_weight表记录的是nice值对应的权重值。

    概念了四个大局的调治攻略变量。开首化如下:

      prio_to_wmult表记录的是nice值对应的权重值倒转后的值inv_weight=2^32/weight。

     1 const struct sched_class fair_sched_class = {
     2     .next            = &idle_sched_class,
     3     .enqueue_task        = enqueue_task_fair,
     4     .dequeue_task        = dequeue_task_fair,
     5     .yield_task        = yield_task_fair,
     6     .yield_to_task        = yield_to_task_fair,
     7 
     8     .check_preempt_curr    = check_preempt_wakeup,
     9 
    10     .pick_next_task        = pick_next_task_fair,
    11     .put_prev_task        = put_prev_task_fair,
    12 
    13 #ifdef CONFIG_SMP
    14     .select_task_rq        = select_task_rq_fair,
    15     .migrate_task_rq    = migrate_task_rq_fair,
    16 
    17     .rq_online        = rq_online_fair,
    18     .rq_offline        = rq_offline_fair,
    19 
    20     .task_waking        = task_waking_fair,
    21 #endif
    22 
    23     .set_curr_task          = set_curr_task_fair,
    24     .task_tick        = task_tick_fair,
    25     .task_fork        = task_fork_fair,
    26 
    27     .prio_changed        = prio_changed_fair,
    28     .switched_from        = switched_from_fair,
    29     .switched_to        = switched_to_fair,
    30 
    31     .get_rr_interval    = get_rr_interval_fair,
    32 
    33 #ifdef CONFIG_FAIR_GROUP_SCHED
    34     .task_move_group    = task_move_group_fair,
    35 #endif
    36 };
    

      runnable_avg_yN_inv表是为了幸免CPU做浮点总计,提前计算了公式2^32*其实衰减因子(第32ms约为0.5)的值,有三十个下标,对应过去0~32ms的负荷进献的衰减因子。

    可以看见该结构体变量中等学园函授数成员很多,它们完毕了分歧的效果,待会用到时大家再做深入分析。

      runnable_avg_yN_sum表为1024*(y+y^2+…+y^n),y为实在衰减因子,n取1~32。(实际衰减因子下图所示)

    4.进度描述符task_struct(include/linux/sched.h)

    图片 7图 实际衰减因子

     1 struct task_struct {
     2     volatile long state;    /* -1 unrunnable, 0 runnable, >0 stopped */
     3     .....
     4     int on_rq;
     5 
     6     int prio, static_prio, normal_prio;
     7     unsigned int rt_priority;
     8     const struct sched_class *sched_class;
     9     struct sched_entity se;
    10     struct sched_rt_entity rt;
    11 #ifdef CONFIG_CGROUP_SCHED
    12     struct task_group *sched_task_group;
    13 #endif
    14     struct sched_dl_entity dl;
    15     .....
    16     .....
    17     unsigned int policy;
    18     .....
    19     .....
    20     struct sched_info sched_info;
    21     .....
    22     .....
    23 };
    

    主题材料十:要是一个家常进度在就绪队列里等待了十分短日子才被调治,那么它的平分负载该怎么总结?

    只列出了和进度调整有关的分子。第6行七个变量代表了平凡进度的多个优先级,第7行的变量代表了实时进程的事先级。关于进度优先级的概念,我们能够看看《浓重精通linux内核框架结构》那本书的进程管理章节。第8-10行便是我们上面提到的这么些结构体在进程描述符中的定义。第17行的policy代表了现阶段历程的调解战略,内核给出了宏定义,它能够在这里些宏中取值,关于详细的传授照旧去看《浓厚驾驭linux内核架构》那本书的进度管理一些。policy取了如何值,sched_class也应当取相应的调治类指针。

       七个经过等待相当短日子过后(过了过八个period),原本的runnable_avg_sum和runable_ave_period值衰减后可能造成0,也就是事先的总括值被清0。

    经过调解进程剖判:

    经过调节进程分成两局地,一是对经过音讯进行退换,首假设修改和调解相关的新闻,比方进程的周转时刻,睡眠时间,进程的情景,cpu的负荷等等,二是经过的切换。和经过调解相关的具有函数中,独有schedule函数是用来拓宽进度切换的,别的函数都以用来修改进度的调整音讯。schedule函数大家在眼下的博文中早已探寻过了,这里不再分析。对于别的函数,我们将奉公守法其职能,逐类来分析。

    1.scheduler_tick(kernel/sched/core.c )

     1 void scheduler_tick(void)
     2 {
     3     int cpu = smp_processor_id();
     4     struct rq *rq = cpu_rq(cpu);
     5     struct task_struct *curr = rq->curr;
     6 
     7     sched_clock_tick();
     8 
     9     raw_spin_lock(&rq->lock);
    10     update_rq_clock(rq);
    11     curr->sched_class->task_tick(rq, curr, 0);
    12     update_cpu_load_active(rq);
    13     raw_spin_unlock(&rq->lock);
    14 
    15     perf_event_task_tick();
    16 
    17 #ifdef CONFIG_SMP
    18     rq->idle_balance = idle_cpu(cpu);
    19     trigger_load_balance(rq);
    20 #endif
    21     rq_last_tick_reset(rq);
    22 }
    

    该函数被石英钟中断管理程序调用,将这两天cpu的载荷情状记载到运行队列struct rq的某个成员中,并更新当前进程的时间片。第3行获得当前的cpu号,第4行得到当前cpu的伏贴队列(每种cpu有三个)rq,第5行从就绪队列中获取当前运维进程的描述符,第10行更新就绪队列中的clock和clock_task成员值,代表当前的大运,日常大家会用到clock_task。第11行进入当前进度的调整类的task_tick函数中,更新当前进程的光阴片,分化调解类的该函数达成不一样,待会大家深入分析下完全公平调节类的该函数。第12行更新就绪队列的cpu负载音讯。第18行决断当前cpu是或不是是空闲的,是的话idle_cpu再次回到1,不然再次回到0。第19行挂起SCHED_SOFTI瑞鹰Q软中断函数,去做周期性的负载平衡操作。第21行将风尚的钟表滴答数jiffies存入就绪队列的last_sched_tick域中。再来看下task_tick_fair函数(kernel/sched/fair.c):

     1 static void task_tick_fair(struct rq *rq, struct task_struct *curr, int queued)
     2 {
     3     struct cfs_rq *cfs_rq;
     4     struct sched_entity *se = &curr->se;
     5 
     6     for_each_sched_entity(se) {
     7         cfs_rq = cfs_rq_of(se);
     8         entity_tick(cfs_rq, se, queued);
     9     }
    10 
    11     if (numabalancing_enabled)
    12         task_tick_numa(rq, curr);
    13 
    14     update_rq_runnable_avg(rq, 1);
    15 }
    

    假诺有些进度的调解类应用完全公平级调动度类的话,那么上个函数scheduler_tick第11行所推行的task_tick函数指针,就对准了本函数。能够回头看看完全公平级调动度对象的早先化,第24行的赋值语句.task_tick

    task_tick_fair。回到本函数,第4行得到当前历程的常备调整实体,将指针寄放到se中,第6-8行遍历当前调治实体的上拔尖实体,以致上上一流实体,由此及彼,然后在entity_tick函数中更新调解实体的运转时刻等新闻。在此边用循环来遍历的缘故是当张开了组调节后,调节实体的parent域就存款和储蓄了它的上一级节点,该实体和它parent指向的实业不是一模一样品级,由此使用循环就把从脚下等级(组)到最顶层的等级遍历完了;固然未选用组扶植,则循环只实行一次,仅对近年来调解实体举行翻新。上面看下entity_tick的代码(kernel/sched/fair.c):

     1 static void
     2 entity_tick(struct cfs_rq *cfs_rq, struct sched_entity *curr, int queued)
     3 {
     4     /*
     5      * Update run-time statistics of the 'current'.
     6      */
     7     update_curr(cfs_rq);
     8 
     9     /*
    10      * Ensure that runnable average is periodically updated.
    11      */
    12     update_entity_load_avg(curr, 1);
    13     update_cfs_rq_blocked_load(cfs_rq, 1);
    14     update_cfs_shares(cfs_rq);
    15 
    16 #ifdef CONFIG_SCHED_HRTICK
    17     /*
    18      * queued ticks are scheduled to match the slice, so don't bother
    19      * validating it and just reschedule.
    20      */
    21     if (queued) {
    22         resched_task(rq_of(cfs_rq)->curr);
    23         return;
    24     }
    25     /*
    26      * don't let the period tick interfere with the hrtick preemption
    27      */
    28     if (!sched_feat(DOUBLE_TICK) &&
    29             hrtimer_active(&rq_of(cfs_rq)->hrtick_timer))
    30         return;
    31 #endif
    32 
    33     if (cfs_rq->nr_running > 1)
    34         check_preempt_tick(cfs_rq, curr);
    35 }
    

    在该函数中对调节实体(进度)的运作时刻等消息进行翻新。第7行update_curr函数对现阶段历程的运营时刻实行更新,随后深入分析。 第21行假设传进来的参数queued不为0的话,当前经过会被白白设置双重调节标识(允许被私吞了)。第33-34行假若当前cfs_rq队列等待调治的进度数量超过1,那么就进行check_preempt_tick函数检查当前历程的时间片是还是不是用完,用完的话就必要调解其他进程来运维(具体来讲,即使当前经过“真实时间片”用完,该函数给当下历程设置need_resched标记,然后schedule程序就足以另行在就绪队列中调治新的历程),上面深入分析update_curr函数(kernel/sched/fair.c):

     1 static void update_curr(struct cfs_rq *cfs_rq)
     2 {
     3     struct sched_entity *curr = cfs_rq->curr;
     4     u64 now = rq_clock_task(rq_of(cfs_rq));
     5     u64 delta_exec;
     6 
     7     if (unlikely(!curr))
     8         return;
     9 
    10     delta_exec = now - curr->exec_start;
    11     if (unlikely((s64)delta_exec <= 0))
    12         return;
    13 
    14     curr->exec_start = now;
    15 
    16     schedstat_set(curr->statistics.exec_max,
    17               max(delta_exec, curr->statistics.exec_max));
    18 
    19     curr->sum_exec_runtime += delta_exec;
    20     schedstat_add(cfs_rq, exec_clock, delta_exec);
    21 
    22     curr->vruntime += calc_delta_fair(delta_exec, curr);
    23     update_min_vruntime(cfs_rq);
    24 
    25     if (entity_is_task(curr)) {
    26         struct task_struct *curtask = task_of(curr);
    27 
    28         trace_sched_stat_runtime(curtask, delta_exec, curr->vruntime);
    29         cpuacct_charge(curtask, delta_exec);
    30         account_group_exec_runtime(curtask, delta_exec);
    31     }
    32 
    33     account_cfs_rq_runtime(cfs_rq, delta_exec);
    34 } 
    

    该函数是立异进度运营时刻最中心的贰个函数。第3行取妥善前的调解实体,第4行从就绪队列rq的clock_task成员中得到当前时光,存入now中,该成员大家在scheduler_tick函数中涉及过。第10行用当下光阴减去进度在上次手表中断tick中的起初时间获得进度运维的小时间隔,存入delta_exec中。第14行业前光阴又改为进度新的开首时间。第19行将经过运行的光阴世距delta_exec累积到调治实体的sum_exec_runtime成员中,该成员表示经过到最近截止运营了多久。第20行将经过运行的时间间距delta_exec也丰盛到公平级调动度就绪队列cfs_rq的exec_clock成员中。第22行calc_delta_fair函数很关键,它将经过奉行的忠实运营时刻调换来虚构运维时刻,然后加上到调治实体的vruntime域中,进度的虚构时间特别重大,完全公平级调动度计谋就是凭仗该时间举办调解。关于进度的不追求虚名时间和设想时间的概念,乃至它们中间的转换算法,作品的背后会提到,详细的剧情大家能够看看《深远明白linux内核架构》的长河管理章节。第23行更新cfs_rq队列中的最小设想运营时刻min_vruntime,该时间是就绪队列中全数进度包罗近些日子经过的已运行的不大虚构时间,只好单调递增,待会大家解析update_min_vruntime函数,该函数相比关键。第25-30行,借使调治单位是经过的话(不是组),会更新进度描述符中的运作时刻。第33行更新cfs_rq队列的剩余运维时刻,并企图出希望运维时刻,供给的话能够对进度重新调解。上面大家先分析update_min_vruntime函数,然后深入分析calc_delta_fair函数(kernel/sched/fair.c):

     1 static void update_min_vruntime(struct cfs_rq *cfs_rq)
     2 {
     3     u64 vruntime = cfs_rq->min_vruntime;
     4 
     5     if (cfs_rq->curr)
     6         vruntime = cfs_rq->curr->vruntime;
     7 
     8     if (cfs_rq->rb_leftmost) {
     9         struct sched_entity *se = rb_entry(cfs_rq->rb_leftmost,
    10                            struct sched_entity,
    11                            run_node);
    12 
    13         if (!cfs_rq->curr)
    14             vruntime = se->vruntime;
    15         else
    16             vruntime = min_vruntime(vruntime, se->vruntime);
    17     }
    18 
    19     /* ensure we never gain time by being placed backwards. */
    20     cfs_rq->min_vruntime = max_vruntime(cfs_rq->min_vruntime, vruntime);
    21 #ifndef CONFIG_64BIT
    22     smp_wmb();
    23     cfs_rq->min_vruntime_copy = cfs_rq->min_vruntime;
    24 #endif
    25 } 
    

    每个cfs_rq队列均有一个min_vruntime成员,装的是就绪队列中全数进度包罗前段时间历程已运营的虚拟时间中细小的特别时间。本函数来更新那些时间。第5-6行纵然当前有经过正在施行,将近日经过已运维的设想时间保存在vruntime变量中。第8-17行若是就绪队列中有下三个要被调治的历程(由rb_leftmost指针指向),则跻身if体,第13-16行从当前经过和下个被调节进度中,选用最小的已运维虚构时间,保存到vruntime中。第20行从脚下队列的min_vruntime域和vruntime变量中,选最大的保存到行列的min_vruntime域中,达成换代。其实第13-17行是最首要的,保险了队列的min_vruntime域中寄放的是就绪队列中幽微的虚构运维时刻,而第20行的意义只是是有限支撑min_vruntime域中的值单调递增,未有其他含义了。队列中的min_vruntime成员极度首要,用于在睡觉进度被提示后以致新进度被成立好时,实行虚拟时间补偿大概惩罚,前面会分析到。

    1 static inline u64 calc_delta_fair(u64 delta, struct sched_entity *se)
    2 {
    3     if (unlikely(se->load.weight != NICE_0_LOAD))
    4         delta = __calc_delta(delta, NICE_0_LOAD, &se->load);
    5 
    6     return delta;
    7 } 
    

    第3行决断当前进度nice值是还是不是为0,假使是的话,间接回到真实运营时刻delta(nice0级其余进度实际运维时刻和设想运行时刻值卓绝);假使不是的话,第4行将真实时间调换到虚构时间。上边大家深入分析__calc_delta函数(kernel/sched/fair.c):

     1 static u64 __calc_delta(u64 delta_exec, unsigned long weight, struct load_weight *lw)
     2 {
     3     u64 fact = scale_load_down(weight);
     4     int shift = WMULT_SHIFT;
     5 
     6     __update_inv_weight(lw);
     7 
     8     if (unlikely(fact >> 32)) {
     9         while (fact >> 32) {
    10             fact >>= 1;
    11             shift--;
    12         }
    13     }
    14 
    15     /* hint to use a 32x32->64 mul */
    16     fact = (u64)(u32)fact * lw->inv_weight;
    17 
    18     while (fact >> 32) {
    19         fact >>= 1;
    20         shift--;
    21     }
    22 
    23     return mul_u64_u32_shr(delta_exec, fact, shift);
    24 }
    

    该函数实践了三种算法:要么是delta_exec * weight / lw.weight,要么是(delta_exec * (weight * lw->inv_weight)) >> WMULT_SHIFT。总结的结果就是改动后的虚构时间。至此,scheduler_tick函数大概就解析完了,当然还会有局地细节尚未深入分析到,进度调治那块特别混乱,要想把装有函数分析完非常耗时和生机,未来若是条分缕析到的话,再逐级补充。scheduler_tick函数主要更新进度的运营时刻,包含物理时间和编造时间。

    2.进程优先级设置的函数,进度的优先级和调节关系紧凑,通过上边解析可以见见,总括进程的虚构运维时刻要用到优先级,优先级决定进程权重,权重决定进程设想时间的充实速度,最后决定进度可运行时刻的长短。权重越大的进程能够实施的岁月越长。从effective_prio函数开首(kernel/sched/core.c):

     1 static int effective_prio(struct task_struct *p)
     2 {
     3     p->normal_prio = normal_prio(p);
     4     /*
     5      * If we are RT tasks or we were boosted to RT priority,
     6      * keep the priority unchanged. Otherwise, update priority
     7      * to the normal priority:
     8      */
     9     if (!rt_prio(p->prio))
    10         return p->normal_prio;
    11     return p->prio;
    12 }
    

    该函数在进度创立时仍然在顾客的nice系统调用中都会被调用到,来安装进度的移动优先级(进度的三种优先级:活动优先级prio,静态优先级static_prio,普通优先级normal_prio),该函数设计的有一定技术性,函数的重返值是用来设置进程的移位优先级,但是在函数体中也把进度的平常优先级设置了。第3行设置进程的日常优先级,随后解析normal_prio函数。第9-11行,通过进程的活动优先级可以剖断出该进程是还是不是实时进程,假使是实时进度,则试行11行,再次回到p->prio,实时进度的移动优先级不改变。不然再次回到p->normal_prio,那表明普通进程的移动优先级等于它的常备优先级。下边大家看看normal_prio函数(kernel/sched/core.c):

     1 static inline int normal_prio(struct task_struct *p)
     2 {
     3     int prio;
     4 
     5     if (task_has_dl_policy(p))
     6         prio = MAX_DL_PRIO-1;
     7     else if (task_has_rt_policy(p))
     8         prio = MAX_RT_PRIO-1 - p->rt_priority;
     9     else
    10         prio = __normal_prio(p);
    11     return prio;
    12 }
    

    该函数用来设置进程的平常优先级。第5行判别当前历程是否悠闲进程,是的话设置进度的常常优先级为-1(-1是悠闲进度的优先级),不然的话第7行判断进程是还是不是实时进度,是的话设置实时进程的常备优先级为0-99(数字越小优先级越高),能够看出那块减去了p->rt_priority,相比较奇异,那是因为实时进程描述符的rt_priority成员中优先寄存了它自个儿的优先级(数字也是0-99,但在那间数字越大,优先级越高),因而往p->prio中倒换的时候,须求管理一下,MAX_RT_PRIO值为100,因此MAX_RT_P帕杰罗IO-1-(0,99)就倒换来了(99,0),那可是是个小本事。假设当前历程亦非实时进度(表达是通常进度喽),则第10行将经过的静态优先级存入prio中,然后再次来到(也便是说普通进度的常备优先级等于其静态优先级)。

    总括下,总共有三种进程:空闲进程,实时进度,普通进度;各个进度都饱含二种优先级:活动优先级,普通优先级,静态优先级。空闲进度的平时优先级永久-1,实时进度的日常优先级是基于p->rt_priority设定的(0-99),普通进程的常见优先级是依赖其静态优先级设定的(100-139)。

    3.进度权重设置的函数,上面大家提到,进度的初期级决定进度的权重。权重进而决定进度运维时刻的长短。我们先解析和权重相关的数据结构。

    struct load_weight(include/linux/sched.h)

    1 struct load_weight {
    2     unsigned long weight;
    3     u32 inv_weight;
    4 };
    

    各种进度描述符,调度实体,就绪对列中都含有多少个该项目变量,用来保存各自的权重。成员weight中寄存进程优先级所对应的权重。成员inv_weight中存放NICE_0_LOAD/weight的结果,这么些结果乘以进度运转的时日间距delta_exec便是进程运营的虚拟时间。由此引进权重正是为了总结进度的设想时间。在此将中间的臆度结果保存下来,前边就毫无再计算了,直接可以用。

    数组prio_to_weight[40](kernel/sched/sched.h)

     1 static const int prio_to_weight[40] = {
     2  /* -20 */     88761,     71755,     56483,     46273,     36291,
     3  /* -15 */     29154,     23254,     18705,     14949,     11916,
     4  /* -10 */      9548,      7620,      6100,      4904,      3906,
     5  /*  -5 */      3121,      2501,      1991,      1586,      1277,
     6  /*   0 */      1024,       820,       655,       526,       423,
     7  /*   5 */       335,       272,       215,       172,       137,
     8  /*  10 */       110,        87,        70,        56,        45,
     9  /*  15 */        36,        29,        23,        18,        15,
    10 };
    

    该数组是平常进度的优先级和权重对应涉及。数组元素是权重值,分别是优先级从100-139依然nice值从-20-+19所对应的权重值。nice值(-20-+19)是从客商空间看见的经常性进度的优先级,和基础空间的优先级(100-139)一一对应。struct load_weight中的weight成员存纠正是这一个权重值。

    高级中学级结果数组prio_to_wmult[40] (kernel/sched/sched.h)

     1 static const u32 prio_to_wmult[40] = {
     2  /* -20 */     48388,     59856,     76040,     92818,    118348,
     3  /* -15 */    147320,    184698,    229616,    287308,    360437,
     4  /* -10 */    449829,    563644,    704093,    875809,   1099582,
     5  /*  -5 */   1376151,   1717300,   2157191,   2708050,   3363326,
     6  /*   0 */   4194304,   5237765,   6557202,   8165337,  10153587,
     7  /*   5 */  12820798,  15790321,  19976592,  24970740,  31350126,
     8  /*  10 */  39045157,  49367440,  61356676,  76695844,  95443717,
     9  /*  15 */ 119304647, 148102320, 186737708, 238609294, 286331153,
    10 };
    

    该数组成分正是上个数组成分被NICE_0_LOAD除的结果,一一对应。struct load_weight中的inv_weight成员存纠正是这一个值。

    上边大家深入分析和权重设置相关的函数。从set_load_weight函数起头(kernel/sched/core.c):

     1 static void set_load_weight(struct task_struct *p)
     2 {
     3     int prio = p->static_prio - MAX_RT_PRIO;
     4     struct load_weight *load = &p->se.load;
     5 
     6     /*
     7      * SCHED_IDLE tasks get minimal weight:
     8      */
     9     if (p->policy == SCHED_IDLE) {
    10         load->weight = scale_load(WEIGHT_IDLEPRIO);
    11         load->inv_weight = WMULT_IDLEPRIO;
    12         return;
    13     }
    14 
    15     load->weight = scale_load(prio_to_weight[prio]);
    16     load->inv_weight = prio_to_wmult[prio];
    17 } 
    

    该函数用来设置进度p的权重。第3行将进度p的静态优先级调换来数组下标(减去100,从100-139--->0-39)。第4行获得当前经过的调整实体中的权重结构体指针,存入load中。第9-12行,假设当前历程是悠闲进度,则设置相应的权重和中路总计结果。第15-16行,设置实时进程和平日进度的权重和高级中学级总计结果。

    出于就绪队列中也含有权重结构体,所以也要对它们进行安装。使用以下函数(kernel/sched/fair.c):

    1 static inline void update_load_set(struct load_weight *lw, unsigned long w)
    2 {
    3     lw->weight = w;
    4     lw->inv_weight = 0;
    5 }
    

    该函数用来设置就绪队列的权重。

    1 static inline void update_load_add(struct load_weight *lw, unsigned long inc)
    2 {
    3     lw->weight += inc;
    4     lw->inv_weight = 0;
    5 }
    

    当有进度步入就绪队列,使用该函数扩展就绪队列的权重。

    1 static inline void update_load_sub(struct load_weight *lw, unsigned long dec)
    2 {
    3     lw->weight -= dec;
    4     lw->inv_weight = 0;
    5 }
    

    当有进度从就绪队列移除时,使用该函数减弱就绪队列的权重。就绪队列的load_weight.inv_weight成员总是0,因为不会接纳到就绪队列的该域。

    4.划算进度延迟周期的相关函数。进程的延迟周恒生期货指数的是将具有进度实施三次的日子。当就绪队列中的进度数量不高于规定的时候,内核有三个固定的推迟周期供调治使用,但是当进程数量超过规定现在,就须要对该固定延迟周期进行扩张(不然随着进度越来越多,每一种进程分配的试行时间会越少)。上面看看sched_slice函数(kernel/sched/fair.c):

     1 static u64 sched_slice(struct cfs_rq *cfs_rq, struct sched_entity *se)
     2 {
     3     u64 slice = __sched_period(cfs_rq->nr_running + !se->on_rq);
     4 
     5     for_each_sched_entity(se) {
     6         struct load_weight *load;
     7         struct load_weight lw;
     8 
     9         cfs_rq = cfs_rq_of(se);
    10         load = &cfs_rq->load;
    11 
    12         if (unlikely(!se->on_rq)) {
    13             lw = cfs_rq->load;
    14 
    15             update_load_add(&lw, se->load.weight);
    16             load = &lw;
    17         }
    18         slice = __calc_delta(slice, se->load.weight, load);
    19     }
    20     return slice;
    21 }
    

    直白看第18行,__calc_delta用来测算当前历程在延迟周期中可占的年华(约等于“时间片”,就是当下进程可用的命宫)。__calc_delta函数很苍劲,记得前边在entity_tick函数中也调用过该函数(entity_tick--->update_curr--->calc_delta_fair--->__calc_delta),那时利用该函数是为着将经过运转过的情理时间(真实时间)转变来设想时间;而在这里处,大家用它来测算当前经过在延迟周期中可占的时辰(也等于“时间片”)。前边提过,__calc_delta中用到param1 * param2 / param3.weight以此公式(param代表该函数接收的参数),当param2的值固定不变(等于NICE_0_LOAD),param3(代表当前进度的权重)在转换时,该函数是用来改动真实时间和设想时间的;当param3(代表当前cfs_rq的权重,cfs_rq->load->weight)的值固定不改变,param2在扭转(代表当前进度的权重)时,该函数则是用来计量当前经过应该运营的年月。因而第18行总结结果slice正是当下进度应该运行的实际时间。下边再看二个函数sched_vslice(kernel/sched/fair.c):

    1 static u64 sched_vslice(struct cfs_rq *cfs_rq, struct sched_entity *se)
    2 {
    3     return calc_delta_fair(sched_slice(cfs_rq, se), se);
    4 }
    

    该函数用来将经过应该运转的真实时间转变到应该运营的虚拟时间,以供调整使用。能够看来该函数调用了cals_delta_fair来开展时间更改,后面已分析过,不再赘述。

    5.取舍下二个亟需调治的进度。所利用的函数是pick_next_task_fair,代码如下(kernel/sched/fair.c):

    图片 8图片 9

      1 static struct task_struct *
      2 pick_next_task_fair(struct rq *rq, struct task_struct *prev)
      3 {
      4     struct cfs_rq *cfs_rq = &rq->cfs;
      5     struct sched_entity *se;
      6     struct task_struct *p;
      7     int new_tasks;
      8 
      9 again:
     10 #ifdef CONFIG_FAIR_GROUP_SCHED
     11     if (!cfs_rq->nr_running)
     12         goto idle;
     13 
     14     if (prev->sched_class != &fair_sched_class)
     15         goto simple;
     16 
     17     /*
     18      * Because of the set_next_buddy() in dequeue_task_fair() it is rather
     19      * likely that a next task is from the same cgroup as the current.
     20      *
     21      * Therefore attempt to avoid putting and setting the entire cgroup
     22      * hierarchy, only change the part that actually changes.
     23      */
     24 
     25     do {
     26         struct sched_entity *curr = cfs_rq->curr;
     27 
     28         /*
     29          * Since we got here without doing put_prev_entity() we also
     30          * have to consider cfs_rq->curr. If it is still a runnable
     31          * entity, update_curr() will update its vruntime, otherwise
     32          * forget we've ever seen it.
     33          */
     34         if (curr && curr->on_rq)
     35             update_curr(cfs_rq);
     36         else
     37             curr = NULL;
     38 
     39         /*
     40          * This call to check_cfs_rq_runtime() will do the throttle and
     41          * dequeue its entity in the parent(s). Therefore the 'simple'
     42          * nr_running test will indeed be correct.
     43          */
     44         if (unlikely(check_cfs_rq_runtime(cfs_rq)))
     45             goto simple;
     46 
     47         se = pick_next_entity(cfs_rq, curr);
     48         cfs_rq = group_cfs_rq(se);
     49     } while (cfs_rq);
     50 
     51     p = task_of(se);
     52 
     53     /*
     54      * Since we haven't yet done put_prev_entity and if the selected task
     55      * is a different task than we started out with, try and touch the
     56      * least amount of cfs_rqs.
     57      */
     58     if (prev != p) {
     59         struct sched_entity *pse = &prev->se;
     60 
     61         while (!(cfs_rq = is_same_group(se, pse))) {
     62             int se_depth = se->depth;
     63             int pse_depth = pse->depth;
     64 
     65             if (se_depth <= pse_depth) {
     66                 put_prev_entity(cfs_rq_of(pse), pse);
     67                 pse = parent_entity(pse);
     68             }
     69             if (se_depth >= pse_depth) {
     70                 set_next_entity(cfs_rq_of(se), se);
     71                 se = parent_entity(se);
     72             }
     73         }
     74 
     75         put_prev_entity(cfs_rq, pse);
     76         set_next_entity(cfs_rq, se);
     77     }
     78 
     79     if (hrtick_enabled(rq))
     80         hrtick_start_fair(rq, p);
     81 
     82     return p;
     83 simple:
     84     cfs_rq = &rq->cfs;
     85 #endif
     86 
     87     if (!cfs_rq->nr_running)
     88         goto idle;
     89 
     90     put_prev_task(rq, prev);
     91 
     92     do {
     93         se = pick_next_entity(cfs_rq, NULL);
     94         set_next_entity(cfs_rq, se);
     95         cfs_rq = group_cfs_rq(se);
     96     } while (cfs_rq);
     97 
     98     p = task_of(se);
     99 
    100     if (hrtick_enabled(rq))
    101         hrtick_start_fair(rq, p);
    102 
    103     return p;
    104 
    105 idle:
    106     new_tasks = idle_balance(rq);
    107     /*
    108      * Because idle_balance() releases (and re-acquires) rq->lock, it is
    109      * possible for any higher priority task to appear. In that case we
    110      * must re-start the pick_next_entity() loop.
    111      */
    112     if (new_tasks < 0)
    113         return RETRY_TASK;
    114 
    115     if (new_tasks > 0)
    116         goto again;
    117 
    118     return NULL;
    119 }
    

    pick_next_task_fair

    该函数会被赋给公平级调动度类的pick_next_task成员(.pick_next_task = pick_next_task_fair),在schedule函数中会调用该函数采纳下多个亟需调用的长河,然后开展进度切换。第11-12行假设当前稳妥队列中的进度数量为0,则脱离函数。第25-49行对持有的调节组举行遍历,从当中挑选下贰个可调解的历程,而不只局限在近期队列的当前组。第26行取安妥前调节实体,第34-37行如若存在贰个当下调治实体(进程)而且正在周转,则更新进程的周转时刻,不然就绪队列cfs_rq.curr置为null,表示方今无进度运维。第47行获取下四个调治实体,待会来解析该函数。第48行获取下个调解实体所持有的就绪队列my_q(代表一个调解组),若是调节组非空,则步向下二回巡回,在新的服服帖帖队列(调解组)中选拔下贰个可调解进度,直到有个别实体未有团结的就绪队列截止(遍历完全部的调解组了)。退出循环后,能够窥见此时的se是所遍历的终极二个调整组的下个可运营实体。第51行获得se对应的进度描述符,第58-77行,假诺当前进程和下贰个进度(se所对应的经过)不是同一个的话,则试行if体,第59行将这段时间历程的调解实体指针装入pse中,第61-72行假使当前进程和下一个调节的进度(se对应的进度)不属于同一调解组,则步向循环。不然,实行第75-76行,将日前历程放回就绪队列,将下个进度从稳当队列中拿出,那四个函数涉及到了就绪队列的出队和入队操作,大家在下边分析。第61-73的巡回依据近些日子实体和下个调整实体的节点深度开展调治(同叁个品级的长河能力切换)。上边看看pick_next_entity,put_prev_entity和set_prev_entity函数代码(kernel/sched/fair.c):

     1 static struct sched_entity *
     2 pick_next_entity(struct cfs_rq *cfs_rq, struct sched_entity *curr)
     3 {
     4     struct sched_entity *left = __pick_first_entity(cfs_rq);
     5     struct sched_entity *se;
     6 
     7     /*
     8      * If curr is set we have to see if its left of the leftmost entity
     9      * still in the tree, provided there was anything in the tree at all.
    10      */
    11     if (!left || (curr && entity_before(curr, left)))
    12         left = curr;
    13 
    14     se = left; /* ideally we run the leftmost entity */
    15 
    16     /*
    17      * Avoid running the skip buddy, if running something else can
    18      * be done without getting too unfair.
    19      */
    20     if (cfs_rq->skip == se) {
    21         struct sched_entity *second;
    22 
    23         if (se == curr) {
    24             second = __pick_first_entity(cfs_rq);
    25         } else {
    26             second = __pick_next_entity(se);
    27             if (!second || (curr && entity_before(curr, second)))
    28                 second = curr;
    29         }
    30 
    31         if (second && wakeup_preempt_entity(second, left) < 1)
    32             se = second;
    33     }
    34 
    35     /*
    36      * Prefer last buddy, try to return the CPU to a preempted task.
    37      */
    38     if (cfs_rq->last && wakeup_preempt_entity(cfs_rq->last, left) < 1)
    39         se = cfs_rq->last;
    40 
    41     /*
    42      * Someone really wants this to run. If it's not unfair, run it.
    43      */
    44     if (cfs_rq->next && wakeup_preempt_entity(cfs_rq->next, left) < 1)
    45         se = cfs_rq->next;
    46 
    47     clear_buddies(cfs_rq, se);
    48 
    49     return se;
    50 }
    

    该函数接纳下贰个调解的实业。第4行将红黑树的最左侧实体赋给left,第11-12行如若红黑树的最左边实体为空或许当前实体运营的杜撰时间低于下几个实体(继续当前的实业),把近期实体赋给left,实际上left指向的是下一个要实施的历程(该进程要么依旧脚下经过,要么是下三个经过),第14行将left赋给se,第20-33行如果se进度是须求跳过的进度(无法被调治),实行if体,借使se进程是当下进度,则选择红黑数最左的历程赋给second,不然se进度一度是最左的长河,就把next指向的长河赋给second(next指向的是刚刚被唤起的进度),第32行将second再度赋给se,se是选择出来的下个进程。第38-45,再一次决断,要么把last指向的历程赋给se,要么把next指向进程赋给se,内核更偏侧于把last赋给se,因为last是提醒next进度的进程(平日就是近期历程),所以进行last就意味着不用切换进度,功用最高。第47行清理掉next和last域。第31,38,44行使用到的wakeup_preempt_entity函数大家在经过唤醒那块再解析。

     1 static void put_prev_entity(struct cfs_rq *cfs_rq, struct sched_entity *prev)
     2 {
     3     /*
     4      * If still on the runqueue then deactivate_task()
     5      * was not called and update_curr() has to be done:
     6      */
     7     if (prev->on_rq)
     8         update_curr(cfs_rq);
     9 
    10     /* throttle cfs_rqs exceeding runtime */
    11     check_cfs_rq_runtime(cfs_rq);
    12 
    13     check_spread(cfs_rq, prev);
    14     if (prev->on_rq) {
    15         update_stats_wait_start(cfs_rq, prev);
    16         /* Put 'current' back into the tree. */
    17         __enqueue_entity(cfs_rq, prev);
    18         /* in !on_rq case, update occurred at dequeue */
    19         update_entity_load_avg(prev, 1);
    20     }
    21     cfs_rq->curr = NULL;
    22 } 
    

    该函数将方今调度实体放回就绪队列。第7-8行要是当前实体正在运维,更新其时间片。第17行将最近实体插手到安妥队列中,待会深入分析__enqueue_entity函数。第21行将就绪队列的curr域置为null,因为近日历程已经放回就绪队列了,就表示近来尚无经过正在实施了,因而curr为空。

     1 static void
     2 set_next_entity(struct cfs_rq *cfs_rq, struct sched_entity *se)
     3 {
     4     /* 'current' is not kept within the tree. */
     5     if (se->on_rq) {
     6         /*
     7          * Any task has to be enqueued before it get to execute on
     8          * a CPU. So account for the time it spent waiting on the
     9          * runqueue.
    10          */
    11         update_stats_wait_end(cfs_rq, se);
    12         __dequeue_entity(cfs_rq, se);
    13     }
    14 
    15     update_stats_curr_start(cfs_rq, se);
    16     cfs_rq->curr = se;
    17 #ifdef CONFIG_SCHEDSTATS
    18     /*
    19      * Track our maximum slice length, if the CPU's load is at
    20      * least twice that of our own weight (i.e. dont track it
    21      * when there are only lesser-weight tasks around):
    22      */
    23     if (rq_of(cfs_rq)->load.weight >= 2*se->load.weight) {
    24         se->statistics.slice_max = max(se->statistics.slice_max,
    25             se->sum_exec_runtime - se->prev_sum_exec_runtime);
    26     }
    27 #endif
    28     se->prev_sum_exec_runtime = se->sum_exec_runtime;
    29 }
    

    该函数将下贰个被调节实体从妥善队列中收取。第12行完结取出操作,待会剖判该函数。第16行将抽出的调治实体指针赋给就绪队列的curr,那么此时就有了正在运作的历程了。前面包车型地铁代码更新当前历程的时日总括消息。

    6.就绪队列的入队和出队操作(kernel/sched/fair.c)。

     1 static void __enqueue_entity(struct cfs_rq *cfs_rq, struct sched_entity *se)
     2 {
     3     struct rb_node **link = &cfs_rq->tasks_timeline.rb_node;
     4     struct rb_node *parent = NULL;
     5     struct sched_entity *entry;
     6     int leftmost = 1;
     7 
     8     /*
     9      * Find the right place in the rbtree:
    10      */
    11     while (*link) {
    12         parent = *link;
    13         entry = rb_entry(parent, struct sched_entity, run_node);
    14         /*
    15          * We dont care about collisions. Nodes with
    16          * the same key stay together.
    17          */
    18         if (entity_before(se, entry)) {
    19             link = &parent->rb_left;
    20         } else {
    21             link = &parent->rb_right;
    22             leftmost = 0;
    23         }
    24     }
    25 
    26     /*
    27      * Maintain a cache of leftmost tree entries (it is frequently
    28      * used):
    29      */
    30     if (leftmost)
    31         cfs_rq->rb_leftmost = &se->run_node;
    32 
    33     rb_link_node(&se->run_node, parent, link);
    34     rb_insert_color(&se->run_node, &cfs_rq->tasks_timeline);
    35 }
    

    该函数实现入队操作。第3行获得就绪队列中红黑树的根节点,将树根指针保存在link中。第12行parent最近指向树根。第13行拿到树根节点的调治实体,保存在entry中。第18-22行,相比较要入队的实业中的已运营设想时间和树根实体中的该音讯,假如前面一个小的话,就要插入到树的左子树上(link指向树根的左孩子,再度步向循环,类似于递归),不然就要插入到树的右子树上(同上)。那块就将经过的调节战术表现的痛快淋漓:依照进程已运维的虚构时间来决定进度的调节,红黑树的左子树比右子树要先被调整,已运营的虚构时间越小的进度越在树的左边手。第30-31行,倘若入队的实体最后被插在左孩子上(该入队实体仍是就绪队列上最靠前的实业,后一次就能被调用),那么将要让就绪队列的rb_leftmost指向入队实体。rb_leftmost指针始终对准下一次要被调节的实业(进度)。最终两行要给红黑数重新上色。

     1 static void __dequeue_entity(struct cfs_rq *cfs_rq, struct sched_entity *se)
     2 {
     3     if (cfs_rq->rb_leftmost == &se->run_node) {
     4         struct rb_node *next_node;
     5 
     6         next_node = rb_next(&se->run_node);
     7         cfs_rq->rb_leftmost = next_node;
     8     }
     9 
    10     rb_erase(&se->run_node, &cfs_rq->tasks_timeline);
    11 }
    

    该函数完毕出队操作。第3行判别要出队的实体是否红黑树最左侧的儿女(rb_leftmost所指向的),假如不是的话,第10行直接删除该出队实体。不然实行if体,第6行找到出队实体的下一个实体(中序遍历的下一个节点,也便是当出队实体删除后,最侧面的儿女),赋给next_node。第7行让rb_leftmost指向next_node。在剔除掉要出队实体后,下三个亟需被调节的实体也就安装好了。

    7.睡眠经过被提示后抢占当前历程。当某些财富空出来后,等待该财富的长河就能被提醒,唤醒后可能将在抢占当前进程,因而那块的函数也亟需分析(kernel/sched/core.c)。

     1 static int
     2 try_to_wake_up(struct task_struct *p, unsigned int state, int wake_flags)
     3 {
     4     unsigned long flags;
     5     int cpu, success = 0;
     6 
     7     /*
     8      * If we are going to wake up a thread waiting for CONDITION we
     9      * need to ensure that CONDITION=1 done by the caller can not be
    10      * reordered with p->state check below. This pairs with mb() in
    11      * set_current_state() the waiting thread does.
    12      */
    13     smp_mb__before_spinlock();
    14     raw_spin_lock_irqsave(&p->pi_lock, flags);
    15     if (!(p->state & state))
    16         goto out;
    17 
    18     success = 1; /* we're going to change ->state */
    19     cpu = task_cpu(p);
    20 
    21     if (p->on_rq && ttwu_remote(p, wake_flags))
    22         goto stat;
    23 
    24 #ifdef CONFIG_SMP
    25     /*
    26      * If the owning (remote) cpu is still in the middle of schedule() with
    27      * this task as prev, wait until its done referencing the task.
    28      */
    29     while (p->on_cpu)
    30         cpu_relax();
    31     /*
    32      * Pairs with the smp_wmb() in finish_lock_switch().
    33      */
    34     smp_rmb();
    35 
    36     p->sched_contributes_to_load = !!task_contributes_to_load(p);
    37     p->state = TASK_WAKING;
    38 
    39     if (p->sched_class->task_waking)
    40         p->sched_class->task_waking(p);
    41 
    42     cpu = select_task_rq(p, p->wake_cpu, SD_BALANCE_WAKE, wake_flags);
    43     if (task_cpu(p) != cpu) {
    44         wake_flags |= WF_MIGRATED;
    45         set_task_cpu(p, cpu);
    46     }
    47 #endif /* CONFIG_SMP */
    48 
    49     ttwu_queue(p, cpu);
    50 stat:
    51     ttwu_stat(p, cpu, wake_flags);
    52 out:
    53     raw_spin_unlock_irqrestore(&p->pi_lock, flags);
    54 
    55     return success;
    56 }
    

    该函数会唤醒参数p钦赐的经过,将经过步向就绪队列中等待调节。第15行判定p进度的状态码和传进来的状态码是或不是一律,不平等的话函数甘休(不均等则表明经过等待的规格未知足)。第19行获得要提示进程p原先所在的cpu号。第37行设置要升迁进度p的状态为TASK_WAKING。第40行试行进度p的调解类中的task_waking函数,该函数指针指向了task_waking_fair函数,该函数根本是对睡眠进度的已运维设想时间开展补缺,待会深入分析该函数。第42表现刚唤醒进程p选拔三个方便的就绪队列供其步向,重返就绪队列所在的cpu号。第43行借使经过p所在的原本cpu和为它选用的cpu不是同多个以来,第45行越来越精雕细琢程p的cpu号。

     1 void wake_up_new_task(struct task_struct *p)
     2 {
     3     unsigned long flags;
     4     struct rq *rq;
     5 
     6     raw_spin_lock_irqsave(&p->pi_lock, flags);
     7 #ifdef CONFIG_SMP
     8     /*
     9      * Fork balancing, do it here and not earlier because:
    10      *  - cpus_allowed can change in the fork path
    11      *  - any previously selected cpu might disappear through hotplug
    12      */
    13     set_task_cpu(p, select_task_rq(p, task_cpu(p), SD_BALANCE_FORK, 0));
    14 #endif
    15 
    16     /* Initialize new task's runnable average */
    17     init_task_runnable_average(p);
    18     rq = __task_rq_lock(p);
    19     activate_task(rq, p, 0);
    20     p->on_rq = 1;
    21     trace_sched_wakeup_new(p, true);
    22     check_preempt_curr(rq, p, WF_FORK);
    23 #ifdef CONFIG_SMP
    24     if (p->sched_class->task_woken)
    25         p->sched_class->task_woken(rq, p);
    26 #endif
    27     task_rq_unlock(rq, p, &flags);
    28 }
    

     该函数用来唤醒刚创立好的进度,而上个函数是用来唤起睡眠中的进程。第13行事将唤起的进度p设置合适的cpu。第17行设置进度p的可运转时刻长度(类似“时间片”),第19行activate_task函数主要将唤起的历程p加入就绪队列,并革新队列的时日,起首化进程p的日子等,该函数中的调用关系大概为activate_task->enqueue_task->enqueue_task_fair(p->sched_class->enqueue_task)->enqueue_entity。第22行check_preempt_curr函数调用了check_preempt_wakeup函数,来检查唤醒进程是不是能抢占当前进程,下边深入分析该函数(kernel/sched/fair.c)。

     1 static void check_preempt_wakeup(struct rq *rq, struct task_struct *p, int wake_flags)
     2 {
     3     struct task_struct *curr = rq->curr;
     4     struct sched_entity *se = &curr->se, *pse = &p->se;
     5     struct cfs_rq *cfs_rq = task_cfs_rq(curr);
     6     int scale = cfs_rq->nr_running >= sched_nr_latency;
     7     int next_buddy_marked = 0;
     8 
     9     if (unlikely(se == pse))
    10         return;
    11 
    12     /*
    13      * This is possible from callers such as move_task(), in which we
    14      * unconditionally check_prempt_curr() after an enqueue (which may have
    15      * lead to a throttle).  This both saves work and prevents false
    16      * next-buddy nomination below.
    17      */
    18     if (unlikely(throttled_hierarchy(cfs_rq_of(pse))))
    19         return;
    20 
    21     if (sched_feat(NEXT_BUDDY) && scale && !(wake_flags & WF_FORK)) {
    22         set_next_buddy(pse);
    23         next_buddy_marked = 1;
    24     }
    25 
    26     /*
    27      * We can come here with TIF_NEED_RESCHED already set from new task
    28      * wake up path.
    29      *
    30      * Note: this also catches the edge-case of curr being in a throttled
    31      * group (e.g. via set_curr_task), since update_curr() (in the
    32      * enqueue of curr) will have resulted in resched being set.  This
    33      * prevents us from potentially nominating it as a false LAST_BUDDY
    34      * below.
    35      */
    36     if (test_tsk_need_resched(curr))
    37         return;
    38 
    39     /* Idle tasks are by definition preempted by non-idle tasks. */
    40     if (unlikely(curr->policy == SCHED_IDLE) &&
    41         likely(p->policy != SCHED_IDLE))
    42         goto preempt;
    43 
    44     /*
    45      *  do not preempt non-idle tasks (their preemption
    46      * is driven by the tick):
    47      */
    48     if (unlikely(p->policy != SCHED_NORMAL) || !sched_feat(WAKEUP_PREEMPTION))
    49         return;
    50 
    51     find_matching_se(&se, &pse);
    52     update_curr(cfs_rq_of(se));
    53     BUG_ON(!pse);
    54     if (wakeup_preempt_entity(se, pse) == 1) {
    55         /*
    56          * Bias pick_next to pick the sched entity that is
    57          * triggering this preemption.
    58          */
    59         if (!next_buddy_marked)
    60             set_next_buddy(pse);
    61         goto preempt;
    62     }
    63 
    64     return;
    65 
    66 preempt:
    67     resched_task(curr);
    68     /*
    69      * Only set the backward buddy when the current task is still
    70      * on the rq. This can happen when a wakeup gets interleaved
    71      * with schedule on the ->pre_schedule() or idle_balance()
    72      * point, either of which can * drop the rq lock.
    73      *
    74      * Also, during early boot the idle thread is in the fair class,
    75      * for obvious reasons its a bad idea to schedule back to it.
    76      */
    77     if (unlikely(!se->on_rq || curr == rq->idle))
    78         return;
    79 
    80     if (sched_feat(LAST_BUDDY) && scale && entity_is_task(se))
    81         set_last_buddy(se);
    82 }
    

    第21-24行,假使张开了NEXT_BUDDY而且唤醒的历程不是新历程(而是睡眠进程),那么第22行就将cfs_rq的next指针指向唤醒的长河,况且安装标识。第36行一旦当前经过可以被私吞,函数直接回到。不然,第40-42行借使当前进度是悠闲进程并且被晋升的历程不是悠闲进度,则跳到preempt处,设置need_resched标记,达成抢占设置。第48行,倘若被唤起进度是悠闲进程恐怕批管理过程,直接重返,这一个经过无法抢占别的进度。第51行若是当前历程和被提示进度不在同超等级(同三个调治组),则寻找它们的古时候的人parent,把它们调治到均品级别,能力拓宽设想运维时刻的相比,进而决定是不是抢占。第54行,对现阶段进度和被升迁进度的设想运营时刻开展相比较,能够抢占的话(唤醒过程的杜撰时间低于当前进度)试行if体,跳到preempt处形成抢占。不然持有都不满足的话,当前经过无法被侵吞,推行第64行重临,随后分析该函数。第80-81行假设展开了LAST_BUDDY,就将cfs_rq的last指针指向唤醒进度的长河。在pick_next_entity函数中,next和last所指的经过会早日就绪队列的left进度被挑选。上边深入分析下wakeup_preempt_entity函数(kernel/sched/fair.c)。

     1 static int
     2 wakeup_preempt_entity(struct sched_entity *curr, struct sched_entity *se)
     3 {
     4     s64 gran, vdiff = curr->vruntime - se->vruntime;
     5 
     6     if (vdiff <= 0)
     7         return -1;
     8 
     9     gran = wakeup_gran(curr, se);
    10     if (vdiff > gran)
    11         return 1;
    12 
    13     return 0;
    14 }
    

    该函数是要保证在se实体在抢占curr实体时,curr实体已经运维过一段时间(具体来讲,物理时间1ms),第9行wakeup_gran函数是将sysctl_sched_wakeup_granularity的值(1ms)转变来se实体的虚拟时间,保存在gran中,第10行相比vdiff和gran大小,实际上是比较curr->vruntime 和 se->vruntime+gran,由此就算想让curr实体多推行gran时间,能力被攻克。

    末尾我们再剖析下 try_to_wake_up函数中第40行遗留的要命函数指针task_waking,该指针指向了task_waking_fair函数,代码如下(kernel/sched/fair.c):

     1 static void task_waking_fair(struct task_struct *p)
     2 {
     3     struct sched_entity *se = &p->se;
     4     struct cfs_rq *cfs_rq = cfs_rq_of(se);
     5     u64 min_vruntime;
     6 
     7 #ifndef CONFIG_64BIT
     8     u64 min_vruntime_copy;
     9 
    10     do {
    11         min_vruntime_copy = cfs_rq->min_vruntime_copy;
    12         smp_rmb();
    13         min_vruntime = cfs_rq->min_vruntime;
    14     } while (min_vruntime != min_vruntime_copy);
    15 #else
    16     min_vruntime = cfs_rq->min_vruntime;
    17 #endif
    18 
    19     se->vruntime -= min_vruntime;
    20     record_wakee(p);
    21 }
    

    该函数完毕对睡眠进程的设想时间补偿。思虑到睡觉时间长日子从没运行,由此第19行从唤醒进程se的已运营设想时间中减去就绪队列的小不点儿虚构时间,做点补偿,让其得以多运转一点小时。

    8.新历程的管理函数(kernel/sched/fair.c):

     1 static void task_fork_fair(struct task_struct *p)
     2 {
     3     struct cfs_rq *cfs_rq;
     4     struct sched_entity *se = &p->se, *curr;
     5     int this_cpu = smp_processor_id();
     6     struct rq *rq = this_rq();
     7     unsigned long flags;
     8 
     9     raw_spin_lock_irqsave(&rq->lock, flags);
    10 
    11     update_rq_clock(rq);
    12 
    13     cfs_rq = task_cfs_rq(current);
    14     curr = cfs_rq->curr;
    15 
    16     /*
    17      * Not only the cpu but also the task_group of the parent might have
    18      * been changed after parent->se.parent,cfs_rq were copied to
    19      * child->se.parent,cfs_rq. So call __set_task_cpu() to make those
    20      * of child point to valid ones.
    21      */
    22     rcu_read_lock();
    23     __set_task_cpu(p, this_cpu);
    24     rcu_read_unlock();
    25 
    26     update_curr(cfs_rq);
    27 
    28     if (curr)
    29         se->vruntime = curr->vruntime;
    30     place_entity(cfs_rq, se, 1);
    31 
    32     if (sysctl_sched_child_runs_first && curr && entity_before(curr, se)) {
    33         /*
    34          * Upon rescheduling, sched_class::put_prev_task() will place
    35          * 'current' within the tree based on its new key value.
    36          */
    37         swap(curr->vruntime, se->vruntime);
    38         resched_task(rq->curr);
    39     }
    40 
    41     se->vruntime -= cfs_rq->min_vruntime;
    42 
    43     raw_spin_unlock_irqrestore(&rq->lock, flags);
    44 }
    

    该函数在do_fork--->copy_process函数中调用,用来设置新创设进程的杜撰时间音讯。第5行取稳妥前的cpu号,第23行事新进度设置该cpu号。第29行将如今进程(父进度)的杜撰运转时刻拷贝给新进程(子进度)。第30行的place_entity函数完成新历程的“时间片”总括以致设想时间惩罚,之后将新进度步入红黑数中,待会剖判。第32行倘诺设置了子进度早日父进程运行的标记而且当前进度不为空且当前进度已运转的设想时间比新进度小,则推行if体,第37行交换当前历程和新历程的杜撰时间(新进度的设想时间变小,就排在了红黑树的左臂,当前进度以前,下一次就能够被调治),第38行设置双重调治标记。第41行给新进程的设想运转时刻减去队列的微小虚构时间来做一些补充(因为在上边的place_entity函数中给新进度的设想时间加了二遍min_vruntime,所以在这里处要减去),再来看看place_entity函数(kernel/sched/fair.c):

     1 static void
     2 place_entity(struct cfs_rq *cfs_rq, struct sched_entity *se, int initial)
     3 {
     4     u64 vruntime = cfs_rq->min_vruntime;
     5 
     6     /*
     7      * The 'current' period is already promised to the current tasks,
     8      * however the extra weight of the new task will slow them down a
     9      * little, place the new task so that it fits in the slot that
    10      * stays open at the end.
    11      */
    12     if (initial && sched_feat(START_DEBIT))
    13         vruntime += sched_vslice(cfs_rq, se);
    14 
    15     /* sleeps up to a single latency don't count. */
    16     if (!initial) {
    17         unsigned long thresh = sysctl_sched_latency;
    18 
    19         /*
    20          * Halve their sleep time's effect, to allow
    21          * for a gentler effect of sleepers:
    22          */
    23         if (sched_feat(GENTLE_FAIR_SLEEPERS))
    24             thresh >>= 1;
    25 
    26         vruntime -= thresh;
    27     }
    28 
    29     /* ensure we never gain time by being placed backwards. */
    30     se->vruntime = max_vruntime(se->vruntime, vruntime);
    31 }
    

    该函数完毕新历程的“时间片”总结和编造时间惩罚,并且将新进程进入就绪队列。第4行将就绪队列的min_vruntime值存入vruntime中,第12-13行,若是initial标识为1的话(表明当前测算的是新进度的时日),将总括出的新进度的设想时间片加上到vruntime中,累计到原因是调整系统要确认保障先把就绪队列中的全部的经过实行二回之后技巧实践新历程,一会具体解释。第16-17行,就算当前划算的不是新进程(睡眠的进度),把一个延迟周期的长短sysctl_sched_latency(6ms)赋给thresh,第24行thresh减半,第26行睡眠进度的杜撰运营时刻减去减半后的thresh,因为睡眠进程好长时间未运营,由此要开展设想时间补偿,把它已运转的杜撰时间减小一些,使得它能多运转一会。第30行将安装好的设想时间保存到进程调整实体的vruntime域。上边解释下怎么要对新历程张开设想时间惩罚,其实原因只有一个,便是调整系统要保管将就绪队列中幸存的长河推行贰次之后再实行新进度,那么就必得使新进程的 vruntime=cfs_rq->min_vruntime+新进度的虚构时间片,技术使得新历程插入到红黑树的入手,最终加入调解,不然不能保险全体进度在新进度此前实践。

    末尾,深入分析下和调治相关的那些函数推行的火候

    眼下在介绍这一个函数的时候,基本上都提到了会在哪里调用那一个函数,最终,大家再系统总括一下:

    经过调治分为三个部分:一是进度音信的改换,二是经过切换。进度切换只有三个函数schedule,schedule的运维机遇大家最后深入分析。其余函数的运转机会如下:

    1.scheduler_tick函数是在每一种石英钟中断中被调用,用来更新当前经过运转的年华。

    2.effective_prio函数是在开创二个新进度或许客商使用nice系统调用设置进度的先行级时调用,用来安装进度的在基础中优先级(不相同于nice值)。

    3.set_load_weight函数也是在成立新历程可能顾客使用nice()设置进度的预先级时调用,用来设置进程的权重。该函数和第22中学的函数其实是配套使用的,当设置可能转移了一个经过的事先级时,要么就要为这一个进程设置也许改动该优先级对应的权重。

    4.sched_slice函数首倘使在scheduler_tick->...->check_preempt_tick中调用(别的地点也是有),因而也是各样时钟周期调用叁次,用来计量当前进度应该实行的“时间片”,从而才具判定进度是还是不是业已超越它的时刻片,超过的话将要设置抢占标记,切换其他进度。

    5.pick_next_task_fair函数schedule中调用,用来挑选下二个要被调解的经过,然后技艺切换进程。它的实践机缘便是schedule的施行机缘,随后解析。

    6.__enqueue_entity/__dequeue_entity函数是在急需入就绪队列大概出就绪队列的地点被调用,由此选拔它们的地方相当多,举个例子schedule中调用(直接调用),就不一一分析了。

    7.try_to_wake_up/wake_up_new_task函数,前面二个在进度所等待的能源满意时被调用(常常在暂停内调用);后面一个是在成立好新历程后被调用。都是用来唤起进程的,前者唤醒睡眠进程,后面一个唤醒新进度并将经过进入就绪队列。

    8.task_fork_fair函数也是在新历程被创制好后调用,用来安装新过程的“时间片”等新闻,设置好现在新历程就能够被指示了。所以该函数和wake_up_new_task函数调用机缘是同等的。

    最后,大家深入分析schedule函数的调用机缘。该函数是并世无两兑现进程切换的函数。

    在深入分析schedule函数的调用时机在此之前,大家先为咱们介绍下“内核调节路线“的概念。所谓内核调整路线,正是由行车制动器踏板恐怕卓殊引出的施行路线,说白了,实行中断也许至极管理程序时就处在内核调节路线中,此时意味着的也是目前历程。内核调节路线能够嵌套(也等于能够嵌套中断),然而无论嵌套多少,最后都要赶回当前经过中,也正是说要从水源调节路线中回到,不得以在根本调节路线中开展进程切换(因而暂停管理程序中差别意调用能唤起进度睡眠的函数)。聊到底,内核要么处在进度中,要么处在内核调整路线中(其实根本调节路线也意味着当前进程,因为其有特殊性,所以大家单列出来谈),不会再有其他什么路线了。那么进度切换的机缘,或然说schedule函数被调用的空子,也就只只怕爆发于上述三种门路中。那么,1.当在进程中调用schedule函数时(正是ULK那本书上说的直白调用),注解当前历程因为等待资源还是别的原因需求挂起,主动抛弃行使cpu,调用schedule函数切换成别的进度中;2.当在根本调节路线中调用schedule函数时(上边说过了,内核调控路线中不一致敬切换进度),其实是在基础调节路线重返经过时调用(该机遇正是ULK上说的延期调用),表明有更重视的长河等待奉行,要求抢占当前进度,因而在脚刹踏板管理程序/格外管理程序重回时都要去反省当前进度是还是不是被私吞,能够抢占的话就要调用schedule函数进行进程切换,满含从系统调用中回到顾客空间时也要检查(那是联合的,因为系统调用本人也是充裕,因而从系统调用中回到也就是从这个管理程序中回到,通过系统调用步向到内核态也足以说是水源调节路线,不过经常不这么叫)当前进度的并吞标识,能发出抢占的话将在去调用schedule函数。至此,进程切换的机遇就分析完了。很好记的,要么是进度上下文产生经过切换(主动调用schedule),要么是从中断重返时切换,因而老是中断再次回到时务须要反省是或不是发生抢占,蕴涵从系统调用重回也属于这种情景。

     

    时至前几日,进程调整机制咱们就分析完了(其实只深入分析了CFS调解)。实时进程调解以往再剖判!

     

    参照书籍:《深切了解linux内核》

         《深切精通linux内核框架结构》

    参谋小说:blog.csdn.net/wudongxu/article/details/8574737

         blog.csdn.net/dog250/article/details/5302869

           chxxxyg.blog.163.com/blog/static/1502811932012912546208/

    本文由澳门新葡4473网站发布于项目,转载请注明出处:Linux内核——进程管理之CFS调度器(基于版本4.

    关键词:

上一篇:17级 车辆工程 周金霖 工资表

下一篇:没有了