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

µs-precision wait in C for linux that does not put program to sleep?

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

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

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

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

在 Linux 2.something 中有一个观点,其中 nanosleep 对根据 SCHED_FIFO 或 SCHED_RR 等实时策略调度的进程具有特定行为当指定的睡眠低于最小时钟分辨率或粒度时忙等待,但它已被删除。 (试试 man 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;
}