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を用いた排他処理などが組み込まれていて、シグナルハンドラではそれがうまく機能ささせられないのである。
ライブラリ関数はほとんど使用できないが、システムコールはファイルアクセスであれ、ソケット通信であれ使用できる。



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