woshidan's blog

そんなことよりコードにダイブ。

「はじめてのOSコードリーディング UNIX V6で学ぶカーネルのしくみ」を読みました

Javaの並行処理の後はこちらの本を読んでいました。

はじめてのOSコードリーディング ~UNIX V6で学ぶカーネルのしくみ (Software Design plus)

はじめてのOSコードリーディング ~UNIX V6で学ぶカーネルのしくみ (Software Design plus)

この本を読む前に転職にあたって少し背伸びをして「Inside Android」や「Androidを支える技術」を読んでいたのですが、initプロセスやスケジューラの話でぴんと来ずもしかしなくてもLinux的な本を何か読んだほうがいいのでは、と感じていました。

この本を手に取ったきっかけは、ゲーム系から来た人にC言語系統で何を読めばいいかと聞かれた時Linuxカーネルだよとばかり返ってくるため、それでは初心者はどこから読んだらいいかと聞きなおしたら勧められた感じです。

読んでいる最中は前半の実行プロセスの切り替えあたりでは知らない単語、知らない形のコード、大量の副作用でのデータのやり取りに頭が結構混乱するのですが、5章くらいから辞書として前半を弾き始めて多少理解できたような、できなかったような。。

前書きの読み解くコツに「まず最後まで読む」とあったのでなんとか最後まで通した感想としては、ファイルシステムと物理的なストレージの対応、パソコンの中でたくさんプロセスが動いておりそのやり取りの様子とは、みたいな当たり前のことに対してなんとなく自信を持てた感じです。

C言語の読解については自信がつきませんでしたが、nginxとかUnicornとかサーバの設定のその辺の話を調べられるようになっていて驚きました。

それにしても、この本で扱われているUnix v6のリリースが1975年、その前のJava並行処理の本が2006年初版なので、年明けはもうちょっと現代の空気にふれようと思います。

以下、いくつかの項目を雑にメモして締めます。口調が違う項目もありますがそういう感じで、現場からは以上です。

スケジューラと実行プロセス

proc[]エントリの1番に登録されていて、特定のアルゴリズムで次の実行プロセスの選択、切り替えを行う特別なプロセスおよびそのプロセスで動くプログラム。

実行プロセスの選択アルゴリズムは、Unix v6の場合、プロセスが使用したCPUの累積時間が大きいほど選択されにくい、というもの。

実行プロセスはカーネルグローバル変数uを通してuser構造体にアクセスできるようにカーネルAPRが設定され、実行プロセスの切り替え時にはカーネルAPR, ユーザAPR, 前回実行時に汎用レジスタに入っていた値の復元を行う(p.75)。

プロセスが今のプログラムに関するデータをどういう形で持っているか

各プロセスはproc構造体とuser構造体を1つずつ持っていて、プロセスの実行状況などはuser構造体が持っている。

user構造体が持っているのは

  • 各種計算やプログラムの実行位置の管理に使う汎用レジスタの状況
  • ユーザID/グループID
  • 実行中のカレントディレクト
  • そのプロセスでオープンしているファイル
  • シグナル、システムコール、ファイル読み込みのためのデータ受け渡し愚痴となる変数

などで、各種計算やプログラムの実行位置の管理に使う汎用レジスタの状況についてはプロセスが中断される時にuser構造体に退避されるが、その詳細が2章で紹介されている。

proc構造体が持っているのは

  • プロセスが実行可能かどうか
  • 実行時間がどれくらいか
  • 何かの資源が必要で実行を中断しているのか

といった、カーネルがプロセスを交代させたりするのに使う情報といった感じ。

proc構造体の情報は次にどのプロセスを実行させるかを判断する際に使われるので、現在実行していないからといってメモリから退避させることはできないが、user構造体は実行中以外はメモリ上にいる必要がないため、スワッピングの対象となる。

ファイルシステムとinodeとは

Unix系のシステムで扱うデバイスはブロックデバイスとキャラクタデバイスがあるのだけれど、プログラム含めたファイルの内容が載っているのがブロックデバイスというイメージ。ファイルシステムはブロックデバイスのデータを構造的に扱う仕組みのこと(p.258)。

その文脈でファイルはそのファイルを定義するinodeとファイルのデータから成っている。inodeはカーネルがいじる単位でのメタデータといった感じでファイルサイズやファイルのアクセス権限、ファイルのデータが保存されている。ファイル操作を行う最、カーネルの一番最初の仕事は目的のファイルのinodeを取得することになる。

inodeはファイル本体のデータとは少し離れた領域に保存されている(p.264)。

面白かったのは、ファイルの削除を行った時、実際にはinode領域だけが削除されていて、ファイルのデータが入っている領域は別のファイルによって上書きされるまでそのまま、という話。

http://www.fujitsu.com/jp/services/infrastructure/maintenance/lcm/service-phase4/h-elimination/

こういうのはそういう意味なんですね!

ブロックデバイスサブシステム

ブロックデバイスに入ってるデータを人間が扱いやすい形で操作するための仕組みがファイルシステムとしたら、コンピュータが効率的にメモリ上に出し入れするための仕組みがブロックデバイスサブシステムっぽい。

メモリ上にバッファ領域があり、そこにブロックデバイスから読み込んだデータをどこから読み込んだかとセットで管理、といった感じ。

プロセスの割り込みについて

(p.144あたり)実行プロセスの処理を中断する場合は、割り込みとトラップによるものがあって、割り込み

  • ブロックデバイスの動作完了通知(ファイルからのデータ読み込み完了通知など)
  • 端末からの入力
  • クロック割込み

など、非同期処理に関して使われます。トラップも同様にプロセスの中断がおこなわれますが

  • 0除算
  • 割り当てられない領域へのアクセス
  • バスタイムアウト

などCPU内部の出来事による中断・再開を扱います。

ユーザプロセスが引き起こすトラップは最終的にシグナルとして処理されます。

正直、ユーザプロセスから送られたトラップがシグナルとして~...のあたりを読んだときはあまりピンとこなかったのですが、こちらの記事など、ウェブアプリケーションフレームワークの設定ファイルでサーバのプロセスをどうこうするコードを読んだときなるほど!となって感動しました。

Unix v6のコード上でkill関数がプロセスにシグナルを送る程度の意味なのは結構面食らいましたが。。

calloutエントリで指された関数の実行タイミングと連結リスト

クロック割込みについての話題ではないのですが、クロック割込みハンドラで呼びだされるclock()関数にて処理されるcallo構造体の配列calloutの部分が面白かったです(p.163)。

calloutは連結リストの形になっているのですが、一つ一つのエントリが実行対象の関数へのポインタと次のエントリへのポインタ、前のエントリから何秒後((日本語の都合でこうなっているが実際の単位はティック)に実行されるかの情報を持っています。

そして、clock()関数のたびに最初のcalloutエントリだけ前のエントリから何秒後に実行してくれ、のカウンタがデクリメントされるわけです。

calloutに登録された一つ一つの関数の実行タイミング(「今から何秒後に実行してくれ」の秒数)は最初のエントリからひとつたどる度に「前のエントリから何秒後に実行してくれ」の秒数を合計していくことで求められます。なので、最初のひとつのエントリの「前のエントリから何秒後に実行してくれ」だけ変更してやれば、それ以降のエントリの関数の「今から何秒後に実行してくれ」の部分は全部変更されるわけですね。

あら便利。

だからなんだ!って話なんですが、いろんな言語でTimer系のクラスのAPIみるとき、これ思い出すと楽しそうだなとなったのでメモ。

実効ユーザID、実効グループIDと実ユーザID、実グループIDについて

アクセス権限はパーミッションと呼ばれる11ビットの制御情報を使ってファイルごとに管理され(p.260)、access()関数にてu_uid, u_gid(実効ユーザID/実効グループID)を元にファイルへアクセス可能かが判定されます(p.308)。

パーミッションの11のビット列のなかでSUIDビット、SGIDビットが立っていると、ファイル実行時にu_uid, u_gidが変更されます。その際、変更前の値が入っているのがu_ruid, u_rgid(実ユーザID/実グループID)のようです(p.261)。

ファイルのハードリンクについて

リンクによって、ファイル(inode+ストレージファイル)に対して複数の名前をつけることができます。

リンクと元のファイルの名前に主従はなく、inode構造体がいくつの名前を持っているかを管理しており、このカウントが0にならない限りファイルは削除されません。

と聞いた時、ハードリンク少し怖いな、と思ったのでメモ。

その他細々

パイプが他でも使っているファイルアクセスの仕組みを利用した特殊なinodeエントリへのデータ読み書きらしいことを知って、標準入出力の言葉に対してなるほど感が増えました。