printf
は再入可能ではないため、シグナル ハンドラで安全に使用することはできません。しかし、この方法を使用する多くのサンプル コードを見てきましたprintf
。
私の質問は次のとおりです。printf
シグナルハンドラでの使用を避ける必要があるのはいつですか?推奨される代替品はありますか?
いくつかのフラグ変数を使用し、そのフラグをシグナルハンドラー内に設定し、そのフラグに基づいて、printf()
通常の操作中に main() またはプログラムの他の部分で関数を呼び出すことができます。
printf
などのすべての関数をシグナル ハンドラ内から呼び出すのは安全ではありません。便利な手法は、シグナル ハンドラを使用して を設定し、それ をメイン プログラムからflag
チェックして、必要に応じてメッセージを出力することです。flag
以下の例では、シグナル ハンドラー ding() がフラグalarm_fired
を 1 に設定することに注意してください。SIGALRM がキャッチされ、メイン関数でalarm_fired
値が検査され、条件付きで正しく printf が呼び出されます。
static int alarm_fired = 0;
void ding(int sig) // can be called asynchronously
{
alarm_fired = 1; // set flag
}
int main()
{
pid_t pid;
printf("alarm application starting\n");
pid = fork();
switch(pid) {
case -1:
/* Failure */
perror("fork failed");
exit(1);
case 0:
/* child */
sleep(5);
kill(getppid(), SIGALRM);
exit(0);
}
/* if we get here we are the parent process */
printf("waiting for alarm to go off\n");
(void) signal(SIGALRM, ding);
pause();
if (alarm_fired) // check flag to call printf
printf("Ding!\n");
printf("done\n");
exit(0);
}
参照: Beginning Linux Programming, 4th Edition、この本ではコードが正確に説明されています (必要なもの)、第 11 章: プロセスとシグナル、484 ページ
さらに、ハンドラー関数は非同期で呼び出される可能性があるため、ハンドラー関数の作成には特別な注意が必要です。つまり、ハンドラはプログラムの任意の時点で予期せず呼び出される可能性があります。非常に短い間隔で 2 つのシグナルが到着した場合、1 つのハンドラーが別のハンドラー内で実行できます。を宣言することをvolatile sigatomic_t
お勧めします。この型は常にアトミックにアクセスされ、変数へのアクセスの中断に関する不確実性を回避します。(読み取り:詳細な有効期限のためのアトミック データ アクセスとシグナル処理)。
シグナルハンドラの定義signal()
を参照して、または関数で確立できるシグナル ハンドラ関数を作成する方法を学習しsigaction()
ます。マニュアルページ
の許可された関数のリスト 、シグナルハンドラ内でこの関数を呼び出すことは安全です。
主な問題は、信号が中断しmalloc()
たり、同様の機能が発生した場合、空きリストと使用済みリストの間でメモリのブロックを移動したり、その他の同様の操作を行っている間、内部状態が一時的に矛盾する可能性があることです。シグナル ハンドラのコードが を呼び出す関数を呼び出すmalloc()
と、メモリ管理が完全に破壊される可能性があります。
C 標準では、シグナル ハンドラでできることについて非常に保守的な見方をしています。
ISO/IEC 9899:2011 §7.14.1.1
signal
関数
abort
¶5 or関数の呼び出しの結果以外でシグナルが発生した場合raise
、値を代入する以外の方法で、ロックフリーのアトミック オブジェクトではない、静的またはスレッド ストレージ期間を持つオブジェクトをシグナル ハンドラが参照する場合、動作は未定義です。として宣言されたオブジェクトにvolatile sig_atomic_t
、またはシグナルハンドラが標準ライブラリ内のabort
関数、_Exit
関数、quick_exit
関数、またはsignal
呼び出しの原因となったシグナルに対応するシグナル番号に等しい最初の引数を持つ関数以外の関数を呼び出します。ハンドラ。さらに、そのようなsignal
関数呼び出しの結果がSIG_ERR
返される場合、 の値errno
は不確定です。252)252)非同期シグナルハンドラによってシグナルが生成された場合、動作は未定義です。
POSIX は、シグナル ハンドラでできることについて、より寛大です。
POSIX 2008 版のSignal Conceptsは次のように述べています。
プロセスがマルチスレッドである場合、またはプロセスがシングルスレッドであり、次の結果以外でシグナル ハンドラーが実行される場合:
abort()
、raise()
、kill()
、pthread_kill()
、またはを呼び出して、sigqueue()
ブロックされていないシグナルを生成するプロセス保留中のシグナルがブロック解除され、ブロック解除した呼び出しが返される前に配信される
errno
として宣言されたオブジェクトに値を代入する以外に、シグナルハンドラが静的記憶域期間以外のオブジェクトを参照するvolatile sig_atomic_t
場合、またはシグナルハンドラが、この標準で定義されている関数のうちの 1 つ以外の関数を呼び出す場合、動作は未定義です。次の表。次の表は、async-signal-safe である関数のセットを定義しています。したがって、アプリケーションは、シグナルをキャッチする関数から制限なくそれらを呼び出すことができます。
_Exit() fexecve() posix_trace_event() sigprocmask() _exit() fork() pselect() sigqueue() … fcntl() pipe() sigpause() write() fdatasync() poll() sigpending()
上記の表にないすべての関数は、シグナルに関して安全でないと見なされます。シグナルが存在する場合、このボリュームの POSIX.1-2008 で定義されているすべての関数は、シグナルをキャッチする関数から呼び出されたとき、またはシグナルをキャッチする関数によって中断されたときに、定義どおりに動作する必要があります。キャッチ関数が安全でない関数を呼び出す場合、動作は未定義です。
の値を取得する
errno
操作と値を割り当てる操作errno
は、async-signal-safe である必要があります。シグナルがスレッドに配信されるとき、そのシグナルのアクションが終了、停止、または継続を指定している場合、プロセス全体がそれぞれ終了、停止、または継続されます。
ただし、printf()
関数のファミリはそのリストに特に含まれておらず、シグナル ハンドラーから安全に呼び出すことができない場合があります。
POSIX 2016の更新により、安全な関数のリストが拡張され、特に からの多数の関数が含まれるようになりました<string.h>
。これは、特に価値のある追加です (または、特に苛立たしい見落としでした)。リストは次のとおりです。
_Exit() getppid() sendmsg() tcgetpgrp()
_exit() getsockname() sendto() tcsendbreak()
abort() getsockopt() setgid() tcsetattr()
accept() getuid() setpgid() tcsetpgrp()
access() htonl() setsid() time()
aio_error() htons() setsockopt() timer_getoverrun()
aio_return() kill() setuid() timer_gettime()
aio_suspend() link() shutdown() timer_settime()
alarm() linkat() sigaction() times()
bind() listen() sigaddset() umask()
cfgetispeed() longjmp() sigdelset() uname()
cfgetospeed() lseek() sigemptyset() unlink()
cfsetispeed() lstat() sigfillset() unlinkat()
cfsetospeed() memccpy() sigismember() utime()
chdir() memchr() siglongjmp() utimensat()
chmod() memcmp() signal() utimes()
chown() memcpy() sigpause() wait()
clock_gettime() memmove() sigpending() waitpid()
close() memset() sigprocmask() wcpcpy()
connect() mkdir() sigqueue() wcpncpy()
creat() mkdirat() sigset() wcscat()
dup() mkfifo() sigsuspend() wcschr()
dup2() mkfifoat() sleep() wcscmp()
execl() mknod() sockatmark() wcscpy()
execle() mknodat() socket() wcscspn()
execv() ntohl() socketpair() wcslen()
execve() ntohs() stat() wcsncat()
faccessat() open() stpcpy() wcsncmp()
fchdir() openat() stpncpy() wcsncpy()
fchmod() pause() strcat() wcsnlen()
fchmodat() pipe() strchr() wcspbrk()
fchown() poll() strcmp() wcsrchr()
fchownat() posix_trace_event() strcpy() wcsspn()
fcntl() pselect() strcspn() wcsstr()
fdatasync() pthread_kill() strlen() wcstok()
fexecve() pthread_self() strncat() wmemchr()
ffs() pthread_sigmask() strncmp() wmemcmp()
fork() raise() strncpy() wmemcpy()
fstat() read() strnlen() wmemmove()
fstatat() readlink() strpbrk() wmemset()
fsync() readlinkat() strrchr() write()
ftruncate() recv() strspn()
futimens() recvfrom() strstr()
getegid() recvmsg() strtok_r()
geteuid() rename() symlink()
getgid() renameat() symlinkat()
getgroups() rmdir() tcdrain()
getpeername() select() tcflow()
getpgrp() sem_post() tcflush()
getpid() send() tcgetattr()
その結果、 et al がwrite()
提供するフォーマット サポートなしで使用するかprintf()
、コード内の適切な場所で (定期的に) テストするフラグを設定することになります。この手法は、 Grijesh Chauhanによる回答でうまく実証されています。
chqrlie は興味深い質問をしていますが、それに対する答えは部分的なものしかありません。
ほとんどの文字列関数
<string.h>
や文字クラス関数、<ctype.h>
さらに多くの C 標準ライブラリ関数が上記のリストにないのはなぜですか?strlen()
シグナル ハンドラからの呼び出しを安全に行わないようにするには、実装を意図的に悪にする必要があります。
の関数の多くについて、なぜ<string.h>
それらが async-signal safe であると宣言されなかったのかを理解するのは困難strlen()
です.かなり複雑であり、非同期信号に対して安全である可能性は低いです。呼び出し間で状態を保持するため、シグナル ハンドラーは、使用しているコードの一部が台無しになるかどうかを簡単に判断できませんでした。および関数はロケールに依存するデータを処理し、ロケールのロードにはあらゆる種類の状態設定が含まれます。strchr()
strstr()
strtok()
strcoll()
strxfrm()
strtok()
strtok()
strcoll()
strxfrm()
の関数 (マクロ)<ctype.h>
はすべてロケールに依存するため、 および と同じ問題が発生する可能性がstrcoll()
ありstrxfrm()
ます。
からの数学関数が非同期シグナルセーフではない理由を理解するのは難しいと思います<math.h>
.SIGFPE(浮動小数点例外)の影響を受ける可能性があるためでない限り.ゼロ除算。同様の不確実性が<complex.h>
、<fenv.h>
およびから発生し<tgmath.h>
ます。
たとえば、一部の関数は<stdlib.h>
除外できます。abs()
その他は特に問題がmalloc()
あります。家族はその代表的な例です。
POSIX 環境で使用される標準 C (2011) の他のヘッダーについても、同様の評価を行うことができます。(標準 C は非常に制限的であるため、純粋な標準 C 環境でそれらを分析することに関心はありません。) 「ロケール依存」とマークされているものは、ロケールを操作するとメモリ割り当てなどが必要になる可能性があるため、安全ではありません。
<assert.h>
—おそらく安全ではない<complex.h>
—おそらく安全<ctype.h>
- 安全ではありません<errno.h>
- 安全<fenv.h>
—おそらく安全ではない<float.h>
— 機能なし<inttypes.h>
— ロケールに依存する関数 (安全でない)<iso646.h>
— 機能なし<limits.h>
— 機能なし<locale.h>
— ロケールに依存する関数 (安全でない)<math.h>
—おそらく安全<setjmp.h>
- 安全ではありません<signal.h>
- 許可された<stdalign.h>
— 機能なし<stdarg.h>
— 機能なし<stdatomic.h>
—おそらく安全、おそらく安全ではない<stdbool.h>
— 機能なし<stddef.h>
— 機能なし<stdint.h>
— 機能なし<stdio.h>
- 安全ではありません<stdlib.h>
— すべてが安全というわけではありません (許可されているものもあれば、許可されていないものもあります)<stdnoreturn.h>
— 機能なし<string.h>
— すべてが安全というわけではありません<tgmath.h>
—おそらく安全<threads.h>
—おそらく安全ではない<time.h>
— ロケール依存 (ただし、time()
明示的に許可されています)<uchar.h>
— ロケール依存<wchar.h>
— ロケール依存<wctype.h>
— ロケール依存POSIX ヘッダーを分析するのは… たくさんあるという点で難しく、一部の関数は安全かもしれませんが、多くはそうではありません… しかし、POSIX はどの関数が非同期シグナルに対して安全であるかを示しているため (それらの多くではない)、単純です。ヘッダー like<pthread.h>
には、3 つの安全な関数と多くの安全でない関数があることに注意してください。
注: POSIX 環境での C 関数とヘッダーの評価のほとんどすべては、ある程度の知識に基づいた当て推量です。標準化団体からの決定的な声明は意味がありません。
デバッグの目的で、実際にリスト上の関数のみを呼び出していることを確認しasync-signal-safe
、シグナル コンテキスト内で呼び出された安全でない関数ごとに警告メッセージを出力するツールを作成しました。シグナル コンテキストから非同期セーフでない関数を呼び出したいという問題は解決しませんが、少なくとも誤って呼び出してしまったケースを見つけるのに役立ちます。
ソースコードはGitHub にあります。をオーバーロードし、安全でない関数のエントリをsignal/sigaction
一時的にハイジャックすることで機能します。PLT
これにより、安全でない関数の呼び出しがラッパーにリダイレクトされます。
write()
async-signal-safe 関数であるを直接使用することもできます。
#include <unistd.h>
int main(void) {
write(1,"Hello World!", 12);
return 0;
}