cコードの信号処理関数で共有原子オブジェクトにアクセスする
The CERT® C Coding Standard,Second Edition:98 Rules for Developing Safe,Relable,and Secure Systems,Second Editionはすぐにリリースされます(早く出てきましたが、電子版は見つかりません).c 11規格に更新され、ISO/IEC TS 17961 c符号化規則に対応しています.この版で一番頭が痛いのはSIG 31-Cです.「信号処理関数で共有オブジェクトにアクセスしないでください.「ルールが存在するのは、信号処理関数で共有オブジェクトにアクセスするとデータの状態が一致しないためです.この記事では、このルールや本の例を超えて、信号処理関数で共有オブジェクトにアクセスするバックグラウンド資料をより多く提供します.このルールは、第1版「certセキュリティ符号化基準」に登場しましたしかし、その本はc 99の範囲内で議論されているため、原子オブジェクトはまだ定義されていません.信号処理関数で共有オブジェクトにアクセスする唯一の合法的な方法は、volatile sig_を読み書きすることです.atomic_tタイプの変数.次のプログラムはsigint信号処理handlerをインストールし、volatile sig_を設定します.atomic_t型変数e_flagは、プログラムが終了する前にhandlerが呼び出されたかどうかをテストします.
C 11,5.1.2.3,第5セグメントではsignal handlerの読み書きを許可するロック解除原子オブジェクトも定義されています.以下は、原子フラグ変数への簡単なアクセスの例です(標準とは一致しません)、atomic_flagタイプは古典的なtest-and-set機能を提供し、2つの状態、設定、クリア、c標準保証atomic_flagタイプの読み書き操作はロックフリーです.
原子変数がサポートされている場合、atomic_flag lタイプは、ロックフリーであることが保証されているもののみであり、signal handlerでアクセス可能なものも同様である.しかし,このタイプの変数は原子関数を呼び出すことによってのみ有効にアクセスできるが,この呼び出しは許されない.c標準7.14.1.1,章5,signal handlerに従って標準ライブラリの関数を呼び出し,関数abort,Exit,quick_exit、最初のパラメータは、今回のsignal handlerが呼び出されたsignal番号のsignal関数であり、他のすべての関数を呼び出すと未定義の動作が発生します.
この制限の存在は、ほとんどのライブラリ関数が非同期信号で安全であるとは限らないためである.標準を変更しない前提でこの問題を解決するためには、atomic_のような異なる原子タイプを使用して上記のコードを書き換える必要があります.int:
このソリューションはatomic_のみですintタイプは常にロックフリーのプラットフォームで成功した.原子変数がサポートされていない場合、またはatomic_intタイプがロックフリーではない場合、次のコードによりコンパイラから診断メッセージが出力されます.
ATOMIC_INT_LOCK_FREEマクロは、atomic_を表す0と定義される可能性があるintは決してロックフリーではありません.値1は、ロックされていない場合があることを示します.値2は、常にロックされていないことを示します.このタイプがロックされていない場合は、実行時にatomicを呼び出す必要があります.is_lock_free関数は、ロックされていないかどうかを決定します.
atomicタイプは、いくつかのプロセッサが、いくつかのマシンアーキテクチャのため、ロックフリー動作の比較および交換(cas)をサポートし、他のプロセッサはサポートしない場合がある(80386 vs 80486).プロセッサのバリエーションに依存して、アプリケーションが異なるダイナミックライブラリにバインドされる可能性があるため、ATOMIC_INT_LOCK_FREE==1の場合,ランタイムチェックメカニズムを導入する必要がある.次のプログラムはatomic_intがロックフリーの場合、運転:
一つ質問があります.なぜe_flag変数はvolatile修飾を使用せず、最初のvolatile sig_を使用します.atomic_t例は異なり,原子オブジェクトのload,store操作にはmemory_がある.order_seq_cst(順序が一致する)意味順序一致性に合致するプログラムの挙動は,それを構成するスレッド間のインタリーブ操作結果のようであり,各オブジェクト値の計算は今回のインタリーブで最後に格納される.原子操作のパラメータ自体は,そのアドレスから新鮮な値を読み出すたびにvolatile修飾を必要としない.
同時支援では,c標準委員会(wg 14)がc++標準委員会(wg 21)の歩みに追随した.wg 21の意図は、c++11規格におけるsignal handler用のロック解除原子変数である.残念なことに、wg 21はいくつかの間違いを犯してc++14で修復できるように努力したいと思っています.c++でプログラムsignal handler挙動を指定する最新の提案はWG 21/N 3910である.これはc++14草案に以下の説明をもたらした.
signal handlerが呼び出され,raise関数を呼び出す結果としてraise呼び出しを開始する同じスレッドに現れる.そうでなければ、signal handlerを実行するスレッドは定義されていません.
posixは、信号がプロセス、またはプロセス内の特定のスレッドのために生成された場合、意思決定を行うことを要求します.ハードウェアエラーなどの特定の動作がスレッドによって実行された場合、この信号は、この信号を発生させるスレッドの生成である.信号がプロセスid、プロセスグループid、または端末アクティビティのような非同期イベントを関連付けた場合、信号はプロセスのために生成される.
volatileオブジェクトへのアクセスは抽象マシンのルールに基づいて厳密に計算されます.volatileオブジェクト上の操作は最適化できません.原子オブジェクトが現れる前にvolatileはsignal handler共有オブジェクトに最も近似的な意味を提供した.volatileは他のスレッドとの可視性規則を強制していないため、原子オブジェクトがより良い選択になりました.これにより、スレッド間で動作することはほとんどできません.だから、volatile sig_atomic_tはsignal handlerでsig_と実行するためにのみ使用できます.atomic_t変数が同じスレッドの場合.
c規格では、マルチスレッドプログラムにsignal handlerをインストールすることは許可されていないが、c 11は、マルチスレッドプログラムでsignal関数を使用する行為が定義されていないことを明確に説明している.従って、標準に従うcマルチスレッドプログラムについて、信号をどのように処理するかについて議論することは意味がない.
次のコードは、このプログラムの最も移植性のあるバージョンで、タイプの置き換えが使用されており、コンパイル時に知られています.この例では、ロックフリー原子タイプのコンパイル時に使用可能であることを決定するには、atomic typeを使用し、そうでなければvolatile sig_を使用します.atomic_t .だからATOMIC_INT_LOCK_FREE==1で、0としても扱われます.
c規格に従ってstatic,thread local storageライフサイクルのオブジェクト、0値が初期化されるのでe_flagは明示的に初期化する必要はありません.
締めくくり
現在、c/c++のsignal handlerで共有オブジェクトにアクセスするには問題があります(c++14で解決する見込みがあります).現在の呼びかけは,signal handlerで原子フラグ関数を呼び出すことができるようにc規格を修正する傾向にあり,この提案はwg 14に提出されている.Austinグループは現在、posix規格の8回目の発行にc 11を統合することに力を入れている(最近はIEEE Std 1003.1、2013 Edition、issue 7).cのマルチスレッドコードでsignal関数を呼び出すのは未定義であるため、posixは言語仕様を拡張することができ、そのために未定義の動作に具体的な定義を提供する.長期的に見ると、c/c++標準委員会はvolatile sigを放棄することを望んでいる.atomic_tは、マルチスレッド実行をサポートできないため、原子タイプがより良い代替を提供する.
gcc-4.9は欠落stdatomicを追加した.h
#include <signal.h>
#include <stdlib.h>
#include <stdio.h>
#if __STDC_NO_ATOMICS__ != 1
#include <stdatomic.h>
#endif
atomic_flag e_flag = ATOMIC_FLAG_INIT;
void handler(int signum) {
(void)atomic_flag_test_and_set(&e_flag);
}
int main(void) {
if (signal(SIGINT, handler) == SIG_ERR) {
return EXIT_FAILURE;
}
/* Main code loop */
if (atomic_flag_test_and_set(&e_flag)) {
puts("SIGINT received.");
}
else {
puts("SIGINT not received.");
}
return EXIT_SUCCESS;
}
C 11,5.1.2.3,第5セグメントではsignal handlerの読み書きを許可するロック解除原子オブジェクトも定義されています.以下は、原子フラグ変数への簡単なアクセスの例です(標準とは一致しません)、atomic_flagタイプは古典的なtest-and-set機能を提供し、2つの状態、設定、クリア、c標準保証atomic_flagタイプの読み書き操作はロックフリーです.
#include <signal.h>
#include <stdlib.h>
#include <stdio.h>
#if __STDC_NO_ATOMICS__ != 1
#include <stdatomic.h>
#endif
atomic_flag e_flag = ATOMIC_FLAG_INIT;
void handler(int signum) {
(void)atomic_flag_test_and_set(&e_flag);
}
int main(void) {
if (signal(SIGINT, handler) == SIG_ERR) {
return EXIT_FAILURE;
}
/* Main code loop */
if (atomic_flag_test_and_set(&e_flag)) {
puts("SIGINT received.");
}
else {
puts("SIGINT not received.");
}
return EXIT_SUCCESS;
}
原子変数がサポートされている場合、atomic_flag lタイプは、ロックフリーであることが保証されているもののみであり、signal handlerでアクセス可能なものも同様である.しかし,このタイプの変数は原子関数を呼び出すことによってのみ有効にアクセスできるが,この呼び出しは許されない.c標準7.14.1.1,章5,signal handlerに従って標準ライブラリの関数を呼び出し,関数abort,Exit,quick_exit、最初のパラメータは、今回のsignal handlerが呼び出されたsignal番号のsignal関数であり、他のすべての関数を呼び出すと未定義の動作が発生します.
この制限の存在は、ほとんどのライブラリ関数が非同期信号で安全であるとは限らないためである.標準を変更しない前提でこの問題を解決するためには、atomic_のような異なる原子タイプを使用して上記のコードを書き換える必要があります.int:
#include <signal.h>
#include <stdlib.h>
#if __STDC_NO_ATOMICS__ != 1
#include <stdatomic.h>
#endif
atomic_int e_flag = ATOMIC_VAR_INIT(0);
void handler(int signum) {
e_flag = 1;
}
int main(void) {
if (signal(SIGINT, handler) == SIG_ERR) {
return EXIT_FAILURE;
}
/* Main code loop */
if (e_flag) {
puts("SIGINT received.");
}
else {
puts("SIGINT not received.");
}
return EXIT_SUCCESS;
}
このソリューションはatomic_のみですintタイプは常にロックフリーのプラットフォームで成功した.原子変数がサポートされていない場合、またはatomic_intタイプがロックフリーではない場合、次のコードによりコンパイラから診断メッセージが出力されます.
#if __STDC_NO_ATOMICS__ == 1
#error "Atomics is not supported"
#elif ATOMIC_INT_LOCK_FREE == 0
#error "int is never lock-free"
#endif
ATOMIC_INT_LOCK_FREEマクロは、atomic_を表す0と定義される可能性があるintは決してロックフリーではありません.値1は、ロックされていない場合があることを示します.値2は、常にロックされていないことを示します.このタイプがロックされていない場合は、実行時にatomicを呼び出す必要があります.is_lock_free関数は、ロックされていないかどうかを決定します.
#if ATOMIC_INT_LOCK_FREE == 1
if (!atomic_is_lock_free(&e_flag)) {
return EXIT_FAILURE;
}
#endif
atomicタイプは、いくつかのプロセッサが、いくつかのマシンアーキテクチャのため、ロックフリー動作の比較および交換(cas)をサポートし、他のプロセッサはサポートしない場合がある(80386 vs 80486).プロセッサのバリエーションに依存して、アプリケーションが異なるダイナミックライブラリにバインドされる可能性があるため、ATOMIC_INT_LOCK_FREE==1の場合,ランタイムチェックメカニズムを導入する必要がある.次のプログラムはatomic_intがロックフリーの場合、運転:
#include <signal.h>
#include <stdlib.h>
#if __STDC_NO_ATOMICS__ != 1
#include <stdatomic.h>
#endif
#if __STDC_NO_ATOMICS__ == 1
#error "Atomics is not supported"
#elif ATOMIC_INT_LOCK_FREE == 0
#error "int is never lock-free"
#endif
atomic_int e_flag = ATOMIC_VAR_INIT(0);
void handler(int signum) {
e_flag = 1;
}
int main(void) {
#if ATOMIC_INT_LOCK_FREE == 1
if (!atomic_is_lock_free(&e_flag)) {
return EXIT_FAILURE;
}
#endif
if (signal(SIGINT, handler) == SIG_ERR) {
return EXIT_FAILURE;
}
/* Main code loop */
if (e_flag) {
puts("SIGINT received.");
}
else {
puts("SIGINT not received.");
}
return EXIT_SUCCESS;
}
一つ質問があります.なぜe_flag変数はvolatile修飾を使用せず、最初のvolatile sig_を使用します.atomic_t例は異なり,原子オブジェクトのload,store操作にはmemory_がある.order_seq_cst(順序が一致する)意味順序一致性に合致するプログラムの挙動は,それを構成するスレッド間のインタリーブ操作結果のようであり,各オブジェクト値の計算は今回のインタリーブで最後に格納される.原子操作のパラメータ自体は,そのアドレスから新鮮な値を読み出すたびにvolatile修飾を必要としない.
同時支援では,c標準委員会(wg 14)がc++標準委員会(wg 21)の歩みに追随した.wg 21の意図は、c++11規格におけるsignal handler用のロック解除原子変数である.残念なことに、wg 21はいくつかの間違いを犯してc++14で修復できるように努力したいと思っています.c++でプログラムsignal handler挙動を指定する最新の提案はWG 21/N 3910である.これはc++14草案に以下の説明をもたらした.
signal handlerが呼び出され,raise関数を呼び出す結果としてraise呼び出しを開始する同じスレッドに現れる.そうでなければ、signal handlerを実行するスレッドは定義されていません.
posixは、信号がプロセス、またはプロセス内の特定のスレッドのために生成された場合、意思決定を行うことを要求します.ハードウェアエラーなどの特定の動作がスレッドによって実行された場合、この信号は、この信号を発生させるスレッドの生成である.信号がプロセスid、プロセスグループid、または端末アクティビティのような非同期イベントを関連付けた場合、信号はプロセスのために生成される.
volatileオブジェクトへのアクセスは抽象マシンのルールに基づいて厳密に計算されます.volatileオブジェクト上の操作は最適化できません.原子オブジェクトが現れる前にvolatileはsignal handler共有オブジェクトに最も近似的な意味を提供した.volatileは他のスレッドとの可視性規則を強制していないため、原子オブジェクトがより良い選択になりました.これにより、スレッド間で動作することはほとんどできません.だから、volatile sig_atomic_tはsignal handlerでsig_と実行するためにのみ使用できます.atomic_t変数が同じスレッドの場合.
c規格では、マルチスレッドプログラムにsignal handlerをインストールすることは許可されていないが、c 11は、マルチスレッドプログラムでsignal関数を使用する行為が定義されていないことを明確に説明している.従って、標準に従うcマルチスレッドプログラムについて、信号をどのように処理するかについて議論することは意味がない.
次のコードは、このプログラムの最も移植性のあるバージョンで、タイプの置き換えが使用されており、コンパイル時に知られています.この例では、ロックフリー原子タイプのコンパイル時に使用可能であることを決定するには、atomic typeを使用し、そうでなければvolatile sig_を使用します.atomic_t .だからATOMIC_INT_LOCK_FREE==1で、0としても扱われます.
#include <signal.h>
#include <stdlib.h>
#include <stdio.h>
#if __STDC_NO_ATOMICS__ != 1
#include <stdatomic.h>
#endif
#if __STDC_NO_ATOMICS__ == 1
typedef volatile sig_atomic_t flag_type;
#elif ATOMIC_INT_LOCK_FREE == 0 || ATOMIC_INT_LOCK_FREE == 1
typedef volatile sig_atomic_t flag_type;
#else
typedef atomic_int flag_type;
#endif
flag_type e_flag;
void handler(int signum) {
e_flag = 1;
}
int main(void) {
if (signal(SIGINT, handler) == SIG_ERR) {
return EXIT_FAILURE;
}
/* Main code loop */
if (e_flag) {
puts("SIGINT received.");
}
else {
puts("SIGINT not received.");
}
return EXIT_SUCCESS;
}
c規格に従ってstatic,thread local storageライフサイクルのオブジェクト、0値が初期化されるのでe_flagは明示的に初期化する必要はありません.
締めくくり
現在、c/c++のsignal handlerで共有オブジェクトにアクセスするには問題があります(c++14で解決する見込みがあります).現在の呼びかけは,signal handlerで原子フラグ関数を呼び出すことができるようにc規格を修正する傾向にあり,この提案はwg 14に提出されている.Austinグループは現在、posix規格の8回目の発行にc 11を統合することに力を入れている(最近はIEEE Std 1003.1、2013 Edition、issue 7).cのマルチスレッドコードでsignal関数を呼び出すのは未定義であるため、posixは言語仕様を拡張することができ、そのために未定義の動作に具体的な定義を提供する.長期的に見ると、c/c++標準委員会はvolatile sigを放棄することを望んでいる.atomic_tは、マルチスレッド実行をサポートできないため、原子タイプがより良い代替を提供する.
gcc-4.9は欠落stdatomicを追加した.h