レガシーガジェット研究所

気になったことのあれこれ。

Linux Kernel~ プロセススケジューリングにおけるロードバラシング ~

概要

「詳解Linux Kernel」を参考にVersion 2.6.11のコードリーディングをしていく。CPUのアーキテクチャは書籍に沿ってIntelx86とする。

今回はプロセススケジューリングにおけるロードバラシングについて見ていく。

マルチプロセッサ環境でのランキューにおけるロードバランシング

Linuxでは対称型マルチプロセッサ(SMP)モデルを採用しており、基本的には全てのCPUが平等である。しかしマルチプロセッサと一口に言ってもハードウェアによって異なる性質を持っておりスケジューラはその違いに合わせて処理方式を変更する。

種別 説明
古典的なマルチプロセッサアーキテクチャ 一般的なマルチプロセッサシステムで、全てのCPUはRAMを共有する。
ハイパースレッディングテクノロ ハイパースレッディング対応のCPUは複数のスレッドを同時に実行することが可能で、単一のコア内に複数のレジスタの組みを保持しており、それらを高速に切り替えることでハイパースレッディングを実現している。メモリへアクセスする際の待ち時間効率的に使用することができ、単一のハイパースレッディング対応の物理CPUはLinuxから複数の論理CPUとして認識されるため、例えば2つの物理CPUを搭載している場合カーネルからは4つの論理CPUとして認識される。
NUMA(Non-Uniform Memory Access) CPUとRAMをセットにしノードとして分割する(通常単一のCPUと複数のRAMチップで構成される)。通常のSMPではメモリ調停回路(複数のCPUから行われるメモリアクセスの順序性を担保する回路)に処理が集中するが、NUMAではノード内のローカル(CPUとセットになっている)RAMにアクセスする際には競合はほとんど場合発生しない(逆にノード外のRAMへのアクセスは非常に遅くなる)。

参考: http://outofsync.net/wiki/index.php?title=File:Smp_numa.png

参考: http://xtreview.com/review12.htm

上記に挙げたSMPの性質は組み合わさっていることも多い。

全ての実行可能なプロセスはある単一CPUに対応するランキュー上に存在しており、複数CPUのランキュー上にまたがって存在することはない。これはCPUのハードウェアキャッシュを考えた時に非常に効率的でシステム性能の向上に大きく影響する。

しかし多数のプロセスが単一のCPUに集中してしまうこともあるためカーネルでは定期的に複数のCPU間でロードバランシング処理を行い。プロセスを別のCPUに対応するランキューに移し替えるような処理を行う。カーネルは「スケジューリングドメイン」という概念を用いてCPU間の負荷分散を行いマルチコアプロセッサにも対応している。

スケジューリングドメイン

スケジューリングドメインカーネルが負荷を均等に保つべきCPUの集合である。ドメインは複数のグループから構成されており、カーネルはそのドメイン内のグループ間に負荷の偏りが発生した場合、負荷分散を行う。

(1)は2つの物理CPUから成る古典的なマルチプロセッサアーキテクチャで、単一のスケジュールドメインの中に2つのグループが存在し、各グループは単一のCPUに対応している。

(2)はハイパースレッディング対応の2つの物理CPU(4つの論理CPU)から成る構成で、スケジューリングドメインは2階層になる。親スケジューリングドメインは2つの子スケジューリングドメインから構成されており、子スケジューリングドメインは単一の物理CPU(2つの論理CPU)を保持する。子スケジューリングは2つのグループを保持しており、各グループが単一の論理CPUと対応している。

(3)は2つのノードから成り各ノードが4つの物理CPUを保持するNUMAアーキテクチャで、これもスケジューリングドメインは複数階層になる。上位スケジューリングドメインは2つの下位スケジューリングドメインから構成されており、これはNUMAアーキテクチャのノードに対応する。下位スケジューリングドメインは4つグループを保持しており、各グループは単一の物理CPUに対応している。

スケジューリングドメインsched_domain構造体で表現され、以下のように定義されている。

// include/linux/sched.h
struct sched_domain {
    /* These fields must be setup */
    struct sched_domain *parent;   /* top domain must be null terminated */
    struct sched_group *groups;    /* the balancing groups of the domain */
    cpumask_t span;         /* span of all CPUs in this domain */
    unsigned long min_interval;   /* Minimum balance interval ms */
    unsigned long max_interval;   /* Maximum balance interval ms */
    unsigned int busy_factor; /* less balancing by factor if busy */
    unsigned int imbalance_pct;   /* No balance until over watermark */
    unsigned long long cache_hot_time; /* Task considered cache hot (ns) */
    unsigned int cache_nice_tries;    /* Leave cache hot tasks for # tries */
    unsigned int per_cpu_gain;    /* CPU % gained by adding domain cpus */
    int flags;         /* See SD_* */

    /* Runtime fields. */
    unsigned long last_balance;   /* init to jiffies. units in jiffies */
    unsigned int balance_interval;    /* initialise to 1. units in ms. */
    unsigned int nr_balance_failed; /* initialise to 0 */

#ifdef CONFIG_SCHEDSTATS
    /* load_balance() stats */
    unsigned long lb_cnt[MAX_IDLE_TYPES];
    unsigned long lb_failed[MAX_IDLE_TYPES];
    unsigned long lb_imbalance[MAX_IDLE_TYPES];
    unsigned long lb_nobusyg[MAX_IDLE_TYPES];
    unsigned long lb_nobusyq[MAX_IDLE_TYPES];

    /* sched_balance_exec() stats */
    unsigned long sbe_attempts;
    unsigned long sbe_pushed;

    /* try_to_wake_up() stats */
    unsigned long ttwu_wake_affine;
    unsigned long ttwu_wake_balance;
#endif
};

parentメンバは上位スケジューリングドメインが存在しない場合にはnullが設定される。gruopsドメインが保持するグループで以下のように定義されており、sched_domain構造体のメンバであるgroupsはグループリストの先頭であることがわかる。

// include/linux/sched.h
struct sched_group {
    struct sched_group *next;  /* Must be a circular list */
    cpumask_t cpumask;

    /*
    * CPU power of this group, SCHED_LOAD_SCALE being max power for a
    * single CPU. This is read only (except for setup, hotplug CPU).
    */
    unsigned long cpu_power;
};

sched_domainは物理CPU毎に存在する変数であるphys_domainsが保持しており以下のように定義されている。これは最下層のドメインになる。

// kernel/sched.c
static DEFINE_PER_CPU(struct sched_domain, phys_domains);

ハイパースレッディング対応CPUを搭載するハードウェアの場合は、以下のようにcpu_domainsという変数で定義されており、これが最下層ドメインとなる。

// kernel/sched.c
static DEFINE_PER_CPU(struct sched_domain, cpu_domains);

rebalance_tick()

システムに存在するランキュー間のロードバラシングを行うために、全てのCPU内でtick毎にschedule_tick()関数内から呼び出される。rebalance_tick()は以下のように定義されている。

// kernel/sched.c
#define CPU_OFFSET(cpu) (HZ * cpu / NR_CPUS)

static void rebalance_tick(int this_cpu, runqueue_t *this_rq,
               enum idle_type idle)
{
    unsigned long old_load, this_load;
    unsigned long j = jiffies + CPU_OFFSET(this_cpu);
    struct sched_domain *sd; // スケジューリングドメイン

    /* Update our load */
    old_load = this_rq->cpu_load; // 直前の負荷状況
    this_load = this_rq->nr_running * SCHED_LOAD_SCALE; // キュー内のプロセス数から負荷を算出
    // 負荷が大きくなっている場合
    if (this_load > old_load)
        old_load++;
    this_rq->cpu_load = (old_load + this_load) / 2; // 平均値を設定

    for_each_domain(this_cpu, sd) {
        unsigned long interval;

        if (!(sd->flags & SD_LOAD_BALANCE)) // 対象ドメイン内でのロードバランシングが許可されていない
            continue;

        interval = sd->balance_interval;
        if (idle != SCHED_IDLE) // キューから空でない場合
            interval *= sd->busy_factor;

        /* scale ms to jiffies */
        interval = msecs_to_jiffies(interval);
        if (unlikely(!interval)) // 0の場合
            interval = 1;

        if (j - sd->last_balance >= interval) { // インターバル分の時間が経過している場合
            if (load_balance(this_cpu, this_rq, sd, idle)) { // ロードバランシングの処理を呼び出す。
                idle = NOT_IDLE; // タスクを他から取得したためアイドル状態ではなくなる。
            }
            sd->last_balance += interval; // 最後にロードバランシングを行なった時間を更新
        }
    }
}

load_balance()

上記のrebalance_tick()関数内で呼び出されるこの関数はスケジューリングドメイン間に大きな負荷の偏りがないか確認し、偏りがあれば最も負荷の高いグループからローカルCPUのランキューにプロセスを移動することでロードバランシングを行う。load_balance()関数は以下のように定義されている。

// kernel/sched.c
static int load_balance(int this_cpu, runqueue_t *this_rq,
            struct sched_domain *sd, enum idle_type idle)
{
    struct sched_group *group;
    runqueue_t *busiest;
    unsigned long imbalance;
    int nr_moved;

    spin_lock(&this_rq->lock); // スピンロックを取得
    schedstat_inc(sd, lb_cnt[idle]);

    group = find_busiest_group(sd, this_cpu, &imbalance, idle); // 一番負荷の高いグループを取得
    if (!group) { // 取得失敗
        schedstat_inc(sd, lb_nobusyg[idle]);
        goto out_balanced; // 終了処理へ
    }

    busiest = find_busiest_queue(group); // 最も負荷の高いキュー
    if (!busiest) {
        schedstat_inc(sd, lb_nobusyq[idle]);
        goto out_balanced;
    }

    // 最も負荷の高いランキューがローカルキューである場合(理論的には起こりえる)
    if (unlikely(busiest == this_rq)) {
        WARN_ON(1);
        goto out_balanced; // 終了処理へ
    }

    schedstat_add(sd, lb_imbalance[idle], imbalance);

    nr_moved = 0;
    // プロセスを取り出す対象キューに存在するプロセスが2つ以上である場合
    if (busiest->nr_running > 1) {
        double_lock_balance(this_rq, busiest);
        nr_moved = move_tasks(this_rq, this_cpu, busiest,
                        imbalance, sd, idle); // タスクの移動
        spin_unlock(&busiest->lock);
    }
    spin_unlock(&this_rq->lock); // スピンロックを解除

    // タスクの移動に失敗した場合
    if (!nr_moved) {
        schedstat_inc(sd, lb_failed[idle]);
        sd->nr_balance_failed++; // バランシングの失敗回数をインクリメント

        if (unlikely(sd->nr_balance_failed > sd->cache_nice_tries+2)) {
            int wake = 0;

            spin_lock(&busiest->lock); // ロック
            if (!busiest->active_balance) { // 当該フラグを見てmitigationカーネルスレッドはロードバラシングを走らせる。
                busiest->active_balance = 1; // フラグをセット
                busiest->push_cpu = this_cpu; // 対象CPUを設定
                wake = 1;
            }
            spin_unlock(&busiest->lock); // アンロック
            if (wake)
                wake_up_process(busiest->migration_thread); // mitigationスレッドを起動

            sd->nr_balance_failed = sd->cache_nice_tries; // スレッドを起動したので失敗回数のカウンタをリセット
        }

        if (sd->balance_interval < sd->max_interval)
            sd->balance_interval++; // 次のバランシング処理のインターバルを大きくする。
    } else {
        sd->nr_balance_failed = 0; // バランシングに成功したのでリセット

        sd->balance_interval = sd->min_interval; // インターバルをリセット
    }

    return nr_moved;

out_balanced:
    spin_unlock(&this_rq->lock); // アンロック

    if (sd->balance_interval < sd->max_interval)
        sd->balance_interval *= 2;

    return 0;
}

ちなみにmitigationカーネルスレッドは以下のように定義されており、上記のactive_balanceフラグを参照しているのがわかる。

// kernel/sched.c
static int migration_thread(void * data)
{
    runqueue_t *rq;
    int cpu = (long)data;
    rq = cpu_rq(cpu);

    set_current_state(TASK_INTERRUPTIBLE);
    while (!kthread_should_stop()) {
        struct list_head *head;
        migration_req_t *req;

        if (current->flags & PF_FREEZE)
            refrigerator(PF_FREEZE);
        if (rq->active_balance) { // HERE !!!!
            active_load_balance(rq, cpu);
            rq->active_balance = 0;
        }

        head = &rq->migration_queue;
        req = list_entry(head->next, migration_req_t, list);
        list_del_init(head->next);

        if (req->type == REQ_MOVE_TASK) {
            spin_unlock(&rq->lock);
            __migrate_task(req->task, cpu, req->dest_cpu);
            local_irq_enable();
        } else if (req->type == REQ_SET_DOMAIN) {
            rq->sd = req->sd;
            spin_unlock_irq(&rq->lock);
        }
        complete(&req->done);
    }
    __set_current_state(TASK_RUNNING);
    return 0;

wait_to_die:
    ...
}

move_tasks()

上記のload_balance()関数内で呼び出され、実際に別のランキューからローカルのキュー(両ランキューともロックする必要がある)にタスクを移動し移動したタスク数を戻り値として返す。当該関数は以下のように定義されている。

// kernel/sched.c
static int move_tasks(runqueue_t *this_rq, int this_cpu, runqueue_t *busiest,
              unsigned long max_nr_move, struct sched_domain *sd,
              enum idle_type idle)
{
    prio_array_t *array, *dst_array;
    struct list_head *head, *curr;
    int idx, pulled = 0;
    task_t *tmp;

    // 移動可能数が0以下、またはプロセスの移動元ランキューに存在するプロセス数が1以下の場合
    if (max_nr_move <= 0 || busiest->nr_running <= 1)
        goto out; // 終了処理へ

    // expiredしているタスクが存在していれば、それをexpiredからプロセス移動を開始する
    if (busiest->expired->nr_active) {
        array = busiest->expired;
        dst_array = this_rq->expired;
    // activeキューからプロセス移動を開始する
    } else {
        array = busiest->active;
        dst_array = this_rq->active;
    }

new_array:
    idx = 0; // 優先度の降順にトラバースしていく
skip_bitmap:
    if (!idx)
        idx = sched_find_first_bit(array->bitmap); // 存在する最高優先度のキューに対応するインデックスを取得
    else
        idx = find_next_bit(array->bitmap, MAX_PRIO, idx); // 次に高い優先度のキューに対応するインデックスを取得
    if (idx >= MAX_PRIO) { // 最大優先度以上
        // 現在トラバースしているキューリストがexpiredで、activeのリストにプロセスが存在する場合
        if (array == busiest->expired && busiest->active->nr_active) {
            array = busiest->active; // activeキューリストを対象に
            dst_array = this_rq->active; // 移動元キューをactiveに
            goto new_array; // 再度優先度の降順からスタート
        }
        goto out; // 終了処理へ
    }

    head = array->queue + idx;
    curr = head->prev;
skip_queue:
    tmp = list_entry(curr, task_t, run_list);
    curr = curr->prev;

    /*
    * 他のCPUがそのプロセスを実行していない &&
    * そのプロセスがローカルCPUで実行可能である &&
    * (
    *   ローカルCPUがアイドル状態 ||
    *   プロセス移動に複数回失敗している ||
    *   そのプロセスが直前に実行されていない(CPUキャッシュにデータがないと推測される)
    * )
    */
    if (!can_migrate_task(tmp, busiest, this_cpu, sd, idle)) { // 
        if (curr != head)
            goto skip_queue;
        idx++;
        goto skip_bitmap; // 次の優先度へ
    }

    schedstat_inc(this_rq, pt_gained[idle]);
    schedstat_inc(busiest, pt_lost[idle]);

    pull_task(busiest, array, tmp, this_rq, dst_array, this_cpu); // プロセスの移動
    pulled++; // 移動したプロセス数をインクリメント
    
    // 指定されたプロセスの移動数に達していない場合は続行
    if (pulled < max_nr_move) {
        if (curr != head)
            goto skip_queue;
        idx++;
        goto skip_bitmap;
    }
out:
    return pulled; // 移動したプロセス数
}

スケジューリング関連のシステムコール

スケジューリングに関するシステムコールは基本的に優先度を下げることは可能だが、優先度をあげる場合には特権が必要となる。

nice()

nice()システムコールはnice値を変更する。現在はsetpriority()に置き換えられており後方互換性のために残っている(setpriority()システムコールの方が処理は遅い)。

// kernel/sched.c
asmlinkage long sys_nice(int increment)

    int retva;
    long nice;
    
    if (increment < 0) { // 0未満の場合(優先度を下げる場合)
        if (!capable(CAP_SYS_NICE)) // "CAP_SYS_NICE"ゲーパビリティを保持していない場合
            return -EPERM; // エラー
        /* バリデーション */
        if (increment < -40) // 40未満の場合
            increment = -40; // -40に丸める
    }
    /* バリデーション */
    if (increment > 40) // 40以上の場合
        increment = 40; // 40に丸める

    nice = PRIO_TO_NICE(current->static_prio) + increment; // niceを変更する
    /* バリデーション */
    if (nice < -20)
        nice = -20;
    if (nice > 19)
        nice = 19;

    retval = security_task_setnice(current, nice); // セキュリティフック
    if (retval)
        return retval;

    set_user_nice(current, nice);  // nice値を変更
    return 0;
}

上記で呼び出されているset_user_nice()の定義は以下。

// kernel/sched.c
void set_user_nice(task_t *p, long nice)
{
    unsigned long flags;
    prio_array_t *array;
    runqueue_t *rq;
    int old_prio, new_prio, delta;

    /* バリデーション */
    if (TASK_NICE(p) == nice || nice < -20 || nice > 19)
        return;
    
    rq = task_rq_lock(p, &flags); // ロック
    
    // リアルタイムタスク
    if (rt_task(p)) {
        p->static_prio = NICE_TO_PRIO(nice);
        goto out_unlock;
    }
    array = p->array;
    if (array)
        dequeue_task(p, array); // プロセスをキューから取り出す

    old_prio = p->prio; // 変更前の優先度
    new_prio = NICE_TO_PRIO(nice); // 新たに設定する優先度
    delta = new_prio - old_prio; // 差分
    p->static_prio = NICE_TO_PRIO(nice); // 静的優先度を設定
    p->prio += delta; // 差分を動的優先度に設定

    if (array) {
        enqueue_task(p, array); // 再度キューにプロセスを代入
        // 優先度を下げた、もしくは優先度上げて且つカレントプロセスの場合
        if (delta < 0 || (delta > 0 && task_running(rq, p)))
            resched_task(rq->curr); // スケジューリングを行う
    }
out_unlock:
    task_rq_unlock(rq, &flags); // アンロック
}

getpriority(), setpriority()

nice()システムコールはカレントプロセス、すなわち呼び出したプロセスの優先度を変更するが、getpriority()及びsetpriority()は指定したプロセスグループの優先度を変更する。

getpriority()は指定したプロセスグループ内に存在する全てのプロセスのうち、最も小さい値(最も高い優先度値)を戻り値として返す。setpriority()は指定したプロセスグループ内の全てのプロセスに指定の優先度値を設定する。setpriority()は以下のように定義されている。

// include/linux/resource.h
#define    PRIO_PROCESS    0 // プロセスIDを指定したプロセス
#define    PRIO_PGRP   1 // プロセスグループIDで指定したプロセス
#define    PRIO_USER   2 // ユーザIDで指定したプロセス

// kernel/sys.c
/*
 * @which: プロセスの指定方法
 * @who: whichで指定したID番号
 * @nice: nice値(-20 ~ +19)
 */
asmlinkage long sys_setpriority(int which, int who, int niceval)
{
    struct task_struct *g, *p;
    struct user_struct *user;
    int error = -EINVAL;

    /* プロセスの指定方法が規定の範囲内でない場合 */
    if (which > 2 || which < 0)
        goto out; // 終了処理へ

    /* normalize: avoid signed division (rounding problems) */
    error = -ESRCH;
    if (niceval < -20) // 優先度値が小さすぎる
        niceval = -20;
    if (niceval > 19) // 優先度値が大きすぎる
        niceval = 19;

    read_lock(&tasklist_lock); // ロック
    switch (which) {
        case PRIO_PROCESS: // プロセスIDでの指定
            if (!who) // IDの指定がない(0)場合
                who = current->pid; // カレントプロセスを対象とする
            p = find_task_by_pid(who); // PIDからプロセスディスクリプタを取得
            if (p) // プロセスディススクリプタが存在する
                error = set_one_prio(p, niceval, error); // 優先度を設定
            break;
        case PRIO_PGRP: // プロセスグループIDでの指定
            if (!who) // IDの指定がない場合
                who = process_group(current); // カレントプロセスのグループを取得
            do_each_task_pid(who, PIDTYPE_PGID, p) { // プロセスグループIDでトラバース
                error = set_one_prio(p, niceval, error); // 優先度の設定
            } while_each_task_pid(who, PIDTYPE_PGID, p);
            break;
        case PRIO_USER: // ユーザIDで指定
            user = current->user; // カレントプロセスのユーザ
            if (!who) // IDの指定がない場合
                who = current->uid; // カレントプロセスのUIDを取得
            else
                /* IDがカレントプロセスのUIDでない、且つユーザが発見できなかった場合 */
                if ((who != current->uid) && !(user = find_user(who)))
                    goto out_unlock; // 指定ユーザのプロセスは存在しない

            do_each_thread(g, p)
                if (p->uid == who) // UIDが指定のUIDと一致する場合
                    error = set_one_prio(p, niceval, error); // 優先度を設定
            while_each_thread(g, p);
            if (who != current->uid) // カレントプロセスでない場合
                free_uid(user); // データの解放
            break;
    }
out_unlock:
    read_unlock(&tasklist_lock); // アンロック
out:
    return error;
}

sched_getaffinity(), sched_setaffinity()

sched_getaffinity()システムコールはプロセスのCPUアフィニティマスク(プロセスの実行を許可するCPUのマスク)であるcpus_allowedメンバの値の取得を行い、sched_setaffinity()システムコールはCPUアフィニティマスクの設定を行う。

sched_getaffinity()は以下のように定義されている。

// kernel/sched.c
/**
 * sys_sched_setaffinity - set the cpu affinity of a process
 * @pid: プロセスID
 * @len: user_mask_ptrがポイントしているビットマスクのバイト数
 * @user_mask_ptr: ユーザ空間内のビットマスクポインタ
 */
asmlinkage long sys_sched_setaffinity(pid_t pid, unsigned int len,
                      unsigned long __user *user_mask_ptr)
{
    cpumask_t new_mask;
    int retval;

    retval = get_user_cpu_mask(user_mask_ptr, len, &new_mask); // ユーザ空間からマスク値を取得
    if (retval) // 取得失敗
        return retval;

    return sched_setaffinity(pid, new_mask);
}

long sched_setaffinity(pid_t pid, cpumask_t new_mask)
{
    task_t *p;
    int retval;

    lock_cpu_hotplug(); // CPUをロック
    read_lock(&tasklist_lock); // リードロックを取得

    p = find_process_by_pid(pid); // PIDからプロセスディスクリプタを取得
    if (!p) { // 失敗
        read_unlock(&tasklist_lock); // ロックの解除
        unlock_cpu_hotplug(); // ロックの解除
        return -ESRCH;
    }

    get_task_struct(p);
    read_unlock(&tasklist_lock); // リードロックの解除

    retval = -EPERM;
    /* 各ID及びケーパビリティのチェック */
    if ((current->euid != p->euid) && (current->euid != p->uid) &&
            !capable(CAP_SYS_NICE))
        goto out_unlock; // 終了処理へ

    retval = set_cpus_allowed(p, new_mask); // CPUにマスクをセット

out_unlock:
    put_task_struct(p);
    unlock_cpu_hotplug(); // CPUをアンロック
    return retval;
}

上記のsched_setaffinity()では各IDやケーパビリティのチェックのみを行う。

sched_setaffinity()内で呼び出されているset_cpus_allowed()は以下のように定義されている。

// kernel/sched.c
int set_cpus_allowed(task_t *p, cpumask_t new_mask)
{
    unsigned long flags;
    int ret = 0;
    migration_req_t req;
    runqueue_t *rq;

    rq = task_rq_lock(p, &flags); // ロック
    if (!cpus_intersects(new_mask, cpu_online_map)) {
        ret = -EINVAL;
        goto out;
    }

    p->cpus_allowed = new_mask; // CPUアフィニティマスクをセット
    /* 現在実行中のCPUでこのまま実行が継続可能である場合 */
    if (cpu_isset(task_cpu(p), new_mask))
        goto out; // 終了処理へ

    if (migrate_task(p, any_online_cpu(new_mask), &req)) {
        /* Need help from migration thread: drop lock and wait. */
        task_rq_unlock(rq, &flags); // アンロック
        wake_up_process(rq->migration_thread); // mitigationカーネルスレッドを起動
        wait_for_completion(&req.done); // 終了まで待機
        tlb_migrate_finish(p->mm);
        return 0;
    }
out:
    task_rq_unlock(rq, &flags); // アンロック
    return ret;
}

static int migrate_task(task_t *p, int dest_cpu, migration_req_t *req)
{
    runqueue_t *rq = task_rq(p);

    /* 指定しれたタスクが動作していない場合 */
    if (!p->array && !task_running(rq, p)) {
        set_task_cpu(p, dest_cpu); // 動作CPUを更新
        return 0;
    }

    init_completion(&req->done);
    req->type = REQ_MOVE_TASK; // タスクの移動をリクエスト
    req->task = p; // 移動対象タスク
    req->dest_cpu = dest_cpu; // 移動先CPU
    list_add(&req->list, &rq->migration_queue);
    return 1;
}

参考

  • Documentation/sched-coding.txt