Linuxカーネルスケジューラソースコード初期化の分析

Linuxカーネルスケジューラソースコード初期化の分析

1. はじめに

スケジューラ サブシステムはカーネルのコア サブシステムの 1 つです。システム内の CPU リソースの合理的な割り当てを担当します。さまざまな種類のタスクの複雑なスケジューリング要件や、さまざまな複雑な同時競合環境を処理できる必要があります。同時に、全体的なスループット パフォーマンスとリアルタイム要件 (それ自体が矛盾しています) を考慮する必要があります。その設計と実装は非常に困難です。

Linux スケジューラの設計と実装を理解するために、Linux カーネル 5.4 バージョン (TencentOS Server3 のデフォルト カーネル バージョン) を対象に、スケジューラ サブシステムの初期化コードから始めて、Linux カーネル スケジューラの設計と実装を分析します。

2. スケジューラの基本概念

スケジューラの関連コードを分析する前に、スケジューラに関係するコアデータ(構造)とその機能を理解する必要があります。

2.1. 実行キュー (rq)

カーネルは各 CPU に対して実行キューを作成します。システム内のすべての準備完了 (実行中) のプロセス (タスク) はカーネル実行キューに整理され、実行キュー上のプロセスは対応する戦略に従って CPU にスケジュールされて実行されます。

2.2 スケジューリングクラス (sched_class)

カーネルは、スケジューリング ポリシー (sched_class) を高度に抽象化して、スケジューリング クラス (sched_class) を形成します。スケジューリング クラスは、スケジューラの共通コード (メカニズム) を、さまざまな特定のスケジューリング クラスによって提供されるスケジューリング戦略から完全に分離できます。これは、典型的な OO (オブジェクト指向) の考え方です。この設計により、カーネル スケジューラは拡張性が非常に高くなります。開発者は、非常に少ないコード (基本的に共通コードを変更せずに) で新しいスケジューリング クラスを追加し、新しいスケジューラ (クラス) を実装できます。たとえば、デッドライン スケジューリング クラスは 3.x で新しく追加されました。コード レベルでは、dl_sched_class 構造体の関連する実装関数のみが追加され、新しいリアルタイム スケジューリング タイプが便利に追加されます。

現在の 5.4 カーネルには 5 つのスケジューリング クラスがあり、優先度は次のように高いものから低いものの順に分散されます。

停止スケジュールクラス:

最も優先度の高いスケジューリング クラスは、idle_sched_class と同様に、専用のスケジューリング タイプです (移行スレッドを除き、他のタスクを停止スケジューリング クラスに設定することはできません、または設定すべきではありません)。このスケジューリング クラスは、移行スレッドの実行に依存するアクティブ バランスやマシンの停止などの「緊急」タスクを実装するために特別に設計されています。

dl_sched_class:

期限スケジューリング クラスの優先度は、停止スケジューリング クラスの次に高くなります。これは、EDL アルゴリズムに基づくリアルタイム スケジューラ (またはスケジューリング戦略) です。

rt_sched_class:

rt スケジューリング クラスの優先度は dl スケジューリング クラスよりも低く、優先度に基づいて実装されるリアルタイム スケジューラです。

フェアスケジュールクラス:

CFS スケジューラの優先度は、上記の 3 つのスケジューリング クラスよりも低くなっています。これは、公平なスケジューリングの考えに基づいて設計されたスケジューリング タイプであり、Linux カーネルのデフォルトのスケジューリング クラスです。

アイドルスケジュールクラス:

アイドル スケジューリング タイプはスワッパー スレッドであり、主に cpuidle/nohz などのフレームワークを通じてスワッパー スレッドが CPU を引き継ぎ、CPU を省エネ状態にすることができます。

2.3 スケジューリングドメイン (sched_domain)

スケジューリング ドメインは、カーネル 2.6 で導入されました。マルチレベル スケジューリング ドメインの導入により、スケジューラはハードウェアの物理的特性に適応しやすくなり (スケジューリング ドメインは、CPU マルチレベル キャッシュと NUMA の物理的特性が負荷分散にもたらす課題に適応しやすくなり)、スケジューリング パフォーマンスが向上します (sched_domain は、CFS スケジューリング負荷分散用に開発されたメカニズムです)。

2.4 スケジューリンググループ (sched_group)

スケジューリング グループは、スケジューリング ドメインとともにカーネルに導入されます。スケジューリング グループは、スケジューリング ドメインと連携して、CFS スケジューラが複数のコア間で負荷分散を完了するのを支援します。

2.5. ルートドメイン (root_domain)

ルート ドメインは、主にリアルタイム スケジューリング クラス (dl および rt スケジューリング クラスを含む) の負荷分散用に設計されたデータ構造を担当し、dl および rt スケジューリング クラスがリアルタイム タスクの適切なスケジューリングを完了できるように支援します。スケジューリング ドメインを変更するために isolate または cpuset cgroup が使用されていない場合、すべての CPU はデフォルトで同じルート ドメインに配置されます。

2.6 グループスケジューリング(group_sched)

システム内のリソースをより正確に制御するために、カーネルはリソース制御を実行する cgroup メカニズムを導入しました。 group_sched は、cpu cgroup の基本的な実装メカニズムです。cpu cgroup を通じて、いくつかのプロセスを cgroup として設定し、cpu cgroup の制御インターフェイスを通じて対応する帯域幅、共有、およびその他のパラメータを設定できます。このようにして、グループに応じて CPU リソースを細かく制御できます。

3. スケジューラの初期化 (sched_init)

では、本題に入り、カーネル スケジューラの初期化プロセスの分析を始めましょう。ここでの分析を通じて、誰もが次のことを理解できることを願っています。

1. 実行キューはどのように初期化されますか?

2. グループ スケジューリングは rq とどのように関連付けられるか (グループ スケジューリングは関連付け後に group_sched を通じてのみ実行できます)

3. CFSソフト割り込みSCHED_SOFTIRQ登録

初期化のスケジュール (sched_init)

カーネルを起動する

|----セットアップアーキテクチャ

|----すべてのゾーンリストを構築

|----mm_init

|----sched_init スケジューリングの初期化

スケジューリングの初期化はstart_kernelの比較的後期に配置されています。この時点でメモリの初期化は完了しているので、sched_initでkzmallocなどのメモリ割り当て関数が呼び出せるようになっていることがわかります。

sched_init は、各 CPU の実行キュー (rq)、dl/rt のグローバル デフォルト帯域幅、各スケジューリング クラスの実行キュー、および CFS ソフト割り込み登録を初期化する必要があります。

次に、sched_init の具体的な実装を見てみましょう (一部のコードは省略されています)。

void __init sched_init(void)
{
    符号なしロングポインタ = 0;
    整数 i;
 
    /*
     * グローバルデフォルト rt および dl CPU 帯域幅制御データ構造を初期化します*
     * ここでの rt_bandwidth と dl_bandwidth は、リアルタイム プロセスが CPU を過剰に使用して通常の CFS プロセスが飢餓状態になるのを防ぐために、グローバル DL および RT 帯域幅の使用を制御するために使用されます */
    init_rt_bandwidth(&def_rt_bandwidth、global_rt_period()、global_rt_runtime());
    init_dl_bandwidth(&def_dl_bandwidth、global_rt_period()、global_rt_runtime());
 
#ifdef CONFIG_SMP
    /*
     * デフォルトのルートドメインを初期化する *
     * ルートドメインは、dl/rtなどのリアルタイムプロセスのグローバルバランスをとるための重要なデータ構造です。rtを例にとると、* root_domain->cpupriは、ルートドメイン内の各CPUで実行されているRTタスクの最高優先度と、CPU上の異なる優先度のタスクの配分です。cpupriのデータを通じて、rtのエンキュー/デキューでは、
     * rt スケジューラが rt タスクの配分に基づいて優先度の高いタスクが最初に実行されるようにできる場合*/
    init_defrootdomain();
#終了
 
#ifdef CONFIG_RT_GROUP_SCHED
    /*
     * カーネルがRTグループスケジューリング(RT_GROUP_SCHED)をサポートしている場合、RTタスクの帯域幅制御はcgroupを使用して行うことができます。
     * 粒度は、各グループ内の rt タスクの CPU 帯域幅の使用を制御するために使用されます。*
     * RT_GROUP_SCHED により、RT タスクは CPU cgroup の形式で帯域幅全体を制御できます。 * これにより、RT 帯域幅制御の柔軟性が向上します (RT_GROUP_SCHED がない場合、RT のグローバル * 帯域幅の使用のみを制御でき、一部の RT プロセスの帯域幅はグループを指定して制御できません)
     */
    init_rt_bandwidth(&root_task_group.rt_bandwidth,
            global_rt_period()、global_rt_runtime());
#endif /* CONFIG_RT_GROUP_SCHED */
 
    /* 各CPUの実行キューを初期化します */
    可能なCPUごとに(i) {
        構造体rq *rq;
 
        rq = cpu_rq(i);
        raw_spin_lock_init(&rq->lock);
        /*
         * rq 上の cfs/rt/dl の実行キューを初期化します。 * 各スケジューリング タイプには rq 上の独自の実行キューがあり、各スケジューリング クラスは独自のプロセスを管理します。 * pick_next_task() を実行すると、カーネルはスケジューリング クラスの優先度の順序に従ってタスクを高いものから低いものの順に選択します。 * これにより、優先度の高いスケジューリング クラスのタスクが最初に実行されるようになります。 *
         * ストップとアイドルは特別なスケジューリングタイプであり、特別な目的のために設計されており、ユーザーが対応するタイプのプロセスを作成することを許可しないため、カーネルは rq に対応する実行キューを設計しません */
        init_cfs_rq(&rq->cfs);
        init_rt_rq(&rq->rt);
        init_dl_rq(&rq->dl);
#ifdef CONFIG_FAIR_GROUP_SCHED
        /*
         * CFS グループ スケジューリング (group_sched) は、cpu cgroup を介して CFS を制御できます。 * cpu.shares を介してグループ間の CPU 比率制御を提供できます (異なる cgroup が対応する比率に従って CPU を共有できるようにします)。また、cpu.cfs_quota_us を介してクォータを設定することもできます (RT の帯域幅制御に似ています)。 CFS group_sched 帯域幅制御は、コンテナ実装の基本的な基盤技術の 1 つです。*
         * root_task_group はデフォルトのルート task_group であり、他の CPU cgroup はこれを親または祖先として使用します。ここでの初期化は、root_task_groupをrq*のcfs実行キューに関連付けます。ここで行われることは非常に興味深いものです。直接root_task_group->cfs_rq[cpu] = &rq->cfsを設定します。
         * この方法では、cpu cgroupルートまたはcgroup tgのsched_entityの下のプロセスがrq->cfsに直接追加されます。
         * キューでは、検索オーバーヘッドの 1 層を削減できます。
         */
        ルートタスクグループ.shares = ROOT_TASK_GROUP_LOAD;
        INIT_LIST_HEAD(&rq->leaf_cfs_rq_list);
        rq->tmp_alone_branch = &rq->leaf_cfs_rq_list;
        init_cfs_bandwidth(&root_task_group.cfs_bandwidth);
        init_tg_cfs_entry(&root_task_group, &rq->cfs, NULL, i, NULL);
#endif /* CONFIG_FAIR_GROUP_SCHED */
 
        rq->rt.rt_runtime = def_rt_bandwidth.rt_runtime;
#ifdef CONFIG_RT_GROUP_SCHED
        /* 上記の CFS のグループ スケジューリング初期化と同様に、rq 上の rt 実行キューを初期化します */
        init_tg_rt_entry(&root_task_group, &rq->rt, NULL, i, NULL);
#終了
 
#ifdef CONFIG_SMP
        /*
         * ここで、rqはデフォルトのdef_root_domainに関連付けられています。SMPシステムの場合は、後でsched_init_smpでカーネルが新しいroot_domainを作成し、このdef_root_domainを置き換えます。
         */
        rq_attach_root(rq, &def_root_domain);
#endif /* CONFIG_SMP */
    }
 
    /*
     * CFS の SCHED_SOFTIRQ ソフト割り込みサービス関数を登録します。* このソフト割り込みは、定期的な負荷分散と nohz アイドル負荷分散のために用意されています。*/
    クラスを初期化します。
 
    スケジューラ実行 = 1;
}

4. マルチコアスケジューリング初期化(sched_init_smp)

カーネルを起動する

|----レスト初期化

|----カーネル初期化

|----カーネル初期化解放可能

|----smp_init

|----sched_init_smp

|---- スケジューリング初期化

|---- sched_init_domains

|---- ビルドスケジュールドメイン

マルチコアスケジューリングの初期化は、主にスケジューリングドメイン/スケジューリンググループの初期化を完了します(もちろんルートドメインも行われますが、相対的に言えばルートドメインの初期化は比較的簡単です)。

Linux は、複数のチップ アーキテクチャと複数のメモリ アーキテクチャ (UMA/NUMA) で実行できるオペレーティング システムであるため、複数の物理構造に適応できる必要があり、スケジューリング ドメインの設計と実装は比較的複雑です。

4.1 スケジューリングドメインの実装原則

特定のスケジューリング ドメイン初期化コードについて説明する前に、スケジューリング ドメインと物理トポロジ構造の関係を理解する必要があります (スケジューリング ドメインの設計は物理トポロジ構造と密接に関連しているためです。物理トポロジ構造を理解しなければ、スケジューリング ドメインの実装を真に理解することはできません)。

CPUの物理トポロジ

ここでは、次のようなコンピュータ システムを想定します (Intel チップに似ていますが、表現を簡単にするために CPU コアの数を減らしています)。

各ソケットが 2 つのコアと 4 つのスレッドで構成されるデュアル ソケット コンピュータ システムは、4 コア 8 スレッドの NUMA システムである必要があります (上記は Intel の物理トポロジーにすぎませんが、AMD ZEN アーキテクチャは、MC ドメインと NUMA ドメインの間に追加の DIE ドメインがあるチップレット設計を使用しています)。

第 1 層 (SMT ドメイン):

上図の CORE0 に示すように、2 つのハイパースレッドが SMT ドメインを構成します。 Intel CPU の場合、ハイパースレッディングは L1 と L2 を共有します (ストア バフもある程度共有されます)。そのため、SMT ドメイン間で移行するときにキャッシュの熱損失は発生しません。

レイヤー2(MCドメイン):

上図に示すように、CORE0 と CORE1 は同じ SOCKET 内に配置され、MC ドメインに属します。 Intel CPU の場合、一般的に LLC (通常は L3) を共有します。この領域では、プロセス移行によって L1 と L2 の熱は失われますが、L3 キャッシュの熱は維持されます。

第 3 層 (NUMA ドメイン):

上図に示すように、SOCKET0 と SOCKET1 の間でプロセスが移行されると、すべてのキャッシュ ヒートが失われ、オーバーヘッドが大きくなります。そのため、NUMA ドメインの移行には比較的注意が必要です。

まさにこのようなハードウェアの物理的特性 (さまざまなレベルでのキャッシュの発熱、NUMA アクセスのレイテンシなどのハードウェア要因) があるために、カーネルは sched_domain と sched_group を抽象化してこのような物理的特性を表現します。負荷分散を実行する場合、CPU 負荷とキャッシュ アフィニティの間のより適切なバランスを実現するために、対応するスケジューリング ドメインの特性に応じて、さまざまなスケジューリング戦略 (負荷分散頻度、不均衡係数、ウェイクアップ コア選択ロジックなど) が実装されます。

スケジューリングドメインの実装

次に、カーネルが上記の物理トポロジ上でスケジューリング ドメインとスケジューリング グループを確立する方法を見てみましょう。

カーネルは、物理トポロジに従って対応するレベルでスケジューリング ドメインを確立し、次にスケジューリング ドメインの各レベルに対応するスケジューリング グループを確立します。スケジューリング ドメインが負荷分散を実行する場合、対応するレベルのスケジューリング ドメイン内で負荷が最も重い最もビジーな sg (sched_group) を見つけ、最もビジーな sg とローカル sg (ただし、フロント CPU が配置されているスケジューリング グループ) の負荷が不均等かどうかを判断します。負荷が不均等な場合は、最も負荷の高い SG から最も負荷の高い CPU が選択され、2 つの CPU 間で負荷が分散されます。

SMT ドメインは、最も低レベルのスケジューリング ドメインです。各ハイパースレッディング ペアが SMT ドメインであることがわかります。 smt ドメインには 2 つの sched_group があり、各 sched_group には CPU が 1 つだけあります。したがって、smt ドメインの負荷分散は、ハイパースレッド間のプロセス移行を実行することです。この負荷分散は、時間が最も短く、条件が最も緩和されます。

ハイパースレッディングのないアーキテクチャ (またはチップでハイパースレッディングが有効になっていない) の場合、最下位レベルのドメインは MC ドメインです (現時点では、ドメインには MC と NUMA の 2 つのレベルしかありません)。このように、MC ドメイン内の各 CORE は sched_group であり、カーネルはスケジューリング中にこのようなシナリオにうまく適応できます。

MC ドメインはソケット上のすべての CPU で構成され、各 sg は親 smt ドメイン内のすべての CPU で構成されます。したがって、上の図では、MC の sg は 2 つの CPU で構成されています。カーネルは MC ドメインでこのように設計されているため、CFS スケジューリング クラスは、ウェイクアップ ロード バランシングとアイドル ロード バランシング中に MC ドメイン内の SG 間のバランスを要求できます。

この設計はハイパースレッディングにとって非常に重要であり、実際のビジネスでもこの状況が見られます。たとえば、当社にはコーデック事業があり、一部の仮想マシンのテスト データは優れている一方で、一部の仮想マシンのテスト データは劣っていることがわかりました。分析の結果、ハイパースレッディング情報が仮想マシンに透過的に送信されたかどうかが原因であることが分かりました。ハイパースレッディング情報を仮想マシンに渡すと、仮想マシンは2層のスケジューリングドメイン(SMTとMCドメイン)を形成します。 負荷分散を起動すると、CFSはビジネスをアイドルSG(つまり、アイドルCPUではなく、アイドル物理CORE)にスケジュールする傾向があります。 このとき、ビジネスのCPU使用率が高くない場合(40%以下)、物理COREのパフォーマンスをより十分に活用できます(これはまだ古い問題です。物理CORE上のハイパースレッドのペアがCPUを消費するビジネスを同時に実行すると、得られるパフォーマンスゲインは単一スレッドの約1.2倍にすぎません)。 これにより、より良いパフォーマンスゲインが得られます。ハイパースレッディング情報が透過的に伝達されない場合、仮想マシンは物理トポロジーの 1 層 (MC ドメイン) のみを持つことになります。この場合、サービスは物理 CORE のハイパースレッディング ペアを介してスケジュールされる可能性が高いため、システムは物理 CORE の性能を十分に活用できず、サービス性能が低下します。

NUMA ドメインは、システム内のすべての CPU で構成されます。ソケット上のすべての CPU が SG を構成します。上の図の NUMA ドメインは 2 つの SG で構成されています。 NUMA 間のプロセス移行は、NUMA sg 間に大きな不均衡がある場合にのみ実行できます (ここでの不均衡は sg レベルであり、つまり、sg 上のすべての CPU 負荷の合計が別の sg と不均衡である必要があります) (NUMA 間の移行により、L1、L2、L3 のすべてのキャッシュ熱損失が発生し、NUMA 間のメモリ アクセスが増える可能性があるため、慎重に処理する必要があります)。

上記の紹介から、sched_domain と sched_group の連携により、カーネルはさまざまな物理トポロジ (ハイパースレッディングが有効かどうか、NUMA が有効かどうか) に適応し、CPU リソースを効率的に使用できることがわかります。

smp_init

/*
 * 残りをアクティブ化するためにブート プロセッサによって呼び出されます。
 *
 * SMPアーキテクチャでは、BSPは他のすべての非ブートcpを起動する必要がある。
 */
void __init smp_init(void)
{
    整数num_nodes、num_cpus;
    符号なし整数CPU;
 
    /* 各CPUにアイドルスレッドを作成する */
    アイドルスレッド初期化();
    /* cpuhp スレッドをカーネルに登録する */
    スレッド初期化();
 
    pr_info("セカンダリ CPU を起動しています...\n");
 
    /*
     * FIXME: これはユーザー空間で実行する必要があります --RR
     *
     * CPUがオンラインでない場合は、cpu_upで起動します。
     */
    各CPUが存在する場合(CPU) {
        num_online_cpus() が setup_max_cpus より大きい場合
            壊す;
        (!cpu_online(cpu))の場合
            CPU をアップします。
    }
     
    .............
}

sched_init_smp スケジューリング ドメインの初期化を実際に開始する前に、すべての非ブート CPU を起動して、これらの CPU が準備完了状態であることを確認する必要があります。その後、マルチコア スケジューリング ドメインの初期化を開始できます。

スケジュール

次に、マルチコアスケジューリング初期化の具体的なコード実装を見てみましょう(CONFIG_SMPが設定されていない場合、ここでの関連する実装は実行されません)。

スケジューリング

sched_init_numa() は、システムが NUMA であるかどうかを検出するために使用されます。NUMA である場合は、NUMA ドメインを動的に追加する必要があります。

/*
 * トポロジ リスト、ボトムアップ。
 *
 * Linux のデフォルトの物理トポロジ *
 * ここでは物理トポロジーのレベルは 3 つだけであり、NUMA ドメインは sched_init_numa() で自動的に検出されます。 * NUMA ドメインが存在する場合、対応する NUMA スケジューリング ドメインが追加されます。 *
 * 注意: デフォルトの default_topology スケジューリング ドメインには、いくつかの問題がある可能性があります。たとえば、一部のプラットフォームには DIE ドメインがないため (Intel プラットフォーム)、LLC ドメインと DIE ドメインが重複する可能性があります。 * そのため、スケジューリング ドメインが確立された後、カーネルは cpu_attach_domain() ですべてのスケジューリングをスキャンします。 * スケジューリングの重複がある場合は、destroy_sched_domain に対応する重複スケジューリング ドメインが破棄されます。*/
静的構造体sched_domain_topology_level default_topology[] = {
#ifdef CONFIG_SCHED_SMT
    { cpu_smt_mask、cpu_smt_flags、SD_INIT_NAME(SMT) },
#終了
#ifdef CONFIG_SCHED_MC
    { cpu_coregroup_mask、cpu_core_flags、SD_INIT_NAME(MC) }、
#終了
    { cpu_cpu_mask, SD_INIT_NAME(DIE) },
    { NULL, },
};

Linuxのデフォルトの物理トポロジ

/*
 * NUMA スケジューリング ドメインの初期化 (ハードウェア情報に基づいて新しい sched_domain_topology 物理トポロジ構造を作成する)
 *
 * カーネルはデフォルトではNUMAトポロジを積極的に追加しないため、設定する必要があります(NUMAが有効になっている場合)
 * NUMA が有効になっている場合は、ハードウェア トポロジ情報に基づいて、* sched_domain_topology_level ドメインを追加するかどうかを決定する必要があります (このドメインを追加した後でのみ、カーネルは後で * sched_domain を初期化するときに NUMA ドメインを作成します)
 */
void sched_init_numa(void)
{
    ................
    /*
     * ここでは、距離に基づいて NUMA ドメイン (複数の NUMA ドメインも含む) が存在するかどうかを確認し、状況に応じて物理トポロジ構造に更新します。後でスケジューリングドメインを作成するときは、この新しい*物理トポロジを使用して新しいスケジューリングドメイン*を作成します。
    (j = 1; j < レベル; i++, j++) {
        tl[i] = (構造体sched_domain_topology_level){
            .mask = sd_numa_mask、
            .sd_flags = cpu_numa_flags、
            .flags = SDTL_OVERLAP、
            .nu​​ma_level = j、
            SD_INIT_NAME(NUMA)
        };
    }
 
    sched_domain_topology を tl に設定します。
 
    sched_domains_numa_levels = レベル;
    sched_max_numa_distance = sched_domains_numa_distance[レベル - 1];
 
    トポロジータイプを初期化します。
}

システムの物理トポロジを検出します。NUMA ドメインが存在する場合は、それを sched_domain_topology に追加します。その後、sched_domain_topology の物理トポロジに基づいて、対応するスケジューリング ドメインが確立されます。

sched_init_domains

次に、スケジューリングドメイン作成関数sched_init_domainsを分析します。

/*
 * スケジューラドメインとグループを設定します。現時点では、分離されたドメインは除外されます。
 * CPU ですが、将来的には他の特殊なケースを除外するために使用される可能性があります。
 */
int sched_init_domains(const struct cpumask *cpu_map)
{
    整数エラー;
 
    zalloc_cpumask_var(&sched_domains_tmpmask, GFP_KERNEL);
    zalloc_cpumask_var(&sched_domains_tmpmask2, GFP_KERNEL);
    zalloc_cpumask_var(&fallback_doms, GFP_KERNEL);
 
    arch_update_cpu_topology();
    ndoms_cur = 1; いいえ
    doms_cur = alloc_sched_domains(ndoms_cur);
    もし (!doms_cur)
        doms_cur = &fallback_doms;
    /*
     * doms_cur[0]は、スケジューリングドメインが上書きする必要があるcpumaskを示します。
     *
     * isolcpus= を使用してシステム内の一部の CPU を分離する場合、これらの CPU はスケジューリング ドメインに追加されません。つまり、これらの CPU は負荷分散に参加しません (ここでの負荷分散には DL/RT と CFS が含まれます)。
     * ここで、isolate は cpu_map と housekeeping_cpumask(HK_FLAG_DOMAIN) によって使用されます。
     * CPU が削除され、分離 CPU が確立されたスケジューリング ドメインに含まれないようにします。
     */
    cpumask_and(doms_cur[0], cpu_map, housekeeping_cpumask(HK_FLAG_DOMAIN));
    /* スケジューリングドメイン確立の実装関数*/
    エラー = build_sched_domains(doms_cur[0], NULL);
    ドメインを登録します。
 
    エラーを返します。
}
/*
 * 指定されたCPUセットのスケジュールドメインを構築し、スケジュールドメインをアタッチする
 * 個々のCPUに
 */
静的整数
build_sched_domains(const 構造体 cpumask *cpu_map、構造体 sched_domain_attr *attr)
{
    列挙型 s_alloc alloc_state = sa_none;
    構造体 sched_domain *sd;
    構造体 s_data d;
    構造体 rq *rq = NULL;
    int i, ret = -ENOMEM;
    構造体 sched_domain_topology_level *tl_asym;
    ブールhas_asym = false;
 
    (WARN_ON(cpumask_empty(cpu_map))の場合)
        エラーに移動します。
 
    /*
     * LinuxのほとんどのプロセスはCFSでスケジュールされるため、CFSのsched_domainは頻繁にアクセスされ、変更されます(nohz_idleやsched_domainのさまざまな統計など)。そのため、sched_domain
     * 設計では効率を優先する必要があるため、カーネルはpercpu方式を使用してsched_domainを実装します。
     * CPU 間の sd の各レベルは独立して適用される percpu 変数であるため、percpu の特性を使用してそれらの間の同時実行競合問題を解決できます。* (1. ロック保護は不要 2. キャッシュラインの疑似共有なし)
     */
    alloc_state = __visit_domain_allocation_hell(&d, cpu_map);
    (alloc_state != sa_rootdomain) の場合
        エラーに移動します。
 
    tl_asym = asym_cpu_capacity_level(cpu_map);
 
    /*
     * cpu_map で指定された CPU のドメインを設定します。
     *
     * ここでは、cpu_map内のすべてのCPUを走査し、これらのCPUに対応する物理トポロジ構造を作成します(
     * for_each_sd_topology のマルチレベル スケジューリング ドメイン。
     *
     * スケジューリングドメインが確立されると、このレベルのスケジューリングドメイン内のCPUに対応するスパンはtl->mask(cpu)を通じて取得されます(つまり、CPUと他の対応するCPUがこのスケジューリングドメインを形成します)。同じスケジューリングドメイン内のCPUに対応するsdは、最初に同じに初期化されます(sd->panを含む、
     * sd->imbalance_pct および sd->flags)。
     */
    for_each_cpu(i, cpu_map) {
        構造体 sched_domain_topology_level *tl;
 
        sd = NULL;
        for_each_sd_topology(tl) {
            int dflags = 0;
 
            tl == tl_asymの場合{
                dflags |= SD_ASYM_CPUCAPACITY;
                非対称性 = true;
            }
 
            sd = build_sched_domain(tl、cpu_map、attr、sd、dflags、i);
 
            tl == sched_domain_topologyの場合
                *per_cpu_ptr(d.sd, i) = sd;
            if (tl->flags & SDTL_OVERLAP)
                sd->フラグ |= SD_OVERLAP;
            (cpumask_equal(cpu_map、sched_domain_span(sd))の場合)
                壊す;
        }
    }
 
    /*
     * ドメインのグループを構築する
     *
     * ディスパッチグループを作成する *
     * 2つのスケジューリングドメインの実装からsched_groupの役割がわかります* 1. NUMAドメイン 2. LLCドメイン*
     * numa sched_domain->span は、NUMA ドメイン上のすべての CPU を含みます。バランス調整が必要な場合、 * NUMA ドメインは CPU ではなくソケット、つまり socket1 と socket2 のみに基づいている必要があります。
     * 極端な不均衡がある場合にのみ、CPU は 2 つのソケット間で移行されます。この抽象化を実装するために sched_domain を使用すると、柔軟性が不十分になります (以下の MC ドメインでわかるように)。そのため、カーネルは sched_group を使用して CPU セットを表し、各ソケットは sched_group に属します。移行は、2つのsched_groupが不均衡な場合にのみ許可されます*
     * MC ドメインも同様です。CPU はハイパースレッディングされていますが、ハイパースレッディングのパフォーマンスは物理コアのパフォーマンスと同等ではありません。ハイパースレッドのペア* は、物理コアのパフォーマンスの約 1.2 倍に相当します。したがって、スケジューリングを行う際には、ハイパースレッディング* ペア間のバランスを考慮する必要があります。つまり、まず CPU 間のバランスを満たし、次に CPU 内のハイパースレッディングのバランスを満たす必要があります。このとき、sched_group は抽象化に使用されます。1 つの sched_group は 1 つの物理 CPU (2 つのハイパースレッド) を表します。このとき、LLC は CPU 間のバランスを確保し、ハイパースレッド間のバランスは取れているが物理コアの不均衡という極端な状況を回避します。同時に、スケジューリングとコアの選択時に、カーネルが物理スレッドを優先することを保証できます。物理スレッドが使い果たされた場合にのみ、別のハイパースレッドの使用が検討され、システムは CPU の計算能力をより有効に活用できます。
    for_each_cpu(i, cpu_map) {
        sd = *per_cpu_ptr(d.sd, i); sd; sd = sd->parent) {
            sd->span_weight = cpumask_weight(sched_domain_span(sd));
            if (sd->flags & SD_OVERLAP) {
                (build_overlap_sched_groups(sd、i))の場合
                    エラーに移動します。
            } それ以外 {
                (build_sched_groups(sd、i))の場合
                    エラーに移動します。
            }
        }
    }
 
    /*
     * 物理パッケージとノードのCPU容量を計算する
     *
     * sched_group_capacity は sg に利用可能な CPU パワーを示すために使用されます*
     * sched_group_capacityは、各CPUの異なる計算能力(異なる最大周波数設定、
     * ARM のビッグコアとスモールコアなど)、RT プロセスが使用する CPU を削除(sg は CFS 用に用意されているため、CPU 上の DL/RT プロセスが使用する CPU 計算能力を削除する必要があります)など、利用可能な計算能力を CFS sg に残します(負荷分散時には、CPU の負荷だけでなく、この sg 上の CFS も考慮する必要があるため)。
     * 利用可能な計算能力。この SG 上のプロセス数が少ないが、sched_group_capacity も小さい場合は、プロセスをこの SG に移行しないでください。
     */
    (i = nr_cpumask_bits-1; i >= 0; i--) {
        (!cpumask_test_cpu(i、cpu_map))の場合
            続く;
 
        sd = *per_cpu_ptr(d.sd, i); sd; sd = sd->parent) {
            請求割り当て(i, sd);
            sd を初期化します。
        }
    }
 
    /* ドメインを添付する */
    rcu_read_lock();
    /*
     * 各 CPU の rq を rd (root_domain) にバインドし、sd が重複しているかどうかを確認します。 * 重複している場合は、destroy_sched_domain() を使用して削除します (次のことがわかります。 * Intel サーバーには 3 つのスケジューリング ドメインしかなく、DIE ドメインは実際には LLC ドメインと重複しているため、ここで削除されます)
     */
    for_each_cpu(i, cpu_map) {
        rq = cpu_rq(i);
        sd = *per_cpu_ptr(d.sd, i);
 
        /* ロード/ストアのティアリングを回避するには、READ_ONCE()/WRITE_ONCE() を使用します: */
        rq->cpu_capacity_orig > READ_ONCE(d.rd->max_cpu_capacity) の場合
            WRITE_ONCE(d.rd->max_cpu_capacity、rq->cpu_capacity_orig);
 
        cpu_attach_domain(sd、d.rd、i);
    }
    rcu_read_unlock();
 
    (非対称性がある場合)
        static_branch_inc_cpuslocked(&sched_asym_cpucapacity);
 
    rq と sched_debug_enabled の場合 {
        pr_info("ルートドメイン範囲: %*pbl (最大CPU容量 = %lu)\n",
            cpumask_pr_args(cpu_map)、rq->rd->max_cpu_capacity;
    }
 
    戻り値 0;
エラー:
    __free_domain_allocs(&d, alloc_state, cpu_map);
 
    ret を返します。
}

これまでにカーネル スケジューリング ドメインを構築し、CFS は sched_domain を使用して複数のコア間の負荷分散を実現できるようになりました。

V. 結論

この記事では、主にカーネル スケジューラの基本概念を紹介し、5.4 カーネルのスケジューラの初期化コードを分析することで、スケジューリング ドメインやスケジューリング グループなどの基本概念の具体的な実装を紹介します。全体的に、3.x カーネルと比較すると、5.4 カーネルでは、スケジューラ初期化ロジックとスケジューラに関連する基本設計 (概念/主要な構造) に本質的な変更はなく、これはカーネル スケジューラ設計の「安定性」と「洗練性」を間接的に裏付けています。

上記は、Linux カーネル スケジューラ ソース コードの初期化を分析する詳細な内容です。Linux カーネル スケジューラ ソース コードの初期化の詳細については、123WORDPRESS.COM の他の関連記事に注目してください。

以下もご興味があるかもしれません:
  • Linux カーネルプログラミングにおけるコンテナの of() 関数の紹介
  • Linuxカーネルとデバイスツリーのコンパイルと書き込みを分析する
  • Hongmengライトカーネル静的メモリの使用分析
  • カーネルスレッド理論の詳細な説明とJavaの例
  • C言語を使用してカーネルを書く方法を説明する記事

<<:  Vue-router ネストルーティングの詳細な説明

>>:  美しいチェックボックススタイル(複数選択ボックス)はIE8/9/10、FFなどと完全に互換性があります。

推薦する

フロントエンドの上級者向けコースでは、JavaScript のストレージ機能の使い方を学習します。

目次序文背景実施計画の考え方js ストレージ機能ソリューション設計やっと要約する序文どの SaaS ...

MySQLデータベースインデックスの詳細な紹介

目次マインドマップシンプルな理解インデックスモデルの進化二分探索木自己バランス型二分木BツリーB+ ...

Vue+swiperでタイムライン効果を実現

この記事では、タイムライン効果を実現するためのvue+swiperの具体的なコードを参考までに共有し...

ウェブサイトはグレー表示されています。画像を含む互換コードはすべてのブラウザをサポートしています

通常、国喪の日、大地震の日、清明節には、ウェブサイト全体を灰色にして、故人への哀悼の意を表します。そ...

jQueryアニメーションを理解するのに役立つ記事

目次1. 要素の表示と非表示を制御する show() hide() 2. 要素の透明度を制御する f...

HTML テーブルの行間隔を変更する方法の例

HTML テーブルを使用する場合、行間隔を変更する必要がある場合がありますが、余白、パディング、折り...

HTML テーブルタグチュートリアル (26): セルタグ

<TD> タグの属性は、テーブル内のセルのプロパティを設定するために使用されます。表 &...

MySQL の準同期レプリケーションについての簡単な説明

導入MySQL はレプリケーションを通じてストレージ システムの高可用性を実現します。現在、MySQ...

jsはaudioContextを通じて3Dサウンド効果を実現します

この記事では、audioContextを介して3Dサウンド効果を実現するためのjsの具体的なコードを...

sqlite を mysql スクリプトに移行する方法

さっそく、コードを直接投稿します。具体的なコードは次のとおりです。 パーレル # # https:/...

Linux nslookup コマンドの使用方法の詳細な説明

[nslookup とは?] 】 nslookup コマンドは、Linux で非常によく使用されるネ...

CSSでスペースを処理する方法

1. 宇宙のルールHTML コード内の空白は通常、ブラウザによって無視されます。 <p>...

HTML コード作成ガイド

共通コンベンションタグ自己終了タグ。閉じる必要はありません (例: img input br hr ...

Virtualbox で Ubuntu 16.04 の起動時に共有ディレクトリを自動的にマウントする最良の方法

仮想マシンを使用する人は通常、操作と使用を容易にするために仮想マシン用の共有ディレクトリを設定します...

Linux で MySQL スケジュールタスクを実装する方法

前提: ストアド プロシージャは、毎日午後 10 時から午前 5 時まで 10 分ごとに実行されます...