2017-11-06 9 views
2

Linux에서 POSIX 타이머 시스템 용 C++ "래퍼"를 작성하려고 시도하고 있으므로 C++ 프로그램이 시스템 시계를 사용하지 않고 (예 : 네트워크를 통해 메시지가 도착할 때까지 기다리는 것과 같은) 시간 제한을 설정할 수 있습니다. POSIX의 못생긴 C 인터페이스를 다루고있다. 대부분의 경우 작동하는 것으로 보이지만 때로는 몇 분이 지나면 내 프로그램이 세그 폴트됩니다. 문제는 내 LinuxTimerManager 개체 (또는 해당 멤버 개체 중 하나)가 메모리가 손상된 것으로 보이지만 불행히도 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_handles.erase(timer_id). 실제 segfault는 std::map 구현 (예 : stl_tree.h)의 깊은 곳에서 발생합니다.

동일한 LinuxTimerManager를 수정하는 다른 타이머 신호 사이의 경쟁 조건으로 인해 메모리가 손상 될 수 있습니까? 한 번에 하나의 타이머 신호 만 전달되었다고 생각했지만 어쩌면 내가 맨 페이지를 오해했을 수도 있습니다. 리눅스 시그널 핸들러가 std::map과 같은 복잡한 C++ 객체를 수정하는 것은 일반적으로 안전하지 않습니까?

+2

신호 처리기에서 안전하게 호출 할 수있는 것은 [async-signal-safe functions] (https://stackoverflow.com/questions/8493095/what-constitutes-asynchronous-safeness)뿐입니다. 신호 처리기가 액세스하는 객체가 반드시 일관된 상태가 아닐 수도 있습니다. –

+1

신호 처리기는 async-signal-safe 함수 만 호출 할 수 있으며 함수가 명시 적으로 나열되지 않으면 (특히 메모리 할당을 수행하는 경우) STL 멤버 함수가 그렇지 않은 경우에는 비동기 신호 안전 함수가 아닌 것으로 간주해야합니다 기능. 할 수있는 일은 휘발성 플래그를 설정하고 나중에 일반 프로그램 컨텍스트에서 확인하고 거기에서 작업을 실행하는 것입니다. – PSkocik

답변

3

신호는 예를 들어, malloc 또는 free이므로 컨테이너로 흥미로운 일을하는 대부분의 호출은 데이터 구조가 임의의 상태에있는 동안 메모리 할당 지원을 다시 입력 할 수 있습니다. 주석에서 지적했듯이 대부분의 함수는 비동기 신호 핸들러를 호출하는 것이 안전하지 않습니다. mallocfree은 예제 일뿐입니다. 이러한 방식으로 구성 요소를 다시 입력하면 거의 임의의 오류가 발생합니다.

라이브러리 내의 모든 작업 중에 전체 프로세스에 대한 신호를 차단하지 않고이 동작에 대해 라이브러리를 안전하게 만들 수 없습니다. 신호 마스크를 관리하는 오버 헤드와 신호가 차단되는 시간 모두에서 엄청나게 비쌉니다. (신호 처리기가 잠금을 막아서는 안되기 때문에 전체 프로세스를 처리해야합니다. 신호 처리기가 필요로하는 뮤텍스를 다른 스레드가 보유하고있는 동안 신호를 처리하는 스레드가 뮤텍스로 보호되는 라이브러리를 호출하면 처리기가 차단됩니다. 이 상황이 발생할 경우 교착 상태를 피하기 매우 어렵습니다.)

일반적으로이 문제를 해결할 수있는 디자인에는 특정 이벤트를 수신 한 다음 처리를 수행하는 스레드가 있습니다. 세마포어를 사용하여 스레드와 신호 처리기를 동기화해야합니다.