1

For my application I have the requirement of accurate periodic threads with relative low cycle times (500 µs).
In particular the application is a run time system of a PLC. It's purpose is to run an application developed by the PLC user. Such applications are organised in programs and periodic tasks - each task with it's own cycle time and priority.
Usually the application runs on systems with real time OSs (eg. vxWorks or Linux with RT patch).

Currently the periodic tasks are implemented via clock_nanosleep. Unfortunately the actual sleep time of clock_nanosleep is disturbed by other threads - even with lower priority. Once every second, the sleep time is exceeded by about 50 ms. I've observed this on Debian 9.5, on RaspberryPi and also on an ARM-Linux with Preemt-RT.

Here's a sample, which shows this behavior:

#include <pthread.h>
#include <unistd.h>
#include <stdint.h>
#include <stdio.h>

typedef void* ThreadFun(void* param);

#define SCHEDULER_POLICY    SCHED_FIFO
#define CLOCK CLOCK_MONOTONIC
#define INTERVAL_NS       (10 * 1000 * 1000)

static long tickCnt = 0;

static long calcTimeDiff(struct timespec const* t1, struct timespec const* t2)
{
  long diff = t1->tv_nsec - t2->tv_nsec;
  diff += 1000000000 * (t1->tv_sec - t2->tv_sec);
  return diff;
}

static void updateWakeTime(struct timespec* time)
{
  uint64_t nanoSec = time->tv_nsec;
  struct timespec currentTime;

  clock_gettime(CLOCK, &currentTime);
  while (calcTimeDiff(time, &currentTime) <= 0)
  {
    nanoSec = time->tv_nsec;
    nanoSec += INTERVAL_NS;
    time->tv_nsec = nanoSec % 1000000000;
    time->tv_sec += nanoSec / 1000000000;
  }
}

static void* tickThread(void *param)
{
  struct timespec sleepStart;
  struct timespec currentTime;
  struct timespec wakeTime;
  long sleepTime;
  long wakeDelay;

  clock_gettime(CLOCK, &wakeTime);
  wakeTime.tv_sec += 2;
  wakeTime.tv_nsec = 0;

  while (1)
  {
    clock_gettime(CLOCK, &sleepStart);
    clock_nanosleep(CLOCK, TIMER_ABSTIME, &wakeTime, NULL);
    clock_gettime(CLOCK, &currentTime);
    sleepTime = calcTimeDiff(&currentTime, &sleepStart);
    wakeDelay = calcTimeDiff(&currentTime, &wakeTime);
    if (wakeDelay > INTERVAL_NS)
    {
      printf("sleep req=%-ld.%-ld start=%-ld.%-ld curr=%-ld.%-ld sleep=%-ld delay=%-ld\n",
          (long) wakeTime.tv_sec, (long) wakeTime.tv_nsec,
          (long) sleepStart.tv_sec, (long) sleepStart.tv_nsec,
          (long) currentTime.tv_sec, (long) currentTime.tv_nsec,
          sleepTime, wakeDelay);
    }
    tickCnt += 1;
    updateWakeTime(&wakeTime);
  }
}

static void* workerThread(void *param)
{
  while (1)
  {
  }
}

static int createThread(char const* funcName, ThreadFun* func, int prio)
{
  pthread_t tid = 0;
  pthread_attr_t threadAttr;
  struct sched_param schedParam;

  printf("thread create func=%s prio=%d\n", funcName, prio);

  pthread_attr_init(&threadAttr);
  pthread_attr_setschedpolicy(&threadAttr, SCHEDULER_POLICY);
  pthread_attr_setinheritsched(&threadAttr, PTHREAD_EXPLICIT_SCHED);
  schedParam.sched_priority = prio;
  pthread_attr_setschedparam(&threadAttr, &schedParam);

  if (pthread_create(&tid, &threadAttr, func, NULL) != 0)
  {
    return -1;
  }

  printf("thread created func=%s prio=%d\n", funcName, prio);
  return 0;
}
#define CREATE_THREAD(func,prio)  createThread(#func,func,prio)


int main(int argc, char*argv[])
{
  int minPrio = sched_get_priority_min(SCHEDULER_POLICY);
  int maxPrio = sched_get_priority_max(SCHEDULER_POLICY);
  int prioRange = maxPrio - minPrio;

  CREATE_THREAD(tickThread, maxPrio);
  CREATE_THREAD(workerThread, minPrio + prioRange / 4);
  sleep(10);
  printf("%ld ticks\n", tickCnt);
}

Is something wrong in my code sample?

Is there a better (more reliable) way to create periodic threads?

Gunman
  • 11
  • 1
  • 3
  • Don't have that many threads. Manage some small [thread pool](https://en.wikipedia.org/wiki/Thread_pool), probably a fixed set of threads. See [time(7)](http://man7.org/linux/man-pages/man7/time.7.html). – Basile Starynkevitch Aug 30 '18 at 10:54
  • 3
    You're not using a real-time operating system, so you can expect real-time guarantees. – Toby Speight Aug 30 '18 at 10:54
  • I ran your code and it is not clear how to interpret the output. What is the expected and what is actual output? – Maxim Egorushkin Aug 30 '18 at 10:58
  • What kind of application and system are your developing? What will happen if the 500µs delay is missed or skipped or forgotten? Please **[edit](https://stackoverflow.com/posts/52095008/edit) your question** to improve it – Basile Starynkevitch Aug 30 '18 at 11:02
  • Implementing a PLC using Linux is not a great idea. (Nor is implementing a PLC in the year 2018, but that's another story...) There's something called RTLinux, but I have no idea how useful it is. – Lundin Aug 30 '18 at 12:51
  • @BasileStarynkevitch: I do not have many threads - currently 32 at most. – Gunman Aug 30 '18 at 12:52
  • 32 threads is often too much on a RaspberryPi 3B+, it has only 4 cores. – Basile Starynkevitch Aug 30 '18 at 12:55
  • 1
    Also this has major code smell: `1000000000 * (t1->tv_sec - t2->tv_sec)`. What makes you think `time_t` is large enough to contain the result, and why do you use signed `int` literals? And you store the result in a `long`... – Lundin Aug 30 '18 at 12:55
  • @MaximEgorushkin: The expected outputs of the application are just the traces for thread create and the number of ticks. If the sleep time exceets the cycle time (10 ms in the sample) it outputs also acutal time stamps. "req" is the reqested end of sleep, "start" is the begin of sleep, "curr" is the current time stamp, "sleep" is the actual sleep period and "delay" is the difference between the requested and the actual end of sleep. – Gunman Aug 30 '18 at 12:58
  • 1
    You should do time calculations in 64-bit nanosecond space, only converting to/from `struct timespec` when necessary. – Maxim Egorushkin Aug 30 '18 at 13:45
  • What is the output of `egrep 'clock |cpu:|resolution:' /proc/timer_list` on your system? – Maxim Egorushkin Aug 30 '18 at 14:04
  • Output of `egrep 'clock |cpu:|resolution:' /proc/timer_list` is cpu: 0 clock 0: .resolution: 1 nsecs clock 1: .resolution: 1 nsecs clock 2: .resolution: 1 nsecs clock 3: .resolution: 1 nsecs – Gunman Aug 31 '18 at 06:54

1 Answers1

3

For my application I have the requirement of accurate periodic threads with relative low cycle times (500 µs)

Probably too strong requirement. Linux is not a hard real-time OS.

I would suggest to have fewer threads (perhaps a small fixed set -only 2 or 3, organized in a thread pool; see this for an explanation, remembering that a RasberryPi3B+ has only 4 cores). You might prefer a single thread (think of a design around an event loop, inspired by continuation-passing style).

You probably don't need periodic threads. You need some periodic activity. They all might happen in the same thread. (the kernel is rescheduling tasks perhaps every 50 or 100 ms, even if it is capable of sleeping a smaller time, and if tasks get rescheduled very frequently -e.g. every millisecond- , their scheduling has a cost).

So read carefully time(7).

Consider using timer_create(2), or even better timerfd_create(2) used in an event loop around poll(2).

On a RaspberryPi, you won't have guaranteed 500µs delays. This is probably impossible (the hardware might not be powerful enough, and the Linux OS is not hard real-time). I feel your expectations are not reasonable.

Basile Starynkevitch
  • 223,805
  • 18
  • 296
  • 547
  • https://linux.die.net/man/7/time : _On a system that supports HRTs, the accuracy of sleep and timer system calls is no longer constrained by the jiffy, but instead can be as accurate as the hardware allows (microsecond accuracy is typical of modern hardware)._ – Maxim Egorushkin Aug 30 '18 at 14:06
  • Yes, but the scheduler is probably not rescheduling threads that often, and OP has 32 threads on a 4 core hardware. They probably won't be rescheduled every millisecond. – Basile Starynkevitch Aug 30 '18 at 14:08
  • Thanks for the hint with timerfd_create - timer_create I've already tried before. Unfortunately both with the same result - also with the usage of timers, there is a delay of about 50ms each second. – Gunman Aug 30 '18 at 14:10
  • The HRT fires in the kernel and makes the woken up thread runnable immediately. If there are no running threads or the woken thread has a higher priority it runs immediately. Scheduler tick is irrelevant here. – Maxim Egorushkin Aug 30 '18 at 14:11
  • Ok, but are you so sure that "immediately" is negligible w.r.t. 500µs? – Basile Starynkevitch Aug 30 '18 at 15:43
  • I just wanted to add that the closest thing to true hard real-time in Linux is using the relatively new SCHED_DEADLINE scheduler. – Erik Alapää Aug 31 '18 at 07:43