Search code examples
clinuxmultithreadingtimerposix

How to use SIGEV_THREAD, sigevent for linux-timer(s) expiration handling in C?


Problem: I have timer(s) running, upon expiration of timer(s) certain function needs to be invoked. Output: There is a segfault inside Hndlr() function

As per man page of sigevent, it says,

SIGEV_THREAD - Notify the process by invoking sigev_notify_function "as if" it were the start function of a new thread. (Among the implement‐ tation possibilities here are that each timer notification could result in the creation of a new thread, or that a single thread is created to receive all notifications.)

The function (sigev_notify_function) is invoked with sigev_value as its sole argument

I did refer to this: UNIX/Linux signal handling: SIGEV_THREAD and it says,

sigev_value contains supplementary data that is passed to the function

So, I have written the following,

typedef struct Info
{
enum Status
{
    Start = 1,
    Expire = 2
} TimerStatus;

int data;
timer_t timerId;
} Info_t;

void Hndlr(union sigval *sv)
   { 
       //Upon expiry I want to set this value of t1.TimerStatus Expire:
       //t1.TimerStatus = Expire;
       //So I have done this:
       sv->sival_int = Expire;
   }

   int TimerInit(Info_t *Type)
   {
       struct sigevent sev;
       sev.sigev_notify = SIGEV_THREAD;
       sev.sigev_value.sival_ptr = Type->timerId;
       sev.sigev_value.sival_int = Type->TimerStatus;
       sev.sigev_notify_function = &Hndlr;
       sev.sigev_notify_attributes = 0;

       timer_create(CLOCK_REALTIME, &sev, &(Type->timerId));
 }

 //other code

int main(int argc, char const *argv[])
{
Info_t t1;
t1.TimerStatus = Start;

TimerInit(&t1);
//start timer
//other code
while (1)
{

    if (t1.TimerStatus == Expire)
    {
        //do something, invoke a function
    }
}

return 0;

}

Warning: assignment to ‘void (*)(__sigval_t)’ {aka ‘void (*)(union sigval)’} from 
incompatible pointer type ‘void (*)(union sigval *)’ [-Wincompatible-pointer-types]
     sev.sigev_notify_function = &Hndlr; 

Since, I am using union sigval *sv in Hndlr, I am receiving this warning.

Q) How to I pass enum type to Hndlr as pass-by-ptr and change it, ie., t1.TimerStatus = Expire

PS: I haven't included the entire code involving timer_set() etc, and it also involves multiple instances of timer. So, How can I achieve this functionality (Q) ?


Solution

  • A few mistakes in the code:

    • Wrong function prototype for timer expiry function Hndlr.
    • Setting all members of union sigval, whereas only one member must be set.
    • A variable modified and read in another thread must be atomic.

    A working example (compiler options -std=c11 -pthread -W{all,extra}, linker options -std=c11 -pthread -lrt):

    #include <stdatomic.h>
    #include <stdlib.h>
    #include <signal.h>
    #include <time.h>
    
    enum Status {
        Start = 1,
        Expire = 2
    };
    
    typedef struct {
        atomic_int status;
        int data;
        timer_t timerId;
    } Info_t;
    
    static void Hndlr(union sigval sigev_value) {
        Info_t* info = sigev_value.sival_ptr;
        atomic_store(&info->status, Expire);
    }
    
    void TimerInit(Info_t* info, unsigned seconds) {
        int r;
        struct sigevent sev;
        struct itimerspec its;
    
        sev.sigev_notify = SIGEV_THREAD;
        sev.sigev_value.sival_ptr = info;
        sev.sigev_notify_function = &Hndlr;
        sev.sigev_notify_attributes = 0;
        r = timer_create(CLOCK_REALTIME, &sev, &info->timerId);
        if(r)
            abort();
    
        its.it_interval.tv_sec = 0;
        its.it_interval.tv_nsec = 0;
        its.it_value.tv_sec = seconds;
        its.it_value.tv_nsec = 0;
        r = timer_settime(info->timerId, 0, &its, NULL);
        if(r)
            abort();
    }
    
    int main() {
        Info_t t1;
        t1.status = Start;
        TimerInit(&t1, 3);
        while(atomic_load(&t1.status) != Expire)
            ;
    
        return 0;
    }
    

    In this particular usage, when the timer callback function just stores into a variable, there is no need to use another thread with SIGEV_THREAD, SIGEV_SIGNAL would work just as well (setup code changes are required), as long as blocking functions that can be interrupted with the signal handle EINTR.