前のセクションでは、CFSの導入、CFSによる公平性の実現、CFSの動作など、CFSの設計原則について学びました。このセクションでは、CFSスケジューラーに関連するいくつかの一般的なデータ構造の分析に焦点を当て、これらのデータ構造の簡単な要約を作成し、さまざまなデータ構造間の関係を整理します。
スケジューリング
CFSスケジューラーはLinux2.6.23で導入された当時、スケジューリングクラスの概念が提案されていましたが、スケジューリングクラスはスケジューリング戦略をモジュール化し、一種のオブジェクト指向の感覚を持つものです。まず、スケジューリングクラスのデータ構造を見てみましょう。スケジューリングクラスは、struct sched_classデータ構造によって表されます。
struct sched_class {
const struct sched_class *next;
void (*enqueue_task) (struct rq *rq, struct task_struct *p, int flags);
void (*dequeue_task) (struct rq *rq, struct task_struct *p, int flags);
void (*check_preempt_curr)(struct rq *rq, struct task_struct *p, int flags);
/*
* It is the responsibility of the pick_next_task() method that will
* return the next task to call put_prev_task() on the @prev task or
* something equivalent.
*
* May return RETRY_TASK when it finds a higher prio class has runnable
* tasks.
*/
struct task_struct * (*pick_next_task)(struct rq *rq,
struct task_struct *prev,
struct rq_flags *rf);
void (*put_prev_task)(struct rq *rq, struct task_struct *p);
void (*set_curr_task)(struct rq *rq);
void (*task_tick)(struct rq *rq, struct task_struct *p, int queued);
sched_classメンバーの数が多いため、最初に一般的に使用されるメンバーをいくつか紹介します。スケジューリングクラスは基本的に関数ポインターであり、これらの関数ポインターはそれぞれ異なる意味を表していることがわかります。
- nextは、次のレベルのスケジューリングクラスを指すために使用されます。スケジューリングクラスは、各スケジューリング戦略のカーネルで提供され、これらのスケジューリングクラスは、次のメンバーを通じてリンクされます。
- enqueue_task:実行可能なプロセスの数を増やしながら、準備完了キューにプロセスを追加するために使用されます
- dequeue_task:実行可能なプロセスの数を減らしながら、準備完了キューからプロセスを削除するために使用されます
- check_preempt_curr:プロセスのステータスが実行可能に設定されたことを検出するために使用されます。現在のプロセスをプリエンプトできるかどうかを確認します
- pick_next_task:実行する実行キューで次に適切なプロセスを選択します
- put_prev_task:現在のプロセスの前にプロセスを取得します
- set_curr_task:現在のプロセスのスケジューリングステータスなどを設定するために使用されます。
- task_tick:各スケジューリングクラスのティック関数は、各クロックティックでスケジュールされます。
Linuxカーネルはこれらのスケジューリングクラスを提供します
extern const struct sched_class stop_sched_class;
extern const struct sched_class dl_sched_class;
extern const struct sched_class rt_sched_class;
extern const struct sched_class fair_sched_class;
extern const struct sched_class idle_sched_class;
Linuxカーネルは5つのスケジューリングクラスを定義し、各スケジューリングには対応するスケジューリング戦略があり、各スケジューリング戦略はプロセスがスケジュールされる場所に対応しています。
/*
* Scheduling policies
*/
#define SCHED_NORMAL 0
#define SCHED_FIFO 1
#define SCHED_RR 2
#define SCHED_BATCH 3
/* SCHED_ISO: reserved but not implemented yet */
#define SCHED_IDLE 5
#define SCHED_DEADLINE 6
6つのスケジューリング戦略も提供されます。以下は、スケジューリング戦略とスケジューリングクラスの関係を示しています。
スケジューリング | スケジューリング戦略 | スケジュールオブジェクト |
---|---|---|
stop_sched_class(停止スケジューリングクラス) | なし | ダウンタイムプロセス |
dl_sched_class(デッドスケジュールクラス) | SCHED_DEADLINE | dlプロセス |
rt_sched_class(リアルタイムスケジューリングクラス) | SCHED_RRまたはSCHED_FIFO | リアルタイムのプロセス |
fair_sched_class(フェアスケジューリングクラス) | SCHED_NORMALまたはSCHED_BATCH | 通常のプロセス |
idle_sched_class(アイドルスケジューリングクラス) | SCHED_IDLE | アイドルプロセス |
同時に、これらのスケジューリングクラス間には優先関係があります。
#ifdef CONFIG_SMP
#define sched_class_highest (&stop_sched_class)
#else
#define sched_class_highest (&dl_sched_class)
#endif
#define for_each_class(class) \
for (class = sched_class_highest; class; class = class->next)
SMPが定義されている場合、最高の優先順位は停止スケジューリングクラスです。スケジューリングクラスの優先関係
stop_sched_class> dl_sched_class> rt_sched_class> fair_sched_class> idle_shced_class
スケジューリングエンティティ
struct sched_entity {
/* For load-balancing: */
struct load_weight load;
unsigned long runnable_weight;
struct rb_node run_node;
struct list_head group_node;
unsigned int on_rq;
u64 exec_start;
u64 sum_exec_runtime;
u64 vruntime;
u64 prev_sum_exec_runtime;
u64 nr_migrations;
struct sched_statistics statistics;
Linux2.6.23以降、スケジューリングエンティティの概念が導入され、スケジューリングエンティティはプロセスのいくつかの重要な情報をカプセル化します。以前のO(1)アルゴリズムでは、スケジューリング単位はtask_structであり、Linux2.6.23でのスケジューリングモジュール化の導入後、スケジューリング単位はスケジューリングエンティティsched_entityになります。
- 負荷はこのプロセスの重みです
- run_node:CFSスケジューリングは赤黒ツリーを介してプロセスを管理することであり、これは赤黒ツリーのノードです
- on_rq:この値が1の場合、プロセスが実行中のキューにあることを意味します
- exec_start:このプロセスがCPUでタスクの実行を開始する時刻を記録します
- sum_exec_runtime:このプロセスの合計実行時間を記録します
- vruntime:プロセスの仮想実行時間を表します
- prev_sum_exec_runtime:前のプロセスの合計実行時間を記録します
- nr_migrations:負荷分散中のプロセス移行の数
- 統計:プロセス統計
赤黒木
赤黒ツリーの誰もが見知らぬ人ではないので、ツリーの左側のノードの値は常にツリーの右側の値よりも小さくなります。
O(n)およびO(1)スケジューラでは、実行キューは配列リンクリストによって管理され、以前のデータ構造はCFSスケジューリングで破棄され、時間をキー値とする赤黒ツリーが使用されます。 。時間キーは、プロセスのvruntimeです。
CFSは、時間でソートされた赤黒ツリーのレッスンを維持しますすべての赤黒ツリーノードは、プロセスのse.vruntimeをキーとしてソートされます。CFSは、スケジュールするたびにこの赤と黒のツリーの左端のノードを常に選択し、次にそれをスケジュールします。時間が経過すると、左側のノードが以前と同様に実行され、プロセスのvruntimeも増加し、これらのプロセスは赤黒ツリーの右側にゆっくりと追加されます。このツリーのすべてのプロセスは、公平性を実現するために、往復でスケジュールされます。
同時に、CFSはこのツリーの最小vruntime値cfs.min_vruntimeも維持し、この値は単調に増加しています。この値は、実行キュー内の最小のvruntime値を追跡するために使用されます。
実行キュー
システムの各CPUには実行キューのstruct rqデータ構造があります。このstruct rqはPER-CPUです。複数のCPUが実行キューに同時にアクセスしないようにするには、各CPUにそのような実行キューが必要です。
/*
* This is the main, per-CPU runqueue data structure.
*
* Locking rule: those places that want to lock multiple runqueues
* (such as the load balancing or the thread migration code), lock
* acquire operations must be ordered by ascending &runqueue.
*/
struct rq {
unsigned int nr_running;
/* capture load from *all* tasks on this CPU: */
struct load_weight load;
unsigned long nr_load_updates;
u64 nr_switches;
struct cfs_rq cfs;
struct rt_rq rt;
struct dl_rq dl;
コメントから、struct rqはCPUごとの変数であることがわかります。
- nr_running:この実行キューで実行中のプロセスの総数を表します
- load:このCPU上のすべてのプロセスの重み。このCPUで実行されている可能性のあるプロセスには、リアルタイムプロセス、通常のプロセスなどが含まれます。
- nr_switches:プロセス切り替えの統計
- struct cfs_rq:CFSスケジューリングクラスの実行キューです
- rt_rq構造体:rtスケジューリングクラスの実行キューを表します
- struct dl_rq:dlスケジューリングクラスの実行キューを表します
導き出せる1つの結論は、struct rqには、DL、リアルタイム、および通常のさまざまなタイプのプロセスが含まれているということです。異なるプロセスを異なる実行キュー管理に一時停止する。
/* CFS-related fields in a runqueue */
struct cfs_rq {
struct load_weight load;
unsigned long runnable_weight;
unsigned int nr_running;
unsigned int h_nr_running;
u64 exec_clock;
u64 min_vruntime;
コメントから、struct cfs_rqはCFSスケジューリング戦略に対応する実行キューを表します
- load:このCFS_rqの重みであり、CFSレディキュー内のすべてのプロセスを含みます
- nr_running:このCFS実行キューで実行できるプロセスの数を表します
- min_vruntime:この値は、CFS実行キュー内のすべてのプロセスの最小vruntimeを表します
実行キューの関係図を見てください
各CPUにはstruct rq実行キューがあり、struct rqはプロセススケジューリング戦略に従って異なる実行キューに分割されます。たとえば、通常のプロセスはcfs_rqにマウントされます。 vruntimeの値に従って、スケジューリングエンティティが赤黒ツリーのノードに追加されます。