どうやらこのコースの目的は、コードを次のように変更することです。
sigwait()
別のスレッドを使用します。このスレッドは、呼び出し元またはsigwaitinfo()
ループ内の信号を受信します。シグナルをブロックする必要があります(すべてのスレッドで最初に、そしてずっと)、または操作が指定されていません(またはシグナルが他のスレッドに配信されます)。
このように、シグナルハンドラー関数自体はなく、非同期シグナルセーフ関数に制限されます。sigwait()
/を呼び出すスレッドsigwaitinfo()
は完全に通常のスレッドであり、シグナルまたはシグナルハンドラーに関連する制限はありません。
(たとえば、グローバルフラグを設定し、ループがチェックするシグナルハンドラーを使用して、シグナルを受信する方法は他にもあります。これらのほとんどは、ビジーウェイト、何もしないループの実行、CPU時間を無駄に消費することにつながります。非常に悪い解決策ここで説明する方法では、CPU時間を浪費しません。カーネルはsigwait()
/sigwaitinfo()
を呼び出すとスレッドをスリープ状態にし、信号が到着したときにのみスレッドをウェイクアップします。スリープの期間を制限したい場合は、次のことができます。sigtimedwait()
代わりに使用してください。)
以来printf()
。スレッドセーフであることが保証されているわけではありません。おそらく、を使用しpthread_mutex_t
て出力を標準出力に保護する必要があります。つまり、2つのスレッドがまったく同時に出力しようとしないようにする必要があります。
Linuxでは、GNU C printf()
(バージョンを除く_unlocked()
)はスレッドセーフであるため、これは必要ありません。これらの関数を呼び出すたびに、すでに内部ミューテックスが使用されています。
Cライブラリは出力をキャッシュする可能性があるため、データが確実に出力されるようにするには、を呼び出す必要があることに注意してくださいfflush(stdout);
。
printf()
ミューテックスは、他のスレッドが間に出力を挿入できないように、複数のfputs()
、、または同様の呼び出しをアトミックに使用する場合に特に便利です。したがって、Linuxでは単純なケースでは必要ない場合でも、ミューテックスをお勧めします。(もちろん、ミューテックスを保持している間もやりたいと思いますがfflush()
、出力がブロックされるとミューテックスが長時間保持される可能性があります。)
私は個人的に問題全体をまったく異なる方法で解決しますwrite(STDERR_FILENO,)
。シグナルハンドラーで標準エラーに出力し、メインプログラムを標準出力に出力します。スレッドや特別なものは必要ありません。シグナルハンドラーの単純な低レベルの書き込みループだけです。厳密に言えば、私のプログラムの動作は異なりますが、エンドユーザーには結果はほとんど同じに見えます。(出力を別のターミナルウィンドウにリダイレクトして並べて表示したり、各入力行にナノ秒の壁掛け時計のタイムスタンプを追加するヘルパースクリプト/プログラムにリダイレクトしたりできることを除いて、調査時に役立つその他の同様のトリックもの。)
個人的には、元の問題から「正しい解決策」へのジャンプを見つけました。実際に私が説明しているのが正しい解決策である場合。私はそれが-少しストレッチになると信じています。このアプローチは、Safが正しいソリューションでpthreadを使用することが期待されていると述べたときに初めて私に気づきました。
これが参考になることを願っていますが、ネタバレではありません。
2013年3月13日編集:
これはwritefd()
、シグナルハンドラーから記述子にデータを安全に書き込むために使用する関数です。また、ラッパー関数も含めましたwrout()
。wrerr()
これを使用して、文字列を標準出力または標準エラーにそれぞれ書き込むことができます。
#include <unistd.h>
#include <string.h>
#include <errno.h>
/**
* writefd() - A variant of write(2)
*
* This function returns 0 if the write was successful, and the nonzero
* errno code otherwise, with errno itself kept unchanged.
* This function is safe to use in a signal handler;
* it is async-signal-safe, and keeps errno unchanged.
*
* Interrupts due to signal delivery are ignored.
* This function does work with non-blocking sockets,
* but it does a very inefficient busy-wait loop to do so.
*/
int writefd(const int descriptor, const void *const data, const size_t size)
{
const char *head = (const char *)data;
const char *const tail = (const char *)data + size;
ssize_t bytes;
int saved_errno, retval;
/* File descriptor -1 is always invalid. */
if (descriptor == -1)
return EINVAL;
/* If there is nothing to write, return immediately. */
if (size == 0)
return 0;
/* Save errno, so that it can be restored later on.
* errno is a thread-local variable, meaning its value is
* local to each thread, and is accessible only from the same thread.
* If this function is called in an interrupt handler, this stores
* the value of errno for the thread that was interrupted by the
* signal delivery. If we restore the value before returning from
* this function, all changes this function may do to errno
* will be undetectable outside this function, due to thread-locality.
*/
saved_errno = errno;
while (head < tail) {
bytes = write(descriptor, head, (size_t)(tail - head));
if (bytes > (ssize_t)0) {
head += bytes;
} else
if (bytes != (ssize_t)-1) {
errno = saved_errno;
return EIO;
} else
if (errno != EINTR && errno != EAGAIN && errno != EWOULDBLOCK) {
/* EINTR, EAGAIN and EWOULDBLOCK cause the write to be
* immediately retried. Everything else is an error. */
retval = errno;
errno = saved_errno;
return retval;
}
}
errno = saved_errno;
return 0;
}
/**
* wrout() - An async-signal-safe alternative to fputs(string, stdout)
*
* This function will write the specified string to standard output,
* and return 0 if successful, or a nonzero errno error code otherwise.
* errno itself is kept unchanged.
*
* You should not mix output to stdout and this function,
* unless stdout is set to unbuffered.
*
* Unless standard output is a pipe and the string is at most PIPE_BUF
* bytes long (PIPE_BUF >= 512), the write is not atomic.
* This means that if you use this function in a signal handler,
* or in multiple threads, the writes may be interspersed with each other.
*/
int wrout(const char *const string)
{
if (string)
return writefd(STDOUT_FILENO, string, strlen(string));
else
return 0;
}
/**
* wrerr() - An async-signal-safe alternative to fputs(string, stderr)
*
* This function will write the specified string to standard error,
* and return 0 if successful, or a nonzero errno error code otherwise.
* errno itself is kept unchanged.
*
* You should not mix output to stderr and this function,
* unless stderr is set to unbuffered.
*
* Unless standard error is a pipe and the string is at most PIPE_BUF
* bytes long (PIPE_BUF >= 512), the write is not atomic.
* This means that if you use this function in a signal handler,
* or in multiple threads, the writes may be interspersed with each other.
*/
int wrerr(const char *const string)
{
if (string)
return writefd(STDERR_FILENO, string, strlen(string));
else
return 0;
}
ファイル記述子がパイプを参照している場合、アトミックに最大(少なくとも512)バイト writefd()
を書き込むために使用できます。I / Oを多用するアプリケーションで使用して、信号(および、関連する値、整数、またはポインターを使用して発生した場合)をソケットまたはパイプ出力(データ)に変換し、複数のI/Oをより簡単に多重化することもできます。ストリームと信号処理。バリアント(close-on-execとマークされた追加のファイル記述子を持つ)は、子プロセスが別のプロセスを実行したか、失敗したかを簡単に検出するためによく使用されます。そうしないと、どのプロセス(元の子、または実行されたプロセス)が終了したかを検出するのが困難になります。PIPE_BUF
writefd()
sigqueue()
この回答へのコメントでは、についての議論があり、変更するerrno
という事実がシグナルハンドラーに適さないかどうかについて混乱がありました。write(2)
errno
まず、POSIX.1-2008(およびそれ以前)では、非同期シグナルセーフ関数をシグナルハンドラーから安全に呼び出すことができる関数として定義しています。2.4.3シグナルアクションの章には、を含むそのような機能のリストが含まれていますwrite()
。また、 「errnoの値を取得する操作、およびerrnoに値を割り当てる操作は、非同期信号に対して安全である」と明示的に記載されていることに注意してください。
つまり、POSIX.1はwrite()
シグナルハンドラーで安全に使用できるように意図されておりerrno
、中断されたスレッドで予期しない変更が発生するのを防ぐために操作することもできますerrno
。
はスレッドローカル変数であるためerrno
、各スレッドには独自のがありerrno
ます。シグナルが配信されると、プロセス内の既存のスレッドの1つが常に中断されます。シグナルは特定のスレッドに送信できますが、通常、カーネルはどのスレッドがプロセス全体のシグナルを取得するかを決定します。システムによって異なります。初期スレッドまたはメインスレッドのスレッドが1つしかない場合は、明らかにそれが中断されます。これはすべて、シグナルハンドラーが最初に認識した値を保存し、errno
戻る直前に復元した場合、への変更errno
はシグナルハンドラーの外部では表示されないことを意味します。
ただし、これを検出する方法は1つありますが、POSIX.1-2008でも、注意深い表現によって示唆されています。
技術的に&errno
は、ほとんどの場合有効であり(システム、コンパイラ、および適用される標準によって異なります)、int
現在のスレッドのエラーコードを保持している変数のアドレスを生成します。したがって、別のスレッドが別のスレッドのエラーコードを監視する可能性があります。そうです、このスレッドは、シグナルハンドラー内で行われた変更を確認します。ただし、他のスレッドがエラーコードにアトミックにアクセスできるという保証はありません(ただし、多くのアーキテクチャではアトミックです)。このような「監視」は、とにかく情報を提供するだけです。
Cのほとんどすべてのシグナルハンドラーの例がstdio.hetceteraを使用しているのは残念ですprintf()
。非非同期セーフからキャッシングの問題、FILE
中断されたコードが同時にI / Oを実行している場合は、フィールドへの非アトミックアクセスまで、多くのレベルで間違っているだけでなく、正しい解決策unistd.h
この編集での私の例と同様の使用は、同じように簡単です。シグナルハンドラーでstdio.hI/ Oを使用する理由は、「通常は機能する」ようです。個人的には、たとえば暴力も「通常は機能する」ので、私はそれを嫌います。私はそれを愚かで怠惰だと思います。
これがお役に立てば幸いです。