【読書メモ】6.1810: Operating System Engineering
MITのオペレーティングシステムのクラスに取り組んだときのメモ。
概要はこちらから:
xv6はUNIX V6を参考にして作った教育用の小さなOS。
ソースコードはこちらから:
教科書はこちらから:
@1.2
ファイル記述子
ファイル記述子は、オープン中ファイルの詳細を記録しているカーネル内データ構造へのインデックスである。POSIXでは、これをファイル記述子テーブルと呼び、各プロセスが自身のファイル記述子テーブルを持つ。
ファイル記述子はint型でプロセスごとに管理されていて、0から始まる。xv6ではデフォルトでファイル記述子0を標準入力、1を標準出力、2を標準エラー出力に割り当てている。3以降はファイルを開くたびにファイルに割り当てられる。
ファイル記述子は現在使われていない番号の中で一番小さい番号が割り当てられる(もしファイルを開く前に0をcloseしたら、次に開くファイルのファイル記述子は未使用の中で一番小さい番号の0になる)。
fd_1 = open("file_1.txt", O_RDWR); // fd_1 == 3
fd_2 = open("file_2.txt", O_RDWR); // fd_2 == 4
fd_3 = open("file_3.txt", O_RDWR); // fd_3 == 5
ファイル記述子がcloseシステムコールにより解放された場合は、その後、番号は再利用される。
fd_1 = open("file_1.txt", O_RDWR); // fd_1 == 3
close(fd_1);
fd_2 = open("file_2.txt", O_RDWR); // fd_2 == 3
close(fd_2);
fd_3 = open("file_3.txt", O_RDWR); // fd_3 == 3
close(fd_3);
forkとexecを分ける理由(←まだ不明確)
forkとexecがくっついていると、I/Oの設定をforkとexecをする前に書き換えてforkとexecをした後に元の状態に戻すという操作が必要になるか、forkとexecのくっついたシステムコールの引数が複雑になるか、が起こるらしい??
追記:forkとexecが分かれているとfork後exec前の子プロセスにて、openやclose、dupによりファイル記述子を変更できる。親プロセス側とexecされるプログラム側(exec("cat", argv)のときはcatなど)でファイル記述子を変更しなくて良くなる。
fork
親プロセスのメモリやレジスタの状態などが子プロセスにもそのままコピーされる。親プロセスと子プロセスはファイル記述子テーブルを共有するだけではなく、ファイルのオフセットまで共有している。
// コンソールには、hello worldと表示される
// つまりファイル(ここでは標準出力)のオフセットまで共有している
if (fork() == 0) {
write(1, "hello ", 6);
exit(0);
} else {
wait(0);
write(1, "world\n", 6);
}
疑問(未解決)
ファイル記述子とファイル記述子テーブル、ファイルテーブル、inodeの関係性がいまいち見えていない。
@2
OSが満たすべき条件
- 多重化(例:プロセスの数が物理的なCPUの数より多くても、全てのプロセスは実行されなければならない)
- 分離(例:あるプロセスでバグや機能不全が発生したとき、依存関係のないプロセスに影響を与えてはならない)
- 相互作用(例:システムコールのpipe)
テキストの日本語訳がある。
@2.2
3つのCPUのモード
マシンモード
CPUが動き出したときのモード。コンピュータを設定するためのモード。xv6では、マシンモードで命令が数行実行された後に、スーパーバイザーモードに移行する。
スーパーバイザーモード
特権的な命令(例:割り込みを許可/不許可にしたり、ページテーブルのアドレスを保持しているレジスタを読み書きしたりする命令など)の実行が許可されるモード。
スーパーバイザーモードで命令を実行することを「カーネルスペースで実行されている」という。カーネルスペースで実行されているプログラムをカーネルという。
ユーザーモード
アプリケーションが実行されるモード。
ユーザーモードで命令を実行することを「ユーザースペースで実行されている」という。
システムコールを呼び出すと...
システムコールを呼び出すと、CPUの使用権??(←この言い方が正しいのかは不明)がアプリケーションからカーネルに移り、CPUのモードもユーザーモードからスーパーバイザーモードに移る(正確には、アプリケーションは直接カーネルを呼び起こせないらしい)。CPUのモードの切り替えはecall
というRISC-Vの命令で実行される。CPUはカーネルが指定したエントリーポイントからカーネルを実行する(カーネルが指定した
が結構重要で、アプリケーションの好きな位置からカーネルを実行されてしまうと、システムコールのバリデーション(正当な呼び出しかどうかの判断)の終わった場所から呼び出されてしまっては、アプリケーションの好きなようにコンピュータを使われてしまう)。
@2.3
モノリシックカーネル
「OSをすべてスーパーバイザーモードで実行してしまえ」というような設計思想。OSの各機能間の連携が簡単になる一方で、インターフェースが複雑になりOSの作成者がミスを起こしやすくなる。スーパーバイザーモードでのミスはシステムの停止に直結するので重大である。
UNIXやLinuxなど。
マイクロカーネル
「モノシリックカーネルのようなスーパーバイザーモードでのミスが怖いから、OSの大部分はユーザーモードで実行してしまおう」という設計思想。
MINIXやL4、QNXなど。
@2.5
プロセス
xv6がプログラムを孤立化させる単位。
ページテーブル
ハードウェア的に実装されているらしい。
RICS-Vのページテーブルは、仮想アドレス(RICS-Vが操作するアドレス)を物理アドレス(CPUからメインメモリに送られるアドレス)に変換する。
スレッド
プロセスの命令を実行するもの。
アイデア
- アドレス空間は、プロセスにあたかも自分専用のアドレスがあるかのように思わせることができる
- スレッドは、プロセスにあたかも自分専用のCPUがあるかのように思わせることができる
表記
for example, p->pagetableis a pointer to the process’s page table
@2.6
メモ:システムが起動してからシェルの準備が完了するまでの説明だった。よく分からなかったので、さらっと読み飛ばした。コードをじっくり読むとより分かりやすいと思う。
今後はこちらに移動