对于我的应用程序,我需要具有相对较低的循环时间(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, ¤tTime);
while (calcTimeDiff(time, ¤tTime) <= 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, ¤tTime);
sleepTime = calcTimeDiff(¤tTime, &sleepStart);
wakeDelay = calcTimeDiff(¤tTime, &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);
}
我的代码示例有问题吗?
是否有更好(更可靠)的方式来创建定期线程?
答案 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的实时性也不强)。我觉得您的期望不合理。