この章では、プロセスのスケジューリングについて説明します。マルチスレッド化されたスケジューリングについては、『マルチスレッドのプログラミング』を参照してください。この章は、プロセスの実行順序についてデフォルトのスケジューリングが提供する以上の制御を行う必要があるプログラマを対象としています。
プロセスは、生成されると 1 つの軽量プロセス (LWP) を割り当てられます。(マルチスレッド化されているプロセスは、さらに多くの LWP を割り当てられることがあります。) LWP は、UNIX システムのスケジューラが実際にスケジューリングするオブジェクトです。スケジューラは、プロセスが実行する時期を決定し、構成パラメタ、プロセスの特性、およびユーザ要求に基づいてプロセス優先順位を維持管理します。スケジューラは、これらの優先順位を使用してプロセスを実行します。
デフォルトでは、タイムシェアリング方式を使用します。この方式は、プロセスの優先順位を動的に調整して、対話型プロセスの応答時間と CPU 時間を多く使用するプロセスのスループットとを調整します。
SunOS 5.8 のスケジューラでは、実時間スケジューリング方式も使用できます。実時間スケジューリング方式では、ユーザがプロセスごとに優先順位を固定できます。最も優先順位の高い実時間ユーザプロセスは、システムプロセスが実行できる場合でも、そのプロセスが実行可能なときはいつでも CPU を利用できます。
実時間プロセスがシステムからの応答時間を保証されるように、プログラムを作成できます。詳細は、第 8 章「実時間プログラミングと管理」を参照してください。
実時間スケジューリングによるプロセススケジューリングの制御は、必要とされることはほとんどなく、それが解決するよりも多くの問題を引き起こすこともあります。しかし、プログラムの要件に厳しいタイミングの制約が含まれるときは、実時間プロセスがそれらの制約を満たす唯一の方法となる場合があります。
実時間プロセスを不用意に使用すると、タイムシェアリングプロセスの性能が極めて悪くなることがあります。
スケジューラ管理を変更すると、スケジューラの特性に影響を与える可能性があるため、プログラマもスケジューラ管理について多少理解しておく必要があります。スケジューラ管理に関連するマニュアルページは次のとおりです。
dispadmin(1M) では、実行中のシステムでスケジューラの構成を変更する方法を説明しています。
ts_dptbl(4) と rt_dptbl(4) では、スケジューラの構成に使用するタイムシェアリングと実時間のパラメタテーブルを説明しています。
図 3-1 に SunOS 5.8 のスケジューラの働きを示します。
作成されたプロセスは、スケジューリングクラスやそのクラス内の優先順位を含むスケジューリングパラメタを継承します。プロセスは、ユーザ要求によってだけクラスを変更します。システムは、ユーザの要求とそのプロセスのスケジューリングクラスに対応する方針に基づいて、プロセスの優先順位を管理します。
デフォルトの設定では、初期化プロセスはタイムシェアリングクラスに属します。そのため、すべてのユーザログインシェルは、タイムシェアリングプロセスとして開始します。
スケジューラは、クラス固有優先順位をグローバル優先順位に変換します。プロセスのグローバル優先順位は、そのプロセスの実行時期を決定します。つまり、スケジューラは常に、グローバル優先順位が最も高い実行可能なプロセスを実行します。優先順位の数値が大きいプロセスが先に実行されます。スケジューラがプロセスを CPU に割り当てると、プロセスは休眠するか、そのタイムスライスを使い切るか、または優先順位がさらに高いプロセスによって横取りされるまで実行されます。優先順位が同じプロセスは、ラウンドロビンで実行されます。
実時間プロセスは、どのカーネルプロセスよりも優先順位が高く、カーネルプロセスは、どのタイムシェアリングプロセスよりも優先順位が高くなっています。
実行可能な実時間プロセスがあり、シングルプロセッサシステムを使用する限り、カーネルプロセスやタイムシェアリングプロセスは実行されません。
管理者は構成テーブルでデフォルトのタイムスライスを指定しますが、ユーザは実時間プロセスにプロセスごとのタイムスライスを割り当てることができます。
プロセスのグローバル優先順位は、ps(1) コマンドの -c1 オプションで表示できます。クラス固有の優先順位についての設定内容は、priocntl(1) コマンドと dispadmin(1M) コマンドで表示できます。
以降の節では、3 つのデフォルトクラスのスケジューリング方式について説明します。
タイムシェアリング方式の目的は、対話型プロセスには良い応答性能、CPU 時間を多く使用するプロセスには良いスループットを提供することです。スケジューラは、切り替え自体に時間がかかりすぎない頻度で CPU の割り当てを切り替え、応答性能を良くします。タイムスライスは通常、数百ミリ秒です。
タイムシェアリング方式は、優先順位が動的に変更され、割り当てられるタイムスライスの長さは異なります。スケジューラは、CPU をほんのわずかだけ使用して休眠しているプロセスの優先順位を上げます (プロセスは、端末からの読み取りやディスク読み取りなどの入出力操作を開始すると休眠します) 。頻繁に休眠するのは、編集や簡単なシェルコマンドの実行など、対話型タスクの特性です。一方、休眠しないで CPU を長時間使用しているプロセスの優先順位は下げられます。
デフォルトのタイムシェアリング方式では、優先順位が低いプロセスに長いタイムスライスが与えられます。優先順位が低いプロセスは、CPU を長時間使用する傾向があるからです。他のプロセスが CPU を先に取得しても、優先順位の低いプロセスが CPU を取得すれば長時間使用できます。ただし、タイムスライス中に優先順位が高いプロセスが実行可能になると、そのプロセスが CPU を横取りします。
グローバルプロセス優先順位とユーザ指定優先順位は、昇順になります。つまり、優先順位が低い数値のプロセスが最初に実行されます。ユーザ優先順位は、設定されている値の、負の最大値から正の最大値までの値になります。プロセスはユーザ優先順位を継承します。ユーザ優先順位のデフォルト初期値は 0 (ゼロ) です。
「ユーザ優先順位限界」は、設定されているユーザ優先順位の最大値です。ユーザ優先は、この限界値以下の任意の値に設定できます。適当なアクセス権を持っていると、ユーザ優先順位限界を上げることができます。ユーザ優先順位限界のデフォルト値は 0 (ゼロ) です。
プロセスのユーザ優先順位を下げて、CPU の使用率を減少させたり、適当なアクセス権を持っている場合は、ユーザ優先順位を上げてサービスを受けやすくしたりできます。ユーザ優先順位はユーザ優先順位限界より大きく設定できないので、この値がどちらもデフォルト値の 0 になっている場合は、ユーザ優先順位を上げる前にユーザ優先順位限界を上げなければなりません。
管理者は、グローバルなタイムシェアリング優先順位とは独立にユーザ優先順位の最大値を設定します。たとえば、デフォルトの設定では、ユーザは -20 から +20 までの範囲だけにユーザ優先順位を設定できますが、60 種類のタイムシェアリングのグローバル優先順位が設定できます。
スケジューラは、タイムシェアリングのパラメタテーブル ts_dptbl(4) 内の設定可能なパラメタを使用して、タイムシェアリングプロセスを管理します。このテーブルには、タイムシェアリングクラスに固有の情報が収められています。
システムクラスでは、固定優先順位方式を使用して、サーバなどのカーネルプロセスや、ページングデーモンなどのハウスキーピングプロセスを実行します。システムクラスは、カーネルが使用するために予約されており、ユーザはシステムクラスに対してプロセスの追加や削除はできません。システムクラスのプロセスの優先順位は、以上のようなプロセスのカーネルコードで設定されていて、いったん設定されるとシステムプロセスの優先順位は変わりません (カーネルモードで実行中のユーザプロセスは、システムクラスにはありません)。
実時間クラスでは、固定優先順位スケジューリング方式を使用しており、クリティカルなプロセスがあらかじめ設定された順序で実行されます。実時間優先順位は、ユーザが変更を要求しない限り変更されません。特権ユーザは、priocntl(1) コマンドまたは priocntl(2) 関数を使用して、実時間優先順位を割り当てることができます。
スケジューラは、実時間パラメタテーブル rt_dptbl(4) 内の設定可能なパラメタを使用して、実時間プロセスを管理します。このテーブルには、実時間クラスに固有の情報が収められています。
図 3-2 は、デフォルトのプロセス優先順位を示しています。
プロセス優先順位が意味を持つのは、スケジューリングクラスについてだけです。プロセス優先順位を指定するには、クラスとクラスに固有の優先順位の値を指定します。クラスとクラスに固有の値は、システムによってグローバル優先順位に割り当てられ、この値を使用してプロセスがスケジューリングされます。
優先順位は、システム管理者から見た場合と、ユーザやプログラマから見た場合とは異なります。スケジューリングクラスを設定する場合、管理者はグローバル優先順位を直接取り扱います。システムでは、ユーザが指定した優先順位は、このグローバル優先順位に割り当てられます。優先順位の詳細は、『Solaris のシステム管理 (第 1 巻)』を参照してください。
-cel オプションを指定した ps(1) コマンドは、有効なすべてのプロセスのグローバル優先順位を報告します。priocntl(1) コマンドは、ユーザとプログラマが使用するクラス固有の優先順位を報告します。
priocntl(1) コマンド、priocntl(2) 関数、および priocntlset(2) 関数は、プロセスのスケジューリングパラメタを設定または検索します。優先順位を設定する場合の基本的な方法はどれも同じで、次のようになります。
ターゲットプロセスを指定する。
そのプロセスに希望するスケジューリングパラメタを指定する。
プロセスにパラメタを設定するコマンドまたは関数を実行する。
以上の ID は UNIX のプロセスの基本的な特性です (詳細は、Intro(2) を参照してください)。クラス ID は、プロセスのスケジューリングクラスです。priocntl(2) は、タイムシェアリングクラスと実時間クラスだけに有効で、システムクラスには使用できません。
priocntl(1) ユーティリティは、プロセスをスケジューリングする際に、次の 4 つの制御機能を実行します。
priocntl -l |
設定内容が表示される |
priocntl -d |
プロセスのスケジューリングパラメタが表示される |
priocntl -s |
プロセスのスケジューリングパラメタが設定される |
priocntl -e |
指定したスケジューリングパラメタでコマンドが実行される |
以下に、priocntl(1) を使用した例をいくつか示します。
デフォルト設定について -l オプションを使用すると、次のように出力されます。
$ priocntl -d -i all CONFIGURED CLASSES ================== SYS (System Class) TS (Time Sharing) Configured TS User Priority Range -20 through 20 RT (Real Time) Maximum Configured RT Priority: 59 |
すべてのプロセスについての情報を表示する例
$ priocntl -d -i all |
すべてのタイムシェアリングプロセスについての情報を表示する例
$ priocntl -d -i class TS |
ユーザ ID が 103 または 6626 のすべてのプロセスについての情報を表示する例
$ priocntl -d -i uid 103 6626 |
ID 24688 のプロセスに実時間プロセスのデフォルトパラメタを設定する例
$ priocntl -s -c RT -i pid 24668 |
ID 3608 のプロセスを優先順位 55、タイムスライス 5 分の 1 秒の実時間プロセスとして設定する例
$ priocntl -s -c RT -p 55 -t 1 -r 5 -i pid 3608 |
すべてのプロセスをタイムシェアリングプロセスに変更する例
$ priocntl -s -c TS -i all |
ユーザ ID 1122 のプロセスに対して、タイムシェアリングユーザ優先順位とユーザ優先順位限界を -10 に減少させる例
$ priocntl -s -c TS -p -10 -m -10 -i uid 1122 |
デフォルトの実時間優先順位で実時間シェルを開始する例
$ priocntl -e -c RT /bin/sh |
タイムシェアリングユーザ優先順位を -10 にして make を実行する例
$ priocntl -e -c TS -p -10 make bigprog |
priocntl(1) は、nice(1) の機能を包んでいます。nice(1) は、タイムシェアリングプロセスについてだけ有効で、数値が大きいほど優先順位が低くなります。上の例は、「増分」に 10 を指定して nice(1) を実効するのと同じです。
$ nice -10 make bigprog |
priocntl(2) 関数は、priocntl(1) ユーティリティがプロセスに対して行うのと同様、1 つのプロセスまたは 1 組のプロセスのスケジューリングパラメタを取得または設定します。priocntl(2) 呼び出しは LWP に、1 つのプロセスだけに、またはプロセスのグループに働かせることができます。プロセスのグループは、親プロセス、プロセスグループ、セッション、ユーザ、グループ、クラス、またはアクティブなすべてのプロセスによって識別できます。使用方法については、マニュアルページを参照してください。
priocntl(2) を使用して % priocntl -l と同等のことを行う例が、付録 A 「完全なコーディング例」 にあります。
PC_GETCLINFO コマンドは、クラス ID を与えるとスケジューリングクラス名とパラメタを取得します。このコマンドは、どのクラスが設定されているかについて想定しないプログラムの作成を容易にします。PC_GETCLINFO と一緒に priocntl(2) を使用して、プロセス ID を元にしてプロセスのクラス名を取得する例は、例 A-2 にあります。
PC_SETPARMS コマンドは、1 組のプロセスのスケジューリングクラスとパラメタを設定します。idtype と id の入力引数は、変更するプロセスを指定します。例 A-3 は、PC_SETPARMS コマンドと一緒に priocntl(2) を使用して、タイムシェアリングプロセスを実時間プロセスに変換する例を示しています。
priocntlset(2) 関数は、priocntl(2) と同様に、1 組のプロセスのスケジューリングパラメタを変更します。priocntlset(2) には、priocntl(2) と同じコマンドセットがあり、cmd と arg の入力引数は同じです。ただし、priocntl(2) は 1 組の idtype と id のペアだけによって指定される 1 組のプロセスに適用されるのに対し、priocntlset(2) は 2 組の idtype と id のペアを論理的に結合した結果の 1 組のプロセスに適用されます。詳細は、マニュアルページを参照してください。
例 A-4 では、priocntlset(2) を使用して、同じユーザ ID のタイムシェアリングプロセスを実時間プロセスに変更しないで、実時間プロセスの優先順位を変更する例を示します。
カーネルは、デーモンやハウスキーピングプロセスをシステムのスケジューリングクラスに割り当てます。ユーザは、このクラスにプロセスを追加または削除したり、これらのプロセスの優先順位を変更したりできません。ps -cel コマンドによって、すべてのプロセスのスケジューリングクラスが示されます。システムクラスのプロセスは、CLS カラムに SYS と表示されます。
スケジューリングクラス、優先順位、その他のスケジューリングパラメタは、fork(2) 関数や exec(2) 関数を実行した場合も継承されます。
nice(1) コマンドと nice(2) 関数は、UNIX システムの以前のバージョンと同じ働きをします。これらは、タイムシェアリングプロセスの優先順位を変更します。これらの関数でも、数値が小さいほどタイムシェアリング優先順位が高くなります。
プロセスのスケジューリングクラスや実時間優先順位を変更するには、priocntl 関数の 1 つを使用しなければなりません。priocntl(2) 関数では、数値が大きいほど優先順位が高くなります。
init(1M) プロセスは、スケジューラによって特殊な場合として扱われます。init(1M) のスケジューラの設定項目を変更するには、idtype と id、または procset 構造体で、init だけが指定されていなければなりません。
スケジューラは、プロセスをいつどれだけの時間実行するかを決定するので、システムの実際の性能と知覚される性能に重要な影響を与えます。
デフォルトでは、プロセスはすべてタイムシェアリングプロセスです。プロセスがクラスを変更するのは、priocntl(2) 関数呼び出しによってだけです。
実時間プロセス優先順位は、どのタイムシェアリングプロセスよりも優先順位が高くなっています。したがって、実行可能な実時間プロセスが存在する限り、タイムシェアリングプロセスやシステムプロセスは実行されません。このため、実時間アプリケーションが適時に CPU 管理を放棄しない場合、ユーザや基本的なカーネルのハウスキーピングが完全にロックアウトされてしまうことがあります。
実時間アプリケーションは、プロセスのクラスと優先順位を制御する以外にも、性能に影響を与えるいくつかの他の要因も制御しなければなりません。性能にとって最も重要な要因は、CPU パワー、一次メモリ量、入出力スループットです。これらの要因は相互に複雑に関連しています。sar(1) コマンドには、すべての性能要因について報告するオプションがあります。
厳しい実時間制約を持つアプリケーションは、プロセスがスワップされたり二次メモリにページアウトされたりしないようにする必要がある場合があります。UNIX のプロセスの状態と状態間の変移の概要を図 3-3 に示します。
有効なプロセスは、通常、上記の図の 5 つの状態のうちの 1 つにあります。矢印は状態が変わる方向を示します。
プロセスは、CPU に割り当てられていれば実行中である。優先順位が高いプロセスが実行可能になると、実行中のプロセスはスケジューラによって横取りされる (実行状態から削除される)。プロセスがタイムスライスをすべて使用して、同じ優先順位のプロセスが実行可能な場合にも、プロセスは横取りされる。
プロセスは一次メモリ内にあり、実行準備ができているが CPU には割り当てられていない場合、メモリ内で実行可能である。
プロセスは一次メモリ内にあるが、実行を継続するために特定のイベントを待っている場合、メモリ内で休眠中である。たとえば、入出力操作の完了、ロッキングされている資源の解放、タイマの終了を待っている場合は休眠中である。イベントが発生すると、プロセスに呼び起こしが送信される。休眠の原因が解消されれば、プロセスは実行可能になる。
プロセスが特定のイベントを待っているのではなく、一次メモリに他のプロセスのための空間を空けるために、アドレス空間がすべて二次メモリに書き込まれている場合、プロセスは実行可能でスワップされている。
プロセスが特定のイベントを待っており、一次メモリに他のプロセスのための空間を空けるために、アドレス空間がすべて二次メモリに書き込まれている場合、プロセスは休眠中でスワップされている。
有効なプロセスをすべて保持するための十分な一次メモリがマシンにない場合は、次のようにして、アドレス空間の一部を二次メモリにページングするかスワップしなければなりません。
システムの一次メモリが不足した場合は、いくつかのプロセスの個々のページが二次メモリに書き込まれるが、そのプロセスは実行可能なままである。プロセスを実行する際にそのページにアクセスする場合は、ページが一次メモリ内に読み戻されるまでプロセスは休眠しなければならない。
システムの一次メモリ不足がさらに深刻になると、いくつかのプロセスの全ページが二次メモリに書き込まれ、そのプロセスがスワップされたとマークされる。このようなプロセスは、システムスケジューラのデーモンプロセスによって選択されてメモリ内に読み戻された場合だけスケジュール可能な状態に戻る。
プロセスが再度実行可能になったときに、ページングとスワップの両方により、遅延が発生します。タイミング要求の厳しいプロセスにとっては、この遅延は受け入れられないものです。
実時間プロセスにすれば、プロセスの一部がページングされることがあっても、スワップはされないためスワップによる遅延を避けることができます。また、プログラムは、テキストとデータを一次メモリ内にロッキングすれば、ページングとスワップを避けることができます。詳細は、memcntl(2) のマニュアルページを参照してください。ロッキングできる量はメモリ設定によって制限されます。また、ロッキングが多すぎると、テキストやデータをメモリ内にロッキングしていないプロセスが大幅に遅れます。
実時間プロセスの性能とその他のプロセスの性能の兼ね合いは、ローカルなニーズによって異なります。システムによっては、必要な実時間応答を保証するためにプロセスのロッキングが必要な場合もあります。
実時間アプリケーションの潜在的な時間については、「ディスパッチ中の潜在的な時間」を参照してください。