Linuxのプロセススケジューラに関するsysctlパラメタ
はじめに
本記事はLinux Advent Calendar 2015 19日目用に書きました。プロセススケジューラ(以下スケジューラと記載)に関するsysctlパラメタについての解説をしています。取り扱ったのは全パラメタではなく、使用頻度が比較的高そうなものだけです。調査に使用したカーネルのバージョンは4.4-rc5です。
Completely Fair Scheduler(CFS)に関するsysctlパラメタ
linux上の各プロセスにはスケジューリングポリシー(以下ポリシーと記載)というものを設定できます。通常のプロセスはデフォルトSCHED_OTHERというポリシーを使います。このポリシーを使うプロセスのスケジューリングにはCFSというスケジューラを使います。ここではCFSに関するsysctlパラメタを紹介します。
スケジューリングポリシーの詳細についてはman 2 sched_{get,set}schedulerを参照してください。
以下CFSの説明をするにあたって、nice値を考慮すると説明が複雑化するので、ここではすべてのプロセスのnice値をすべてデフォルトの0と考えてください。
kernel.sched_latency_ns
時分割によるスケジューリングの一単位の期間を示すlatencyという値。単位はナノ秒。デフォルト値は"6,000,000 * (1+log2(CPU数))"。
実行可能プロセスが複数存在する場合、CFSは時分割形式でCPU実行権を各プロセスに公平に分け合います。latencyと呼ばれる期間毎に各プロセスはlatency/実行可能プロセス数だけCPU時間を使えます。この割り当て時間をタイムスライスと呼びます。例えばlatencyが100ミリ秒で実行可能プロセスが10個なら、各プロセスは100ミリ秒ごとに"100/10=10"ミリ秒動けます。同様に実行可能プロセスの数が5個なら、100ミリ秒ごとに"100/5=20"ミリ秒動けます。
上記の動作を実現するために、CFSは次のような内部論理を持っています。CFSで管理されるプロセスは各自vruntimeという値を持っています。CFSはvruntimeが小さいプロセスから順番にCPUを割り当てます。vruntimeは、次のような特徴を持つ、各プロセスの仮想的な累計実行時間を指しています。
- 実行可能なプロセスについて意味を持つ
- 現在時刻がlatencyの倍数になったときに、全プロセスがのvruntimeがその値になる
- タイムスライスで示されるの期間、CPU上で動くと、latencyで示される期間だけ動いたように値が修正される
- スリープしたプロセスが起床すると、当該プロセスのvruntimeは、ランキュー内のプロセスのvruntime最小値に切り上げられる
言葉で説明してもわかりづらいので、例えば以下のような状況を考えます。
*latencyは20ミリ秒
*P0,P1という2つのプロセスが存在する(つまりタイムスライスは"20/2=10"ミリ秒)
*P0,P1は常に実行可能状態(スリープしない)
*P0,P1の実際の実行時間をt0,t1とする。このときvruntimeはv0,v1。初期値は全て0ミリ秒
この場合、実時間の経過と共に、2つのプロセスのvruntimeは以下の表のように変化します。表の中の数値の単位はすべてミリ秒です。v0,v1が同じ値の場合はどちらが先に動いてもいいのですが、ここでは借りにP0が先に動くものとします。
実時間 | t0 | t1 | v0 | v1 |
---|---|---|---|---|
0 | 0 | 0 | 0 | 0 |
10 | 10 | 0 | 20 | 0 |
20 | 10 | 10 | 20 | 20 |
30 | 20 | 10 | 40 | 20 |
40 | 20 | 20 | 40 | 40 |
latencyの倍数の時間において(0,20,40ミリ秒時点)、次のことがわかります。
- t0, t1を足すと実時間の経過時間に等しい
- 実時間がlatencyの倍数の時間には、それぞれのプロセスのvruntimeの時間は実時間に等しい
P1が実時間でいう0ミリ秒から20ミリ秒までスリープしていた場合はどうなるでしょうか。上記の通り、スリープしていたプロセスが起床したときには、当該プロセスのvruntimeを、実行可能なプロセスのvruntimeのうちの最小値にするという仕組みが存在します。これを踏まえると、実時間20ミリ秒時点でP1が起床する場合、次のような挙動になります。
実時間 | t0 | t1 | v0 | v1 |
---|---|---|---|---|
0 | 0 | 0 | 0 | 0 |
10 | 10 | 0 | 10 | 0 |
20 | 20 | 0 | 20 | 20 |
30 | 30 | 0 | 40 | 20 |
40 | 30 | 10 | 40 | 40 |
この場合も、latencyの倍数の時間において(0,20,40ミリ秒時点)、1つ前の例と同じ条件を満たしていることがわかります。
kernel.sched_min_granularity_ns
各プロセスがCPU実行権を得た際の最低実行時間を示すmin_granularityという値。単位はナノ秒。デフォルト値は"750_000 * (1+log2(CPU数))"。
上述したCFSのスケジューリングの仕組みをそのまま使うと、実行可能なプロセスが大量に存在する場合に、コンテキストスイッチによるオーバーヘッドが無制限に大きくなってしまいます。例えばlatencyが100ミリ秒でプロセスが1,000個あった場合、各プロセスは一度に"100/1,000=0.1"ミリ秒、つまり100マイクロ秒しか動けません。仮にコンテキストスイッチのコストを10マイクロ秒とすると(もちろん実際の値はシステムによって異なります)、コンテキストスイッチだけでCPUリソースの10%を食い潰してしまいます。これを防ぐためにmin_granularityという値が存在します。"latency/実行可能プロセス数 < min_granularity"の場合、実際のlatencyも"min_granularity*プロセス数"になる(sysctlパラメタの値は変わらない)と共に、各プロセスのタイムスライスはmin_granularityになります。
kernel.sched_wakeup_granularity_ns
スリープ状態から起床したプロセスが現在実行中のプロセスに割り込んで実行できるかどうか(preemptできるかどうか)を決めるwakeup_granularityという値。単位はナノ秒。デフォルト値は"1,000,000 * (1+log2(# of CPUS))"。
スリープ状態から起床したプロセスは、動作直後に短時間だけCPUを使ってまたスリープするという傾向にあります。それが顕著なのがshellなどの対話型のプロセスです。システムの応答性を上げるためには、これらプロセスの応答性を上げる必要があります。そのためにCFSは、スリープ状態から起床したプロセスのvruntimeが現在実行中のプロセスのそれより少なければ、前者は後者をpreemptできるという仕組みになっています。
ただし、この仕組みをそのまま使ってしまうと、スリープと起床を繰り返すようなプロセスが大量に存在する場合に、preemptによるコンテキストスイッチが大量に発生すると共に、そのオーバーヘッドも大きくなってしまいます。本パラメタはそれを防ぐために存在します。latencyで定められた期間の中で、起床したプロセスの実行時間が現在実行中のプロセスのそれよりもwakeup_granularityだけ少ない場合のみpreemptできるようになっています。
リアルタイムスケジューラに関するsysctlパラメタ
SCHED_OTHERの他には、SCHED_FIFO, SCHED_RRというポリシーがあります。これらのポリシーはリアルタイムポリシーと呼ばれます。リアルタイムポリシーを使うプロセスは、SCHED_OTHERポリシーを使うすべてのプロセスより優先的に実行することができます。これらのプロセスのスケジューリングにはリアルタイムスケジューラと呼ばれるスケジューラを使います。ここではリアルタイムスケジューラに関するsysctlパラメタについて説明します。
kernel.sched_rr_timeslice_ms
SCHED_RRポリシーを使うプロセスのタイムスライスを示す値。単位はミリ秒。デフォルト値はHZ(カーネルコンフィグの中のCONFIG_HZ_<HZ>という値に該当)/10。
SCHED_RRポリシーを使う実行可能なプロセスが複数存在する場合、所定のタイムスライスごとにそれらプロセスの間でCPU実行権を順番に割り当てるラウンドロビン形式のスケジューリングをします。本パラメタはそのタイムスライスに該当します。
kernel.sched_rt_period_us
リアルタイムポリシーを使うプロセスが暴走した場合にシステムがハングしないようにする仕組みに使うrt_periodという値。単位はマイクロ秒。デフォルトは1,000,000。
リアルタイムプロセスが全CPU上でCPU実行権を明け渡さない状態になると、shellを含めて誰もこれらプロセスをkillすることができなくなってしまい、システムがハング状態に陥ります。これを避けるために、リアルタイムプロセスの実行時間をrt_periodと呼ばれる所定の期間中に、rt_runtimeと呼ばれる時間しか実行できないようにしています。
kernel.sched_rt_runtime_us
上記rt_runtime。単位はマイクロ秒。デフォルト値は950,000。
Discussion