我真的想在C程序中实现一个25μs的延迟,我正在编写通过RPi 3读取传感器。我已经使用了nanosleep()和usleep(),但准确性似乎有点偏离 - 可能是因为程序将线程时间交给其他程序,然后必须等待它们完成。我使用'nice -n -20'来确保优先级,但它似乎仍然不如我想要的那么准确。我也尝试了一个for循环,但不能完全确定时钟滴答:获得25μs所需的for-loop-count比率(我对这一切都很新)......或者gcc正在优化空荡荡的循环变成了遗忘?
无论如何,有人能够指向我的microDelay()函数或类似的东西? (我花了几个小时谷歌搜索和试验,但似乎无法找到我正在寻找的东西)。谢谢!
在没有硬件支持的情况下,传统的多任务操作系统几乎不可能实现这种低分辨率(小于1ms),但有一种软件技术可以帮助您。 (我之前测试过)
软件延迟循环不是准确的解决方案,因为操作系统的调度程序会抢占进程。但是你可以用RT_PREEMPT修补你的内核并通过CONFIG_RT_PREEMPT启用它,现在你有一个具有实时调度支持的内核,实时内核让你运行一个具有实时优先级的进程,实时优先级的进程运行直到它希望没人能抢占它,因此,如果您运行延迟循环,则该过程不会被操作系统抢占,因此您可以使用这些循环创建准确的延迟。
在Linux 2.something中有一点,nanosleep对于在SCHED_FIFO或SCHED_RR等实时策略下调度的进程具有特定的行为,当指定的睡眠低于最小时钟分辨率或粒度时,它将忙于等待,但它是除去。 (尝试男人nanosleep,我相信这种行为在那里提到)。
我需要有一个更精确的睡眠间隔,所以我写了自己的版本来调用这些特殊情况。在目标机器上,我能够得到<10μs的延迟,只有偶尔的突然显示(参见代码中的注释)。
请记住,对于非实时调度策略,如果您的应用程序尝试以低于最小时钟分辨率的方式休眠,则可能仍会被抢占。
这是我编写的一个测试程序,繁忙的循环调用clock_gettime(),因此它知道什么时候唤醒:
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <unistd.h>
#include <time.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <signal.h>
void usage(char *name, const char *msg)
{
if ( msg )
fprintf(stderr,"%s\n",msg);
fprintf(stderr,"Usage: %s, -s<sleepNanoseconds> [-c<loopCount>] [-e]\n", name);
fprintf(stderr," -s<sleepNanoseconds> is the number nanoseconds to busy-sleep, usually < 60000\n");
fprintf(stderr," -c<loopCount> the number of loops to execute the busy sleep, default 1000000 \n");
fprintf(stderr," -e do not calculate min, max and avg. only elapsed time \n");
}
# define tscmp(a, b, CMP) \
(((a)->tv_sec == (b)->tv_sec) ? \
((a)->tv_nsec CMP (b)->tv_nsec) : \
((a)->tv_sec CMP (b)->tv_sec))
# define tsadd(a, b, result) \
do { \
(result)->tv_sec = (a)->tv_sec + (b)->tv_sec; \
(result)->tv_nsec = (a)->tv_nsec + (b)->tv_nsec; \
if ((result)->tv_nsec >= 1000000000) \
{ \
++(result)->tv_sec; \
(result)->tv_nsec -= 1000000000; \
} \
} while (0)
# define tssub(a, b, result) \
do { \
(result)->tv_sec = (a)->tv_sec - (b)->tv_sec; \
(result)->tv_nsec = (a)->tv_nsec - (b)->tv_nsec; \
if ((result)->tv_nsec < 0) { \
--(result)->tv_sec; \
(result)->tv_nsec += 1000000000; \
} \
} while (0)
///////////////////////////////////////////////////////////////////////////////
///
/// busySleep uses clock_gettime and a elapsed time check to provide delays
/// for less than the minimum sleep resolution (~58 microseconds). As tested
/// on a XEON E5-1603, a sleep of 0 yields a delay of >= 375 Nsec, 1-360 about
/// 736 Nsec, 370-720 a little more than 1 Usec, 720-1080 a little less than
/// 1.5 Usec and generally it's pretty linear for delays of 10 Usec on up in
/// increments of 10 Usec, e.g., 10 Usec =~ 10.4, 20 Usec =~ 20.4 and so on.
///
///////////////////////////////////////////////////////////////////////////////
int busySleep( uint32_t nanoseconds )
{
struct timespec now;
struct timespec then;
struct timespec start;
struct timespec sleep;
if ( nanoseconds > 999999999 )
{
return 1;
}
clock_gettime( CLOCK_MONOTONIC_RAW, &start);
now = start;
sleep.tv_sec = 0;
sleep.tv_nsec = nanoseconds;
tsadd( &start, &sleep, &then );
while ( tscmp( &now, &then, < ) )
{
clock_gettime( CLOCK_MONOTONIC_RAW, &now);
}
return 0;
}
int main(int argc, char **argv)
{
uint32_t sleepNsecs = 1000000000;
uint32_t loopCount = 1000000;
bool elapsedOnly = false;
uint32_t found = 0;
int opt;
if ( argc < 2 )
{
sleepNsecs = atol(argv[1]);
usage( argv[0], "Required options were not given" );
return 1;
}
while ( (opt = getopt(argc, argv, "s:d:e")) != -1 )
{
switch ( opt )
{
case 's':
sleepNsecs = strtoul(optarg,NULL,0);
break;
case 'd':
loopCount = strtoul(optarg,NULL,0);
break;
case 'e':
elapsedOnly = true;
break;
default:
usage(argv[0],"Error: unrecognized option\n");
return 1;
}
found++;
}
if ( found < 1 )
{
usage( argv[0], "Invalid command line." );
return 1;
}
if ( sleepNsecs > 999999999 )
{
usage( argv[0], "Sleep nanoseconds must be less than one second." );
return 1;
}
printf("sleepNsecs set to %d\n",sleepNsecs);
struct timespec start;
struct timespec now;
struct timespec prev;
struct timespec elapsed;
struct timespec trem;
uint64_t count = 0;
int64_t sum = 0;
int64_t min = 99999999;
int64_t max = 0;
clock_gettime( CLOCK_MONOTONIC_RAW, &start);
now = start;
prev = start;
//while ( tscmp( &now, &then, < ) )
for ( uint32_t i = 0; i < loopCount; i++ )
{
int rc = busySleep( sleepNsecs );
if ( rc != 0 )
{
fprintf( stderr, "busySleep returned an error!\n" );
return 1;
}
if ( ! elapsedOnly )
{
clock_gettime( CLOCK_MONOTONIC_RAW, &now);
tssub( &now, &prev, &trem );
min = ( min < trem.tv_nsec ? min : trem.tv_nsec );
max = ( max > trem.tv_nsec ? max : trem.tv_nsec );
count++;
sum += trem.tv_nsec;
prev = now;
}
}
if ( ! elapsedOnly )
{
printf("Min: %lu, Max: %lu, avg %lu, count %lu\n",min,max,(sum / count),count);
}
else
{
clock_gettime( CLOCK_MONOTONIC_RAW, &now);
tssub( &now, &start, &elapsed );
double secs = ((double)elapsed.tv_sec) + ((double) elapsed.tv_nsec / (double)1e9 );
fprintf( stderr, "Elapsed time of %ld.%09ld for %u sleeps of duration %u, avg. = %.9f Ns\n",
elapsed.tv_sec, elapsed.tv_nsec, loopCount, sleepNsecs, (secs / loopCount) );
}
return 0;
}