Linux CPUスケジューラの仕組みを徹底解説|Run Queue・CFS・EEVDF・nice値

  1. はじめに
  2. CPUスケジューラとは
    1. CPUスケジューラの役割
    2. CPUスケジューラはタスクを選択する
    3. CPUスケジューラはCPUを効率よく利用するための仕組み
  3. Run Queueとは
    1. Run Queueには実行可能なタスクが管理される
    2. CPUごとにRun Queueが存在する
    3. CPU間で負荷を調整する
  4. CPUスケジューラがタスクを選択する仕組み
    1. すべてのタスクを同じように扱うわけではない
    2. タスクが切り替わるタイミング
    3. CPUを公平かつ効率的に利用する
  5. Linuxのスケジューリングクラス
    1. スケジューリングクラスとは
    2. 通常タスクのスケジューリング
    3. リアルタイムタスクのスケジューリング
    4. SCHED_DEADLINE
  6. CFSとEEVDF
    1. CFSとは
    2. vruntimeとは
    3. nice値によって公平性に重みを付ける
    4. EEVDFとは
    5. CFSからEEVDFへ
  7. nice値とCPU優先度
    1. nice値とは
    2. nice値はCPU時間の配分に影響する
    3. nice値とスケジューラの関係
    4. niceコマンドで優先度を指定する
    5. reniceコマンドで実行中のプロセスを変更する
    6. nice値はCPU負荷対策そのものではない
  8. マルチコア環境ではどのCPUで実行されるのか
    1. CPUごとにRun Queueを持つ
    2. CPU間で負荷に偏りが発生する
    3. Load Balancingでタスクを分散する
    4. タスクを単純に均等配置するわけではない
    5. CPU Affinityで実行するCPUを制限できる
  9. CPUスケジューリングをLinuxで確認する
    1. psでプロセスのスケジューリング情報を確認する
    2. topで実行中のプロセスを確認する
    3. vmstatでRun Queueの状況を確認する
    4. /procでスケジューリング情報を確認する
    5. 複数の情報を組み合わせて確認する
  10. まとめ
  11. 参考サイト

はじめに

Linuxでは、多数のプロセスやスレッドが同時に動作しています。しかし、CPUで実行できるタスクの数には限りがあるため、実行可能なすべてのタスクを常にCPUで実行できるわけではありません。

例えば、ApacheがHTTPリクエストを処理しながら、PostgreSQLがSQLを実行し、さらに別のプロセスがバックグラウンド処理を行っているとします。このような状況では、複数のタスクが同時にCPUでの実行を必要とすることがあります。

そこでLinuxカーネルは、実行可能状態(Runnable)のタスクの中から、次にCPUで実行するタスクを選択します。この役割を担っているのが「CPUスケジューラ」です。

CPUスケジューラは、限られたCPUを複数のタスクで効率よく利用するための重要な仕組みです。実行可能なタスクを管理しながら、優先度やスケジューリングポリシーなどを考慮して、CPUで実行するタスクを決定します。

また、マルチコア環境では「どのタスクを実行するか」だけでなく、「どのCPUで実行するか」というタスクの配置も重要になります。

CPUスケジューラの仕組みを理解すると、「Runnableなタスクはどこで待っているのか」「複数のタスクからどのように実行対象を選ぶのか」「nice値を変更するとCPUの利用にどのような影響があるのか」といったLinuxのCPU管理を理解しやすくなります。

本記事では、LinuxのCPUスケジューラの基本的な役割から、Run Queue、タスクを選択する仕組み、スケジューリングクラス、nice値などについて、分かりやすく解説します。

CPUスケジューラとは

Linuxでは、多数のプロセスやスレッドが同時に動作しています。

しかし、1つのCPUで同時に実行できるタスクには限りがあります。そのため、複数のタスクが実行可能状態(Runnable)になっている場合、「次にどのタスクをCPUで実行するのか」を決める必要があります。

この役割を担うのがCPUスケジューラです。

CPUスケジューラの役割

CPUスケジューラは、CPUで実行可能なタスクの中から、次に実行するタスクを選択するLinuxカーネルの仕組みです。

複数のタスクがCPUでの実行を待って場合、これらを1つのCPUですべて同時に実行することはできません。CPUスケジューラが実行するタスクを選択し、選ばれたタスクがCPU上で命令を実行します。

CPUスケジューラの役割を単純化すると、以下の図のように考えることができます。

CPUスケジューラはタスクを選択する

Linuxにおけるスケジューリングの対象は、カーネルが管理する「タスク」です。同じプロセスに複数のスレッドが存在する場合、それぞれのスレッドが独立したタスクとしてスケジューリングの対象になります。

そのため、複数のCPUコアが存在する環境では、同じプロセスに属する異なるスレッドが、それぞれ別のCPUで実行されることもあります。

CPUスケジューラは、こうした多数のタスクの中から実行対象を選択することで、CPUを複数のプロセスやスレッドで共有できるようにしています。

CPUスケジューラはCPUを効率よく利用するための仕組み

CPUスケジューラが単純に同じタスクだけを実行し続けてしまうと、ほかのタスクがCPUを利用できなくなってしまいます。そこでLinuxでは、タスクの優先度やスケジューリングポリシーなどを考慮しながら、CPUで実行するタスクを決定します。これにより、多数のプロセスやスレッドが動作する環境でも、限られたCPUを効率的に利用できます。

では、CPUで実行可能になったタスクは、スケジューラによって選択されるまでどこで管理されているのでしょうか。次の章では、実行可能なタスクを管理する「Run Queue」について詳しく見ていきます。

Run Queueとは

CPUで実行可能になったタスクは、すぐにCPUで実行されるとは限りません。すでに別のタスクがCPUを使用している場合などは、自分がCPUで実行されるまで待つ必要があります。

Linuxでは、このような実行可能なタスクを管理するために「Run Queue(ランキュー)」という仕組みが用意されています。

Run Queueには実行可能なタスクが管理される

Run Queueでは、CPUで実行可能な状態(Runnable)になっているタスクが管理されます。

例えば、タスクAがCPUで実行されている間に、タスクBとタスクCもRunnableになったとします。この場合、タスクBとタスクCはCPUで実行される機会を待つことになります。

CPUスケジューラは、Run Queueで管理されている実行可能なタスクの中から、次にCPUで実行するタスクを選択します。

そのため、Run Queueは「CPUで実行できる状態になったタスクが、実行される機会を待つ場所」と考えると分かりやすいでしょう。

CPUごとにRun Queueが存在する

現在のLinuxでは、システム全体で1つのRun Queueを共有するのではなく、CPUごとにRun Queueが存在します。

例えば、4つの論理CPUがある場合、それぞれのCPUに対応するRun Queueがあります。

CPU 0:Run Queue → CPU 0
CPU 1:Run Queue → CPU 1
CPU 2:Run Queue → CPU 2
CPU 3:Run Queue → CPU 3

それぞれのCPUは、基本的に自身のRun Queueで管理されているタスクから、次に実行するタスクを選択します。

CPUごとにRun Queueを持つことで、複数のCPUがそれぞれスケジューリングを行い、タスクを並行して実行できます。

以下にイメージ図を示します。

CPU間で負荷を調整する

CPUごとにRun Queueが存在すると、CPUによって実行待ちのタスク数に偏りが生じることがあります。

例えば、CPU 0のRun Queueには多くのタスクが存在する一方で、CPU 1にはほとんどタスクが存在しない、といった状況です。

CPU 0:タスクA・B・C・D
CPU 1:タスクE

この状態では、CPU 0側で多くのタスクが実行を待っているにもかかわらず、CPU 1の処理能力を十分に活用できない可能性があります。

そこでLinuxでは、必要に応じてCPU間でタスクを移動させ、負荷の偏りを調整します。この仕組みをLoad Balancing(ロードバランシング)と呼びます。

ただし、単純にタスク数を均等にすればよいわけではありません。CPUキャッシュやCPU Affinity、NUMAなども関係するため、Linuxカーネルはさまざまな要素を考慮してタスクを配置します。

CPUスケジューラがタスクを選択する仕組み

では、CPUスケジューラはどのような条件で実行対象を選び、タスクを切り替えるのでしょうか。

すべてのタスクを同じように扱うわけではない

Linuxでは、すべてのタスクに対してまったく同じ方法でCPUを割り当てるわけではありません。タスクには、どのようなルールでCPUを割り当てるかを決める「スケジューリングポリシー」が設定されています。

例えば、一般的なアプリケーションを実行する通常のタスクと、リアルタイム性が求められるタスクでは、CPUを割り当てる方法が異なります。さらに通常のタスクでも、nice値などによってCPUを利用する優先度を調整できます。

このようにCPUスケジューラは、タスクに設定されたスケジューリングのルールに従って、次に実行するタスクを決定します。

タスクが切り替わるタイミング

CPUで実行するタスクは、さまざまなタイミングで切り替わります。代表的なのは、実行中のタスクがI/O待ちなどによってCPUを必要としなくなった場合です。

例えば、タスクAがストレージからデータを読み込むために待ち状態になると、タスクAはCPUを手放します。CPUスケジューラは別の実行可能なタスクを選択し、そのタスクへCPUを割り当てます。

また、実行中のタスクより優先して実行すべきタスクが実行可能になった場合などにも、スケジューリングが行われます。

実行するタスクが変更される場合には、現在のタスクの実行状態を保存し、次のタスクの実行状態を復元する必要があります。この処理が「コンテキストスイッチ」です。

コンテキストスイッチの詳しい仕組みについては、別の記事で解説します。

CPUを公平かつ効率的に利用する

CPUスケジューラの重要な役割の一つが、限られたCPUを複数のタスクで効率的に利用することです。特定のタスクだけがCPUを長時間占有すると、ほかのタスクが実行されにくくなります。

一方で、すべてのタスクへ単純に同じ時間だけCPUを割り当てればよいわけでもありません。タスクによって優先度や求められる応答性が異なるためです。そのためLinuxでは、タスクの種類や優先度などを考慮しながら、どのタスクをCPUで実行するかを決定しています。

では、Linuxは異なる種類のタスクに対して、どのようにスケジューリング方法を使い分けているのでしょうか。

次の章では、Linuxの「スケジューリングクラス」について見ていきます。

Linuxのスケジューリングクラス

Linuxでは、すべてのタスクを同じルールでスケジューリングしているわけではありません。

一般的なアプリケーションを実行するタスクもあれば、決められた時間内に処理することが重要なリアルタイムタスクもあります。それぞれ求められる性質が異なるため、Linuxでは複数のスケジューリング方法が用意されています。

このような異なるスケジューリング方法をLinuxカーネル内部で扱う仕組みが「スケジューリングクラス」です。

スケジューリングクラスとは

スケジューリングクラスは、どのようなルールでタスクをCPUへ割り当てるかを決める仕組みです。CPUスケジューラは、タスクに設定されたスケジューリングポリシーなどに応じて、適切なスケジューリングクラスを利用します。

Linuxには複数のスケジューリングクラスがありますが、通常のLinuxサーバを理解するうえでは、大きく次のように考えると分かりやすいでしょう。

種類主なスケジューリングポリシー用途
通常タスクSCHED_OTHER(SCHED_NORMAL)一般的なアプリケーション
リアルタイムタスクSCHED_FIFO / SCHED_RRリアルタイム性が求められる処理
DeadlineタスクSCHED_DEADLINE実行期限を重視する処理

普段Linux上で実行しているApacheやPostgreSQL、シェル、Linuxコマンドなどの多くは通常タスクとしてスケジューリングされます。

通常タスクのスケジューリング

一般的なプロセスやスレッドでは、SCHED_OTHERと呼ばれるスケジューリングポリシーが使用されます。Linuxカーネル内部ではSCHED_NORMALという名称も使われます。

通常タスクでは、特定のタスクだけがCPUを占有し続けないようにしながら、複数のタスクへCPUを割り当てます。また、nice値によってタスク間のCPU配分に差を付けることもできます。

例えば、通常のタスクAとタスクBがCPUを必要としている場合、Linuxはそれぞれの優先度やこれまでのCPU利用状況などを考慮しながらCPUを割り当てます。

通常のLinuxサーバ運用では、この通常タスクのスケジューリングを理解することが特に重要です。

リアルタイムタスクのスケジューリング

Linuxには、通常タスクより高い優先度で実行できるリアルタイムスケジューリングも用意されています。代表的なポリシーが、SCHED_FIFOとSCHED_RRです。

SCHED_FIFOでは、同じ優先度のタスクに対して基本的にFIFO(First In, First Out)の考え方で実行します。実行中のタスクは、自らCPUを手放したり、より高い優先度のタスクが実行可能になったりしない限り、実行を継続できます。

一方、SCHED_RR(Round Robin)では、同じ優先度のリアルタイムタスクが複数存在する場合、一定の時間ごとにCPUを交代しながら実行します。

例えば、同じ優先度のタスクA、B、Cが存在する場合、以下のようにCPUを順番に利用します。

タスクA → タスクB → タスクC → タスクA → ・・・

リアルタイムタスクは通常タスクより優先して実行されるため、設定を誤ると通常のプロセスがCPUを利用しにくくなる可能性がありますので、ご留意ください。

SCHED_DEADLINE

LinuxにはSCHED_DEADLINEというスケジューリングポリシーもあります。SCHED_DEADLINEでは、「どのくらいのCPU実行時間が必要なのか」「いつまでに処理する必要があるのか」といった条件をもとにタスクをスケジューリングします。

一般的なLinuxサーバで頻繁に設定するものではありませんが、Linuxには通常タスクやリアルタイムタスク以外にも、用途に応じたスケジューリング方式が存在することを理解しておけば十分です。

CFSとEEVDF

Linuxで実行される一般的なタスクでは、特定のタスクだけがCPUを占有するのではなく、複数のタスクへCPUを適切に割り当てる必要があります。

Linuxでは長年、通常タスクのスケジューリングにCFS(Completely Fair Scheduler)が使われてきました。Linux 6.6からは、CFSで使われてきたvirtual runtimeなどの考え方を利用しながら、EEVDF(Earliest Eligible Virtual Deadline First)への移行が始まっています。

ここでは、まずCFSの基本的な考え方を理解したうえで、EEVDFによって何が変わったのかを見ていきます。

CFSとは

CFS(Completely Fair Scheduler)は、通常タスクへCPU時間を公平に割り当てることを目的としたスケジューラです。

例えば、同じ優先度を持つタスクA、B、CがCPUを必要としているとします。理想的には、特定のタスクだけがCPUを長時間利用するのではなく、それぞれのタスクが公平にCPUを利用できることが望まれます。

そこでCFSでは、「それぞれのタスクがどの程度CPUを利用したのか」を考慮して、次に実行するタスクを決定します。このとき重要になるのが「vruntime」です。

vruntimeとは

vruntime(Virtual Runtime)は、タスクがどの程度CPUを利用したのかを、優先度を考慮して管理するための値です。基本的には、CPUを利用するとvruntimeが増加します。

例えば、次のようなタスクが存在するとします。

タスクA:vruntime 10
タスクB:vruntime 20
タスクC:vruntime 30

CFSでは、vruntimeが小さいタスクを優先して実行することで、CPUをあまり利用できていないタスクへ実行機会を与えます。この例では、タスクAが次の実行対象として選択されやすくなります。タスクAがCPUを利用するとvruntimeが増加し、ほかのタスクにもCPUを利用する機会が回ってきます。

この仕組みによって、複数のタスクでCPUを公平に共有します。

nice値によって公平性に重みを付ける

ただし、すべてのタスクに完全に同じ量のCPU時間を与えるわけではありません。通常タスクにはnice値を設定でき、その値によってCPU時間の配分に重みを付けることができます。優先度の高いタスクはvruntimeの進み方が相対的に遅くなるため、より多くのCPU時間を得やすくなります。

つまりCFSにおける「公平」とは、すべてのタスクへ同じCPU時間を与えるという意味ではなく、設定された重みを考慮しながら公平にCPUを割り当てるという考え方です。

EEVDFとは

CFSではvruntimeを中心に実行するタスクを選択していましたが、現在のLinuxでは通常タスクの選択にEEVDF(Earliest Eligible Virtual Deadline First)が導入されています。

EEVDFでは、タスクごとのCPU利用状況だけでなく、「どのタスクがCPUを受け取るべき状態なのか」と「どのタスクを先に実行すべきなのか」を考慮して実行対象を決定します。

そのために重要になるのが、eligibility(実行資格)とvirtual deadline(仮想デッドライン)です。

まず、CPUを受け取る資格があるタスクを判定し、その中からvirtual deadlineが早いタスクを優先して実行します。

CFSからEEVDFへ

CFSとEEVDFは、まったく無関係な仕組みというわけではありません。

CFSで使われてきたvruntimeやタスクの重みといった考え方を利用しながら、EEVDFによって「次にどのタスクを実行するか」という選択方法が改善されています。

大まかに整理すると、次のように理解できます。

CFSEEVDF
vruntimeを中心に管理vruntimeなどの考え方を引き継ぐ
CPU利用の公平性を重視公平性に加えて応答性も考慮
vruntimeが小さいタスクを優先eligibleなタスクからvirtual deadlineが早いものを選択

そのため、「現在のLinuxではCFSが完全になくなり、まったく別のスケジューラに置き換わった」と考えるよりも、通常タスクの公平なスケジューリングの仕組みがEEVDFによって発展した、と理解すると分かりやすいでしょう。

nice値とCPU優先度

Linuxでは、通常タスクに対してCPUの利用優先度を調整できます。その代表的な仕組みが「nice値」です。

例えば、CPU負荷の高いバッチ処理と、ユーザーからのリクエストを処理するアプリケーションが同時に動作している場合、バッチ処理の優先度を下げて、ほかのタスクがCPUを利用しやすくするといった調整ができます。

nice値とは

nice値は、通常タスクのCPUスケジューリングにおける優先度を調整するための値です。

nice値は、次の範囲で設定できます。

  • -20:優先度が最も高い
  • 0:デフォルト
  • 19:優先度が最も低い

つまり、nice値が小さいほどCPUを利用する優先度が高く、nice値が大きいほど優先度が低くなります。

例えば、nice値が0のタスクとnice値が10のタスクが同時にCPUを必要としている場合、nice値が0のタスクの方が多くのCPU時間を割り当てられやすくなります。

nice値はCPU時間の配分に影響する

nice値について注意したいのは、「nice値が高いタスクはCPUで実行されない」という意味ではないことです。

nice値は、複数の通常タスクがCPUを必要としているときのCPU時間の配分に影響します。例えば、CPUに十分な余裕があり、ほかに実行可能なタスクが存在しなければ、nice値が19のタスクでもCPUを利用できます。

一方、複数のCPUバウンドなタスクが同じCPUを取り合っている場合は、nice値によるCPU時間の配分の違いが現れやすくなります。

つまり、nice値は、「CPUを使用できるかどうか」ではなく、「CPUが競合したとき、どの程度CPU時間を割り当てるか」を調整する仕組みと考えると分かりやすいでしょう。

nice値とスケジューラの関係

通常タスクでは、nice値に応じてスケジューリングで使用される重みが変化します。優先度の高いタスクほど大きな重みを持ち、CPU時間を多く割り当てられやすくなります。反対に、nice値を大きくすると重みが小さくなり、ほかのタスクとCPUが競合した際に割り当てられるCPU時間が少なくなります。

単純化すると、次のように考えることができます。

niceコマンドで優先度を指定する

Linuxでは、niceコマンドを使用して、プログラムを起動するときのnice値を指定できます。例えば、nice値を10にしてプログラムを実行する場合は、次のように指定します。

[root@localhost ~]# nice -n -5 ./sample.sh

この場合、sample.shは通常よりCPU優先度を上げた状態で実行されます。

topコマンド等で優先度を確認することが可能です。NIの値が「-5」になっていることが確認できます。

root@ubuntu20:~# top
top - 22:25:50 up  1:00,  2 users,  load average: 1.05, 0.29, 0.10
Tasks: 127 total,   2 running, 124 sleeping,   0 stopped,   1 zombie
%Cpu(s):  9.7 us, 32.6 sy,  0.0 ni, 52.1 id,  0.0 wa,  0.0 hi,  5.7 si,  0.0 st
MiB Mem :   3919.9 total,   2724.5 free,    231.2 used,    964.3 buff/cache
MiB Swap:   2290.0 total,   2290.0 free,      0.0 used.   3459.1 avail Mem
    PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND
  22015 root      15  -5    7024   3672   3240 R  40.9   0.1   0:02.59 sample.sh

CPU負荷の高いバッチ処理などを実行するときに、ほかのアプリケーションへの影響を抑えたい場合などに利用できます。

reniceコマンドで実行中のプロセスを変更する

すでに実行しているプロセスのnice値を変更したい場合は、reniceコマンドを使用できます。例えば、PID 1234のプロセスのnice値を-10へ変更する場合は、次のように実行します。

root@ubuntu20:~# renice -10 -p 22015
22015 (process ID) old priority -5, new priority -10
root@ubuntu20:~#

変更後は、topやpsコマンドで確認します。

root@ubuntu20:~# top
top - 22:25:50 up  1:00,  2 users,  load average: 1.05, 0.29, 0.10
Tasks: 127 total,   2 running, 124 sleeping,   0 stopped,   1 zombie
%Cpu(s):  9.7 us, 32.6 sy,  0.0 ni, 52.1 id,  0.0 wa,  0.0 hi,  5.7 si,  0.0 st
MiB Mem :   3919.9 total,   2724.5 free,    231.2 used,    964.3 buff/cache
MiB Swap:   2290.0 total,   2290.0 free,      0.0 used.   3459.1 avail Mem
    PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND
  22015 root      15  -10    7024   3672   3240 R  40.9   0.1   0:02.59 sample.sh

NIの値が-10に変わっていることが確認できました。

なお、一般ユーザーが自分のプロセスの優先度を上げる、つまりnice値を小さくする操作には権限上の制限があります。

※タスク処理の順番がどのように変わるのか、実機で動作確認しているので、合わせて参照ください。
【インフラエンジニア入門】Linuxのプロセス管理をわかりやすく解説|スケジューラ機能

nice値はCPU負荷対策そのものではない

nice値を変更するとCPUスケジューリングの優先度を調整できますが、プロセスそのものが使用するCPU量を直接制限する仕組みではありません。

例えば、CPUを利用するタスクが1つしか存在しなければ、nice値を大きくしても、そのタスクがCPUを大きく利用することがあります。

そのため、nice値は「CPU使用率を○%以下に制限する」ための設定ではなく、CPUが競合したときのCPU時間の配分を調整するための仕組みです。

マルチコア環境ではどのCPUで実行されるのか

現在のLinuxサーバでは、複数のCPUコアを搭載した環境が一般的です。

マルチコア環境では、CPUスケジューラは「どのタスクを実行するのか」だけでなく、「そのタスクをどのCPUで実行するのか」も考える必要があります。

例えば、4つのCPUが存在する環境で、多数のタスクをCPU 0だけに集中させてしまうと、CPU 1〜3の処理能力を十分に活用できません。

Linuxでは、複数のCPUへタスクを適切に配置することで、システム全体のCPUを効率的に利用しています。

CPUごとにRun Queueを持つ

前述したように、LinuxではCPUごとにRun Queueを持っています。それぞれのRun Queueでは、そのCPUで実行可能なタスクが管理されています。CPUスケジューラは基本的に、そのCPUに対応するRun Queueから次に実行するタスクを選択します。この仕組みにより、それぞれのCPUで独立してタスクを実行できます。

CPU間で負荷に偏りが発生する

CPUごとにRun Queueを持っていると、CPU間で負荷に偏りが発生することがあります。

例えば、次のような状態です。

CPU 0:タスクA、B、C、D
CPU 1:タスクE
CPU 2:タスクなし
CPU 3:タスクなし

この状態では、CPU 0では複数のタスクがCPUでの実行を待っている一方、CPU 2とCPU 3は利用されていません。

システム全体で見ると、利用できるCPU資源を十分に活用できていない状態です。

Load Balancingでタスクを分散する

CPU間の負荷の偏りを調整するため、Linuxでは、CPUスケジューラによりLoad Balancing(ロードバランシング)が行われます。負荷が高いCPUから負荷が低いCPUへ実行可能なタスクを移動させることで、複数のCPUを効率的に利用します。

以下のようなイメージです。

これにより、複数のCPUでタスクを並行して実行しやすくなります。

タスクを単純に均等配置するわけではない

ただし、Linuxは単純にRun Queueのタスク数が同じになるように配置しているわけではありません。

例えば、あるタスクをCPU 0からCPU 1へ移動すると、それまでCPU 0のキャッシュに保持されていたデータをCPU 1では利用できない場合があります。その結果、キャッシュミスが増えて性能が低下する可能性があります。

また、NUMA環境※ではCPUとメモリの物理的な位置関係も性能に影響します。そのためLinuxでは、CPUの負荷だけでなく、CPUの構成やキャッシュなども考慮しながらタスクを配置します。

※NUMA(Non-Uniform Memory Access)は、複数のCPUを持つサーバで、CPUとメモリをいくつかのグループ(NUMAノード)に分けて管理する仕組みです。

CPU Affinityで実行するCPUを制限できる

Linuxでは、タスクを実行できるCPUを制限する「CPU Affinity」という仕組みもあります。例えば、あるタスクをCPU 0とCPU 1だけで実行できるように設定すると、そのタスクは基本的にCPU 2やCPU 3では実行されません。

CPU Affinityは、tasksetコマンドなどで確認・設定できます。例えば、PID 1234のCPU Affinityを確認する場合は、次のように実行します。

[root@almalinux ~]# taskset -cp 2671217
pid 2671217's current affinity list: 0-3
[root@almalinux ~]#

CPU 0とCPU 1で実行できるように設定する場合は、次のように実行します。

[root@almalinux ~]# taskset -cp 0,1 2671217
pid 2671217's current affinity list: 0-3
pid 2671217's new affinity list: 0,1
[root@almalinux ~]#

CPU Affinityを利用すると、特定のタスクが実行されるCPUを制御できます。ただし、CPUを固定するとLinuxが自由にタスクを配置できる範囲が狭くなるため、必ずしも性能が向上するわけではありません。

CPUスケジューリングをLinuxで確認する

ここまで、Run Queueやスケジューリングポリシー、nice値、CPU Affinityなど、LinuxのCPUスケジューラの仕組みについて説明してきました。これらの情報の一部は、psやtopなどのLinuxコマンドを使って実際に確認できます。

ここでは、CPUスケジューリングを理解するうえで役立つ代表的な確認方法を紹介します。

psでプロセスのスケジューリング情報を確認する

psコマンドでは、プロセスのPIDやCPU使用率だけでなく、nice値やスケジューリングポリシーなども確認できます。

例えば、次のように実行します。

[root@almalinux ~]# ps -eo pid,comm,stat,ni,pri,cls,psr
    PID COMMAND         STAT  NI PRI CLS PSR
      1 systemd         Ss     0  19  TS   1
      2 kthreadd        S      0  19  TS   2
      3 pool_workqueue_ S      0  19  TS   0
      4 kworker/R-rcu_g I<   -20  39  TS   0
      5 kworker/R-sync_ I<   -20  39  TS   0
      6 kworker/R-slub_ I<   -20  39  TS   0

主な項目は次のとおりです。

項目内容
PIDプロセスID
COMMANDコマンド名
STATプロセスの状態
NInice値
PRIスケジューリング上の優先度
CLSスケジューリングクラス
PSR最後に実行されたCPU

例えば、CLSにTSと表示されている場合は、通常のタイムシェアリング対象のタスクです。また、PSRを見ることで、そのタスクがどのCPUで実行されていたのかを確認できます。

topで実行中のプロセスを確認する

topコマンドでは、CPUを利用しているプロセスをリアルタイムに確認できます。

代表的な項目として、次のような情報があります。

PID:プロセスID
PR:優先度
NI:nice値
S:プロセスの状態
%CPU:CPU使用率

特にSを見ることで、プロセスの状態を確認できます。

例えば、

R:RunningまたはRunnable
S:割り込み可能なSleep
D:割り込み不可能なSleep
Z:Zombie

などが表示されます。

CPU負荷が高い場合は、%CPUだけでなく、どのプロセスがRになっているのかを見ることで、CPUを必要としているタスクを確認できます。

vmstatでRun Queueの状況を確認する

CPUの実行待ちを確認するときに役立つのがvmstatです。例えば、1秒間隔で確認する場合は次のように実行します。

[root@almalinux ~]# vmstat 1
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st
 0  0      0 2383156   5756 251992    0    0     5    20    0    0  0  0 99  0  0
 0  0      0 2383668   5756 252036    0    0     0     0  943 1306  1  1 99  0  0
 0  0      0 2383668   5756 252036    0    0     0     4  647 1144  1  0 99  0  0
 0  0      0 2383668   5756 252036    0    0     0    96  651 1148  0  0 100  0  0
 0  0      0 2383668   5756 252036    0    0     0     0  662 1144  1  1 99  0  0

出力の中で特に注目したいのが、procsのrです。rは、実行可能なタスク数を確認するための重要な指標です。例えば、CPU数に対してrが継続的に大きい場合、多くのタスクがCPUでの実行を必要としており、CPUがボトルネックになっている可能性があります。

ただし、瞬間的にrが増加しただけでCPU不足と判断することはできません。CPU使用率やロードアベレージなど、ほかの情報と組み合わせて確認することが重要です。

/procでスケジューリング情報を確認する

Linuxでは、/procからプロセスごとのスケジューリング情報を確認することもできます。

[root@almalinux ~]# cat /proc/2671217/sched
java (2671217, #threads: 59)
-------------------------------------------------------------------
se.exec_start                                :   19742068175.495653
se.vruntime                                  :         16298.120623
se.sum_exec_runtime                          :             9.517587
se.nr_migrations                             :                    1
nr_switches                                  :                    5
nr_voluntary_switches                        :                    4
nr_involuntary_switches                      :                    1
se.load.weight                               :              1048576
se.avg.load_sum                              :                   42
se.avg.runnable_sum                          :                43008
se.avg.util_sum                              :                43008
se.avg.load_avg                              :                    0
se.avg.runnable_avg                          :                    0
se.avg.util_avg                              :                    0
se.avg.last_update_time                      :    19742068175495168
se.avg.util_est                              :                  400
policy                                       :                    0
prio                                         :                  120
clock-delta                                  :                  107
mm->numa_scan_seq                            :                    0
numa_pages_migrated                          :                    0
numa_preferred_nid                           :                   -1
total_numa_faults                            :                    0
current_node=0, numa_group_id=0
numa_faults node=0 task_private=0 task_shared=0 group_private=0 group_shared=0
[root@almalinux ~]#

このファイルには、そのタスクのスケジューリングに関するさまざまな情報が記録されています。主な項目は以下です。

項目今回の値今回の値
se.vruntimeタスクがCPUをどれくらい使ったかを公平性を考慮して表す仮想実行時間16298.120623
se.sum_exec_runtimeこのタスクが実際にCPU上で実行された累積時間
9.517587
nr_switchesこのタスクで発生したコンテキストスイッチの回数5

より詳細にCPUスケジューラの動作を調査したい場合に利用できますが、通常の運用では、まずps、top、vmstatなどから確認すると分かりやすいでしょう。

複数の情報を組み合わせて確認する

CPUスケジューリングの状況を確認するときは、1つの値だけで判断しないことが重要です。

例えばCPU負荷が高い場合は、

  • topでCPU使用率と実行中のプロセスを確認する
  • vmstatで実行可能なタスク数を確認する
  • psでnice値やスケジューリング情報を確認する
  • tasksetでCPU Affinityを確認する

といった形で、複数の情報を組み合わせて調査します。

CPUスケジューラの仕組みを理解しておくことで、単に「CPU使用率が高い」という数値を見るだけではなく、「どのタスクがCPUを必要としているのか」「CPUでの実行待ちが発生しているのか」「CPUの配置や優先度に問題がないか」といった観点からCPU負荷を分析できるようになります。

まとめ

本記事では、LinuxのCPUスケジューラがどのようにタスクを管理し、CPUへ割り当てているのかを解説しました。

Linuxでは、CPUで実行可能になったタスクはRun Queueで管理されます。CPUスケジューラは、その中から次に実行するタスクを選択し、CPUへ割り当てます。

また、すべてのタスクが同じ方法でスケジューリングされるわけではありません。通常タスクやリアルタイムタスクなど、それぞれの用途に応じたスケジューリングポリシーが用意されています。

通常タスクでは、CPUを複数のタスクで適切に共有するため、タスクの優先度やCPU利用状況などを考慮して実行対象が決定されます。nice値を利用することで、CPUが競合した際のCPU時間の配分を調整することもできます。

さらに、マルチコア環境ではCPUごとにRun Queueが存在し、Load BalancingによってCPU間の負荷が調整されます。CPU Affinityを利用すれば、タスクを実行できるCPUを制限することも可能です。

CPUスケジューリングの基本的な流れを整理すると、次のようになります。

Runnableなタスク

Run Queueで管理

CPUスケジューラが実行対象を選択

CPUでタスクを実行

必要に応じて別のタスクへ切り替え

実際のLinuxサーバでは、ps、top、vmstat、tasksetなどを利用することで、タスクの状態や優先度、CPUの実行待ち、CPU Affinityなどを確認できます。

CPUスケジューラの仕組みを理解しておくと、CPU使用率やロードアベレージといった数値を見るだけでなく、「なぜCPUで実行待ちが発生しているのか」「どのようにCPUがタスクへ割り当てられているのか」というLinux内部の動きまで考えられるようになります。

LinuxにおけるCPUの仕組みについては、以下の書籍にまとめています。興味がありましたら、ぜひ読んでみてください。

Linux入門・図解

LinuxのCPUの仕組みを
図解で理解したい方へ

命令実行・プロセス・スケジューラ・割り込み・ キャッシュ・マルチコアなど、 LinuxにおけるCPUの仕組みを図解を中心にわかりやすく解説しています。

Kindleで書籍を見る →

参考サイト

EEVDF Scheduler — The Linux Kernel documentation
An EEVDF CPU scheduler for Linux [LWN.net]

コメント