実行する命令
コンパイラが生成した機械語命令が置かれるメモリ領域です。各スレッドは同じプログラムの命令を実行します。
プロセスとスレッドからCPUの割り当てまで
複数のプログラムが動くコンピューターでは、LinuxカーネルがCPUを使う対象を選び、必要に応じて切り替えます。まず、何を一つの実行単位として数えるのかから説明します。
プログラムプログラムコンピューターに実行させる命令とデータを記録したもの。を起動すると、OSはプロセスプロセス実行中のプログラムに、仮想アドレス空間やファイルディスクリプタ表などの実行環境を持たせた管理単位。を作ります。プロセスは、実行に使う仮想アドレス空間仮想アドレス空間一つのプロセスが使用できる仮想アドレスの範囲。、開いているファイルやソケット、権限などをまとめる境界です。
スレッドスレッドプロセス内で命令を順に実行する流れ。は、そのプロセス内で命令を実行する流れです。各スレッドは、現在の命令位置、CPUレジスタ、stackを個別に持ちます。同じプロセスに属するスレッドは、次の図にあるcode、data、heapheapプログラムが実行中に必要な大きさを確保し、不要になれば解放するメモリ領域。、open filesを共有します。
コンパイラが生成した機械語命令が置かれるメモリ領域です。各スレッドは同じプログラムの命令を実行します。
グローバル変数やstatic変数などが置かれる領域です。あるスレッドによる変更を、ほかのスレッドからも参照できます。
mallocやnewなどで確保したオブジェクトや配列が置かれます。同時に読み書きする場合は、ロックなどで整合性を保ちます。
開いているファイルやソケットを番号で参照する表です。あるスレッドがcloseすると、同じ番号を使うほかのスレッドにも影響します。
仮想アドレス空間、ファイルディスクリプタ表、権限情報、シグナルの設定などをプロセスごとに管理します。別のプロセスとデータを共有する場合は、共有メモリやIPCなどを明示的に使います。
同じプロセスの仮想アドレス空間やファイルディスクリプタ表を共有し、命令位置、CPUレジスタ、stackは個別に持ちます。
Linuxカーネルは、各スレッドの実行状態を個別に管理します。この管理単位をタスクタスクLinuxカーネルがCPUへ割り当てる実行単位。と呼び、その情報をtask_structというデータ構造に記録します。同じプロセスに属するスレッドのまとまりをthread groupthread groupLinuxで、同じプロセスに属するスレッドをまとめて扱う単位。と呼びます。
I/Oやイベントを待っておらず、CPUを割り当てられれば命令を実行できる状態を実行可能実行可能CPUが空けば実行できる状態。と呼びます。Linuxカーネルが、実行可能なタスクのうち、次にどれをどのCPUで実行するか、いつ別のタスクへ切り替えるかを決める処理がCPUスケジューリングです。その判断を担当するカーネルの仕組みをスケジューラスケジューラ実行可能なタスクをCPUへ割り当てるカーネルの仕組み。と呼びます。実行中のタスクを中断して別のタスクへ切り替える処理はプリエンプションプリエンプション実行中のタスクを中断し、別のタスクへCPUを渡すこと。です。
Linuxは、実行可能なタスクをlogical CPUごとに分けて管理します。その管理に使うカーネル内部の構造をrunqueuerunqueue現在実行中のタスクと、次に実行する候補をCPUごとに管理する構造。と呼びます。runqueueには、現在実行中のタスクと、次に実行する候補が記録されます。待機中のタスクは条件が満たされると実行可能になり、再びrunqueueへ入ります。
一つの物理コアから複数の実行状態を見せる機能をSMTSMT一つの物理CPUコアへ複数の実行状態を持たせ、Linuxから複数のlogical CPUとして見せる機能。と呼びます。物理コアとSMTによる各実行単位は、Linuxから個別のCPUとして見えます。
待っていたI/Oが完了したり、タイマーが満了したりすると、タスクはCPUで実行できる状態へ戻ります。
スケジューラはタスクを実行するCPUを決め、そのCPUのrunqueueへ追加します。複数CPUでは、各runqueueの負荷なども考慮します。
タスクの用途や設定に対応する選択規則を順に確認し、次に実行するタスクを決めます。この規則の実装単位は、次の節で説明します。
現在のタスクのCPUレジスタなどを保存し、次のタスクの状態を復元します。CPUは復元した位置から実行を再開します。
logical CPUごとにrunqueueがあるため、Linuxは次に実行するタスクだけでなく、そのタスクをどのlogical CPUへ置くかも決めます。特定のCPUだけが混雑しないように調整しつつ、アプリや管理者が指定した範囲と、CPUやRAMの構成を考慮します。
各スレッドには、実行を許可するCPUの集合があります。スケジューラは、その範囲から配置先を選びます。
空いているCPUと混雑しているCPUがあれば、許可された範囲内でタスクを別のrunqueueへ移します。
CPUに近いRAMほど短い時間でアクセスできる構成では、タスクと、そのタスクが使うpageの位置も性能へ影響します。
CPU affinityCPU affinityスレッドを実行できるCPUの集合を指定する仕組み。とcgroupのcpusetは、タスクを実行できるCPUの範囲を制限します。cgroupのcpu.weightとcpu.maxは、プロセス群へ配るCPU時間の重みと上限を設定します。cgroupの章で設定の違いを見る
通常のアプリへCPU時間を公平に配る場合と、決めた優先度や時間制約を優先する場合では、タスクを選ぶ規則が異なります。アプリがスレッドへ指定するSCHED_OTHER、SCHED_FIFO、SCHED_DEADLINEなどを、スケジューリングポリシーと呼びます。Linuxカーネルは、近い規則を持つポリシーをまとめて実装しています。この実装単位がスケジューリングクラスです。
スケジューラコアは、優先順位が高いクラスから実行候補の有無を確認します。候補を持つクラスは、そのクラスの規則に従って次のタスクを選びます。
runtime、deadline、periodを指定し、時間制約と帯域を管理します。
固定優先度を基準に選びます。SCHED_RRは同じ優先度のタスクへ時間を配分します。
通常のアプリやサーバープロセスを扱います。公平なCPU配分と応答性を調整します。カーネル内部ではSCHED_OTHERをSCHED_NORMALと呼びます。
通常のタスクを選ぶ仕組みは、候補数の増加、対話的な処理の応答性、複数CPUでの拡張性に対応してきました。現在のEEVDFは、公平さの計算と要求された時間配分から仮想デッドラインを求めます。
選択対象になったタスクから、仮想デッドラインが最も早いものを選びます。
カードは、アプリやサービスを構成するスレッドの例です。図では次のタスクを選ぶ考え方に絞り、数値には説明用の模式値を使っています。
CPUレジスタなどを保存し、次のタスクの状態を読み込みます。
優先度を反映したCPU使用量です。CFSとEEVDFが計算に使います。
選択対象と仮想デッドラインを使い、待ち時間と公平さを調整します。