Linux上でPOSIXタイマーシステム用のC++ "ラッパー"を作成しようとしています。そのため、C++プログラムはシステムクロックを使用せずに(ネットワーク経由でメッセージが到着するのを待つなど)タイムアウトを設定できます。 POSIXの醜いCインタフェースを扱います。ほとんどの場合はうまくいくと思われますが、プログラムが正常に実行されてから数分後にセグメンテーションが失敗することがあります。問題は、私のLinuxTimerManagerオブジェクト(またはメンバオブジェクトの1つ)がメモリを破壊しているようですが、残念ながら、Valgrindの下でプログラムを実行すると問題が現れないので、私のコードを見て、何が間違っているのか。POSIXタイマーはC++ STLオブジェクトを安全に変更できますか?
ここに私のタイマー・ラッパーの実装のコアがあります:
LinuxTimerManager.h:
namespace util {
using timer_id_t = int;
class LinuxTimerManager {
private:
timer_id_t next_id;
std::map<timer_id_t, timer_t> timer_handles;
std::map<timer_id_t, std::function<void(void)>> timer_callbacks;
std::set<timer_id_t> cancelled_timers;
friend void timer_signal_handler(int signum, siginfo_t* info, void* ucontext);
public:
LinuxTimerManager();
timer_id_t register_timer(const int delay_ms, std::function<void(void)> callback);
void cancel_timer(const timer_id_t timer_id);
};
void timer_signal_handler(int signum, siginfo_t* info, void* ucontext);
}
LinuxTimerManager.cpp:
namespace util {
LinuxTimerManager* tm_instance;
LinuxTimerManager::LinuxTimerManager() : next_id(0) {
tm_instance = this;
struct sigaction sa = {0};
sa.sa_flags = SA_SIGINFO;
sa.sa_sigaction = timer_signal_handler;
sigemptyset(&sa.sa_mask);
int success_flag = sigaction(SIGRTMIN, &sa, NULL);
assert(success_flag == 0);
}
void timer_signal_handler(int signum, siginfo_t* info, void* ucontext) {
timer_id_t timer_id = info->si_value.sival_int;
auto cancelled_location = tm_instance->cancelled_timers.find(timer_id);
//Only fire the callback if the timer is not in the cancelled set
if(cancelled_location == tm_instance->cancelled_timers.end()) {
tm_instance->timer_callbacks.at(timer_id)();
} else {
tm_instance->cancelled_timers.erase(cancelled_location);
}
tm_instance->timer_callbacks.erase(timer_id);
timer_delete(tm_instance->timer_handles.at(timer_id));
tm_instance->timer_handles.erase(timer_id);
}
timer_id_t LinuxTimerManager::register_timer(const int delay_ms, std::function<void(void)> callback) {
struct sigevent timer_event = {0};
timer_event.sigev_notify = SIGEV_SIGNAL;
timer_event.sigev_signo = SIGRTMIN;
timer_event.sigev_value.sival_int = next_id;
timer_t timer_handle;
int success_flag = timer_create(CLOCK_REALTIME, &timer_event, &timer_handle);
assert(success_flag == 0);
timer_handles[next_id] = timer_handle;
timer_callbacks[next_id] = callback;
struct itimerspec timer_spec = {0};
timer_spec.it_interval.tv_sec = 0;
timer_spec.it_interval.tv_nsec = 0;
timer_spec.it_value.tv_sec = 0;
timer_spec.it_value.tv_nsec = delay_ms * 1000000;
timer_settime(timer_handle, 0, &timer_spec, NULL);
return next_id++;
}
void LinuxTimerManager::cancel_timer(const timer_id_t timer_id) {
if(timer_handles.find(timer_id) != timer_handles.end()) {
cancelled_timers.emplace(timer_id);
}
}
}
私のプログラムがクラッシュし、 segfaultは常にtimer_signal_handler()
から来ており、通常はtm_instance->timer_callbacks.erase(timer_id)
またはtm_instance->timer_handles.erase(timer_id)
。実際のセグメンテーションはstd::map
実装の深いどこか(つまりstl_tree.h
)からスローされます。
同じLinuxTimerManagerを変更している異なるタイマー信号間の競合状態によって、メモリが破損する可能性がありますか?一度に1つのタイマー信号しか届かないと思ったのですが、マニュアルページを誤解しているかもしれません。 std::map
のような複雑なC++オブジェクトをLinuxシグナルハンドラで変更するのは、一般的に危険ですか?
シグナルハンドラから安全に呼び出せるのは、[async-signal-safe functions](https://stackoverflow.com/questions/8493095/what-constitutes-asynchronous-safeness)だけです。シグナルハンドラがアクセスするオブジェクトは必ずしも一貫した状態にあるとは限りません。 –
シグナルハンドラはasync-signal-safe関数のみを呼び出すことがあります。関数が明示的にそのようにリストされていない限り(STLメンバ関数がメモリ割り当てを行っている場合は特に)、非同期シグナルセーフ関数。あなたができることは、揮発性のフラグを設定し、後でそれを通常のプログラムのコンテキストでチェックし、あなたのアクションをそこで実行することです。 – PSkocik