Linux计时器挂起信号

时间:2018-04-10 14:23:43

标签: c++ c linux timer

我正在使用带有SIGEV_THREAD参数的timer_create创建linux计时器。

有时在解除定时器并删除它后调用回调。这会导致段错误,因为它会尝试访问已删除的资源。

http://man7.org/linux/man-pages/man2/timer_delete.2.html

Linux手册说

  

timer_delete()删除在timerid中给出ID的计时器。如果   计时器在这次通话时被武装起来,之前被解除武装   被删除。 处理由此产生的任何待处理信号   删除的计时器未指定。

这基本上意味着我真的不知道回叫是否会被调用,而且在我清理资源之前,我没有办法取消它,也没有强迫待处理的信号传递

class timer_wrapper
{
private:
    std::function<void()> callback_;
    timer_t timer_;

    static void timer_callback(sigval_t val)
    {
        static_cast<timer_wrapper*>(val.sival_ptr)->callback_();
    }
public:
    timer_wrapper(std::function<void()> callback, uint32_t interval_sec)
        : callback_(std::move(callback))
    {
        struct sigevent ev;
        ev.sigev_notify = SIGEV_THREAD;
        ev.sigev_signo = 0;
        ev.sigev_value.sival_ptr = this;
        ev.sigev_notify_function = &timer_wrapper::timer_callback;
        ev.sigev_notify_attributes = 0;
        timer_create(CLOCK_REALTIME, &ev, &timer_);

        struct itimerspec spec = {{0, 0}, {interval_sec, 0}};
        timer_settime(timer_, 0, &spec, nullptr);
    }

    ~timer_wrapper()
    {
        timer_delete(timer_);
    }
};

如果timer_wrapper超出范围,我希望不再调用回调,但有时会调用它,而且根据man的说法,这是预期的行为。

解决此问题的建议方法是什么?

1 个答案:

答案 0 :(得分:1)

在删除使用timer_create()创建的计时器时,避免计时器生成的事件的最简单方法是完全避免使用它。相反,使用volatile sig_atomic_t disarmed = 0;标志,并让事件函数在执行任何其他操作之前测试该标志,并在disarmed非零时立即返回。

这样,您首先设置disarmed,然后删除计时器。

(最好使用原型内置函数,无论是旧式__sync_fetch_and_add(&disarmed, 0)__sync_fetch_and_and(&disarmed, 0),还是__atomic_load_n(&disarmed, __ATOMIC_SEQ_CST)__atomic_exchange_n(&disarmed, 0, __ATOMIC_SEQ_CST)来代替访问该标志,以确保正确的订购。)

对于SIGEV_SIGNAL,您可以先阻止信号(使用pthread_sigmask()),删除定时器,检查是否在定时器删除期间使用sigtimedwait()以零超时来引发信号,以及最后恢复旧的信号掩码。

(我个人使用单个POSIX实时信号(SIGRTMIN+0SIGRTMAX-0,在编译时定义),以及一个以事件时间键入的最小堆(每个堆插槽包含时间和对自定义超时/事件结构的引用,用专用线程处理大量事件。)