自作OSでのプロセス実装について (1) ~初めてのユーザプロセス~

この投稿は Aizu Advent Calendar 2014 の 5 日目の記事です。

前の人 @i__yahoo
次の人 @masaponto

また、自作OS Advent Calendar 2014 の 5日目の記事でもあります。

Aizuの方に登録してたのですが、ネタ的に自作OSだし、自作OSカレンダーの方は空きが激しいので、まとめて登録してしまいました。
今回は自作OS Advent Calendar的に先日の記事の続きみたいなものです。
また、ALTで話した内容をもっとわかりやすく書いたものです。(次回のALTもあるらしいので興味のある人はぜひに)


この記事では、もぷりんOSの現在の実装における観点から話を進めていきますので、LinuxなどのOSとは異なることもあると思いますので注意してください。
特にもぷりんOSは権限周りなどがまだまだですし、私もよくわかって無いことが多いです。それだと危なくない?みたいなことがめっちゃ有ります(つまり、吹けば飛ぶ)
それに、趣味で楽しいからやっていることです。(だからといってセキュリティを軽視しているわけではないです。安心安全もぷりんOSを目指しますよ)
なので、あんまり細かいことは言わず、こんな風に書いてれば動くんだふーん程度の気持ちでお願いしたいです。
そして、何より自作OSを作ってる人、少し興味を持ってる人が見て、こんなの俺でもできるじゃねーかと感じて、自作OS作成してくれればなあと思いつつ書いていきます。


さてさて、本題に入るその前に、通常話しているプロセスについていくつか事前知識として、さらっと話そうと思います。
詳しいことはプロセス-Wikipediaにどうぞ
大雑把に言うと、プロセスは実行されているあるプログラムのことです。
もぷりんOSは一応?UNIX系のOSなので、ここでいうプロセスはUNIX系のプロセスを指しています。UNIX系プロセスは親子関係を持ちます。この親子関係というのはデータ構造で言うところの木構造で、大体こんな感じです。
数字はプロセスID(pid)を表します。
プロセス0がルートプロセス、その子プロセスがプロセス1
この時、プロセス1から見れば親はプロセス0になります。

当たり前ですが、プロセスの生成と削除はOSでは頻繁に行われます。
例えば、目の前のターミナルでlsコマンドを実行したとしましょう。
その瞬間、シェルが子プロセスlsを生成し、lsが実行されます。
色々表示した後に、lsは終了し、プロセスは削除され、元のシェルに戻るというわけです。
ここで、シェルのプロセスの親は何でしょうか、更にその親は?それのまた更に親は?
という疑問が湧いてきます。

と、いうことで本題です。
上図のルートプロセスであるプロセス0とその子であるプロセス1の話です。
プロセス識別子-Wikipediaからの引用ですが、
Unix系OSでは、プロセス識別子 0 と 1 は特別なタスクを指している。プロセス識別子 0 は swapper または sched と呼ばれ、ページングを担当している。これは実はカーネルの一部であり、ユーザーモードのプロセスではない。プロセス識別子 1 は init プロセスで、主にシステムの立ち上げとシャットダウンを担当している。
と、言うわけです。

もぷりんOSでもプロセス0はOSカーネル自体の事を指します。
ハードウェア(CPU,ディスク,マウス,キーボードなど)の初期化を主に行っています。
それらの初期化を終えた後に初めてのユーザプロセスであるプロセス1を立ち上げるわけです。

ここで、ユーザモードという言葉が出てきました。
これは大事です。テストに出ます。
このモードというのはIntel CPUの持つリングプロテクションのものです。
もぷりんOSはx86のみのサポートなのでこれ以降、x86前提で話していきます。
カーネル管理下の領域にはハードウェア情報やらなにやらがあり、誰でもアクセス可能だったら、めちゃくちゃ困りますし、セキュリティもあったもんじゃありません。なので、カーネルとユーザを分離する必要があります。そのための機構がリングプロテクションで、以下のような図で表されます。

x86リングプロテクション
数字が小さいほうが権限が高く、大きい方が権限が低いです。
x86では4つのリングがありますが、もぷりんOSでは0と3しか使っていません。
LinuxやFreeBSDも基本的に同様です(XenでRing1を使うとかなんとかあるらしいですが)
そして、このRing0をカーネルモード、Ring3をユーザモードと言っています。
このリングは外側のリング領域全てにアクセス可能です。
つまり、Ring0なら全てにアクセス可能で、Ring3なら外にはもう無いので、自分自身だけです。
カーネルはユーザに干渉できて、ユーザはカーネルに干渉出来ないことなります。
この機構はOSの基本的なセキュリティにぴったりですね。

と言ってみましたが、全くアクセス出来なくてはそれはそれで困ります。
上記で既に言ったように、カーネル領域にはハードウェア情報などがあります。
全くアクセス出来ないのではユーザプログラムはハードウェア資源を使えないことになってしまいます。ですので、一定の手順を踏むとユーザモードからカーネルモードに切り替わることが出来ます。イメージとしてはリングの内側にジャンプする感じですね。
そして、そういったカーネルプログラムをユーザプロセスが実行出来るようになるわけです。そのためのインターフェースがシステムコールというやつです。
ユーザプログラムはシステムコールを発行し、リング内側のカーネルに使いたい機能を要求して、色々と処理をしていくわけです。このとき、実行中プロセスはユーザプロセスですが、実際に動いているのはカーネルプログラムなので、OSが処理をしているのと変わりません。


かなりてきとうですが、大まかにユーザモードとカーネルモードについて説明しました。
話を戻しますと、起動時にはカーネルしか無いわけです、ユーザプログラムは一切存在しません。つまりカーネルモードプロセス一個のみです。
そして、今やりたいことは、それとは別のユーザプロセスを起動することです。
ここで暗に言っていますが、別のプロセスを起動することは、2つのプログラムを並列実行させることです。
これは何を隠そうマルチタスク-wikipediaです。
一般的に、CPUの計算処理時間に比べ、ディスクやネットワークの処理時間は数十から数百倍かかる。シングルタスク環境では、逐次処理が行われるため、入力待ちや通信待ちなど、CPUが計算を実行できずに、待つ時間が発生する。マルチタスクの導入によって、これらの待ち時間の間にCPUを動作させ別の計算を行い、全体の処理時間の短縮を実現することが可能になる。
またもやWikipediaからの引用です。
別の計算を行うということが、つまり別のプロセスに切り替えるということです。
この切り替えることをコンテキストスイッチと言います。
CPUの持つ汎用レジスタ(eax, ecx, ebx, edx)や現在のスタック(esp)やインストラクションポインタ(eip)などのコンテキストを全て切り替えるためにコンテキストスイッチと言われます。もちろん、それを実行出来るのはカーネルモードのみです。
図にしてみるとわかりやすいかと思います。
プロセスがいっぱいあるときも同様です。

もぷりんOSにおいて、コンテキストスイッチはタイマー割り込みによって生じます。
上記リンクのプリエンプティブ・マルチタスクというやつです。
これはタイマー割り込みによって、一定時間が経過すると、次のプロセスに切り替わる処理が実行されます。また、割り込みの処理をするもの(割り込みハンドラ)は全てカーネルが提供するものです。ゆえに、この時、実行していたプロセスはカーネルモードに切り替わって、コンテキストスイッチを実行します。
具体的には10ms秒ごとに割り込まれます。結構早いように思えますが、CPUの周波数からすればある程度プログラムを実行するには十分です。
コンテキストスイッチの流れを以下に図示します。
これはかなり重要です。マルチタスクの肝と言ってもいいでしょう。
以下の流れで動作します。

タイマ割り込み発生
実行中プロセスは割り込まれる
(中断される)
カーネルモード突入
今のカーネルスタックへ
今の実行コンテキストを保存
(CPUのレジスタとか)
次のプロセスを選ぶ
次のカーネルスタックから
次の実行コンテキスト復元
ユーザモードに復帰

ここで出てきた、プロセスのカーネルスタックとは、それぞれのプロセスがカーネルモード時に使用するスタックのことです。
先程は説明しませんでしたが、カーネルモードに切り替わるときに、スタックもカーネルスタックに切り替わります。またユーザプログラムのコンテキストも退避されます。これはx86の仕様です。上で同じことを言いましたが、雰囲気としてはカーネルに化けるような感じかなと思っています。なお、カーネルスタックはプロセスを生成する過程で設定されます。
ここで、この一連の割り込みの流れを、ユーザプログラムの立場から見ると、割り込まれた時、カーネルプログラムに移り、処理再開される時にはきっちり割り込まれた部分から再開するので、割り込まれたことすらわかりません。これによってユーザはマルチタスクのことなど気にせずにプログラミングができるし、我々も普段からしているというわけです。
やってみるとわかりますが、当たり前の事のように行われているのに裏では大変なんだなとしみじみ思います。



さて、だいぶ長くなってしまいました、疲れてしまったので今回の記事はここまでとします。思ったより大変な記事になりそう…
(2)も数日中に書いて公開します。

コメント

このブログの人気の投稿

カーソルキーさん@つかわない インサートモード編

Android で MIME Type 判別

Erlang & Elixir Fest 2019 に参加してきた