45
34

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

【読解入門】Linuxカーネル (スケジューラ編その3-2)

Last updated at Posted at 2019-03-12

各クラスのsched_class構造体メンバの処理内容(前回の続き)

前回はCPUのホット プラグ等で使用される緊急性が高い最高優先度のSTOPクラスの各メンバを見ました。今回は、リアルタイム応答性を実現する為のクラス、DEADLINEクラスとRTクラスを見てみる予定でした。しかし、DEADLINEクラスのアルゴリズムとコードの対応関係で腹落ちしない部分があり、そのまま投稿しても皆様も同じようになると判断し、DEADLINEクラスは次回 or 次々回に延伸し、今回はRTクラスを読解したいと思います。(DEADLINEクラスの説明を待っていた方がいたらすみません。)
* できる限り、一定の周期で投稿したかったので。

RTクラスについて

皆さんの中には、Linuxを搭載した製品開発者としてLinuxカーネルを使ったことがある方もいらっしゃると思います。その時にリアルタイムで処理したいプロセスのスケジューリングクラスを一般クラスからRTクラスに変更された方もいると思います。
ここでは、そのRTクラスの実装についてみていきます。
まずは、RTクラスの特徴を以下に箇条書きで述べます。(復習も兼ねて)
・RTクラスのプロセス(以後、RTプロセスと記載します)はFAIR(一般)クラスのプロセスより必ず優先して実行される。

・RTプロセスは、SCHED_FIFOとSCHED_RRのいずれかを設定できる。
 SCHED_FIFO:自発的にCPUを明け渡さない or 自分より高優先度のプロセスが実行可能とならない限り、CPUを握り続ける。
 SCHED_RR:一定周期ごとに同一優先度のプロセス間でラウンド ロビンする。
タイム スライスは/proc/sys/kernel/sched_rr_timeslice_msで決定し、デフォルトは100msのタイム スライスである。

include/linux/sched/rt.h
/*
 * default timeslice is 100 msecs (used only for SCHED_RR tasks).
 * Timeslices get refilled after they expire.
 */
#define RR_TIMESLICE            (100 * HZ / 1000)

・RTプロセスが実行可能状態になると、FAIRクラスや自分より優先度の低いRTプロセスからCPUの実行権を奪う。(プリエンプションする)

・RTプロセス暴走によるCPUハング状態を防ぐためにデフォルトではCPUの95%をRTプロセスに割り当て、残りの5%は一般プロセスに割り当てる。この割合はチューニング可能である。
 この設定は、/proc/sys/kernel/sched_rt_period_us、/proc/sys/kernel/sched_rt_runtime_usで行う。デフォルトでは、sched_rt_period_usが1000000 us、sched_rt_runtime_usが950000 us。
この意味は1,000,000us周期のうち、950,000usをrtプロセスに割り当てるということ。より、シビアに設定したいのであれば周期を小さくしてそれに応じて割り当て時間を変更する。(ただし、小さくすればするほどスループットは低下するので注意)
これらのコードは下記で定義されている。

kernel/sysctl.c
:
        {
                .procname       = "sched_rt_period_us",
              .data           = &sysctl_sched_rt_period,
                .maxlen         = sizeof(unsigned int),
                .mode           = 0644,
                .proc_handler   = sched_rt_handler,
        },
        {
                .procname       = "sched_rt_runtime_us",
              .data           = &sysctl_sched_rt_runtime,
                .maxlen         = sizeof(int),
                .mode           = 0644,
                .proc_handler   = sched_rt_handler,
        },

☆と★は以下で定義されている。

kernel/sched/core.c
/*
 * period over which we measure -rt task CPU usage in us.
 * default: 1s
 */
unsigned int sysctl_sched_rt_period = 1000000;



/*
 * part of the period that we allow rt tasks to run in us.
 * default: 0.95s
 */
int sysctl_sched_rt_runtime = 950000;

・割込みハンドラスレッド(以後、割込みスレッドと記載)は優先度50で動作する。これより高くすれば、割込みスレッドより優先して実行される。

・タイマ割込みのみ割込みスレッド化せずに従来の割込みハンドラとして動作する。(request_irq( )という割込みハンドラ登録関数にIRQF_TIMER(=__IRQF_TIMER | IRQF_NO_SUSPEND | IRQF_NO_THREAD★)を設定する)

・各RTプロセスの管理方法
a)下図の通り、0-99のbitmapの配列があり、それぞれに実行可能状態のプロセスが存在するか否かを示すbitを設けています。
この図の場合は、最高優先度の0はプロセスは存在せず、その次に高優先度(ユーザーが設定した優先度は98)にプロセスAとプロセスBが存在します。
これをリストで管理します。この図を頭に入れておきましょう。そうするとコードの読解力が向上します。
bitmap.jpg

b)RTクラスのプロセスのスケジューリングの管理はrunqueueで行います。このrunqueueはCPU毎に存在します。以下にrunqueueの構造体を示します。(理解しやすくするためにSMP関連、グループ スケジューリングを省略しています)
それぞれのメンバについてインラインでコメントします。

kernel/sched/sched.h
/* Real-Time classes' related field in a runqueue: */
struct rt_rq {
        struct rt_prio_array    active;
        //前述の優先度のbitmapと各優先度のlistを管理する構造体
        unsigned int            rt_nr_running;
        //RTクラスのプロセスで実行可能状態の総数
        unsigned int            rr_nr_running;
        //RTクラスのプロセスでSCHED_RRポリシーで実行可能状態の総数
        int                     rt_queued;
        //RTクラスのプロセスで実行可能状態のプロセスが存在するか否か
        //1が存在する、0が存在しない
        int                     rt_throttled;
        //RTクラスのプロセス群の帯域制限が必要か否か
        //1が必要、0が不要
        u64                     rt_time;
        //RTプロセス群の総実行時間
        u64                     rt_runtime;
        //RTプロセス群の帯域を示す。前述の950msが設定される
        /* Nests inside the rq lock: */
        raw_spinlock_t          rt_runtime_lock;
        //RTプロセス群の実行時間の計算をする際に使用する
};

3.RTクラスのsched_class構造体メンバ

それではRTクラスのsched_class構造体を見てみましょう。

kernel/sched/rt.c
const struct sched_class rt_sched_class = {
 1        .next                   = &fair_sched_class,
 2        .enqueue_task           = enqueue_task_rt,
 3        .dequeue_task           = dequeue_task_rt,
 4        .yield_task             = yield_task_rt,

 5        .check_preempt_curr     = check_preempt_curr_rt,

 6        .pick_next_task         = pick_next_task_rt,
 7        .put_prev_task          = put_prev_task_rt,

 8        .set_curr_task          = set_curr_task_rt,
 9        .task_tick              = task_tick_rt,

10        .get_rr_interval        = get_rr_interval_rt,

11        .prio_changed           = prio_changed_rt,
12        .switched_to            = switched_to_rt,

13        .update_curr            = update_curr_rt,
};

1行目は次の優先度のクラス、fairクラスをポイントしています。
2行目のenqueue_taskメンバ、3行目のdequeue_taskメンバを以下に示します。
*関数の先頭に記載されているコメントは非常に重要です。
例えば、この関数は割込み禁止で呼び出す必要がある、呼び出してはいけないなどルールのようなことも記載されています。

本関数は、優先度の配列へプロセスを追加する(enqueue_task_rt)、優先度の配列からプロセスを削除する(dequeue_task_rt)、と説明文があります。この説明を頭に入れてコードを見てみましょう。

kernel/sched/rt.c
/*
 * Adding/removing a task to/from a priority array:
 */
static void
enqueue_task_rt(struct rq *rq, struct task_struct *p, int flags)
{
1        struct sched_rt_entity *rt_se = &p->rt;

2        if (flags & ENQUEUE_WAKEUP)
3                rt_se->timeout = 0;

4        enqueue_rt_entity(rt_se, flags);

5        if (!task_current(rq, p) && p->nr_cpus_allowed > 1)
6                enqueue_pushable_task(rq, p);
}

static void dequeue_task_rt(struct rq *rq, struct task_struct *p, int flags)
{
1        struct sched_rt_entity *rt_se = &p->rt;

2        update_curr_rt(rq);
3        dequeue_rt_entity(rt_se, flags);

4        dequeue_pushable_task(rq, p);
}

enqueue_taskメンバ

2行目、3行目:ENQUEUE_WAKEUPフラグが設定されている場合、sched_rt_entityのtimeoutに0を設定しています。このtimeoutはタイマーの満了の判定で使用されます。timeoutを0に設定するのは、即座に実行可能状態にすることを意味します。

4行目:本関数のポイントです。
enqueue_rt_entityを呼び出します。下記に示します。

5、6行目:CONFIG_SMPが無効の場合は何もしません。

kernel/sched/rt.c
static void enqueue_rt_entity(struct sched_rt_entity *rt_se, unsigned int flags)
{
1        struct rq *rq = rq_of_rt_se(rt_se);

2        dequeue_rt_stack(rt_se, flags);
3        for_each_sched_rt_entity(rt_se)
4                __enqueue_rt_entity(rt_se, flags);
5        enqueue_top_rt_rq(&rq->rt);
}

ポイントを絞ります。この関数のポイントは4行目の__enqueue_rt_entity関数の呼び出しです。
下記に示します。
5行目のenqueue_top_rt_rq関数の呼び出しではrunqueueのrt_nr_runningのインクリメント処理とrt_queuedを1に設定することで実行可能状態のプロセスが存在する事を判るようにしています。

kernel/sched/rt.c
static void __enqueue_rt_entity(struct sched_rt_entity *rt_se, unsigned int flags)
{
 1        struct rt_rq *rt_rq = rt_rq_of_se(rt_se);
 2        struct rt_prio_array *array = &rt_rq->active;
 3        struct rt_rq *group_rq = group_rt_rq(rt_se);
 4        struct list_head *queue = array->queue + rt_se_prio(rt_se);

        /*
         * Don't enqueue the group if its throttled, or when empty.
         * The latter is a consequence of the former when a child group
         * get throttled and the current group doesn't have any other
         * active members.
         */
 5        if (group_rq && (rt_rq_throttled(group_rq) || !group_rq->rt_nr_running)) {
 6                if (rt_se->on_list)
 7                        __delist_rt_entity(rt_se, array);
 8                return;
 9        }

10        if (move_entity(flags)) {
11                WARN_ON_ONCE(rt_se->on_list);
12                if (flags & ENQUEUE_HEAD)
13                        list_add(&rt_se->run_list, queue);
14                else
15                        list_add_tail(&rt_se->run_list, queue);

16                __set_bit(rt_se_prio(rt_se), array->bitmap);
17                rt_se->on_list = 1;
18        }
19        rt_se->on_rq = 1;

20        inc_rt_tasks(rt_se, rt_rq);
}

まず4行目で対象のRTプロセスの優先度に対応するrunqueueのリストの位置を求めます。

enqueue1.jpg

13 or 15行目でリストに繋ぎます。(下図①)13行目と15行目の違いは、対象プロセスと同一優先度のプロセスが存在した場合、対象プロセスをリストの先頭に繋ぐか、リストの最後に繋ぐか、が異なります。
16行目で対象プロセスに対応する優先度のビットマップ配列の要素に1をセットします。(下図②)
enqueue2.jpg

dequeue_taskメンバ

次にRTプロセスの対象の処理が完了した場合などに呼び出されるrunqueueから対象プロセスを削除する処理を見ます。

kernel/sched/rt.c
static void dequeue_task_rt(struct rq *rq, struct task_struct *p, int flags)
{
1        struct sched_rt_entity *rt_se = &p->rt;

2        update_curr_rt(rq);
3        dequeue_rt_entity(rt_se, flags);

4        dequeue_pushable_task(rq, p);
}

3行目のdequeue_rt_entity( )がポイントです。

kernel/sched/rt.c
static void dequeue_rt_entity(struct sched_rt_entity *rt_se, unsigned int flags)
{
1        struct rq *rq = rq_of_rt_se(rt_se);

2        dequeue_rt_stack(rt_se, flags);

3        for_each_sched_rt_entity(rt_se) {
4                struct rt_rq *rt_rq = group_rt_rq(rt_se);

5                if (rt_rq && rt_rq->rt_nr_running)
6                        __enqueue_rt_entity(rt_se, flags);
7        }
8        enqueue_top_rt_rq(&rq->rt);
}

2行目のdequeue_rt_stack( )を見てみます。

kernel/sched/rt.c
/*
 * Because the prio of an upper entry depends on the lower
 * entries, we must remove entries top - down.
 */
static void dequeue_rt_stack(struct sched_rt_entity *rt_se, unsigned int flags)
{
1        struct sched_rt_entity *back = NULL;

2        for_each_sched_rt_entity(rt_se) {
3                rt_se->back = back;
4                back = rt_se;
5        }

6        dequeue_top_rt_rq(rt_rq_of_se(back));

7        for (rt_se = back; rt_se; rt_se = rt_se->back) {
8                if (on_rt_rq(rt_se))
9                        __dequeue_rt_entity(rt_se, flags);
        }
}

ここでのポイントは9行目の__dequeue_rt_entity( )です。

kernel/sched/rt.c
static void __dequeue_rt_entity(struct sched_rt_entity *rt_se, unsigned int flags)
{
1        struct rt_rq *rt_rq = rt_rq_of_se(rt_se);
2        struct rt_prio_array *array = &rt_rq->active;

3        if (move_entity(flags)) {
4                WARN_ON_ONCE(!rt_se->on_list);
5                __delist_rt_entity(rt_se, array);
6        }
7        rt_se->on_rq = 0;

8        dec_rt_tasks(rt_se, rt_rq);
}

2行目で対象のRTプロセスの優先度に対応するrunqueueのリストの位置を求めます。
そして、5行目で実行可能リストから削除しています。
その後、8行目のdec_rt_tasks関数呼び出しでrunqueueのrt_nr_runningのデクリメントなどを行います。

yield_taskメンバ

ここではyield処理を見てみます。

kernel/sched/rt.c
static void yield_task_rt(struct rq *rq)
{
        requeue_task_rt(rq, rq->curr, 0);
}

requeue_task_rt( )を呼び出しています。第3引数に0を入れていることを覚えておいてください。

kernel/sched/rt.c
static void requeue_task_rt(struct rq *rq, struct task_struct *p, int head)
{
1        struct sched_rt_entity *rt_se = &p->rt;
2        struct rt_rq *rt_rq;

3        for_each_sched_rt_entity(rt_se) {
4                rt_rq = rt_rq_of_se(rt_se);
5                requeue_rt_entity(rt_rq, rt_se, head);
        }
}

この関数のポイントは5行目ですね。headは0を渡します。

kernel/sched/rt.c
/*
 * Put task to the head or the end of the run list without the overhead of
 * dequeue followed by enqueue.
 */
static void
requeue_rt_entity(struct rt_rq *rt_rq, struct sched_rt_entity *rt_se, int head)
{
1        if (on_rt_rq(rt_se)) {
2                struct rt_prio_array *array = &rt_rq->active;
3                struct list_head *queue = array->queue + rt_se_prio(rt_se);

4                if (head)
5                        list_move(&rt_se->run_list, queue);
6                else
7                        list_move_tail(&rt_se->run_list, queue);
        }
}

3行目で、対象のRTプロセスの優先度に対応するrunqueueのリストの位置を求めます。

headには0が入っている為、7行目が実行されます。list_move_tail( )で対象プロセスを同優先度のリストの最後に移します。

yield.jpg

check_preempt_currメンバ

  • SMP向けコードは削っています
kernel/sched/rt.c
/*
 * Preempt the current task with a newly woken task if needed:
 */
static void check_preempt_curr_rt(struct rq *rq, struct task_struct *p, int flags)
{
1        if (p->prio < rq->curr->prio) {
2                resched_curr(rq);
3                return;
4        }

}

引数で与えられたプロセスの優先度が現在のプロセスの優先度より高い場合、resched_curr( )を呼び出します。
resched_curr( )は以下の通りです。

kernel/sched/core.c
void resched_curr(struct rq *rq)
{
        struct task_struct *curr = rq->curr;
        int cpu;

        lockdep_assert_held(&rq->lock);

      if (test_tsk_need_resched(curr))
                return;

        cpu = cpu_of(rq);

        if (cpu == smp_processor_id()) {
               set_tsk_need_resched(curr);
                set_preempt_need_resched();
                return;
        }

        if (set_nr_and_not_polling(curr))
                smp_send_reschedule(cpu);
        else
                trace_sched_wake_idle_without_ipi(cpu);
}

★ですでにTIF_NEED_RESCHEDというリスケジューリングが必要というフラグがセットされていた場合はそのまま復帰します。
セットされていない場合は□でTIF_NEED_RESCHEDフラグをセットします。

pick_next_taskメンバ

本メンバは非常に重要です。一番重要なメンバと言っても過言ではありません。
次にディスパッチ(実行させる)するプロセスを決定する処理を行います。

kernel/sched/rt.c
static struct task_struct *
pick_next_task_rt(struct rq *rq, struct task_struct *prev, struct rq_flags *rf)
{
        struct task_struct *p;
        struct rt_rq *rt_rq = &rq->rt;

        if (need_pull_rt_task(rq, prev)) {
                /*
                 * This is OK, because current is on_cpu, which avoids it being
                 * picked for load-balance and preemption/IRQs are still
                 * disabled avoiding further scheduler activity on it and we're
                 * being very careful to re-start the picking loop.
                 */
                rq_unpin_lock(rq, rf);
                pull_rt_task(rq);
                rq_repin_lock(rq, rf);
                /*
                 * pull_rt_task() can drop (and re-acquire) rq->lock; this
                 * means a dl or stop task can slip in, in which case we need
                 * to re-start task selection.
                 */
                if (unlikely((rq->stop && task_on_rq_queued(rq->stop)) ||
                             rq->dl.dl_nr_running))
                        return RETRY_TASK;
        }

        /*
         * We may dequeue prev's rt_rq in put_prev_task().
         * So, we update time before rt_queued check.
         */
        if (prev->sched_class == &rt_sched_class)
                update_curr_rt(rq);

        if (!rt_rq->rt_queued)
                return NULL;

        put_prev_task(rq, prev);

      p = _pick_next_task_rt(rq);

        set_next_task(rq, p);

        rt_queue_push_tasks(rq);

        /*
         * If prev task was rt, put_prev_task() has already updated the
         * utilization. We only care of the case where we start to schedule a
         * rt task
         */
        if (rq->curr->sched_class != &rt_sched_class)
                update_rt_rq_load_avg(rq_clock_task(rq), rq, 0);

        return p;
}

★からの復帰値のtask_struct構造体が次に実行されるプロセスになります。

kernel/sched/rt.c
static struct task_struct *_pick_next_task_rt(struct rq *rq)
{
        struct sched_rt_entity *rt_se;
        struct rt_rq *rt_rq  = &rq->rt;

        do {
              rt_se = pick_next_rt_entity(rq, rt_rq);
                BUG_ON(!rt_se);
                rt_rq = group_rt_rq(rt_se);
        } while (rt_rq);

        return rt_task_of(rt_se);
}

☆を見てみましょう。

kernel/sched/rt.c
static struct sched_rt_entity *pick_next_rt_entity(struct rq *rq,
                                                   struct rt_rq *rt_rq)
{
        struct rt_prio_array *array = &rt_rq->active;
        struct sched_rt_entity *next = NULL;
        struct list_head *queue;
        int idx;

1       idx = sched_find_first_bit(array->bitmap);
2       BUG_ON(idx >= MAX_RT_PRIO);

3       queue = array->queue + idx;
4       next = list_entry(queue->next, struct sched_rt_entity, run_list);

5       return next;
}

冒頭で図で示した通り、優先度毎にbitを持っています。
1行目で最も高優先度のbit(インデックス)を求めます。(高優先度のbitから順に見ていき、セットされているbitを返す)

3行目で目的の優先度のリストをポイントした後、4行目の処理で次に実行するプロセスを返します。
5行目では、求めた次に実行すべきプロセスを復帰値として返します。
スケジューラはこの復帰値で返したプロセスをディスパッチします。

put_prev_taskメンバ

kernel/sched/rt.c
static void put_prev_task_rt(struct rq *rq, struct task_struct *p)
{
        update_curr_rt(rq);

        update_rt_rq_load_avg(rq_clock_task(rq), rq, 1);

        /*
         * The previous task needs to be made eligible for pushing
         * if it is still active
         */
        if (on_rt_rq(&p->rt) && p->nr_cpus_allowed > 1)
                enqueue_pushable_task(rq, p);
}

1行目で現在のRTプロセスの統計情報(実行時間など)を更新します。中を見てみましょう。

kernel/sched/rt.c
static void update_curr_rt(struct rq *rq)
{
        struct task_struct *curr = rq->curr;
        struct sched_rt_entity *rt_se = &curr->rt;
        u64 delta_exec;
        u64 now;

        if (curr->sched_class != &rt_sched_class)
                return;

        now = rq_clock_task(rq);
1.      delta_exec = now - curr->se.exec_start;
        if (unlikely((s64)delta_exec <= 0))
                return;

2.      schedstat_set(curr->se.statistics.exec_max,
                      max(curr->se.statistics.exec_max, delta_exec));

3.      curr->se.sum_exec_runtime += delta_exec;
        account_group_exec_runtime(curr, delta_exec);

        curr->se.exec_start = now;
        cgroup_account_cputime(curr, delta_exec);

        if (!rt_bandwidth_enabled())
                return;

        for_each_sched_rt_entity(rt_se) {
                struct rt_rq *rt_rq = rt_rq_of_se(rt_se);

a.                if (sched_rt_runtime(rt_rq) != RUNTIME_INF) {
b.                        raw_spin_lock(&rt_rq->rt_runtime_lock);
c.                        rt_rq->rt_time += delta_exec;
d.                        if (sched_rt_runtime_exceeded(rt_rq))
e.                               resched_curr(rq);
f.                        raw_spin_unlock(&rt_rq->rt_runtime_lock);
                }
        }
}

2.と3.でcurrentプロセスの実行時間に関する統計情報を更新しています。

折角なので、冒頭で述べた/proc/sys/kernel/sched_rt_period_us、/proc/sys/kernel/sched_rt_runtime_usに関連するコードを見ましょう。
a.ではsched_rt_runtime_usが-1でないことを確認しています。
c.ではRTプロセスの実行時間を加算しています。(1.でdelta_execを計算)
d.でRTプロセスの規定の実行時間(sched_rt_runtime_us)を超えた場合、resched_currでfairクラスに実行権を譲ります(e.)。

set_curr_taskメンバ

kernel/sched/rt.c
static void set_curr_task_rt(struct rq *rq)
{
        set_next_task(rq, rq->curr);
}

この段階で既にrq->currは、currentプロセスをポイントしています。
set_next_taskは、以下の通りです。

kernel/sched/rt.c
static inline void set_next_task(struct rq *rq, struct task_struct *p)
{
        p->se.exec_start = rq_clock_task(rq);

        /* The running task is never eligible for pushing */
        dequeue_pushable_task(rq, p);
}

1行目:p->se.exec_startに実行開始時間を設定します。
dequeue_pushable_task( )はUni-Processor向けコードでは空関数です。

task_tickメンバ

kernel/sched/rt.c
/*
 * scheduler tick hitting a task of our scheduling class.
 *
 * NOTE: This function can be called remotely by the tick offload that
 * goes along full dynticks. Therefore no local assumption can be made
 * and everything must be accessed through the @rq and @curr passed in
 * parameters.
 */
static void task_tick_rt(struct rq *rq, struct task_struct *p, int queued)
{
        struct sched_rt_entity *rt_se = &p->rt;

        update_curr_rt(rq);
        update_rt_rq_load_avg(rq_clock_task(rq), rq, 1);

        watchdog(rq, p);

        /*
         * RR tasks need a special form of timeslice management.
         * FIFO tasks have no timeslices.
         */
        if (p->policy != SCHED_RR)
                return;

        if (--p->rt.time_slice)
                return;

        p->rt.time_slice = sched_rr_timeslice;

        /*
         * Requeue to the end of queue if we (and all of our ancestors) are not
         * the only element on the queue
         */
        for_each_sched_rt_entity(rt_se) {
                if (rt_se->run_list.prev != rt_se->run_list.next) {
                        requeue_task_rt(rq, p, 0);
                        resched_curr(rq);
                        return;
                }
        }
}

本関数はtick割込み契機で実行されます。
統計情報の更新やSCHED_RRのときはタイム スライスを経過していたら、リスケジューリング フラグを設定します。

get_rr_intervalメンバ

kernel/sched/rt.c
static unsigned int get_rr_interval_rt(struct rq *rq, struct task_struct *task)
{
        /*
         * Time slice is 0 for SCHED_FIFO tasks
         */
        if (task->policy == SCHED_RR)
                return sched_rr_timeslice;
        else
                return 0;
}

対象プロセスがSCHED_RRのときタイム スライス値(/proc/sys/kernel/sched_rr_timeslice_ms)を返します。

prio_changedメンバ

kernel/sched/rt.c
static void
prio_changed_rt(struct rq *rq, struct task_struct *p, int oldprio)
{
1        if (!task_on_rq_queued(p))
2                return;

3        if (rq->curr == p) {
                /* For UP simply resched on drop of prio */
4                if (oldprio < p->prio)
5                        resched_curr(rq);
6        } else {
                /*
                 * This task is not running, but if it is
                 * greater than the current running task
                 * then reschedule.
                 */
7                if (p->prio < rq->curr->prio)
8                        resched_curr(rq);
        }
}

プロセスの優先度を変更した時に実行される処理になります。
3行目で引数で指定したプロセスがcurrentプロセスかどうかを見て、currentプロセスの場合は、変更した優先度が元の優先度より低い場合に(4行目)、リスケジューリング フラグを設定します(5行目)。
設定するプロセスがcurrentプロセスでない場合でcurrentプロセスより引数で指定したプロセスの優先度が高い場合(7行目)、リスケジューリング フラグを設定して(8行目)、プリエンプションするようにします。

switched_toメンバ

static void switched_to_rt(struct rq *rq, struct task_struct *p)
{
        /*
         * If we are already running, then there's nothing
         * that needs to be done. But if we are not running
         * we may need to preempt the current running task.
         * If that current running task is also an RT task
         * then see if we can move to another run queue.
         */
        if (task_on_rq_queued(p) && rq->curr != p) {
                if (p->prio < rq->curr->prio && cpu_online(cpu_of(rq)))
                        resched_curr(rq);
        }
}

スイッチしようとするプロセスの優先度が現在のプロセスの優先度より高い場合(p->prio < rq->curr->prio)、resched_curr( )でリスケジューリング フラグを設定します。

update_currメンバ

put_prev_taskメンバの所でも出ましたが、update_curr_rt( )を実行します。
currentプロセスの統計情報の更新やRTクラスの帯域超過時の処理を実施します。

端折りすぎている部分もありますので、随時更新して記事の質を向上させていきます。

初回:【読解入門】Linuxカーネル (概要編)
前回:【読解入門】Linuxカーネル (スケジューラ編その3-1)
次回:【読解入門】Linuxカーネル (スケジューラ編その3-3)

45
34
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
45
34

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?