スレッド・プログラミングでつきものの関数の再入についてです。

要はヒープ領域のメモリ確保と一緒です。

スレッドがたくさんあるとその1つしかない資源をどう順番に使用できるようにするかの管理が必要の延長上の話で、今回は、ヒープ領域でなくて関数中で記述されるグローバル変数やstatic変数をどう混乱なく扱えるようにするかということです。

(スタック領域はスレッドごとに確保されるわけですので他のスレッドとの競合は問題になりません。)


想定する場面としては、スレッドAがlog_output関数中のstrcpyでbuf[]を編集中に突然割込みが実行されスレッドBが実行されたときを想定します。

void log_output(char* message)
{
..static char buf[256];
..time_t t;

..time(&t);
..strcpy(buf,ctime(&t));
..strcpy(buf+strlen(buf),message);

..puts(buf);
}

そのときスレッドBでも同様にlog_output関数を呼び出しstrcpyでbuf[]を編集し始めたらせっかくスレッドAが処理していた内容が上書きされてしまうわけです。

スレッドAに戻ったときに編集中のbuf[]はスレッドBによって書き換えられてしまったのでもう残っていません。スタック領域ではないので編集途中の情報が残っているわけでもなく、レジスタのように退避されているわけでもありません。

そのため、buf[]をスレッドAが編集しているときにはスレッドBには編集させないような仕組みを作ってやらないといけないわけで、スレッド・ベースでのプログラミングをする際には避けて通れない、知らないとならない問題です。(CGIつくるときはグローバル変数使うのは御法度だよなぁ)

で、その回避策にはいくつかの方法があるのでそれらの方法を詳しくみていくことにします。

①仮想メモリを実装し、タスクをスレッドでなくプロセス化する。
②サービスをOS内部に実装し、システム・コールを利用してプロセス化する。
③関数をリエントラントな構造にする。
④関数内の排他が必要な部分に排他処理を入れる。(割込み禁止にするなど)
⑤再入が発生しない設計にする。
⑥サービスをスレッド化する。
組込みOSでは、高速性やリアルタイム性を求められる場合が多く、カーネルは極力コンパクトに、付加機能はアプリケーションとして実装するという思想があります。

汎用OSならばカーネル内部でサポートしているような機能でも、組込みOSではアプリケーションとして提供されている場合があります。

汎用OSのようなすべてカーネルの内部に包含した設計をモノシリック・カーネルと呼び、組込みOSのようにOSにリアルタイム性を持たせるるときそれを実現するのを妨げるサービスをカーネルから分離するような設計をマイクロ・カーネルと呼びます。

付加機能をシステムコールを介しカーネルで実装すると、例えば検索時間が推測できない問題を内包していることなどがリアルタイム性の妨げになるわけです。

アプリケーションにするとは具体的にはスレッドを作成するということになります。

メモリ管理などを行うようなスレッドを作成し、任意サイズの領域が必要な場合にはそちらのスレッドでタスク間通信で依頼して領域を割り当ててもらうなどの実装が考えられます。

そのようなどのアプリケーションでも使うような基本サービスを行う管理スレッドを一般にシステム・タスクなどと呼びます。

反対に、OSのユーザ側で作成するアプリケーションをユーザ・タスクと呼びます。

もっとつっこむと、基本機能をサービスとして提供する側なのか、もしくはサービスを利用する側なのかの使われ方の違いによって分類されます。