Каков наилучший способ создания периодических потоков Linux в C - PullRequest
0 голосов
/ 30 августа 2018

Для моего приложения у меня есть требование точных периодических потоков с относительно малым временем цикла (500 мкс).
В частности, приложение представляет собой систему времени выполнения PLC . Его цель - запустить приложение, разработанное пользователем ПЛК. Такие приложения организованы в программы и периодические задачи - каждая задача имеет свое время цикла и приоритет.
Обычно приложение работает в системах с операционными системами реального времени (например, vxWorks или Linux с исправлением RT).

В настоящее время периодические задачи реализуются через clock_nanosleep. К сожалению, фактическое время сна clock_nanosleep нарушается другими потоками - даже с более низким приоритетом. Раз в секунду время ожидания превышается примерно на 50 мс. Я наблюдал это на Debian 9.5, на RaspberryPi, а также на ARM-Linux с Preemt-RT.

Вот пример, который показывает это поведение:

#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 голосов
/ 30 августа 2018

Для моего приложения у меня есть требование точных периодических потоков с относительно малым временем цикла (500 мкс)

Вероятно, слишком сильное требование. Linux не является жесткой ОС реального времени.

Я бы предложил иметь меньше потоков (возможно, небольшой фиксированный набор - только 2 или 3, организованный в пул потоков ; см. это для объяснения, помня, что RasberryPi3B + имеет только 4 ядра). Вы можете предпочесть один поток (например, дизайн вокруг цикла событий, вдохновленный продолжением стиля ).

Возможно, вам не нужны периодические темы. Вам нужна периодическая активность. Все они могут происходить в том же потоке . (ядро перепланирует задачи, возможно, каждые 50 или 100 мс, даже если оно способно спать меньше времени, и если задачи перепланируются очень часто, например, каждую миллисекунду, их планирование требует затрат).

Так что читайте внимательно время (7) .

Попробуйте использовать timer_create (2) или даже лучше timerfd_create (2) , используемое в цикле событий вокруг poll (2) .

На RaspberryPi у вас не будет гарантированных 500 мкс задержек. Это, вероятно, невозможно (аппаратное обеспечение может быть недостаточно мощным, а ОС Linux не является жесткой в ​​режиме реального времени). Я чувствую, что ваши ожидания не являются разумными.

...