μs-precision在C中等待linux,它不会让程序进入睡眠状态?

问题描述 投票:0回答:2

我真的想在C程序中实现一个25μs的延迟,我正在编写通过RPi 3读取传感器。我已经使用了nanosleep()和usleep(),但准确性似乎有点偏离 - 可能是因为程序将线程时间交给其他程序,然后必须等待它们完成。我使用'nice -n -20'来确保优先级,但它似乎仍然不如我想要的那么准确。我也尝试了一个for循环,但不能完全确定时钟滴答:获得25μs所需的for-loop-count比率(我对这一切都很新)......或者gcc正在优化空荡荡的循环变成了遗忘?

无论如何,有人能够指向我的microDelay()函数或类似的东西? (我花了几个小时谷歌搜索和试验,但似乎无法找到我正在寻找的东西)。谢谢!

c sensor raspberry-pi3
2个回答
4
投票

在没有硬件支持的情况下,传统的多任务操作系统几乎不可能实现这种低分辨率(小于1ms),但有一种软件技术可以帮助您。 (我之前测试过)

软件延迟循环不是准确的解决方案,因为操作系统的调度程序会抢占进程。但是你可以用RT_PREEMPT修补你的内核并通过CONFIG_RT_PREEMPT启用它,现在你有一个具有实时调度支持的内核,实时内核让你运行一个具有实时优先级的进程,实时优先级的进程运行直到它希望没人能抢占它,因此,如果您运行延迟循环,则该过程不会被操作系统抢占,因此您可以使用这些循环创建准确的延迟。


0
投票

在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;
}
© www.soinside.com 2019 - 2024. All rights reserved.