学校では教えてくれないこと

マルチタスクとは

2006.10

みんなもマルチタスクという言葉、聞いたことはあるよね。「開発と設計と事務作業をマルチタスクで行う」「お客さんのサポート対応をマルチタスクでこなせれば一人前」といったように、業界用語的な使われ方をしたりもするんだ。

マルチタスクはプログラムを作るときにも非常に重要な役割を果たすんだけど、そもそもマルチタスクとは一体なんだろうか?

マルチタスクなサポート対応

お客さんA、B、Cがいたとしよう。サポート要員が3人いれば、それぞれの客に対応すればいいんだけど、残念ながら今はきみ一人しかいない。

客Aの対応を開始し、終われば客B、客Cと対応するのはどうだろう?全部1日で終わればよいかも知れないけど、客1箇所あたり1日かかるとすると、客Cは丸2日待たされることになり、不満が出るかもしれないよね。

では、客Aの対応を開始し、何か客Aに問い合わせなければわからない点が出てきたので客Aに質問を送り、その返事が返ってくるまでの間に客B、Cの対応を開始するというのはどうだろうか? これなら、効率よくすべてのお客さんの対応が進みそうだね。

このように、複数の仕事を並行でこなすことを、「マルチタスク」と呼ぶんだ。マルチ=複数、タスク=仕事だね。13:00~13:10 の10分間だけを見てみると、きみは客Bの仕事しかしていないかも知れないけれど、1日という単位で見ると、A、B、C すべての仕事を並行して進めていることになる。

マルチタスクなソフトウェア

実は、ソフトウェアの世界でもあなた(サポート要員)=CPU、お客さん=何かの処理、と置き換えれば、全く同じことが言えるんだ。ソフトウェアの世界の例として、WindowsでExcelとWordとInternet Explorerを同時に開き、ExcelとWordの書類を作りながらWebの閲覧を行っている場面を考えてみよう。

この場合も、実はWindowsはExcelとWordとInternet Explorerを切り替えながら実行しているんだ。マルチタスク機能がなかったとしたら、Excelを開いているときWord もIEも開けなくなり、Wordを開きたければいったんExcelを終了しなければならない、ということになってしまうよね。

こんなふうに、複数の処理を切り替えながら、ある期間で見ると複数の処理を並行して実行できる仕組みのことをマルチタスクといい、マルチタスクを実現できるように設計されたOSのことをマルチタスクOSと呼ぶんだ。組込みの世界では、iTRONなどの組込み機器用に設計されたマルチタスクOSを使うことが一般的なんだ。ただ、最初のサポートの例と同じように、あくまでもある瞬間を見ると実行できる処理は1つなので、複数の処理を並行で進めるためには、マルチタスク環境にあわせたプログラムの書き方をしなければならないんだ。

このために、マルチタスクOSではいろいろな手段が用意されていて、プログラムはこれらの手法を駆使しながら作っていくことになるんだけど、その具体的な方法については次回以降で取り上げるとして、まずは基本的なところを見ていこう。

OSとしての動き方

実際のマルチタスクOSではどのように複数の処理を並行で実行するんだろうか? 今までに見てきたように、1つしかないCPUの上で複数の処理を実行するわけだから、並行で動かすには、それぞれの処理を少し実行しては他の処理に切り替え、ということを行う必要があるよね。

ほとんどのマルチタスクOSでは、OS自体がこの切り替えを制御しているんだけど、制御方法はOSによってそれぞれ工夫されている。まずもっとも単純なものは、現在動作しているすべてのタスクに対して、一定時間ごと(通常は数msec~数十msec)にタスクの切り替えを順番に行い、各タスクに均等に時間が配分される方法だ。

サポートの例で言うと、客A,B、Cを30分ずつ順番に処理しましょう、ということだね。この方法は単純で公平だけど、処理の緊急度が高い場合にも自分の順番が回ってくるまで待たないといけないなど、最適な時間配分にはならないことがあるんだ。

優先度と実行権

そこで、一般的な組込みOSの場合は、各タスクに優先度をつけ、優先度の高いタスクを優先して動かそう、という仕組みになっているんだ。

同じ優先度のタスクが複数個存在する場合はどうしているんだろう。ひとつの方法は、同じ優先度のタスクに対しては均等に時間を配分する方法。もうひとつの方法は、早い者勝ちで一番先に実行権を得たタスクが動き続けるというものなんだ。

どちらの方法も一長一短があるけど、後者の場合下手をするとそのタスクが永遠に実行権を占有してしまい、そのタスクと同じ、あるいはそれ以下の優先度のタスクには実行権が回らない、ということにもなりかねないよね。そこで、マルチタスクOSには、タスクが自らを待ち状態にして、待ち状態が解除されるまで他のタスクに実行権を譲る、という機能が用意されているんだ。

これには、期間を指定して一定時間の間待ち状態になり、その時間が経過したあとは再び実行権を得る場合(一般にDELAY状態という)と、イベント発生待ちに入り、割り込みや他のタスクからイベントが発行されれば再び実行権を得る場合(PEND、WAIT 状態などという)があるんだ。

マルチタスクOSでは、このように他のタスクとの関係を考えながらプログラムを書く必要があるんだ。これらの方法をいかにうまく使いこなすかが、システム全体のパフォーマンスにも影響してくるということになるよ。

例えば、他のタスクと共通の資源(同じファイルに読み書きするなど)を使用する場合には、排他制御を考えないと誤動作を起こしたり、システムが動かなくなったりする場合もあるんだ。それでは、マルチタスクOSにどのような機能が用意されているのか、どのような場面で使えばよいのかを見ていこう。

タスクを一定時間休みの状態にする

例えば、あるタスクでUSBのデータを送信するとしよう。説明を簡単にするため、送信が完了したかどうかは、USBコントローラのステータスレジスタが「完了」になったかどうかでわかる、とするね。この場合、単純には以下のプログラムでいけそうだ。

プログラム1

ところが、マルチタスク環境の場合、この方法はお行儀がよいとはいえないんだ。USBの送信がすぐに終わらないと、その間ループは回り続け、優先度の低いタスクが実行できないまま、ムダにCPUを使うことになる。そこで、プログラムを以下のように変更してみよう。

プログラム2

こうすれば、このタスクが休みに入っている間は、優先度の低いタスクに実行権が回るようになる。このタスクは 20msecごとに動き、一回のループは普通、CPUを占有する時間はわずかなので、CPUを有効に使うことができるんだ。「タスクを一定時間休みにする」機能は一般的にOSのシステムコール(C言語関数)として標準的に用意されていて、OSによって名前が違うんだけど、tsk_dly()や sleep()などの関数だよ。

イベント待ちにする

複数のタスクが分担して処理を行っているとしよう。タスクAはUSBからデータを読み出して、タスクBにデータを渡す。タスクBはタスクAから渡されたデータを加工して画面に表示する。このとき、タスクBはタスクAからデータを渡されたことを、どうやって知ることができるのだろうか?

すぐに思いつく方法は、タスクAとタスクBから共通に読み書きできるフラグを作っておいて、タスクBが定期的にこのフラグをチェックするというものかな。先ほど使った、タスクを休止させる方法を使ってみよう。

プログラム3

タスクAがwaitflagを0にすると、タスクBはそれを検知して次の処理に進むことができるよね。うまくいくように思えるけど・・・。ところが、マルチタスク環境ではもっと効率のよい方法が用意されているんだ。

それが、「セマフォ」や「メッセージキュー」などに代表される、イベントを使用する方法なんだ。これを使うと、「20msec間隔で休止しつつフラグを定期的に確認する」という処理は必要なくなるんだ。用途に応じていろいろな手段が用意されているけど、ここでは「セマフォ」を使ってみることにしよう。

セマフォ(semaphore)はもともと、「手旗信号」という意味で、直接の由来は鉄道の「腕木式信号機」であると言われている。「進め」「止まれ」の状態があり、この「進め=実行可能状態」「止まれ=イベント待ち状態」をOS側でコントロールしてくれるんだ。これを使って先ほどのプログラムを書き換えてみよう。

プログラム4

と簡単になるよね。しかも、セマフォ待ちの命令を実行すると、OS側がそのタスクを自動的に待ち状態にしてくれる。そして、タスクAがセマフォを変更するとOS側はそれを認識し、タスクBのセマフォ待ちを解除して実行可能状態に戻す。つまり、この間タスクAは全くCPUを使わないから、非常に効率のよいプログラムを書くことができるんだ。

排他制御

セマフォを、タスク間でタイミングの同期を取る手段として使うことを勉強したけど、実はセマフォにはもうひとつの使い方があるんだ。それは、「排他制御」なんだ。先ほどの「腕木式信号機」の話に戻るね。信号機は何のためにあるんだろうか?もちろん、列車が衝突しないように交通整理を行うためだよね。

マルチタスクの世界でも全く同じことが当てはまり、複数のタスク(列車)で共通の資源(線路)を使うためには、処理(通行)が重ならないように排他制御(交通整理)を行ってやる必要があるんだ。

例えば、タスクA、タスクBから同じバッファにデータを書き込むことを考えよう。タスクAが書き込んでいる最中にタスクBが書き込みを開始すると、バッファの整合が取れなくなる。このような場合は、共通の資源であるバッファに対してセマフォを設け、それぞれのタスクが、

プログラム5

というルールを守ってプログラムを作るんだ。

こうすると、タスクAが書き込み最中にタスクBに実行権が回っても、セマフォ待ちのところで停止し、タスクAがセマフォを戻した時点でタスクBがセマフォを取り、書き込み処理に入るから、同時にバッファに書き込む、という事態を防ぐことができるんだ。

今回はマルチタスクでプログラムを書く場合に使う基本的な機能を紹介してみたけど、これらはほんの一部で、注意しなくてはいけないことはもっともっとあるんだ。ただ、最後に出てきたセマフォや、今回は書くスペースがなかったメッセージキューなどは、組込みシステムのプログラムには欠かせない基本中の基本なので、市販の書籍や研修などでしっかり理解してほしい。

ページのトップへ