w_tl00’s blog

インプットをまとめておく

グループスケジューリングはじめ

Linuxコンテナの基礎となる技術にcgroupがあります. 本ブログでは,cgroupの中でグループスケジューリングについて取り上げて少しまとめてみようと思います.

まずグループスケジューリングは何のためのものかというと次の例を考えるとわかりやすいです. システムにXさんのタスクが9つ,Yさんのタスクが1つあるとします.単純にスケジューリングすると,タスク数の割合が9:1でXさんの方がスケジューリングに有利になってしまいます. そこでこの不公平を解決しようと生まれたのがグループスケジューリングです. X,Yさんのプロセスをそれぞれまとめて扱い,50%ずつの時間を与えられるようにすればいいのでは?というモチベーションです.

Linuxではグループスケジューリングは,cgroup(control group)のcpuサブシステムを使用して実装されています.グループスケジューリングではtask_groupという構造体が重要になります.

/* Task group related information */
struct task_group {
    struct cgroup_subsys_state css;

#ifdef CONFIG_FAIR_GROUP_SCHED
    /* schedulable entities of this group on each CPU */
    struct sched_entity    **se;
    /* runqueue "owned" by this group on each CPU */
    struct cfs_rq      **cfs_rq;
    unsigned long     shares;

#ifdef CONFIG_SMP
    /*
    * load_avg can be heavily contended at clock tick time, so put
    * it in its own cacheline separated from the fields above which
    * will also be accessed at each tick.
    */
    atomic_long_t       load_avg ____cacheline_aligned;
#endif
#endif

#ifdef CONFIG_RT_GROUP_SCHED
    struct sched_rt_entity **rt_se;
    struct rt_rq       **rt_rq;

    struct rt_bandwidth    rt_bandwidth;
#endif

    struct rcu_head        rcu;
    struct list_head   list;

    struct task_group  *parent;
    struct list_head   siblings;
    struct list_head   children;

#ifdef CONFIG_SCHED_AUTOGROUP
    struct autogroup   *autogroup;
#endif

    struct cfs_bandwidth   cfs_bandwidth;

#ifdef CONFIG_UCLAMP_TASK_GROUP
    /* The two decimal precision [%] value requested from user-space */
    unsigned int      uclamp_pct[UCLAMP_CNT];
    /* Clamp values requested for a task group */
    struct uclamp_se   uclamp_req[UCLAMP_CNT];
    /* Effective clamp values used for a task group */
    struct uclamp_se   uclamp[UCLAMP_CNT];
#endif

};

task_struct構造体はhttps://elixir.bootlin.com/linux/v5.8.7/source/kernel/sched/core.c#L7313 このあたりでallocされています.

例えば,task_group構造体においてCompletely Fair Scheduler(だいたいのプロセスはこのスケジューラでスケジューリングされます)に関する部分を見ると,

 /* schedulable entities of this group on each CPU */
    struct sched_entity    **se;
    /* runqueue "owned" by this group on each CPU */
    struct cfs_rq      **cfs_rq;

各CPU上のcfs_rqに乗るsched_entity (スケジューリング対象となる構造体)と,そのsched_entityが選ばれたときに使うcfs_rqの情報を持っていることがわかります. ここにおけるsched_entityは実際のタスクではなく,このtask_groupに属するタスク群であることに注意してください.(task_struct構造体を見ると,task_groupへのポインタを持っていることがわかります.https://elixir.bootlin.com/linux/v5.8.7/source/include/linux/sched.h#L684) つまり,タスクをまとめてスケジューリングするためにタスク群としてのまとまりsched_entityをcfs_rqに乗せる.そして,タスク群としてのまとまりsched_entityが選ばれたとき,さらにtask_groupがもつcfs_rqを使ってsched_entityを選び直すという入れ子構造になっているということです.

最初のX, Yさんの例でいうと次のようになります.まず公平にXさん,Yさんのどちらかを選ぶ.次に選んだ人のタスクの中からタスクを選ぶ. このようにしてLinuxのグループスケジューリングは行われています. 次は帯域幅制御について書こうかな.