組み込みソフト開発101 ~ OS

前置き

ここでは、OSのマルチタスク機能(複数のタスクを並列実行する機能)について書きます。ちょっと意味が違いますが、似たような単語の「マルチプロセス」についても簡単に触れます。

タスクとは

タスクとは、「OSのスケジューリング対象となるもの」です。じゃぁスケジューリング対象って何? ってことになるわけですが、それはこの記事を読み進めれば分かると思います。

OSによっては、「スケジューリング対象となるもの」をスレッドと呼ぶ場合があります。WindowsやLinuxがその一例です。組み込みソフト向けのOS(μITRONとか)では、タスクと呼ぶことが多いようです。

スレッド(thread、糸、細い線)という名の方が、(タスクよりも)体を表しているかもしれません。前項「ハードウェア」では、プログラムが動くときPCレジスタが変化していく様子を説明しました。プログラムの命令が書かれたROM上のアドレスを格納するのがPCレジスタです。PCが示すアドレスを次々と線でつないでいってみましょう。

スレッド

この線(PC値の変化の流れを示した線)がスレッド、あるいはタスクの正体です。関数を呼ぶと遠くの番地にジャンプしますが、線が切れるわけではありません。関数がreturnすれば、元の番地に戻ってきます。この線は、途中で分岐したり、他の線と合流することはありません。

マルチタスク、マルチスレッド

OSは複数のタスクを管理することができます。つまり複数の線を管理できるわけです。各線には必ず始点と終点が1つずつあります(分岐や合流は無いので)。始点は、そのタスクが最初に実行した命令のアドレスです。これをタスクのエントリポイントと呼びます。終点は、そのタスクが最後に実行した(あるいは、現在実行中の)命令のアドレスです。CPUのPCレジスタは1つしかないので、ある瞬間に実行できるタスクは1つだけです。現在実行中のタスクとは別のタスクを実行する場合、現在のPC値をどこかへ退避して、新しいタスクの終点の値をPCへ格納します。元のタスクを再開する場合は、退避しておいたPC値をPCレジスタへ復帰します。

PCの退避

スケジューリング

OSが複数のタスクを並列実行するとき、どのタスクをどんな順番で実行するかを決めることをスケジューリングと呼びます。スケジューリングの際の大きな要素となるのがタスクの優先度です。優先度が高いタスクほど先に(あるいは長く)実行されます。タスクの優先度の決め方とか、優先度が同じ場合にどうするかといった細かいルールはOSによって異なります。

組み込みソフト向けのOSの場合、スケジューリングが予想できることが期待されます。例えば、機器のストップボタンを押してから100msec以内に必ず機器が止まらないと人命に関わる、といったシステム(ハード・リアルタイム・システムと呼ぶ)の制御に使われることが多いためです。このような厳密なスケジューリングをサポートするOSをリアルタイムOS(RTOS)と呼びます。ハード・リアルタイム性を保証するには、OSが勝手にタスクの優先度を変更しないことや、後述のプリエンプトをサポートする必要があります。

タスクを切り替える

前述の通り、タスクを切り替えるときはPCレジスタの値を退避/復帰する必要があります。実際には、PC以外のレジスタも退避/復帰する必要があります。例えばAレジスタには計算の途中結果が一時的に格納されているかもしれないし、SPレジスタはスタックの使用状況を示しているからです。よって、タスクを切り替えるときは、CPUのレジスタ一式を総入れ替えすることになります。こうすることにより、一旦実行を中断されたタスクが再開したとき、何事もなかったかのように、やりかけの処理を継続できるわけです。この、タスクの実行を継続するために必要な情報一式のことをコンテキストと呼びます。

OSは、タスクごとに、そのコンテキストを管理しています。スケジューリングで決めたタスク切り替えのタイミングが来ると、現在タスクのコンテキストを退避し、切り替え先タスクのコンテキストを復帰します。この切り替え処理のことをディスパッチ(dispatch)と呼びます。

ディスパッチ

スケジューリングの例

システムコール呼び出し

OSが提供する関数のことをシステムコールとかサービスコールと呼びます。低レベルAPIと呼んでもいいでしょう。Win32はWindowsのシステムコールと見ることができます。

OSによっては、プログラムがシステムコールを呼んだタイミングでディスパッチが起きることがあります。例えば、あるシステムコールを呼んだ結果、そのタスクが待ち状態(データ受信待ちとか)になるような場合です。この場合、そのタスクにお付き合いしてCPUを待たせるのはもったいないので、別のタスクを実行します。

プリエンプト

前述の、システムコール呼び出しをトリガとするスケジューリングは、タスク上で実行されるプログラムに依存したスケジューリングの例です。プログラムがシステムコールを呼ばない限りディスパッチは起きないので、OS側は主導権を持ちません。これでは、ハード・リアルタイム性は実現できませんし、最悪の場合、あるタスクの暴走により全タスクが被害を受けてしまいます。

そこでOSによっては、現在タスクが何をしているかに関係なく、任意のタイミングでディスパッチできるようになっています。このようなOSでは、例えば、一定時間経過したら強制的に別のタスクへ切り替えるといったスケジューリングが可能です。

OS側の都合で実行中のタスクをディスパッチさせることをプリエンプト(preempt)と呼びます。プリエンプト可能なOSをプリエンプティブ(preemptive)OSと呼んだりします。逆に、プリエンプトできないOSは非プリエンプティブ(non-preemptive)OSです。古い話しですが、Windows95より前のWindows(3.1とか)は非プリエンプティブでした。

TSS

TSS(タイムシェアリングシステム)は、一定時間ごとにタスクを切り替えるスケジューリング(あるいは、それを用いるOS)のことです。1つのタスクに1回に与えられる実行権のことをタイムスライスとかタイムクァンタム(time quantum)と呼びます。

タスクの状態

複数のタスクは、それぞれが独立して動作します。タスクがとりうる状態の種類はOSによって異なりますが、一般的には以下のような感じでしょう。

non-exist
存在しない。
ready
実行可能。
run
実行中。readyのタスクの中から、1つだけがrunになる。
wait
何かを待っている。セマフォ待ちとか。
sleep
寝ている。一定時間経過するとreadyになる。
suspend
サスペンドされている。他タスクからリジュームされればreadyになる。

タスク状態遷移

マルチプロセスと仮想アドレス

プロセスという概念は、組み込みソフト向けOSではなく、汎用OS(WindowsやLinux)のものです。プロセスはスレッドを包含します。プロセスとスレッドは1対多の関係になります(1つのプロセスの中には1つ以上のスレッドが存在し、1つのスレッドは必ず1つのプロセス内に存在する)。

例えば、Windows上でアプリ(exe)を起動すると、プロセスが1つ生成され、その中にスレッドが1つ生成され、そのスレッド上でプログラムが動き始めます。同じアプリをもう1つ起動すれば、やはり新しいプロセスとスレッドが1つずつ生成されます(アプリの中には、多重起動を禁止している場合もありますが)。

プロセス自体はOSのスケジューリング対象にはなりません。プロセスが優先度という属性を持つ場合もありますが、それはあくまでも、プロセス内のスレッドの優先度を決めるための参考情報として使われるだけです。 プロセスとスレッドの関係

プロセスは仮想アドレスと組み合わせることで存在意義を持ちます。仮想アドレスとは、ROM/RAM(物理的なメモリ)が作るアドレス空間とは別の、仮想的なメモリ空間を示すアドレスです。これも汎用OSで良く使われるメモリ管理手法です。

仮想アドレスを用いるシステムでは、プロセスごとに専用の仮想アドレス空間を持ちます。プログラム内では仮想アドレスが使われ、CPUが仮想アドレス上のデータを読み書きするときは、MMU(Memory Management Unit)と呼ばれるチップが仮想アドレスをROM/RAM上の物理アドレスに変換します。例えば、プロセスAにとっての0x00008000番地が物理アドレス0x40000000番地で、プロセスBにとっての0x00008000番地は物理アドレス0x50000000番地だ、といった具合です。

仮想アドレスと物理アドレス

プロセスと仮想アドレスを組み合わせる利点の1つは、あるプロセスによる別プロセスのデータ破壊を防ぐのが容易になることです。詳しいことは割愛しますが、直感的に分かると思います(プロセスは、MMUが変換してくれないことには物理メモリにアクセスできないのですから)。

また仮想アドレスを使うと、見かけ上、実際のROM/RAMサイズよりも大きなアドレスを得ることができます。例えばRAMが2GBのシステム上で、プロセスAとBが動いているとしましょう。1GBをOSが使い、残りをプロセスAとBが512MBずつ分け合っています。ここでプロセスCを起動したいのですが、もうRAMが空いてません。こんなときは、プロセスAとBが使っているRAM内容の一部分をハードディスクなどの大容量ストレージへ退避し、空いたRAMスペースをプロセスCへ割り当てます。これをスワップと呼びます。

スワップアウト

スワップ処理は全てOSとMMUが管理するので、プロセスA、B、Cには気付かれません。プロセスAやBが、ハードディスクへ退避された部分をアクセスしようとした場合は、またMMUがコッソリとRAMへデータを復帰してくれます。退避前と復帰後とで物理アドレスが変化する可能性もありますが、仮想アドレスを変えない限りプロセスAとBには影響がありません。

Last modified:2011/04/29 15:20:12
Keyword(s):
References:[組み込みソフト開発101]
This page is frozen.