差分
プロセス管理
,/* スケジューラのチューニング */
;補足: 話を単純化するために、ここではマルチコアとかハイパースレティングなどの能力も持っていない単純な1つのCPUとします。話を単純化するために、ここではマルチコアとかハイパースレティングなどの能力も持っていない単純な1つの計算ユニットだけあるCPUとします。
==== カーネル側から見る ====
[[File:Process-resource.png|thumb|leftright|300px350px|プロセスは色々なリソースを持っている]]
今度はカーネル側から見てみましょう。物理的なCPUが1個しかありません。
また、その実行のためにCPUリソース以外にも必要なリソースも割り当てられています。
実行中プロセスの持つリソースを分類すると次のようになります。
* CPUリソース (タスク系リソース)
* プロセス間通信系リソース
* ファイル管理系リソース
* ユーザ管理系リソース
* ほか
;調べてみよう:個々のプロセスのデータ構造は[httphttps://ucgithub.h2np.netcom/srctorvalds/linux/blob/master/include/linux/sched_h sched.h sched.h]に定義されている[httphttps://ucgithub.h2np.netcom/srctorvalds/sched_hlinux/blob/master/include/linux/sched.h#N1228 L723 struct task_struct]です。個々のプロセスが保持する情報(とリソース)にはどのようなものがあるかチェックしてみよう。
さて、今度はプロセスを実際にCPUに割り当てていくことを考えてみましょう。
プロセスを割り当てるためにスケジュール(予定)を立て処理を進めていく必要があります。
=== プロセス・タスク・スレッド ===
スレッドやタスクの考え方(と、実装)は80年代にUNIXに入って来た、UNIXの歴史から見ると、わりと新しい技術です。
80年代に入ってから色々な組織がスレッドの実装を試みました。
米ブラウン大学の [https://cs.brown.edu/research/pubs/techreports/reports/CS-96-18.html Brown Thread Package (BTP)]、
サンマイクロシステムズのLWP (Light-weight process)、DECのDECthreads、
[https://kilthub.cmu.edu/articles/journal_contribution/C_threads/6603980/1 CMUのCスレッドパッケージ]
がありました。各々互換性はありません。
紆余曲折あり、現在では
[https://hpc-tutorials.llnl.gov/posix/ POSIX仕様のスレッド]である
[http://man7.org/linux/man-pages/man7/pthreads.7.html pthreads(7)]
が主流になっています。
タスクが1つのスレッドしか持たない時、これはUNIX の伝統的な実行実体のプロセスです。
ですから、単純にプロセスと呼んでも構わないですし、
また同時に(ここまでの説明の範囲では)「プロセス=タスクのこと」と考えてもかまいません。
このあたりは新旧の考え方が入り乱れて呼び方が混乱しているのは確かなようです。
UNIXが生まれた当時の処理の粒度と今時のUNIXでの処理の粒度は違うので、
そこでここでは説明は理解しやすいようにLinuxそのままではなく
<ref>
LinuxカーネルではTASK_RUNNING、TASK_INTERRUPTIBLE、 TASK_UNINTERRUPTIBLE、 TASK_STOPPEDなどの状態をもちますが、詳しくはincludeTASK_STOPPEDなどの状態をもちます。詳しくはヘッダファイルの [https://github.com/torvalds/linux/blob/master/include/linux/sched.h#L82 sched.hを参照してください。h] を参照してください。
</ref>
ある程度単純化したプロセス状態の基本モデルを考えて説明したいと思います。
;調べてみよう: コマンドuptimeのマニュアルを調べてみよう。またロードアベレージの意味も調べてみよう。コマンド[http://man7.org/linux/man-pages/man1/uptime.1.html uptime]のマニュアルを調べてみよう。またロードアベレージの意味も調べてみよう。
=== プロセスの状態 ===
皆さんが動かしているデスクトップレベルでは、いつもそんなに激しく色々なプロセスが同時にCPUを奪いあっている状態ではありません。動いているプロセスのほとんどは入力を待つSPLEEP状態です。コマンドtopを使ってプロセスの状況を見てみましょう。
<tt><PREpre class="bash">
% top
18:49:03 up 49 days, 4:38, 8 users, load average: 2.00, 0.82, 0.31
3 root 19 19 0 0 0 SWN 0.0 0.0 0:00 ksoftirqd_CPU0
</PRE>
;調べてみよう:プロセスの状態を表しているSTATの欄をみるとR/S/D/Z/Tの後ろにさらに文字がついていますがこの意味は何でしょうか?
親プロセスが子プロセスを生むという形を取り、子プロセスは親プロセスのコピーとして動き始めます。例外はすべてのプロセスの始まりであるinitだけです。
;調べてみよう: コマンドpstreeを使って、すべてのプロセスの親子関係がどうなっているかチェックしてみよう。コマンド [http://man7.org/linux/man-pages/man1/pstree.1.html pstree]を使って、すべてのプロセスの親子関係がどうなっているかチェックしてみよう([[pstreeの出力例]])。
==== fork(2) ====
pid_t pid, wait_pid;
int wait_stat;
exit(EXIT_SUCCESS);
}
else { if ( pid == -1 ){ exit(EXIT_FAILURE); } fprintf(stderr,"pid in parent: %d\n",pid); wait_pid=wait(&wait_stat); fprintf(stderr,"wait returns : %d(%o)\n",wait_pid,wait_stat); exit(EXIT_SUCCESS); } return(0);}</pre >
システムコールfork(2)はフォーク後、親プロセスでは子プロセスのプロセスIDを返却し、子プロセスでは0を返却します。
このプログラムを実行すると次のような出力になります。
<pre class="bash">
$ ./a.out
pid in parent: 5008
pid in child: 0
wait returns : 5008(0)
</pre>
;補足: プログラム中ではシステムコールexecve(2)よりもライブラリ関数execlよりも下のサンプルコードのようにライブラリ関数execl(3)などを使う場合の方が多いでしょうが、ここでは説明としてexecveなどを使う場合の方が多いでしょう。下のコードはライブラリ関数execl(23) を取り上げています。下のコードはを使って/bin/dateを実行している例です。
;調べてみよう: カーネルの中でforkをするのはkernelカーネルの中でforkをするのは[https://github.com/torvalds/linux/blob/master/kernel/fork.cの中の関数do_fork です。関数do_forkは関数copy_processを呼び出してプロセスをコピーするのがメインの役割です。関数copy_processは、必要なものを親プロセスからコピーし子プロセス独自に必要な情報を初期化しています。c#L2541 kernel_clone()]です。それまでは_do_fork()でしたが2020年にリリースされたLinux Kernel 5.9以降kernel_clone()になりました。[https://patchwork.kernel.org/project/linux-kselftest/cover/20200818173411.404104-1-christian.brauner@ubuntu.com/#23554325 参考資料]
==== プロセスを生成するシステムコール ====
その反対にプロセスを殺すシステムコールはkill(2)です。ここでは内部でkill(2)を呼んでいるコマンドkillを紹介しましょう。
<pre class="bash">
$ kill -9 8321
</pre>
killにプロセスIDを与えた場合、そのプロセス番号にSIGKILLを送ります。SIGKILLはプロセスを殺すシグナルです。killに与えるプロセスIDが0の場合、自分自身を殺します。-1は、シグナルを受け取ることができる全てのプロセスに送られます。プロセスinitはプロセスIDが1で、むかしは1を指定するとシステム全体が死んでいたのですが、今はルート権限でkill -9 1としても死なないようになっています。
;調べてみよう: killでマイナスの値を指定することができます。それはどのような役目をするのでしょうか?
=== スケジュリング スケジューリング ===
プロセスにCPU資源をどのように与えるかの順番を決めるスケジュール (schedule)ことを行うことがスケジュリングことを行うことがスケジューリング(scheduling)です。それを司るのがスケジュラです。それを司るのがスケジューラ(scheduler)<ref>Linux Schedulerhttps://www.kernel.org/doc/html/latest/scheduler/index.html</ref>です。
==== 基本的なスケジューリング方式 ====
* FIFO 独自優先順位 ( SCHED_OTHER ) : 最初に入ったプロセスが終了するまでそのプロセスが専有する方式。最初に入ったものが最初に出るのでFirst-In-First-Out / FIFOと呼ばれる。 実行に使用した時間の違いなどにより優先順位をつけるなどし、その優先順位に従って割り当てる方式。(Linuxデフォルト)
* RR : ラウンドロビンFIFO ( Round RobinSCHED_FIFO ) : 一定の間隔で順繰りに回ってくる)でプロセスが割り当てられる方式。最初に入ったプロセスが終了するまでそのプロセスが専有する方式。最初に入ったものが最初に出るのでFirst-In-First-Out / FIFOと呼ばれる。
* 独自優先順位RR ( SCHED_RR ) : 実行に使用した時間の違いなどにより優先順位をつけるなどし、その優先順位に従って割り当てる方式。ラウンドロビン(LinuxデフォルトRound Robin: 一定の間隔で順繰りに回ってくる)でプロセスが割り当てられる方式。
* バッチ( SCHED_BATCH ): (たとえば大量の処理をしても)プライオリティの変化をつけない。尚、当然ながらタイムシェアリングシステムにおいてのバッチという意味である。 * アイドル ( SCHED_IDLE ): CPUがアイドリング状態になった時に処理が行われる。 独自優先順位は、何らかの評価方式でCPUに割り当てる優先度や割り当てる時間を決めて順番を決める方式です。何らかという通り、色々な方法があって、オペレーティングシステムの違いによって異なりますし、あるいは同じカーネルであってもバージョンの違いによっても異なります。
ラウンドロビンは一定時間CPU時間を消化したプロセスを待ち状態に戻し、待っていた次のプロセスに切替えて順繰りにプロセスを実行していく方法です。この一定時間のことをタイムスライス(timesliceTime Slice)といいます。公平にプロセスにCPUを割り当てることになります。タイムスライスを小さくすれば小さくするのど、細かくプロセスをCPUへ割り当てることができますが、しかし割り当て処理の回数が増えるにつれカーネル自体が消費するCPU時間も多くなっていきます。ですから、リーズナブルな値にしなければなりません。あるいはタイムクアンタム (Time Quantum)と呼びます。公平にプロセスにCPUを割り当てることになります。タイムスライスを小さくすれば小さくするのど、細かくプロセスをCPUへ割り当てることができますが、しかし割り当て処理の回数が増えるにつれカーネル自体が消費するCPU時間も多くなっていきます。ですから、リーズナブルな値にしなければなりません。
;補足: 筆者の知る範囲では大体においてラウンドロビン方式と優先順位づけなどを併用しているので、完全に公正で総当たりするようなラウンドロビン方式というのを見たことがありません。筆者の知る範囲ではラウンドロビン方式と優先順位づけなどを併用しているので、単純に順番を待つだけのラウンドロビン方式というのを見たことがありません。
Linux 2.6系におけるスケジュラーは以前26系におけるスケジューラは以前2.4系で使われていたスケジュラーからみると画期的に性能がいいO4系で使われていたスケジューラからみると画期的に性能がいいO(1)スケジュラスケジューラ(Order one scheduler)に変更されました。さらにLinux 2.6.23からO(1)スケジュラからCFSスケジューラからCFS(Completely Fair Scheduler)に変更されました。 ==== 使えるスケジューリングを調べてみる ==== カーネルにどのスケジューラを入れるかはディストリビューションによって異なりますし、またカーネルのバージョンによっても違ってきます。どのようなスケジューリングが可能かを簡単に調べるのにはコマンド [[chrt]] が便利です。 <pre class="bash">$ uname -r4.4.0-134-generic$ sudo chrt -mSCHED_OTHER min/max priority : 0/0SCHED_FIFO min/max priority : 1/99SCHED_RR min/max priority : 1/99SCHED_BATCH min/max priority : 0/0SCHED_IDLE min/max priority : 0/0</pre> またchrtはプロセスのスケジューリング・ポリシーを変更することが出来ます。詳しくはマニュアル [https://man7.org/linux/man-pages/man1/chrt.1.html chrt] を確認してください。 ;追記: その後 Linux 3.14 (2014/3/20リリース) からリアルタイム指向の処理に必要なデットラインを処理するための [https://core.ac.uk/download/pdf/14699805.pdf SCHED_DEADLINE] がスケジューラ <ref> デフォルトで使えるかどうかはディストリビューションによります。</ref> に加わっています。Linux 3.14ではデットライン・スケジューラのアルゴリズムはEarliest Deadline First (EDF)を採用していましたが、Linux 4.13 (2017/9/5リリース)からはEDFを改良しさらに Constant Bandwidth Server ( CBS ) 加えた( と、 [https://github.com/torvalds/linux/blob/master/kernel/sched/deadline.c カーネルのコメント] では表現している )アルゴリズムを採用しています。 タイムスライスの値はスケジューリングのポリシーによっても変化しますし、スケジューリング・ポリシーによっては動的にも変化します。指定されたプロセスの SCHED_RR 間隔を取得するシステムコール[http://man7.org/linux/man-pages/man2/sched_rr_get_interval.2.html sched_rr_get_interval(2)]を使うと実際の[[プロセスのタイムスライスの値を計測する]]ことが可能です。 これらのスケジューリングもカーネルのパラメータを変えることでチューニングができます。ただし、それに関しての説明は本サイトの主旨を越えてしまいますし、当然ながらカーネル内のパラメータを変更するわけですから、不適切な値の場合、システムに大きな損傷を与える可能性があります。ここでは[https://doc.opensuse.org/documentation/leap/archive/42.1/tuning/html/book.sle.tuning/cha.tuning.taskscheduler.html openSUSEのタスクスケジュラーのチューニング]のページを紹介するに留めます。
==== CFS ====
Linux 2.6.23からCFS(Completely Fair Scheduler)は、
<ref>
Inside the Linux 2.6 Completely Fair Scheduler
https://developer.ibm.com/tutorials/l-completely-fair-scheduler/
(リンク切れ)
Linux カーネル 2.6 Completely Fair Scheduler の内側
http://www.ibm.com/developerworks/jp/linux/library/l-completely-fair-scheduler/
</ref>
それまでの Linux のスケジューラは、タスクを管理するキューをもち、その中で処理をしていました。CFSでは平衡2分探索木の一種であるCFS スケジュラに関しての評価赤黒木
<ref>
</ref>
あるいはさらなる効率化のために変更される可能性は十分にあります。
これからも、あちらこちらの腕利きのカーネルハッカーがどんどん新しい提案をしてくることでしょう。
==== スケジューラのチューニング ====
スケジューラのチューニングに関しては下記の情報が参考になります。
* SUSE [https://www.belbel.or.jp/opensuse-manuals_ja/cha-tuning-taskscheduler.html タスクスケジューラのチューニング]
* Red Hat Enterprise Linux 9 [https://access.redhat.com/documentation/ja-jp/red_hat_enterprise_linux/9/pdf/monitoring_and_managing_system_status_and_performance/red_hat_enterprise_linux-9-monitoring_and_managing_system_status_and_performance-ja-jp.pdf 第30章 スケジューリングポリシーの調整]
=== プライオリティ ===
プロセス待機キューの並べ変え、プロセスのタイムスライスの値を設定する時に、プライオリティが高い程、よりCPUが割り当てられるような計算がされます。
もう一度、コマンドtopの出力例を見てみましょう。
<pre class="bash">
PID USER PRI NI SIZE RSS SHARE STAT %CPU %MEM TIME COMMAND
6981 hironobu 20 0 6552 6552 140 R 33.4 1.0 0:17 pi
6980 hironobu 17 0 6552 6552 140 R 32.8 1.0 0:17 pi
6979 hironobu 14 0 6504 6504 140 R 32.6 1.0 0:18 pi
</pre>
RRIと書いてあるのがプライオリティ値です。NIと書いてあるのがnice値です。通常で使う範囲では、プロセスがCPUを専有すればするほどプライオリティ値が大きくなっていきます。カーネルの中では0から139の範囲で変化しています。優先順位が0が最低で139が最高です。ユーザ側に表示にされるのは20から-19 で、優先順はプラス側低く、マイナス側が高くなっています。
コマンド実行開始時にコマンドniceを使い底上げ値(優先順位を下げるのでハンディキャップ値といった方がいいかも知れませんが)を与えることができます。一般ユーザは優先順位を下げることしか出来ません。root権限では優先順位を上げることもできます。reniceは既に動いているプロセスのnice値を変更するものです。
<pre class="bash">
% nice -19 ./setiathome
</pre>
=== スレッド ===
スレッドは計算資源を共有しCPU資源だけは別の状態であるような処理の流れをいいます。スレッドにはclone(2)のようなカーネルレベルで実行するスレッドとglibcなどユーザライブラリに含まれているユーザレベルで実行するスレッドの2つがあります。ですからスレッドが必ずしもカーネルモードで動作しているわけではなく、それはプログラムの書かれ方によります。ここの説明ではカーネルスレッドとユーザスレッドとの両方取り上げてみます。
既に説明した通りfork(2)が親と子のプロセスで内容をコピーしているのですが、とはいえ fork(2) 後は各々の実体は独立しています。しかし、スレッドは内容が一緒ですから、つまり処理の流れを別に持つCPU資源だけは複数個持つのですから、新しいプロセスが生まれていないので親も子もありません。CPU資源以外の計算資源のコピーが必要ない場合、あるいは同じ計算資源でCPU 資源を複数持ちたい時、計算資源を共有したい時などにスレッドは便利です。
CPUリソースだけを同時に複数個持つことができることで、たとえば実行しているタスク中でネットワーク入出力からの処理を行いつつ同時に他の計算を繰り返すような場合に非常に有効に働きができます。
1000 回プロセスを生成するのとスレッドを生成するプログラムを書いてみます。結果はAthlon 1GHzマシンでスレッド生成側がトータル0.157秒、プロセス生成側が0.360秒と2倍以上の差をつけました。このようにCPUリソースのみだけで処理が出来るようなものを大量に生成するような場合はスレッドが有利に働きます。
% cc -O fork.c -o f
</pre>
% cc -O thread.c -lpthread -o t
</pre>
% time ./t
...........(続く)....1000
user 0m0.xxxs
sys 0m0.xxxs
</pre>
=== マルチプロセッサとプロセス ===
=== 脚注 ===