ここより
Linux Kernel 2.6 関連
の方が情報が多いです。
| 解説 | リンク |
| Linuxデバイスドライバ開発のヒント | http://www.devdrv.co.jp/linux/ |
| Kernel Newbies | http://kernelnewbies.org/ |
| @IT: Linux Square カーネル関連記事 | http://www.atmarkit.co.jp/flinux/index/indexfiles/index-linux.html#kernel |
Unable to open a suitable terminal device.とエラーメッセージが出る。
カーネルコンフィグで
File Systems >
Pseudo filesystems >
[*] /dev/pts
これで解決。
NPTL は古い Linux Threads 実装とバイナリで互換性があるようになっていますが、古い Linux Threads の実装で POSIX 標準以外の実装の部分を使用しているソフトウェアの場合、NPTL の環境では修正が必要になります。
NPTL でソフトがうまく動作しない場合は環境変数 LD_ASSUME_KERNEL を設定すると古い Linux Threads を使えます。
| 古い Linux Threads を使う | 設定値 |
| フローティングスタックあり | 2.4.1 |
| フローティングスタックなし | 2.2.5 |
今のところ上記二種類の設定があるようです。
hoge というプログラムを使うときに設定したい場合は以下のようにすれば一時的に環境変数を設定して実行する事ができます。(bash の例)
LD_ASSUME_KERNEL=2.2.5 hoge
カーネルはとても小さな時間で次々とプロセスを切り替えることでプログラムの並列実行を可能としている。
具体的には、レジスタの内容をtask_structという構造体に退避し、プロセスを安全に停止し、切り替えながら再実行する。
プロセスには、タイムスライスと呼ばれる「とても小さな時間」が与えられ、Linuxカーネルはスケジューラによってこれを次々に切り替える。これをコンテキスト切り替えと呼ぶ。
主なスケジューリングアルゴリズムに、ラウンドロビン、協調的スケジューラ、多段フィードバックキューなどがある。Linux 2.6.23以降ではCFSという新しいスケジューラが使われている。
プロセスには、「実行中」「実行可能」「待ち状態」の3つの状態があり、それぞれに切り替わる。待ち状態とは、たとえばfind . | grep hogeでfindの実行終了を待っているgrepのような状態。この間grepの実行はブロックされる。
カーネルは、実行するプロセスに、テキスト領域(プログラムが格納される)、スタック領域、ヒープ領域などを与える。
カーネルはプログラムがメモリ領域にアクセスした段階で、論理アドレスを物理アドレスに翻訳する。これを仮想アドレス空間と呼ぶ。ハードウェアのMMUという機構を使ってこれを実現している。このようにすることで、CPUとメモリの組がひとつしかなくても、プログラムの数だけあるかのように仮想化することができる。
カーネルは三段階のメモリアドレッシングを行う。すなわち、ページグローバルディレクトリ、ページミドルディレクトリ、ページテーブルである。もともとは二段階だったが、64bit化された結果二段階では不十分ということになり、Alphaプロセッサを参考に三段階のアドレッシングがされるようになった。
カーネルは、ページングやセグメンテーションによってメモリ空間を小さく分割して管理している。C言語のプログラムをコンパイルすると、ネイティブに実行できるバイナリファイルが出来るが、このプログラムを実行すると、カーネルがこのプロセスがきちんと動くようにメモリ領域を割り当てて管理する。
カーネルは、できるだけ連続したメモリ領域を確保し、空き領域の断片化が起きないように、ページの置換のために頻繁に使用されていない空き領域を選ぶページ置換アルゴリズムや、2を基底する数で管理するバディシステムのような仕組みを使って努力している。
メモリを動的に確保するにはmalloc(), realloc(), calloc()などのAPIを使う。またbrk()やsbrk()も使うが、malloc()系列のAPIとbrk()系列のAPIを同時に使うことはできない。これはmalloc()がbrk()などとともに使われないことを前提として実装されているため。malloc()した領域は必ずfree()してプログラムの中で解放しなければならない。C言語ではJavaのようなガーベッジコレクションの仕組みがないため、解放しなければメモリリークが起きる。
C++ではnewとdeleteを使ってオブジェクト指向のオブジェクトをmalloc()やfree()と同様に動的スコープで管理できる。この際には構造体のポインタと同じように.の代わりに->を用い、型宣言に*をつける。また、C++でもスマートポインタを用いれば、Javaと同じように自動的に解放されるメモリ領域を管理できる。
また、メモリではないが、プログラムを書く時はエラーチェックをきちんとすること。malloc()のエラーチェックをしなかった場合、システムに存在するメモリが使い果たされた時などの対応ができない。C言語には例外の仕組みがないため、エラーチェックは返り値を条件式で比較してチェックする。
カーネルは、IOデバイスの応答を待機している間、別の仕事をする。IOデバイスが処理を完了して応答した時点で、デバイスがカーネルに割り込み、処理を継続する。
割り込みとは、このようにカーネルの処理に対してさまざまな場面で割り込んでくる処理のことで、場合によって例外と呼ばれることもある。ソフトウェア割り込みとかハードウェア割り込みと区別されることもある。
プロセスは、それぞれに独立したメモリ領域を用意する。これに対して、スレッドはプロセスの中に軽量な並列処理を実行し、
メモリ領域を共有する。
スレッドにおいては、競り合い状態が発生しないように、変数を排他制御(ロック)する必要がある。
プロセスにおいても、プロセス間通信の仕組みを使うことで、異なるプロセスでデータを共有できる。
セマフォを使うことで、排他制御ができる。セマフォは、同時に使える人数のうち、あとどれくらいの人数が使えるかを示す整数値を提供する。
プロセス関連のシステムコール・APIとして、fork(), exec(), wait(), exit()などがある。forkをするとその時点からプログラムが二つに分岐する。execをすると自分のプロセスを上書きしてそのプログラムを実行する。特にサーバーなどでは面倒くさくてもforkしよう。
デーモンを作る場合、fork()した上でsetsid()で制御端末を切り離せばよい。
デバイスドライバは、カーネルの中で唯一デバイスコントローラの詳細な仕様を知っている部分。キャラクタ型デバイスとブロック型デバイスがある。
キャラクタ型デバイスは、ランダムアクセス(好きな時に好きな場所にアクセス)することができない。
ブロック型デバイスは、ランダムアクセスができる。多くの場合、ハードウェアなどのストレージはブロック型デバイスであり、マウントして利用する。
カーネルはVFSという機構を使うことで、ファイルシステムが違ってもプログラムを修正する必要はなく、同じシステムコールからアクセスできる。
ext2やext4はブロック型ファイルシステムで、ブロックでデータを管理し、i-nodeと呼ばれるインデックスからブロック内のデータにアクセスする。
カーネルは、低速なディスクに頻繁にアクセスしないように、ストレージの内容を内部でキャッシュしている。ファイルの書き込みはまずキャッシュに書かれてからその後にディスクに書かれる。なんらかの理由でキャッシュからディスクに書きたいならsyncコマンドを実行する。
i-nodeはデータへとアクセスするためのインデックスのことで、「未使用」「使用中」「ダーティ」に分けられる。ダーティとは「書き込み中」の意味。ダーティフラグがつけられると、カーネルはそれをダーティリストに加えて、I/Oリクエストキューに加え、順次それが書き込まれる。
ext2などのブロック型ファイルシステムは、ブロックの集合であるブロックグループの集合としてデータを保管し、メタデータをスーパーブロックに格納する。
これに対してReiserFSやXFSはデータベースで用いられるB-Treeと呼ばれるインデックスを用いる。
BtrFSのようなコピーオンライトのファイルシステムでは、ファイルはコピーだけされても実際のデータは複製されず、読み取り専用となっている元のファイルの部分を参照する。実際にこのデータに書かれた時にその部分だけが複製される(コピーオンライト)。
このためBtrFSではファイル容量が大幅に削減できるため、過去のその時読まれたデータを残しておくスナップショットの機能を持たせても構わないぐらいの現実的な余裕がある。そのため、障害復旧に優れている。
カーネルはBSDソケットインターフェースを提供する。これは互いに関連づけられると有効になり、片方から書き込むと片方から読み込める。
TCPでは再送制御など信頼性確保のための多くの機能があるが、UDPは機能がない代わり、ビデオや音声などを多少誤りがあっても高速に通信できる。
APIは、socket(), bind(), listen(), accept(), connect()となる。サーバ側で待ち受けて待機しているソケットに、クライアントが接続することで、ソケットの連携が可能となる。
カーネル内部では、BSDソケット層とINETソケット層がある。OSI参照モデルでは、ヘッダを付加して上層から下層へとデータを送り、ヘッダを除去して下層から上層へとデータを受け取る。Linuxでは、ソケットバッファsk_buffのポインタをOSI参照モデルの各層に渡していくことでこれを実現している。
シグナルはプログラムを強制終了するのに使う。デフォルトの動作は強制終了やコアダンプなどに限られているが、プロセスの側でシグナルを捕捉(トラップ)し、別の動作をさせることができる。
シグナルはシステムコールsignal()で捕捉できるが、古いので新しいプログラムではsigaction()を使う。
システムコールは、基本的にopen(), close(), write(), read()で行う。
しかしながら、バッファリングを行うstdioのライブラリ関数を使った方が高速。この場合、fopen(), fclose(), fwrite(), fread()などが使えるほか、ほかにもたくさんのAPI関数がある。
システムコールは固定長の読み書きしかできないが、stdioは文字単位や行単位で入出力でき、フォーマット出力などの便利な機能がある。一文字だけ出力するにはputchar()、一文字だけ入力するにはgetchar()を使う。この二つの関数だけでもいろんなプログラムが書ける。
システムコールを使う場合、ファイルディスクリプタと呼ばれる整数値をストリームの識別に用いる。プロセスには、標準入力、標準出力、標準エラー出力のファイルディスクリプタが与えられるが、ファイルやソケットをオープンした場合にもファイルディスクリプタが与えられ、これに対してread()やwrite()することでストリームの読み書きが可能となる。
stdioでは、ファイルディスクリプタをFILEポインタというラッパーに格納する。FILEポインタにはバッファリングの情報や、ファイルのどの位置を今読んでいるかの情報などが格納されている。
単に入出力をするだけなら、fgets()やfputs()、あるいはprintf()などを使う。ファイルに書き込む時はfprintf()を、出力せずに文字列をフォーマットだけしたい場合はsprintf()を使う。また、テキスト処理をする場合、トークン解析などの理由で一文字だけ元に戻したいことがある。この時はungetc()を使う。
insmodで、カレントディレクトリにあるカーネルモジュールをロードできる。
insmod hello.ko
ロードされたモジュールはlsmodで確認できる。
モジュールをアンロードするにはrmmodを行う。
rmmod hello
また、「/lib/modules/`uname -r`」からドライバをロードするにはmodprobeコマンドを使う。
カーネルモジュールのロードの設定は、/etc/modules-load.d/*.confに記述する。オプションは、/etc/modprobe.d/*.confに記述する。