カーネル開発(プロセス管理)Linuxのカーネルの開発に関する世界観(プロセス管理)です。Linuxカーネルも参照のこと。Linuxの仕組みから学べるgccプログラミングの王道を参考に執筆・編集して引用しました。仮想CPUと仮想メモリという「幻想」プロセスは、複数同時に動作することができる。素のコンピュータでは、プロセスは1つしか動かない。CPUとメモリの組がたくさんあれば、複数のプロセスを同時に動かすことができる。CPUとメモリが1つずつしかなかったとしても、プロセスから見たときに、そのプロセス専用のCPUとメモリがあるように見えれば良い。現代のコンピュータでは、そのような「幻想」を、ハードウェアとカーネルが協力することで生み出している。これを「仮想CPU」と「仮想メモリ」と呼べる。素のコンピュータでは、CPUとメモリが1つずつしかないため、プロセス(プログラム)は1つしか動かない。ax」を実行すれば分かるように、現代のコンピュータでは、普通にLinuxを使うだけでも、プロセスは何十と動いている。CPUとメモリが「1つしかなければ1つしか動かない」ため、たとえCPUとメモリが1つしかなくても、「仮想的にCPUとメモリがたくさんあるように見せかける」ことで、Linuxではマルチタスクを実現している。タイムスライスとスケジューラCPUを増やすには、非常に短い時間単位で、たとえば0.001秒ごとに、実行するプロセスを次々に切り替えれば良い。それだけで、プロセスからはCPUが自分専用であるかのように見える。CPUが実行するプログラムを次々と切り替える時、この時、各プロセスに配分される時間のことを「タイムスライス(時間の切り身)」と呼び、いつタイムスライスを与えるかを管理するカーネルの機構を「スケジューラ(ディスパッチャとも)」と呼ぶ。プロセスには動作優先度が決まっているので、タイムスライスは公平にはもらえない。niceコマンドでプロセスの優先度を変更できる。CPUに余裕が無い時はタイムスライスをもらえなくなる。read()でハードディスクから読み込んでいるプロセスは、ハードディスクからデータが届くまでは動作する必要が無い。そのようなプロセスもタイムスライスはもらえない。仮想メモリ仮想メモリについてはLinuxカーネル(メモリ管理)を参照のこと。第2版」を参考に執筆・編集して引用しました。カーネルモデルUNIXのカーネルは、CPUのカーネルモードとユーザモードで動作する。ユーザモードで実行されている時は、プログラムはカーネルのデータやコードに直接アクセスできない。カーネルモードで実行されているプログラムは、この制限は適用されない。ユーザモードとカーネルモードを切り替える命令が、CPUにおいて用意されている。プログラムは、大部分の時間がユーザモードで実行される。カーネルモードに切り替わるのは、カーネルが提供するサービスを要求する場合だけである。カーネルは要求を満たした後、プログラムをユーザモードに戻す。プロセスは動的で、システム内で限られた寿命を持ち、プロセスの作成、削除、プロセス間の同期などは、カーネルのルーチンに任されている。カーネルはプロセスではなく、プロセスの管理者(マネージャ)である。プロセス・カーネルモデルでは、カーネルのサービスを必要とするプロセスは、特別な手続きである「システムコール」で使用する。UNIXシステムには、ユーザプロセス以外にも、「カーネルスレッド」と呼ばれる特権プロセスがある。これはカーネルアドレス空間において、カーネルモードで動作する。ユーザとのやり取りはないため、端末デバイスを必要としない。普通は、システムの起動時に作成され、シャットダウンするまで動作し続ける。プロセスは、システムコールの発行によってカーネルモードとなり、サービスが実行されるとユーザモードに戻る。ここで、タイマ割り込みが発生する。カーネルモードでスケジューラが実行されると、プロセスが別のプロセスに切り替わる。ハードウェアデバイスからの割り込みが発生すると、カーネルモードになり、割り込みのための処理を行う。UNIXカーネルは、システムコール、例外(異常信号)、割り込み信号(データが届いたなどの何らかの処理が必要となり、CPUの注意を喚起したり、状態の変更やI/O操作の完了などの事象を知らせたりする)、そしてカーネルスレッドの実行などに関して、カーネルルーチンが駆動する。プロセスの実装カーネルがプロセスを管理するために、各プロセスはプロセスディスクリプタによって表される。プロセスディスクリプタには、プロセスの現在の状態における情報が含まれている。カーネルは、プロセスの実行を停止する際に、その時点でのCPUプロセッサのレジスタの内容を、プロセスディスクリプタの中に退避する。レジスタには以下のようなものがある。・プログラムカウンタ(PC)レジスタとスタックポインタ(SP)レジスタ。・浮動小数点レジスタ。・プロセッサ制御レジスタ(プロセッサステータスワード)。CPUの状態の情報がある。・メモリ管理レジスタ。プロセスによって使われるRAMの管理に使われる。カーネルはプログラムの実行の再開を決めると、停止しておいたCPUレジスタを復旧するために、対応するプロセスディスクリプタのメンバ情報を使用する。プログラムカウンタとして退避された値は、最後に実行された命令の次に続く命令を指している。よって、プロセスは停止した位置から実行を再開する。UNIXカーネルはリエントラントであり、同時に複数のプロセスをカーネルモードで実行することがある。(後日注記:リエントラントとは「再入可能」という意味で、あるプログラムやサブルーチンの実行をしている時に同じプログラムやサブルーチンを実行できるという意味。e-Wordsを参照のこと。)それぞれのプロセスは、固有なアドレス空間において実行される。ユーザモードで実行中のプロセスは、固有のスタックとデータとコード領域を参照する。カーネルモードで実行するプロセスは、カーネルのデータとコード領域を参照し、もうひとつのスタックを用いる。UNIXのシグナルは、システムの事象をプロセスに通知する仕組みを提供する。プロセス切り替えプロセスの実行を管理するために、カーネルは実行プロセスの休止や再開を行う必要がある。このような処理のことをプロセス切り替え、タスク切り替え、コンテクスト切り替えと呼ぶ。各プロセスは自身のアドレス空間を持つことができるが、CPUレジスタはすべてのプロセスで共有されている。そのためカーネルはプロセスの実行を再開する前に、共有しているレジスタの値をプロセスが休止した瞬間の値に戻す必要がある。プロセスの実行を再開する前にレジスタに戻す必要のある一連のデータのことをハードウェアコンテキストと呼ぶ。ハードウェアコンテキストはプロセスの実行に必要なすべての情報が格納されているプロセス実行コンテキストのサブセット。Linuxではプロセスのハードウェアコンテキストの一部はプロセスディスクリプタ内に、残りはカーネルモードスタックに格納されている。ここではローカル変数prevは切り替えられるプロセスのプロセスディスクリプタを、nextはその代わりに実行されようとしているプロセスのプロセスディスクリプタを指しているものとする。これによりプロセス切り替えのことを、「prevのハードウェアコンテキストを退避してnextのハードウェアコンテキストで置き換える動作」と定義することができる。プロセス切り替えは頻繁に発生するため、ハードウェアコンテキストの退避や復旧にかかる時間を最小限に抑えることは重要。プロセス切り替えは、明確な一か所、schedule()関数にて起きる。本質的に、すべてのプロセス切り替えは以下の2つの処理からなる。1.ページグローバルディレクトリを切り替え、新しいアドレス空間を有効にする。2.カーネルモードスタックとハードウェアコンテキストを切り替える。プロセスが実行するために必要な情報すべてである(CPUレジスタも含む)。prevは切り替え元プロセスのディスクリプタを指し、nextは次に実行するプロセスのディスクリプタを指す。prevとnextはschedule()関数内のローカル変数。プロセス切り替えの2つ目の処理は、switch_toマクロが実行する。switch_toマクロは、もっともハードウェア依存なカーネル処理の1つ。switch_toマクロは、prev、next、lastの3引数を取る。schedule()関数の、実際のマクロ呼び出しは以下のようになる。last);prex引数とnext引数の役割が、単にローカル変数prevとnextの代替であることは理解できる。最後のlast引数についてだが、実は、どのプロセス切り替えにおいても、それに関与するプロセスは2つではなく3つである。カーネルがプロセスAからプロセスBに切り替えようとしているとする。shcedule()関数では、prevはプロセスAのディスクリプタを指し、nextはプロセスBのディスクリプタを指す。switch_toマクロがプロセスAを停止した瞬間、プロセスAの処理の流れは止まる。後になり、カーネルがプロセスAを再実行しようとしたとき、他のプロセスCから切り替わらなければならない。そのとき、prevがプロセスCを指し、nextがプロセスAを指している、別のswitch_toマクロが実行される。プロセスAが実行の流れを再開したとき、プロセスは元のカーネルスタックを見つけ、prev変数がプロセスAのディスクリプタを指し、next変数がBのディスクリプタを指す。プロセス切り替え処理の後半はプロセスAの上で動作し、カーネルはプロセスCへの参照を失ってしまう。swicth_toマクロのlast引数は、prev変数にプロセスCのディスクリプタのアドレスを再設定するために利用する。この仕組みは、関数呼び出し時に使わないレジスタを利用して実現している。先頭のprev引数はCPUレジスタに対応しており、マクロが実行を始めたときのprev変数の値を読み込む。マクロが終了するとき、last変数に割り当てられた同じレジスタの内容を、prev変数に書き戻す。CPUレジスタはプロセス切り替えの前後で変化しない。prevにプロセスCのディスクリプタを受け取り、スケジューラはプロセスCが他のCPU上で動作できないか調べる。以下のサイトに、Linuxカーネル2.6解読室という書籍の第2章までが公開されています。OSDNコンテキスト切り替え動いているプログラムのことをプロセスと呼ぶ。プロセスが動作するためのプロセス空間や、CPUレジスタの値などが、プロセス固有の「コンテキスト」として保持される。これらはtask_struct構造体(task_t型)で管理される。CPUの中のレジスタ群の値などのコンテキストは、コンテキスト切り替えの際、一時的にtask_struct構造体やカーネルスタックに退避され、再び実行状態に戻るとそのコンテキストをCPU上に読み込む。以下のページを参考に執筆しました。Linuxの仕組みから学べるgccプログラミングの王道を参考に執筆・編集して引用しました。プロセスの概念Linuxでは、どんなプロセスもfork()かそれに類するAPIで作成される。このプロセスを親子関係でつないでいくと、1つのツリー構造にまとめられる。pstreeを使うことで、この親子関係を表示できる。親子関係以外に、プロセスを体系づける、セッションとプロセスグループと呼ばれる概念もある。コマンドをたくさん使ったパイプを起動した時、コマンドの一部を間違えて時間がかかったりして、Ctrl+Cを打って処理を中断した場合に、パイプを構成する全プロセスを止めるために、プロセスグループが考案された。セッションは、ログインシェルを起点に、ユーザが同じ端末から起動したプロセスを1つにまとめる働きをする。j」を実行すると、セッションやプロセスグループを見ることができる。表示されている欄の意味は、PIDがプロセスID、PPIDがプロセスの親のID、PGIDがプロセスグループID、SIDがセッションID。PIDとSIDが等しいプロセスはセッションリーダーで、最初にそのセッションを作ったプロセスがセッションのリーダーになる。PIDとPGIDが等しいプロセスはプロセスグループリーダーで、セッションと同じく、最初にそのプロセスグループを作ったプロセスがリーダーになる。ax」でTTYの欄が「?」になっているプロセスは、制御端末を持たないプロセスで、デーモンプロセス、あるいは単にデーモンと呼ぶ。サーバーを作る時にお世話になる。wait()fork(2)fork()を呼び出すと、カーネルはプロセスを複製して、2つのプロセスに分裂させる。これによって新しいプロセスを作り出す。分裂された時点で、複製前と複製後のプロセスは、どちらもfork()を呼び出した状態になっている。2つのプロセスの両方にfork()の呼び出しが戻って、両方のプロセスでfork()以後のコードが実行される。この時、元のプロセスを親プロセス、複製されたプロセスのことを子プロセスと呼ぶ。exec(2)自分を上書きして、新しいプログラムを実行する。execを実行すると、その時点で現在実行しているプログラムが消滅し、自プロセス上に新しいプログラムをロードして実行する。waitpid(2)親プロセスが子プロセスの終了を待つ。fork()したプロセスの終了を待つには、wait()かwaitpid()を使う。いずれもシステムコール。wait()は、子プロセスのうちどれか1つが終了するのを待つ。waitpid()は、pidで指定したプロセスが終了するのを待つ。exit(3)は、プロセスを終了させる。pipe(2)などについて。pclose(3)などは、パイプとしてストリームを繋げる一連のAPI。getppid(2)は、プログラムから自分の親を知る。setsid(2)新しいセッションを作る。デーモンを作る時に役に立つ。新しいセッションを作成し、自分がセッションリーダーになる。同時に、そのセッションで最初のプロセスグループを作成し、そのグループリーダーになる。setsid()を読んで作成した新しいセッションは制御端末を持たない。セッションリーダーになると同時にデーモンにもなる。Linuxの仕組みから学べるgccプログラミングの王道を参考に執筆・編集して引用しました。カレントディレクトリ(コマンドが今居る作業中のディレクトリ)を取得する。カレントディレクトリを変更する。環境変数にアクセスするためのグローバル変数。