Search code examples
pthreadsreal-timelinux-rt

How to schedule a real-time task with absolute start time?


For a TSN-aware communication protocol stack and framework, I need to be able to execute real-time threads not only running at very exact intervals, but starting at precise timeslots deducted from ptpd.

Currently, I did that by delaying / yielding the task until the start time. For a PoC of my TSN communication this was sufficient, but with the Linux real-time extensions, there must be a better / more effective way. The jitter introduced was about 800us between two Beaglebones (running Linux 4.14.71-ti-r80 #1 SMP PREEMPT).

Using a cyclic pthread, I synchronised the start of my network producer task (Note: vos_ prefix denotes my OS abstraction methods, actually matching POSIX calls):

for (;; )
{
    /* Synchronize with starttime */
    vos_getTime(&now);                      /* get initial time */
    vos_subTime(&now, &startTime);

    /* Wait for multiples of interval */

    execTime = ((UINT32)now.tv_usec % interval);
    waitingTime = interval - execTime;
    if (waitingTime >= interval)
    {
        ...error checks omitted
    }

    /* Idle for the difference */
    vos_threadDelay(waitingTime); /* pthread_testcancel() inside */

    vos_getTime(&priorCall);  /* get initial time */
    pFunction(pArguments);    /* thread function: send data */
    vos_getTime(&afterCall);  /* get time after function has returned */

    /* get the runtime of pFunction() */
    vos_subTime(&afterCall, &priorCall);

    /* afterCall holds now the time difference... */
    if (afterCall.tv_sec <= MAXSEC_FOR_USECPRESENTATION)
    {
        ...runtime / error handling omitted
    }
}

The above code produces a jitter of about 800us at interval times of 5ms (using Linux 4.14 without RT-extension and policy Round-Robin), which is fairly good - but ineffective when it comes to multiple threads all doing scheduled traffic...

With Linux 4.1.15-ti-rt-r43 #1 SMP PREEMPT RT, I'm planning to use SCHED_DEADLINE as policy, but struct sched_attr provides no start-time-parameter. The runtime-checking in the above code is not needed, then. But:

How can I effectively use Linux's real-time extensions to have a thread running at exact absolute times, say for example at every 1ms starting at absolute real time 'now' + 0.0075s (e.g. now.0085, now.0095, now.0105 ...), without the overhead of the above code?


Solution

  • The clock_nanosleep() POSIX function has an absolute deadline mode:

    #define NSEC_PER_SEC 1000000000
    /* Initial delay, 7.5ms */
    const long start_delay_ns = 7500000;
    /* Cycle time, 1ms */
    const long cycle_time_ns = 1000000;
    struct timespec deadline;
    
    clock_gettime(CLOCK_MONOTONIC, &deadline);
    deadline.tv_nsec += start_delay_ns;
    deadline.tv_sec += deadline.tv_nsec / NSEC_PER_SEC;
    deadline.tv_nsec %= NSEC_PER_SEC;
    
    for (;;)
    {
        struct timespec now;
    
        /* Sleep until deadline */
        while (clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &deadline, NULL) != 0)
            if (errno != EINTR)
                return; /* error handling here */
    
        pFunction(pArguments);    /* thread function: send data */
    
        /* Calculate next deadline */
        deadline.tv_nsec += cycle_time_ns;
        deadline.tv_sec += deadline.tv_nsec / NSEC_PER_SEC;
        deadline.tv_nsec %= NSEC_PER_SEC;
    
        clock_gettime(CLOCK_MONOTONIC, &now);
        if (now.tv_sec > deadline.tv_sec || (now.tv_sec == deadline.tv_sec && deadline.tv_nsec > now.tv_nsec))
            return; /* time overrun error handling here */
    }