在C中创建定期Linux线程的最佳方法是什么

时间:2018-08-30 10:41:02

标签: c linux timer pthreads

对于我的应用程序,我需要具有相对较低的循环时间(500 µs)的准确的周期性线程。
特别是该应用程序是一个运行时系统 PLC。 目的是运行由PLC用户开发的应用程序。 此类应用程序按程序和定期任务进行组织-每个任务都有自己的循环时间和优先级。
通常,应用程序在具有实时OS的系统上运行(例如vxWorks或带有RT补丁的Linux)。

当前,定期任务是通过clock_nanosleep实现的。 不幸的是clock_nanosleep的实际睡眠时间受到其他线程的干扰-即使优先级较低。 每秒一次,睡眠时间超过大约50毫秒。 我已经在Debian 9.5,RaspberryPi以及带有Preemt-RT的ARM-Linux上观察到了这一点。

这是一个示例,显示了此行为:

#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);
}

我的代码示例有问题吗?

是否有更好(更可靠)的方式来创建定期线程?

1 个答案:

答案 0 :(得分:1)

  

对于我的应用程序,我需要具有相对较低的循环时间(500 µs)的准确的周期性线程

可能是太强的要求。 Linux不是硬实时操作系统。

我建议使用更少的线程(也许是一个小的固定集-仅2或3,以thread pool进行组织;有关说明,请参见this,记住RasberryPi3B+有只有4个核心)。您可能更喜欢单线程(以continuation-passing style为灵感,围绕事件循环进行设计)。

您可能不需要定期线程。您需要一些定期活动。它们都可能发生在相同线程中。 (内核可能每50或100 ms重新安排任务,即使它能够休眠更短的时间,并且如果任务频繁地重新安排(例如,每毫秒一次),它们的安排也要付出代价。)

请仔细阅读time(7)

考虑使用timer_create(2),甚至在timerfd_create(2)周围的事件循环中使用更好的poll(2)

在RaspberryPi上,您不会得到保证 500µs的延迟。这可能是不可能的(硬件功能可能不够强大,Linux OS的实时性也不强)。我觉得您的期望不合理。

相关问题