2013年11月13日水曜日

非同期シグナルセーフ その2 (asynchronous signal safe stderr library -- experimental)

シグナルハンドラの中から安全に呼び出すことのできるエラー出力関数のセットを紹介する。

printfのような書式指定機能は付いていないため、幾つかの関数をバラバラに並べて呼び出さなければならない。

実は、softiesプロジェクトで使われている mallocライブラリ内部のエラーハンドリングに用いているため、各々の関数には static宣言が付いているが、掲載したものは staticは外してある。(この mallocライブラリについては後日紹介する機会があるかも知れない。)

クラス名などのプレフィックスは付いてないため、使う場合には関数名が干渉しないか注意しなければならない。

コンパイルを通すには unistd.hのインクルードが必要となる。

例によって十分なテストは行われていないため、動作は保証しない。


改行出力

void newline() {
 write(STDERR_FILENO, "\n", 1);
 fsync(STDERR_FILENO);
}


文字列出力

NUL文字で終端された char配列 stringを出力する。
void printString(char* string) {
 if (string == null)
  string = "(null)";

 int length = 0;
 while (string[length] != '\0')
  ++length;
 write(STDERR_FILENO, string, length);
}


バイナリデータ中の ASCII文字列を抽出して出力

char配列 stringの lengthで指定されたバイト数分を出力する。
ASCII文字で無い値はスキップされる。
void printAsciiLetters(char* string, int length) {
 if (string == null)
  string = "(null)";

 int index = 0;
 while (index < length && string[index] >= 0x20 && string[index] < 0x7f)
  ++index;
 write(STDERR_FILENO, string, index);
}

整数出力

符号ありの整数しか扱えない。 int型が符号を含めて 16桁を超える処理系では正しく動作しない。
void printInteger(int value, int length) {
 char buffer[16];
 if (length > 16)
  length = 16;
 int index = 0;
 boolean negative = false;
 if (value < 0) {
  negative = true;
  value = -value;
 }
 do {
  buffer[15 - index++] = (value % 10) + '0';
  value /= 10;
 } while (value != 0 && (length == 0 || index < length));
 if (negative && index <= 15)
  buffer[15 - index++] = '-';
 while (index < length)
  buffer[15 - index++] = ' ';
 write(STDERR_FILENO, &buffer[15 - index + 1], index);
}

ポインタ出力

ポインタを '0x'が先行する 16進数で出力する。 ポインタが 0xを含めて 16桁を超える処理系では正しく動作しない。
void printPointer(void* pointer, int length) {
 char buffer[16];
 long value = (long)pointer;
 if (length > 16)
  length = 16;
 int index = 0;
 do {
  int d = (int)(value & 0xf);
  buffer[15 - index++] = (d < 10)
   ? (d + '0')
   : (d - 10 + 'a');
  value >>= 4;
  value &= 0xfffffff;
 } while (value != 0 && (length == 0 || index < length));
 buffer[15 - index++] = 'x';
 buffer[15 - index++] = '0';
 while (index < length)
  buffer[15 - index++] = ' ';
 write(STDERR_FILENO, &buffer[15 - index + 1], index);
}

2013年11月12日火曜日

非同期シグナルセーフ

ライブラリ関数の多くは非同期なシグナルハンドラの中で使用できない。

printfもその一つ。
プログラムのエラーをシグナルで捕えても、そこで printfなどのライブラリを用いてメッセージを出力することはできない。

たしか System V Release 3.0が出た頃はまだそのような制約は無かったと思う。
その頃は平気で printfを使っていた記憶があるが、いつの日かそのような使い方は禁止されている。
そこで手元にある文献を幾つか調べてみた。

Marc J.Rochkind著、福崎俊博訳
『UNIXシステムコール・プログラミング』
アスキー出版局 1987年(原書は1985年)

第8章 シグナル
シグナルハンドラのクリーンアップ時に fprintfする例が示されている。
また、longjumpの例もある。
シグナルが到着したときに単にフラグをセットし、プログラムがレディになってからそれをチェックする賢いトリックを使うのは悪いテクニックとあり、これは現在の常識とは反対である。


AT&Tユニックス パシフィック発行
『UNIX System V プログラマ・リファレンス・マニュアル 第2版 リリース 3.0』
共立出版 1986年(原書も1986年)

signal, sigset系システムコールの説明があるが、シグナルセーフについては書かれていない。
同シリーズの UNIX System V プログラマ・ガイド リリース3.1は調べていない。


塩谷修著
『実用UNIXシステムプログラミング』
日刊工業新聞社 1986年

第13章 シグナル
SIGINT, SIGHUP, SIGSEGV, SIGFPE, SIGPIPE, SIGALARM, SIGCLDについて、各々 printfを使った例が示されている。
第18章 SetJmp LongJmp
シグナルハンドラから longjmpする例が示されている。


David A.Curry著、アスキー書籍編集部監訳
『UNIX Cプログラミング』
アスキー出版局 1997年(原書は1989年版)

8章 シグナル処理
シグナルのリセット、システムコールの再スタートに関する問題が示されている。
4.2BSDのバークレーUNIXのシグナル機構の説明も示されている。
しかし、例示されているハンドラの内部では printfが使われており、さらにはハンドラの中から longjumpを行う例まで扱われている。


M.R.ホートン著、長尾高広訳
『ポータブルCプログラミング』
トッパン 1990年(原書も 1990年)

12章 シグナル処理ルーチンの設定
元のシグナルのメカニズムには信頼性上の問題があったが、4.2BSD, ANSI C, POSIX, System V release 3では何らかの改良が施されたことが書かれている。
ANSI Cでは、シグナルハンドラでの longjumpの動作が未定義となったことは書かれている。


Bil Lewis, Daniel J.Berg共著、岩本信一訳
『マルチスレッドプログラミング入門』
アスキー出版局 1996年(原書も 1996年)

第6章 オペレーティングシステムの問題
非同期に関する安全性のところで、非同期シグナルの問題をmallocを例に説明している。
非同期シグナル安全な関数の一覧は掲載されていないが、マニュアルを参照するように書かれている。
第10章 プログラミング事例
シグナルハンドラでは、非同期シグナルのハンドリングは安全な sigwaitを使った例を示している。


David R.Butenhof著、油井尊訳
『POSIXスレッドプログラミング』
アジソン・ウェスレイ 1998年(原書は 1997年)

6.6 シグナル
まず、スレッドとシグナルの関係について説明している。
その後で、スレッドコード内部で非同期シグナルを扱うには必ず sigwaitを使用するように書かれている。
ここでは、非同期シグナル安全という説明はされていない。
その後、非同期シグナル安全についての説明と、その関数の一覧が示されている。


ビル・ヒルズ+ダニエル・バーグ著、岩本信一訳
『Pスレッドプログラミング』
プレンティスホール 1999年(原書は1998年)

10章 シグナル
非同期セーフ
mallocの例を挙げて非同期シグナルの問題を提起し、非同期シグナルセーフのカテゴリがあることが説明されている。
非同期シグナルセーフの関数一覧は示されず、ベンダのドキュメントを参照するように書かれている。


こう見ると、スレッドプログラミングが導入されるようになってからの制約の様である。
元々シグナル処理には再入性などの問題があったが、スレッドプログラミングでは mutexを用いた排他処理などが組み込まれていて、シグナルハンドラではそれがうまく機能ささせられないのである。
ライブラリ関数はほとんど使用できないが、システムコールはファイルアクセスであれ、ソケット通信であれ使用できる。



国会議員には国民の先頭に立って震災の復興のために尽力して頂きたい。
綸旨を奏請するのはあらゆる手を尽くしてからだ。

2013年8月12日月曜日

マルチスレッド その2 (multi thread 2)

pthreadでは、スレッドごとにデフォルトサイズのスタックが割り当てられるが、このサイズは pthread属性によって変更することができる。
デフォルトサイズはシステムで定義された値で、OSごとに経験的に決められた一般的なスレッドに充分な値になっている。

しかし、より多くのスタックを必要とする場合は大きな値にしなければならない。

また、使用するアプリケーションがスタックをあまり消費しないことが解かっていて、多くのスレッドを使用したい場合には積極的にスタックサイズを小さくしたい。
ただし、1スレッドあたりのスタックの最小値が決められていて、limits.hファイルの中にある記号名 PTHREAD_STACK_MINに定義されているのでこれを下回らないようにしなければならない。

Solaris 11の場合

Solarisのデフォルトサイズは 1Mバイトである。(OpenMPを使った場合は 32bitシステムでは 4Mバイト、64bitシステムでは 8Mバイト)

スタックサイズを指定しなければならないようなケースは多くないはずである。
スタックのサイズをオーバした場合、なにも警告することなしに隣接スレッドを破壊することが Solarisのドキュメントには明記されている。
値は試行錯誤を経て決められるようになっているようだ。

mprotectを呼び出してレッドゾーンを付加しておけば、オーバした場合にセグメント例外が発生するはずである。
(OpneMPでは-xcheck=stkovf を付けると良いと示されているが、gccを使用した場合にどうするかは不明。)


PTHREAD_STACK_MINを使用したプログラムを gcc 4.6.0でコンパイルしようとすると、limits.hをインクルードしてあるにも係わらずつぎのようなエラーが出てしまう。
error: ‘PTHREAD_STACK_MIN’ undeclared (first use in this function)


ヘッダファイルを調べると、次の条件を満たしていなければならないことが判る。
#if     defined(__EXTENSIONS__) || (_POSIX_C_SOURCE >= 199506L)



そういえば、『鎌坪商店これにて閉店。ちょわーん。』してから半年近く経った。
夏休み子供電話相談室くらいはと期待してたのだが…。

2013年8月1日木曜日

マルチスレッド (multi thread)

当プロジェクトのクラスはマルチスレッドのためのコードが含まれている。
しかし、実装が不完全で限られた使い方でしか動作は保証されていない。
取り分け、シングルトンオブジェクトの排他制御がまともに出来ていないのと、スレッドごとに異なる値を持たなければならない変数が静的変数になっていてスレッド間で共有されているのが課題。


mutexの初期化

オブジェクト固有の mutexは、そのオブジェクトを生成するときに初期化を行えばよい。

問題はシングルトンの場合だ。
static boolean initialized = false;

struct Something object;

  :

 if (!initialized) {
  /* object will be initialized. */
 }

このようなコードは、単一のスレッドから呼び出す場合にのみ動作が保証される。

複数スレッドから同時に呼ばれる可能性がある場合、initializedのチェックは役に立たない。

そこで、mutexにより排他制御を行って見よう。(pthreadの場合。)

#include 

static boolean initialized = false;
static pthread_t mutex;

struct Something object;

  :

 pthread_mutex_lock(&mutex);
 if (!initialized) {
  /* object will be initialized. */
 }
 pthread_mutex_unlock(&mutex);

この処理が呼ばれる度に mutex操作が発生するとオーバヘッドが大きくなるので、それを回避する必要がある場合には外側にもう一段 initializedのチェックを入れると良い。

いづれにしてもこの mutexは事前に初期化されていなければならない。

特定のアプリケーションであれば、mutex(または objectの)初期化が完了してから複数スレッドを起動するように設計すれば十分であるが、ライブラリやフレームワークなどのミドルウェアの場合そのような制約はあてにならない。

そのようなミドルウェアは、mutex(または objectの)初期化を何らかの初期化関数として提供することになるが、仮にアプリケーションが初期化関数を呼んだ後にしかスレッドを起動しなかったとしてもまだ不十分である。

アプリケーションだけでは初期化の順序を守れないケースがあるからである。


2011年のことだったと思う。開発していた組み込み製品のデバッグ担当者から彼らの上司に『main関数にブレークポイントを設定したのに効きません。』とか『main関数よりも前に関数が呼ばれます。』という電話連絡が入った。

組み込みの開発では言語 Cを使用することが多いが、そのプロジェクトは C++が使われていたのだ。

Cでは、静的変数や外部変数を初期化するときに、main関数を呼ぶ startupルーチンの中から 変数領域の初期化が行われる。(bss領域はゼロフィルされ、data領域は初期値がコピーされる。)

ユーザのコードが mainよりも前に走り始めることは無い。
しかし、C++では定数で初期化するだけでなく、コンストラクタを起動したり、関数の結果を初期値に出来る。
#include 
#include 

std::string message("Hello, world.");

int len = message.length();

int main() {
        std::cout << message << std::endl;
        std::cout << len << std::endl;
        return 0;
}
C++のときだけではない。gccの拡張機能である constructor属性を関数に指定すると、main関数よりも前に実行される。
アプリケーションが main関数よりも前に何もしないつもりでも、使用しているライブラリが何かスレッドを起こして、なにかするかも知れない。

pthread_once

pthreadの場合、pthread_onceを使えば、そのプロセスでただ一度だけ処理を実行することが保証される。
mutexの初期化を気にせずにシングルトンを実現できる。
#include 

struct Something object;

void initializer() {
 /* object will be initialized. */
}

 :

 pthread_once(initializer)
 /* use object */

これで簡単確実にオブジェクトを初期化することができるようになった。

PTHREAD_MUTEX_INITIALIZERによる mutexの初期化

多くの場合のオブジェクトの初期化は pthread_onceでうまくいくが、pthread_onceでロックしてしまう場合がある。
softiesプロジェクトでは、Objectクラスはは Classクラスのインスタンスを持っていてこれは Objectを使用する前に初期化されなければならない。
その Classクラスは Objectクラスのサブクラスであり、Classクラスを初期化する場合にはそれに先立って Objectクラスを初期化しなければならない。
このように循環している場合、pthread_onceではロックするのである。
勿論、Object用の初期化関数と Class用の初期化関数は分けてあり、pthread_onceは各々別々に呼び出されるが、その中で相手側を呼び出すことになる。 (アプリケーションから明示的にフレームワーク、ライブラリの初期化関数を呼び出すようにしていないため、順序固定で初期化されるようなコーディングは行っていない。)
Objectの pthread_onceで指定された初期化関数の中から、Classの pthread_onceで指定された初期化関数を呼ぶところまでは何の問題もない。 (順序が逆になってもそうだ。)
ところが、その Classの初期化関数から再び Objectの pthread_onceを呼び出すと pthread_onceの呼び出しから復帰しなくなる。
そこで、何とかもう一度 mutexを使う方法を検討してみた。 普段、 mutexを使用するときは pthread_mutex_init関数を使用していた。そうしなければならないと思っていた。
しかし、ドキュメントをよく読むと、PTHREAD_MUTEX_INITIALIZERという初期値があるではないか。
つぎのように定数として mutexを初期化できる。
static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
これなら、main関数よりも前に startupルーチン関連以外の処理が走るよりも前に mutexが使えるようになる為、最初のような initializer変数と組み合わせてオブジェクトの初期化を行うことができる。
Classクラスと Objectクラスのような初期化が循環していても、どちらの初期化から先に行おうとしたときでもロックすることなく処理を行うことができるようになった。
ただし、ヒープメモリから割り当てた領域に mutexを配置したり、pthread_mutexattr_tを指定した初期化したい場合には PTHREAD_MUTEX_INITIALIZERは使えない。
数ヶ月前から、電車のディスプレイに中日新聞ニュースが流れないことがしばしばあり、折角μチケット買ったのに残念な思いをする。

2013年6月30日日曜日

Objectクラスと同期プリミティブ その3

条件変数 (condition variable)

あるスレッドで変数を更新し、それを別のスレッドで待って処理を行うために Objectクラスのモニタ機構を用いることができる。
このモニタによって更新の通知と待ち合わせが行われる変数を条件変数と呼ぶ。

変数の更新を待つスレッドはつぎのように記述される。
 Object_lock(object);
 Object_wait(object, 0L);
 // objectに関連付けられた変数を読み出す
 Object_unlock(object);

Object_lockによりロックが行われると Object_waitにより更新されたことの通知を待つ。
この間一時的にロックは自動解除される。そうしないと、更新する側もロックを取得しようとするため、更新動作に入れなくなる。
重要なのは、再び waitを行っていたスレッドがロックを獲得した状態で Object_waitから復帰するということである。
Object_waitの 2つ目の引数は待ち時間の上限をミリ秒単位の long値で指定する。 0Lを指定した場合は通知が行われるまで無限に待ち続ける。
条件変数は任意の変数で構わないが、objectが Objectのサブクラスであれば、条件変数はそのクラスのメンバとするのが良い。
変数を読み書きする処理をそのクラスの関数にする事で処理の詳細を隠蔽し、併せてその関数の中でロックも行うようにすることができる。
ロック操作を利用者側に任せるのはロック漏れの原因となる。
変数を読み出している最中に更なる変数の更新が行われないようオブジェクトをロックしなければならない。

変数を更新するスレッドはつぎのように記述される。
 Object_lock(object);
 // objectに関連付けられた変数に書き込む
 Object_notify(object);
 Object_unlock(object);

Object_lockと Object_unlockで囲まれた中で変数を更新し、Object_notifyで objectで何かが起こることを待っているスレッドを再開させる。
最後に Object_unlockでロックを解放する。
Object_lockを行おうとしたときに、他のスレッドが変数に書き込み中だったり、読み出し中のものがあればそれらが完了するまで待たされる。
objectを待っているスレッドが複数あるとき、Object_notifyはその中の 1個のスレッドだけを再開させる。
どのスレッドが再開されるかは処理系の実装依存である。最初に待ち状態に入ったものが解除されることを期待するようなコードを書いてはならない。
objectを待っている複数のスレッド全てを再開させるには Object_notifyの変わりに Object_notifyAllを使用する。

なお、softiesプロジェクトでは Object_lockは必ずロックを獲得した状態で復帰する。
ロックが獲得できるかどうか試してみる tryLockの仕組みは提供していない。
他のスレッドから割り込まれてキャンセルされた場合例外をスローすることを検討されているが、現在のところ例外機構は実験段階であり、キャンセルは行われない。



アンタって少な目。 半分の整髪。