例えば、iPhoneのOSはiOSです。
OS(オペレーションシステム)の作り方は難しい(OSは人類の創作物で最も難しいものだって何かに書いてました)ので、ここではOSがどんな働きをするかを書いてみます。
OSは何してるの?
現代のOSにおいて基本的な概念では、プログラムとプログラムが実行する動作を区別する。
イメージとしては、
プログラム:本棚の中にある楽譜(命令の静的な集合)
プログラムが実行する動作:楽譜を音楽家が演奏する(時間経過とともにその状態が変化する動的な活動)
OSの制御下でプログラムを実行する動作をプロセスと呼び、
プロセスによって生じている状態をプロセス状態と呼ぶ。
プロセス状態のイメージとしては、プログラムの実行中の状態を写真に撮って、CPUレジスなどのその時の値をみている感じだ。
コンピュータは多くのプロセスを実行する。
コンピュータの資源(メインメモリ。CPUへのアクセス。)をうまく管理するのがOSの役割だ。
プロセス管理(OSがしてること)
プロセスの実行を調節する作業は、OSのカーネル(OSの内部部分のこと。コンピュータが動くための一番基本的な機能を実行するなめのソフトウェアを含む)中のスケジューラとディスパッチャが行う。
スケジューラ
スケジューラは、コンピュータ内のプロセスの記録を保持したり、新しくきたプロセスを現在プロセスに加えたり、完了したプロセスを削除したりする。
スマホでLINEアプリのタップ(アプリケーションの実行を要求)したら、LINEの画面が開く(スケジューラがLINEアプリの実行を現在プロセスに加えて、実行が完了する)。って感じですね。
全てのプロセスの状態を把握するために、スケジューラはプロセステーブルと呼ばれる情報のブロックをメインメモリ中に保持している。
プログラムの実行(プロセス)が要求されるたびに、スケジューラはプロセステーブル中にそのプロセスのエントリを新しく生成する。
このエントリってやつには、プロセスに割り当てられたメモリ領域、プロセスの優先順位、プロセスが待機中(ストレージの操作を待ってる・キーボードが押されるのを待っている・他のプロセスからのメッセージを待っている、など)か準備完了かなどの情報が含まれている。
ディスパッチャ
ディスパッチャもカーネルの要素であり、スケジュールされたプロセスの実行を管理する。
マルチタスキングシステムなどでは、この作業はマルチプログラミングによって達成される。
マルチプログラミングってのは、時間をタイムスライスと呼ばれる短いセグメント(ミリ秒とかマイクロ秒)に分割してCPUが一度に1つのタイムスライス分だけプロセスAを実行する。それが終わったら、別のプロセスBを1つのタイムスライス分だけ実行する。
ディスパッチャがプロセスをタイムスライスに割り当てるたびに、タイマー回路も起動して、タイムスライスの終わりに割り込みと呼ばれるシグナルを発生させる。
CPUが割り込みシグナルを受け取れば、どこまで作業が進んだか記録し、別の作業をして、また元の作業に戻る。(人間もこんな感じですよね。読書中に話しかけられたら、栞挟んで、話終わったら栞のとこから読む、みたいな。)
ここで注意したいのは、OSは自ら何か考えているわけではなく、ただ指示に従っているだけということです。なので、いいOSは、作った時点でめちゃめちゃ低い確率で起きるようなトラブルを考慮するそうです。
セマフォ(バグらないように考慮されたフラグ)とは
フラグはメモリ内の1ビットで、1と0ではなく、セット(set)とクリア(clear)として参照されることが多い。
何か(プリンタとか)にアクセスしたければ、アクセス先がすべてクリアなら要求を通し、セットならOSは要求を出したプロセスを待機状態にすれば良さそうだ。しかし、これだけではマズい。割り込みを考慮していないからだ。フラグがクリアであると分かってからセットするまでの間に割り込みが入るかもしれない。
なのでフラグを検査してクリアであれば、セットするまでは割り込みが発生しないようにすればいい。
それを実現するための1つの方法は、大抵マシン語で提供されている「割り込み不可」命令と、「割り込み可能」命令を使うことだ。(そんなんあるなら最初から言ってよ、って感じですが)
割り込み不可命令が実行されると、将来発生する割り込みは阻止され、割り込み可能命令が実行されていれば、CPUは割り込みシグナルに反応するようになる。(本来の状態ですね)
別の方法は、これもやっぱり多くのマシン語で提供されているテストアンドセット(test-and-set)命令を使う方法だ。
この命令は、CPUに対して、フラグの値を取り出し、受け取った値をクリアかセットか調べてからフラグをセットするという一連の動作を、1つのマシンの命令内で行う。
この方法だと、CPUが割り込みを認識する前に命令を完了するのが嬉しい点だ。
上記の方法などで適切に実装されたフラグを、セマフォ(semaphore)と呼ぶ。セマフォという名前は、線路の一部区間へのアクセスを制御する鉄道の信号に由来する。(実際、ソフトウェアのセマフォも鉄道のそれも同じように使用される)
一度に1本しか列車が通れない区間は、一度に1つのプロセスしか実行できない一連の命令と同じだ。(さっきの2つ目の方法、テストアンドセット命令がこんな感じでしたね)
そのような一連の命令の部分をクリティカル領域(列車が1本しか通れない区間)と呼ぶ。一度に1つのプロセスしか実行できないようにすることを、相互排除と呼ぶ。
つまり、クリティカル領域にて相互排除されているためには、クリティカル領域をセマフォで守るのが大事なのだ。
言い換えると、列車が1本しか通れない区間に列車が2本以上突っ込まないようにするためには、列車が1本しか通れない区間に列車があれば旗を上げ、なければ下げるという作業をするのが大事。
クリティカル領域に入るためのプロセスは、その直前にセマフォがクリアであるのを確認して、セマフォをセットしなければならない。
つまり、セマフォがセットなら、クリティカル領域に入ろうとしたプロセスは、セマフォがクリアになるまで待たなければならない。(なんか遊戯王のルール読んでるみたい)
Windowsなら、Ctrl, Alt, Deleteを同時に押すとプロセステーブルが見れるらしいです。
デッドロック(資源割り当ての時に起こりうる別のバグ)
デッドロックとは、2つ以上のプロセスが、それぞれ他者に割り当てられた資源を待って処理が進まない状態だ。
太郎「肉をくれたら手伝うよ」
花子「手伝ってくれたら肉をあげるよ」
太郎・花子「・・・」
みたいな感じです。
プロセスAがプリンタへのアクセス権を持ち、CDプレーヤのアクセスを待っている。
プロセスBがCDプレーヤのアクセス権を持ち、プリンタへの待っている。
これもデッドロックの一例だ。
他にも、プロセスが部分作業を実行させるために新しいプロセスを生成する(UNIXの用語でフォークという動作)ことができるシステムでデッドロックは発生する。
プロセステーブルが満杯で、スケジューラが新しいプロセスを登録できないのに、システム内の各プロセスが作業を完了する前に追加のプロセスを生成しなければならない時、どのプロセスも処理できなくなってしまう。
デッドロックの状況を分析すると、3つの発生条件が分かった
- 共有不能な資源をめぐって競合がある
- 資源は部分的に要求できる。つまり、何か資源を先に受け取ったらプロセスは後に追加の要求を出せる
- いったん資源を割り当てられたら、強制的に終了させることができない
3つ揃ったらデッドロックが発生する。なので、1つでも取り除けばデッドロックは避けられる。
1・2番目を回避する方法はデッドロック回避方法として知られている。
例えば2番目の回避なら、各プロセスが全ての資源を一度に要求しなければならない、とすればよい。
3番目の回避はデッドロックの検出と修正として知られていて、デッドロックが発生するとそれを検出して、割り当てた資源を強制的に取り上げる技法だ。
プロセステーブルが満杯になった時はこんな感じらしい。
プロセステーブル満杯でデッドロック発生→OS内のルーチン(中心的な機能を果たすプログラム)が、プロセスの一部を削除(専門用語でkill)する→プロセステーブルに余裕が生まれる→デッドロックが解消し残りのプロセスが進む
最後に
もちろん情報学部で、こればっかり習うわけじゃありませんが、こんな感じの話が好きな人は向いているのかなと思うます。
それでは!
コメント