如何让计算机睡眠数微秒?

12

考虑以下代码:

#include <stdio.h>
#include <time.h>
#include <math.h>

// Compile with gcc -lrt -lm -o test_clock test_clock.c

#define CLOCK CLOCK_MONOTONIC

int main(int argc, char** argv) {
    double temp, elapsed;
    int j;
    struct timespec requestStart, requestEnd, req;

    // Pseudo-sleep
    clock_gettime(CLOCK, &requestStart);
    temp = 0;
    for(j=0; j < 40; j++)
        temp += sin(j);
    clock_gettime(CLOCK, &requestEnd);
    elapsed = ( requestEnd.tv_sec - requestStart.tv_sec ) / 1e-6
                 + ( requestEnd.tv_nsec - requestStart.tv_nsec ) / 1e3;
    printf("Elapsed: %lf us\n", elapsed);

    // Nanosleep
    clock_gettime(CLOCK, &requestStart);
    req.tv_nsec = 5000;
    req.tv_sec = 0;
    clock_nanosleep(CLOCK, 0, &req, NULL);
    clock_gettime(CLOCK, &requestEnd);
    elapsed = ( requestEnd.tv_sec - requestStart.tv_sec ) / 1e-6
                 + ( requestEnd.tv_nsec - requestStart.tv_nsec ) / 1e3;

    printf("Elapsed: %lf us\n", elapsed);

}

在我的2.6.32系统上,结果是:

Elapsed: 5.308000 us
Elapsed: 69.142000 us

我同意这可能是因为nanosleep()要求内核重新安排进程的原因。我该如何避免这种情况?我想保持对CPU的所有权,只是在精确的时间间隔内闲置。


你同时使用了Linux和实时操作系统这两个标签,为什么?它们是完全不同的东西。 - David Heffernan
5
@David:并不是说没有任何疯狂的人在lunux-rt上工作,或者什么的...等等。 - ephemient
我很感兴趣:具体是什么背景?你需要那种准确性做什么? - asdf
7
实时反馈控制磁约束等离子体。 - Nikratio
9个回答

12
如果您希望您的应用程序能够尽可能精确地“休眠”,请首先将您的应用程序置于实时条件下:
  • 为您的程序/线程使用实时调度器类:SCHED_FIFOSCHED_RR
  • 提高程序/线程的优先级
  • 如果您要“休眠”的时间少于内核处理的最小时间,请手动忙等待
请参考:http://www.drdobbs.com/184402031和这个问题:nanosleep high cpu usage?

4
有了实时优先级,nanosleep 就能按我想要的方式工作。谢谢! - Nikratio
SCHED_DEADLINE来了!http://lwn.net/Articles/356576/ http://lwn.net/Articles/396634/ 希望在Linux上改善实时性能。 (如果您对SCHED_FIFO / SCHED_RR不小心处理,可能会导致整个机器死机。) - ephemient
我真的很想知道如何启用SCHED_RR软实时轮询调度程序,所以我自己研究了一下,并在这里添加了一个答案。链接 - Gabriel Staples

8

操作系统的调度程序不会像“噢,把这个线程从处理器上拿下来86个时钟周期,然后再放回去”这样做。

你放弃了处理器,你就放弃了处理器。操作系统会在它感觉合适的时候把你放回去。很可能你得等到其他正在运行的东西放弃处理器之前才能悄悄地回来。


1
@Nikratio:如果您需要在特定时间精确执行,则基本上需要实时操作系统。否则,您无法保证不会在任何给定点被交换处理器。为什么您需要在此时精确运行? - Anon.
@Anon:好的,我明白你的意思了。我把它理解为“你必须等待”,因为有些根本原因阻止了它更早地发生。你的意思是,“你必须等待”,实际上你不必等待更长时间,因为大多数计算机大部分时间都处于空闲状态? - Steve Jessop
@Anon,如果你有足够的核心,那不应该是一个理由。 - bestsss
@bestsss:或者你可以运行一个实时操作系统。你知道,就像他们登上月球时使用的那个一样。我很确定那不需要一个8核怪物。 - Anon.
2
@bestsss:这绝对是一个多任务系统。也许你应该阅读一下阿波罗11号着陆的记述,其中一个软件组件(着陆雷达)无法跟进并满足其截止日期,但该系统能够使所有其他具有实时要求的组件平稳运行。 - Anon.
显示剩余8条评论

4

如何配置Linux的SCHED_RR软实时轮询调度程序,以便clock_nanosleep()具有更低的睡眠分辨率,最低可达约4微秒,最低下降至约55微秒,具体取决于您的硬件

问题概述

OP正确地使用了clock_nanosleep(CLOCK_MONOTONIC, 0, &requested_time, NULL)的等效方法,尝试睡眠精确的5000纳秒或5微秒。请参见:https://man7.org/linux/man-pages/man2/clock_nanosleep.2.html和问题。然而,实际睡眠时间为69微秒,尽管被命令睡眠5微秒。为什么?

我遇到了同样的问题。我使用clock_nanosleep()命令让程序休眠1纳秒0.001微秒),但它平均休眠55微秒。这似乎是可能的最小休眠时间间隔。为什么?我们能否改进这个情况?

答案概述

  1. 我经过实证发现,在我的x86-64 Linux Ubuntu系统上,使用默认的Linux调度程序运行时,最小的nanosleep时间间隔可能~55000 ns (~55 us)。如果您命令的睡眠时间少于该时间,则会睡眠该时间。然而,这是使用Linux的默认常规SCHED_OTHER/SCHED_NORMAL“默认Linux时间共享”调度程序时的情况。
    1. 在此处阅读有关各种Linux调度程序类型的信息:https://man7.org/linux/man-pages/man7/sched.7.html
  2. 您可以通过使用SCHED_RR循环轮换软实时调度程序(建议)SCHED_FIFO先进先出软实时调度程序,将此改进为最小睡眠时间为~4000 ns (~4 us)

以下是我sleep_nanosleep_minimum_time_interval.c程序的测试结果:

  1. SCHED_OTHER/SCHED_NORMAL是Linux系统默认的时间共享调度程序:
    1. 可能的最小纳秒休眠时间:约55000纳秒(约55微秒)
  2. SCHED_RR是轮询软实时调度程序,优先级为1
    1. 可能的最小纳秒休眠时间:约4000纳秒(约4微秒)
  3. SCHED_RR是轮询软实时调度程序,优先级为99
    1. 可能的最小纳秒休眠时间:约4000纳秒(约4微秒)(与上述相同)
  4. SCHED_FIFO是先进先出的软实时调度程序,优先级为1
    1. 可能的最小纳秒休眠时间:约4000纳秒(约4微秒)(与上述相同)
  5. SCHED_FIFO是先进先出的软实时调度程序,优先级为99
    1. 可能的最小纳秒休眠时间:约4000纳秒(约4微秒)(与上述相同)

正如您所看到的,只需从SCHED_OTHER/SCHED_NORMAL 非实时 Linux调度器切换到软实时 Linux调度器(例如SCHED_FIFOSCHED_RR)并设置任何优先级级别,您就可以立即获得即时和巨大的(55000/4000 = 13.75倍)改进。根据我的测试,选择这两个调度程序中的哪一个以及选择哪个优先级不那么重要。您必须知道想要获得什么优先级的线程以及何时才能调整自己的线程。作为入门者,将具有非常短的睡眠时间(通常为<1~10 ms)的线程设置为:

  1. SCHED_RR软实时轮询调度程序,并且:
  2. 更高的优先级。
作为入门,对于所有实时线程,请使用最低优先级的SCHED_RR1,然后根据需要逐步提高它们的优先级。不要一开始就选择非常高的优先级并阻止高优先级任务。只在需要时提高线程或进程的优先级。
如何设置Linux“策略”(调度程序)和“优先级” 这个来自@ Yann Droneaud的答案很有帮助,但它没有展示如何使用实时调度程序。然而,它链接的文章解释得更多(但有一些错误和疏忽),这个Ask Ubuntu Q&A也是如此。我研究了这两个来源,以了解我以下介绍的选项。
  1. Dr. Dobb's: Linux下的软实时编程
  2. Ask Ubuntu: 如何从命令行运行一个使用SCHED_RR策略的程序?

以下是如何设置Linux调度器的“策略”(scheduler)和“优先级”:

  1. Option 1 (easiest): from the command-line, call your program with the chrt "change real-time" command:
    # Call your program with `chrt` to specify the scheduler at call-time!
    
    # General format:
    sudo chrt [--scheduler_policy] <priority> <command>
    # OR
    sudo chrt [--scheduler_policy] -p <priority> <pid>
    
    # Examples of the first form above: 
    
    # call `my_prog` with SCHED_RR with lowest priority of 1
    sudo chrt --rr 1 my_prog
    # call `my_prog` with SCHED_RR with highest priority of 99
    sudo chrt --rr 99 my_prog
    # call `my_prog` with SCHED_FIFO with lowest priority of 1
    sudo chrt --fifo 1 my_prog
    # call `my_prog` with SCHED_FIFO with highest priority of 99
    sudo chrt --fifo 99 my_prog
    
    To see what priority values are available for a given scheduler, run chrt --max. If on an embedded Linux system with the BusyBox implementation of chrt, use chrt -m instead. Here is the run and output on my x86-64 Linux Ubuntu machine:
    $ chrt --max
    SCHED_OTHER min/max priority    : 0/0
    SCHED_FIFO min/max priority : 1/99
    SCHED_RR min/max priority   : 1/99
    SCHED_BATCH min/max priority    : 0/0
    SCHED_IDLE min/max priority : 0/0
    SCHED_DEADLINE min/max priority : 0/0
    
    As you can see, for both SCHED_FIFO and SCHED_RR, 1/99 shows that the lowest priority is 1 and the highest priority is 99.
  2. Option 2: in your code, set your running process ID (PID) to a desired scheduler policy and priority. I have provided 5 thorough demos related to all this. See "Demo 1" through "Demo 5" inside function set_scheduler() in my sleep_nanosleep_minimum_time_interval.c test file:
    int retcode; // return code to check for errors from function calls
    
    // -------------------------------------------------------------------------
    // Demo 1: use `sched_setscheduler()` to change the current process's
    // scheduler "policy" **and** "priority".
    // See:
    // 1. https://man7.org/linux/man-pages/man2/sched_setscheduler.2.html
    // 1. All `errno` errors: https://man7.org/linux/man-pages/man3/errno.3.html
    // 1. `mlockall()`: https://man7.org/linux/man-pages/man2/mlock.2.html
    // 1. *****https://www.drdobbs.com/soft-real-time-programming-with-linux/184402031?pgno=1
    // -------------------------------------------------------------------------
    {
        const struct sched_param priority_param =
        {
            // the priority must be from 1 (lowest priority) to 99
            // (highest priority) for the `SCHED_FIFO` AND `SCHED_RR`
            // (round robin) scheduler policies; see:
            // https://man7.org/linux/man-pages/man7/sched.7.html
            .sched_priority = 1,
        };
    
        // Note: use `0` as the `pid` (1st param) to indicate the PID of this
        // running process
        retcode = sched_setscheduler(0, SCHED_RR, &priority_param);
        if (retcode == -1)
        {
            printf("ERROR: in file %s: %i: Failed to set scheduler. "
                   "errno = %i: %s.\n",
                __FILE__, __LINE__, errno, strerror(errno));
            if (errno == EPERM)  // Error: Permissions
            {
                printf("  You must use `sudo` or run this program as root to "
                       "have proper privileges!\n");
            }
        }
        else
        {
            printf("`sched_setscheduler()` successful.\n");
        }
    
        // Memory lock: also lock the memory into RAM so that the kernel is NOT
        // allowed to move it into the swap space, which would otherwise be a
        // slow operation and break the "real-time" characteristics of this
        // process.
        // See:
        // 1. https://man7.org/linux/man-pages/man2/mlock.2.html
        // 1. This tutorial/blog post:
        //    https://www.drdobbs.com/soft-real-time-programming-with-linux/184402031?pgno=1
        retcode = mlockall(MCL_CURRENT | MCL_FUTURE | MCL_ONFAULT);
        if (retcode == -1)
        {
            printf("ERROR: in file %s: %i: Failed to lock memory into RAM. "
                   "errno = %i: %s.\n",
                __FILE__, __LINE__, errno, strerror(errno));
            if (errno == EPERM)  // Error: Permissions
            {
                printf("  You must use `sudo` or run this program as root to "
                       "have proper privileges!\n");
            }
        }
        else
        {
            printf("`mlockall()` successful.\n");
        }
    } // end of Demo 1
    
    // -------------------------------------------------------------------------
    // Demo 2: use `sched_setparam()` to change **only** the "priority" of the
    // running process.
    // See:
    // 1. https://man7.org/linux/man-pages/man2/sched_setparam.2.html
    // 1. https://www.drdobbs.com/soft-real-time-programming-with-linux/184402031?pgno=1
    //      1. "Listing 1" demo code: this code shows how to raise a child
    //      priority, lower a child priority, and raise a priority in order to
    //      obtain a mutex lock which otherwise it would never be able to
    //      obtain if a higher-priority process has it:
    //      https://www.drdobbs.com/soft-real-time-programming-with-linux/184402031?pgno=2
    // -------------------------------------------------------------------------
    {
        const int new_priority = 2;
        const struct sched_param priority_param =
        {
            // the priority must be from 1 (lowest priority) to 99
            // (highest priority) for the `SCHED_FIFO` AND `SCHED_RR`
            // (round robin) scheduler policies; see:
            // https://man7.org/linux/man-pages/man7/sched.7.html
            .sched_priority = new_priority,
        };
        // Note: use `0` as the `pid` (1st param) to indicate the PID of this
        // running process
        retcode = sched_setparam(0, &priority_param);
        if (retcode == -1)
        {
            printf("ERROR: in file %s: %i: Failed to set priority. "
                   "errno = %i: %s.\n",
                __FILE__, __LINE__, errno, strerror(errno));
            // NB: through testing, it seems that `errno` gets set to 22
            // (EINVAL), if `sudo` is not used to run this code. That seems
            // like a compiler bug, because it should be `EPERM`, but let's
            // just handle it as though it was `EPERM`.
            if (errno == EPERM || errno == EINVAL)  // Error: Permissions
            {
                printf("  You must use `sudo` or run this program as root to "
                       "have proper privileges!\n");
            }
        }
        else
        {
            printf("`sched_setparam()` successful.\n");
        }
    } // end of Demo 2
    
    // -------------------------------------------------------------------------
    // [THIS IS MY PREFERRED TECHNIQUE FOR GENERAL USE]  <==================
    // Demo 3 (the pthread version of Demo 1): if using pthreads: use
    // `pthread_setschedparam()` to change the current thread's scheduler
    // "policy" and "priority".
    // See:
    // 1. https://man7.org/linux/man-pages/man3/pthread_setschedparam.3.html
    // 1. https://man7.org/linux/man-pages/man3/pthread_self.3.html
    // 1. https://www.drdobbs.com/soft-real-time-programming-with-linux/184402031?pgno=1
    // 1. https://askubuntu.com/a/1129915/327339
    // -------------------------------------------------------------------------
    {
        pthread_t this_thread = pthread_self();
        const struct sched_param priority_param =
        {
            // the priority must be from 1 (lowest priority) to 99
            // (highest priority) for the `SCHED_FIFO` AND `SCHED_RR`
            // (round robin) scheduler policies; see:
            // https://man7.org/linux/man-pages/man7/sched.7.html
            .sched_priority = 1,
        };
        retcode = pthread_setschedparam(this_thread, SCHED_RR, &priority_param);
        if (retcode != 0)
        {
            printf("ERROR: in file %s: %i: Failed to set pthread scheduler. "
                   "retcode = %i: %s.\n",
                    __FILE__, __LINE__, retcode, strerror(retcode));
            if (retcode == EPERM)  // Error: Permissions
            {
                printf("  You must use `sudo` or run this program as root to "
                       "have proper privileges!\n");
            }
        }
        else
        {
            printf("`pthread_setschedparam()` successful.\n");
        }
    
        // Memory lock: also lock the memory into RAM to prevent slow operations
        // where the kernel puts it into swap space. See notes above.
        retcode = mlockall(MCL_CURRENT | MCL_FUTURE | MCL_ONFAULT);
        if (retcode == -1)
        {
            printf("ERROR: in file %s: %i: Failed to lock memory into RAM. "
                   "errno = %i: %s.\n",
                __FILE__, __LINE__, errno, strerror(errno));
            if (errno == EPERM)  // Error: Permissions
            {
                printf("  You must use `sudo` or run this program as root to "
                       "have proper privileges!\n");
            }
        }
        else
        {
            printf("`mlockall()` successful.\n");
        }
    } // end of Demo 3
    
    // -------------------------------------------------------------------------
    // Demo 4 (the pthread version of Demo 2): if using pthreads: use
    // `pthread_setschedprio()` to change only the current thread's "priority".
    // See:
    // 1. https://man7.org/linux/man-pages/man3/pthread_setschedprio.3.html
    // -------------------------------------------------------------------------
    {
        pthread_t this_thread = pthread_self();
    
        // the priority must be from 1 (lowest priority) to 99
        // (highest priority) for the `SCHED_FIFO` AND `SCHED_RR`(round robin)
        // scheduler policies; see:
        // https://man7.org/linux/man-pages/man7/sched.7.html
        const int priority = 3;
        retcode = pthread_setschedprio(this_thread, priority);
        if (retcode != 0)
        {
            printf("ERROR: in file %s: %i: Failed to set pthread priority. "
                   "retcode = %i: %s.\n",
                    __FILE__, __LINE__, retcode, strerror(retcode));
            // NB: through testing, it seems that `pthread_setschedprio
            // ()` returns 22(EINVAL), if `sudo` is not used to run this code.
            // That seems like a compiler bug, because it should be `EPERM`,
            // but let's just handle it as though it was `EPERM`.
            if (retcode == EPERM || retcode == EINVAL)  // Error: Permissions
            {
                printf("  You must use `sudo` or run this program as root to "
                       "have proper privileges!\n");
            }
        }
        else
        {
            printf("`pthread_setschedprio()` successful.\n");
        }
    } // end of Demo 4
    
    // -------------------------------------------------------------------------
    // Demo 5 (create a pthread with the desired scheduler **policy**
    // and **priority** at creation time): if using pthreads: use
    // `pthread_attr_setschedpolicy()` and `pthread_attr_setschedparam()` to
    // set an initial scheduler **policy** and **priority** at the time of
    // thread creation via `pthread_create()`. Don't forget to use
    // `pthread_attr_setinheritsched()` to force `pthread_create()` to use our
    // new settings instead of inheriting scheduler settings from the calling
    // thread! You should use `pthread_attr_init()` and `pthread_attr_destroy()`
    // as well to initialize and destroy the attributes object.
    // See:
    // 1. https://man7.org/linux/man-pages/man3/pthread_attr_init.3.html
    // 1. https://man7.org/linux/man-pages/man3/pthread_attr_setschedpolicy.3.html
    // 1. https://man7.org/linux/man-pages/man3/pthread_attr_setschedparam.3.html
    // 1. https://man7.org/linux/man-pages/man3/pthread_attr_setinheritsched.3.html
    // 1. https://man7.org/linux/man-pages/man3/pthread_create.3.html
    // 1. https://man7.org/linux/man-pages/man3/pthread_join.3.html
    // 1. https://www.drdobbs.com/soft-real-time-programming-with-linux/184402031?pgno=1
    //      1. "Listing 2" code which demonstrates some of this code below:
    //         https://www.drdobbs.com/soft-real-time-programming-with-linux/184402031?pgno=3
    // -------------------------------------------------------------------------
    {
        // 0. Memory lock: also lock the memory into RAM to prevent slow operations
        // where the kernel puts it into swap space. See notes above.
        retcode = mlockall(MCL_CURRENT | MCL_FUTURE | MCL_ONFAULT);
        if (retcode == -1)
        {
            printf("ERROR: in file %s: %i: Failed to lock memory into RAM. "
                   "errno = %i: %s.\n",
                __FILE__, __LINE__, errno, strerror(errno));
            if (errno == EPERM)  // Error: Permissions
            {
                printf("  You must use `sudo` or run this program as root to "
                       "have proper privileges!\n");
            }
        }
        else
        {
            printf("`mlockall()` successful.\n");
        }
    
        // 1. Create and initialize a pthread attribute object.
    
        pthread_attr_t pthread_attr;
        retcode = pthread_attr_init(&pthread_attr);
        if (retcode != 0)
        {
            printf("ERROR: `pthread_attr_init()` failed. "
                   "retcode = %i: %s.\n",
                   retcode, strerror(retcode));
        }
    
        // 2. Set the scheduler **policy** (scheduler type) for the next thread
        // to be created.
    
        // Set to RR (round robin) soft real-time scheduler.
        int scheduler_policy = SCHED_RR;
        retcode = pthread_attr_setschedpolicy(&pthread_attr, scheduler_policy);
        if (retcode != 0)
        {
            printf("ERROR: `pthread_attr_setschedpolicy()` failed. "
                   "retcode = %i: %s.\n",
                   retcode, strerror(retcode));
        }
    
        // 3. Set the scheduler **priority** for the next thread to be created.
    
        const struct sched_param priority_param =
        {
            // the priority must be from 1 (lowest priority) to 99
            // (highest priority) for the `SCHED_FIFO` AND `SCHED_RR`
            // (round robin) scheduler policies; see:
            // https://man7.org/linux/man-pages/man7/sched.7.html
            .sched_priority = 1,
        };
        retcode = pthread_attr_setschedparam(&pthread_attr, &priority_param);
        if (retcode != 0)
        {
            printf("ERROR: `pthread_attr_setschedparam()` failed. "
                   "retcode = %i: %s.\n",
                   retcode, strerror(retcode));
        }
    
        // 4. Set the scheduler inheritance attribute so that `pthread_create()`
        // will use the scheduler settings set above inside the `pthread_attr`
        // object rather than inheriting scheduler attributes from the calling
        // thread! If you don't call this function, the default behavior is for
        // `pthread_create()` to IGNORE your scheduler policy and priority
        // settings inside the `pthread_attr` object, and use the calling
        // threads scheduler policy and priority instead!
        retcode = pthread_attr_setinheritsched(&pthread_attr,
            PTHREAD_EXPLICIT_SCHED);
        if (retcode != 0)
        {
            printf("ERROR: `pthread_attr_setinheritsched()` failed. "
                   "retcode = %i: %s.\n",
                   retcode, strerror(retcode));
        }
    
        // 5. Create any number of new pthread (POSIX thread) threads with this
        // scheduler policy and priority set at thread creation time. Here is
        // a demo creating just one pthread.
    
        pthread_t new_thread;
        retcode = pthread_create(&new_thread, &pthread_attr,
            dummy_pthread_action, "new_thread");
        if (retcode != 0)
        {
            printf("ERROR: `pthread_create()` failed. "
                   "retcode = %i: %s.\n",
                   retcode, strerror(retcode));
            if (retcode == EPERM)  // Error: Permissions
            {
                printf("  You must use `sudo` or run this program as root to "
                       "have proper privileges!\n");
            }
        }
    
        // 6. Destroy the thread attribute object. When done using the
        // `pthread_attr_t` attribute object above to create any number of
        // pthreads you desire, destroy it, presumably to free up dynamic
        // memory and prevent memory leaks.
    
        retcode = pthread_attr_destroy(&pthread_attr);
        if (retcode != 0)
        {
            printf("ERROR: `pthread_attr_destroy()` failed. "
                   "retcode = %i: %s.\n",
                   retcode, strerror(retcode));
        }
    
        // 7. thread cleanup: wait for the `new_thread` to finish with its
        // task by joining with it to wait and then clean it up.
        // See: https://man7.org/linux/man-pages/man3/pthread_join.3.html
        const char* return_message;
        retcode = pthread_join(new_thread, (void**)&return_message);
        if (retcode != 0)
        {
            printf("ERROR: `pthread_join()` failed. "
                   "retcode = %i: %s.\n",
                   retcode, strerror(retcode));
        }
        else
        {
            printf("`pthread_join()` successful: return_message = \"%s\"\n",
                return_message);
        }
    } // end of Demo 5
    

另请参阅

  1. 我的其他文件:
    1. sleep_nanosleep.c
    2. sleep_nanosleep_minimum_time_interval.c
    3. timinglib_sleep_and_sleep_until.c
    4. timinglib.h
    5. timinglib.c
    6. timinglib_pthread_periodic_loop.c
      1. 我展示了如何使用SCHED_RR实时轮询调度程序和带有绝对时间标志的clock_nanosleep()进行10 kHz固定周期循环。
  2. [我的回答] pthread_create在pthread_attr_setschedparam下无法正常工作
  3. [我的回答] 如何从nanosleep()创建sleep_us()

SCHED_RR 循环调度实时调度程序下 sleep_nanosleep_minimum_time_interval.c 的输出:

请注意误差平均在4微秒左右(向右滚动一点):

eRCaGuy_hello_world/c$ gcc -Wall -Wextra -Werror -O3 -std=c17 sleep_nanosleep_minimum_time_interval.c timinglib.c -o bin/a -lm -pthread && time sudo bin/a
Attempt to sleep 1 ns per `clock_nanosleep()` call, 100000 times.
ts_requested.tv_sec  = 0
ts_requested.tv_nsec = 1
failure_cnt                                              =                0
average time required per `clock_nanosleep()` sleep call =             4124 ns;   error =        -4 **us**
minimum time for a `clock_nanosleep()` sleep call        =             2720 ns;   error =        -3 **us**
maximum time for a `clock_nanosleep()` sleep call        =            32544 ns;   error =       -33 **us**

Attempt to sleep 100 ns per `clock_nanosleep()` call, 100000 times.
ts_requested.tv_sec  = 0
ts_requested.tv_nsec = 100
failure_cnt                                              =                0
average time required per `clock_nanosleep()` sleep call =             4456 ns;   error =        -4 **us**
minimum time for a `clock_nanosleep()` sleep call        =             2682 ns;   error =        -3 **us**
maximum time for a `clock_nanosleep()` sleep call        =            29349 ns;   error =       -29 **us**

Attempt to sleep 1000 ns per `clock_nanosleep()` call, 100000 times.
ts_requested.tv_sec  = 0
ts_requested.tv_nsec = 1000
failure_cnt                                              =                0
average time required per `clock_nanosleep()` sleep call =             4096 ns;   error =        -3 **us**
minimum time for a `clock_nanosleep()` sleep call        =             2693 ns;   error =        -2 **us**
maximum time for a `clock_nanosleep()` sleep call        =            37962 ns;   error =       -37 **us**

Attempt to sleep 10000 ns per `clock_nanosleep()` call, 1000 times.
ts_requested.tv_sec  = 0
ts_requested.tv_nsec = 10000
failure_cnt                                              =                0
average time required per `clock_nanosleep()` sleep call =            13583 ns;   error =        -4 **us**
minimum time for a `clock_nanosleep()` sleep call        =            11991 ns;   error =        -2 **us**
maximum time for a `clock_nanosleep()` sleep call        =            29361 ns;   error =       -19 **us**

Attempt to sleep 100000 ns per `clock_nanosleep()` call, 1000 times.
ts_requested.tv_sec  = 0
ts_requested.tv_nsec = 100000
failure_cnt                                              =                0
average time required per `clock_nanosleep()` sleep call =           103944 ns;   error =        -4 **us**
minimum time for a `clock_nanosleep()` sleep call        =           102299 ns;   error =        -2 **us**
maximum time for a `clock_nanosleep()` sleep call        =           121937 ns;   error =       -22 **us**

Attempt to sleep 1000000 ns per `clock_nanosleep()` call, 1000 times.
ts_requested.tv_sec  = 0
ts_requested.tv_nsec = 1000000
failure_cnt                                              =                0
average time required per `clock_nanosleep()` sleep call =          1005035 ns;   error =        -5 **us**
minimum time for a `clock_nanosleep()` sleep call        =          1002823 ns;   error =        -3 **us**
maximum time for a `clock_nanosleep()` sleep call        =          1108260 ns;   error =      -108 **us**

Attempt to sleep 1000000003 ns per `clock_nanosleep()` call, 2 times.
ts_requested.tv_sec  = 1
ts_requested.tv_nsec = 3
failure_cnt                                              =                0
average time required per `clock_nanosleep()` sleep call =       1000008524 ns;   error =        -9 **us**
minimum time for a `clock_nanosleep()` sleep call        =       1000007190 ns;   error =        -7 **us**
maximum time for a `clock_nanosleep()` sleep call        =       1000009859 ns;   error =       -10 **us**

Attempt to sleep 100000000 ns per `clock_nanosleep()` call, 10 times.
ts_requested.tv_sec  = 0
ts_requested.tv_nsec = 100000000
failure_cnt                                              =                0
average time required per `clock_nanosleep()` sleep call =        100008022 ns;   error =        -8 **us**
minimum time for a `clock_nanosleep()` sleep call        =        100007113 ns;   error =        -7 **us**
maximum time for a `clock_nanosleep()` sleep call        =        100008965 ns;   error =        -9 **us**

Total program run time = 5.404382936 sec.

real    0m5.447s
user    0m0.105s
sys 0m0.691s

2
// busy wait for 10 microseconds
struct timespec ttime,curtime;

// get the time
clock_gettime(CLOCK_REALTIME,&ttime);

// clear the nanoseconds and keep the seconds in order not to overflow the nanoseconds
ttime.tv_nsec = 0;

// set it back
clock_settime(CLOCK_REALTIME,&ttime);

// get the time again 
clock_gettime(CLOCK_REALTIME,&ttime);

// increase the nano seconds by 10*1000
ttime.tv_nsec += 10000;

// loop
while(true){
  clock_gettime(CLOCK_REALTIME,&curtime);
  if (curtime.tv_nsec > ttime.tv_nsec)
    break;
}

// 它比usleep好得多。


2

好的,由于手册中部分内容指出:“实际休眠时间可能更长,因为系统延迟和硬件计时器分辨率的可能限制”,所以你需要学会接受它 :-)

现在回答你的问题,我的最佳猜测是因为你的第一个循环正在进程内运行。换句话说,没有涉及上下文切换,因为你一直在全力运行 CPU,并且你将在调度程序给你的100ms量子内完成所有工作。

但是,使用nanosleep很可能会将你切换出去,因为你明确要求被休眠。它不会像将你的进程放在紧密的while循环中直到持续时间结束那样效率低下 :-)

这意味着你受制于调度程序的各种变数,包括另一个进程完全使用其量子的事实,因此你的进程可能至少离开100ms。在负载足够重的系统上,它可能会离开相当长的时间。


1

您可以使用usleep方法以微秒为单位进行睡眠。


这与clock_nanosleep基本相同,正如问题和其他回答中所解释的那样,它并不能完全完成工作。 - Nikratio

0

效率 - 一个允许任务在几个时钟周期内精确切换的操作系统除此之外几乎不做其他事情。

有一些专门的操作系统可以实现这一点 - 但在常规硬件上,你需要为虚拟机监视器支付很高的开销。


0

这只是一个暂时的回答 - 我不知道相关的Linux内部细节,希望专家能够来解决。

一个可能性是69微秒仅仅是挂起和重新调度线程的原始开销。即使睡眠时间很短,内核也可能会做很多工作来执行上下文切换(或者如果没有要调度的内容,则执行半个上下文切换),然后几乎立即撤消它。我不知道在典型PC上Linux上应该花费多长时间。

如果这不能解释它,那么调度程序通常有一个“时间片”概念,即在调度的线程考虑切换之前将被保留运行的时间长度,除非它自己取消调度,或者其他具有更高优先级的内容变得可调度。内核将在时间片结束时触发低级定时器中断(除了为某些其他事件触发的中断,例如可以解除线程阻塞的I/O)。当时间片结束时,调度程序可以决定是否继续使用相同的线程,还是切换到另一个线程。

看起来当您睡觉时,要么(a)调度程序实际上没有设置定时器,以便在请求的时间使您的线程可调度,它只是等待一个时间片,因此CPU空闲的时间比必要的时间更长;否则(b)在您通过休眠放弃执行时,某个其他相同优先级的线程进入了,调度程序没有理由在根据其通常用于决定调度哪个线程的规则再次“轮到您”之前选择您。

69微秒相对短,不足以作为时间片的结果。

您似乎有一个基本解决方案-可以通过像自旋锁一样坐在循环中检查时间来延迟非常短的时间。然而,正如其他人所说,在非实时系统中,根据定义,您不能要求调度程序在任何特定时间运行您的线程。即使在实时系统中,如果您与相同优先级的线程竞争,您可能会输掉比赛,如果您正在与较高优先级的线程竞争,则输掉。


0

以上任何答案都没有提到线程的PR_SET_TIMERSLACK参数。默认情况下,它设置为50微秒。有关PR_SET_TIMERSLACK的更多信息,请参见https://man7.org/linux/man-pages/man2/prctl.2.html

您可以使用sudo cat /proc/PID/timerslack_ns检查任何进程的计时器间隔(不要忘记用实际进程ID替换PID)。

具有实时调度程序策略的线程会忽略计时器间隔值,因此接受的答案有效,因为在SCHED_FIFOSCHED_RR中,计时器间隔被忽略。此外,使用比任何其他线程优先级更高的线程立即进行调度更容易。

也许在某些情况下将计时器间隔设置为0就足够了(当每次5微秒不必严格执行时)。如果您不想更改调度策略,则可以这样做。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接