How to sleep for a few microseconds
Consider the following code:
#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(C开发者_Python百科LOCK, &requestEnd);
elapsed = ( requestEnd.tv_sec - requestStart.tv_sec ) / 1e-6
+ ( requestEnd.tv_nsec - requestStart.tv_nsec ) / 1e3;
printf("Elapsed: %lf us\n", elapsed);
}
On my 2.6.32 system, the result is
Elapsed: 5.308000 us
Elapsed: 69.142000 us
I agree that this is most likely because nanosleep() asks the kernel to reschedule the process. How can I avoid this? I want to keep ownership of the CPU and just idle around for a precise amount of time.
If you want your application to be able to "sleep" as precisely as possible, first put your application in realtime conditions
- use a realtime scheduler class for your program / thread :
SCHED_FIFO
orSCHED_RR
- elevate your program / thread priority
- and if you're going to "sleep" for less than the minimum amount the kernel is going to handle, manually busywait
Have a look at http://www.drdobbs.com/184402031
And this other question: nanosleep high cpu usage?
The OS scheduler is not going to do anything like "oh, take this thread off the processor for exactly 86 clock cycles then put it back on".
You give up the processor, you've given up the processor. The OS will put you back on when it feels like it. Chances are you'll have to wait until whatever else is running gives up the processor before you can sneak back on.
How to configure the Linux SCHED_RR
soft real-time round-robin scheduler so that clock_nanosleep()
can have improved sleep resolution as low as ~4 us minimum, down from ~55 us minimum, depending on your hardware
Summary of the question
The OP correctly used the equivlanet of clock_nanosleep(CLOCK_MONOTONIC, 0, &requested_time, NULL)
to try to sleep a requested time of exactly 5000 ns, or 5 us. See: https://man7.org/linux/man-pages/man2/clock_nanosleep.2.html and the question. However, the actual time slept was 69 us despite being commanded to sleep 5 us. Why?
I ran into this same type of issue. I was commanding clock_nanosleep()
to sleep 1 ns (0.001 us), and it slept on average 55 us. That seems to be the smallest sleep time interval possible. Why? Can we make this any better?
Answer summary
- I have empirically confirmed that the smallest nanosleep time interval possible on my x86-64 Linux Ubuntu system is ~55000 ns (~55 us) with the default Linux scheduler running. Any sleep time you command which is less than that amount of time will sleep that amount of time. This is when using Linux's default regular
SCHED_OTHER
/SCHED_NORMAL
"Default Linux time-sharing" scheduler, however.- Read about the various Linux scheduler types here: https://man7.org/linux/man-pages/man7/sched.7.html.
- You can improve this to a minimum sleep time of ~4000 ns (~4 us) instead, simply by using the
SCHED_RR
round-robin soft real-time scheduler (recommended), or theSCHED_FIFO
first-in/first-out soft real-time scheduler.
Here are my test results from my sleep_nanosleep_minimum_time_interval.c
program:
SCHED_OTHER
/SCHED_NORMAL
"Default Linux time-sharing" scheduler:- Minimum nanosleep time possible: ~55000 ns (~55 us)
SCHED_RR
round-robin soft real-time scheduler with the lowest priority of1
:- Minimum nanosleep time possible: ~4000 ns (~4 us)
SCHED_RR
round-robin soft real-time scheduler with the highest priority of99
:- Minimum nanosleep time possible: ~4000 ns (~4 us) (same as above)
SCHED_FIFO
first-in/first-out soft real-time scheduler with the lowest priority of1
:- Minimum nanosleep time possible: ~4000 ns (~4 us) (same as above)
SCHED_FIFO
first-in/first-out soft real-time scheduler with the highest priority of99
:- Minimum nanosleep time possible: ~4000 ns (~4 us) (same as above)
As you can see, you get an immediate and huge (55000/4000 = 13.75x) improvement immediately just by switching from the SCHED_OTHER
/SCHED_NORMAL
non-realtime Linux scheduler to a soft realtime Linux scheduler, such as SCHED_FIFO
or SCHED_RR
, with any priority level. Which of those two schedulers, and which priority you choose, based on my testing, is less important. You'd have to know which thread you want to get what priority, and when, in order to tweak your own threads. As a starter, set threads which have really short sleep times, generally < 1~10 ms to:
- The
SCHED_RR
soft real-time round-robin scheduler, and to: - Higher priorities.
As a starter, use SCHED_RR
with the lowest priority of 1
for all your real-time threads, and then adjust their priorities up from there, as needed. You do not want to choose a really high priority right off the bat and unnecessarily block high-priority tasks. Only raise the priority of a thread or process as-needed.
How to set the Linux "policy" (scheduler) and "priority"
This answer by @Yann Droneaud is helpful, but it doesn't show how to use a real-time scheduler. The article it links to, however, explains more (but is has a few errors and oversights), as does this Ask Ubuntu Q&A. I studied these two sources to learn the options I present below:
- Dr. Dobb's: Soft Real-Time Programming with Linux
- Ask Ubuntu: How to run a program with
SCHED_RR
policy from command line?
Here is how to set your Linux scheduler "policy" (scheduler) and "priority":
- Option 1 (easiest): from the command-line, call your program with the
chrt
"change real-time" command:
To see what# 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
priority
values are available for a given scheduler, runchrt --max
. If on an embedded Linux system with the BusyBox implementation ofchrt
, usechrt -m
instead. Here is the run and output on my x86-64 Linux Ubuntu machine:
As you can see, for both$ 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
SCHED_FIFO
andSCHED_RR
,1/99
shows that the lowest priority is1
and the highest priority is99
. - 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
See also
- my other files:
- sleep_nanosleep.c
- sleep_nanosleep_minimum_time_interval.c
- timinglib_sleep_and_sleep_until.c
- timinglib.h
- timinglib.c
- timinglib_pthread_periodic_loop.c
- I show how to do a 10 kHz fixed-period loop using
SCHED_RR
real-time round-robin scheduler, andclock_nanosleep()
with the absolute timing flag on.
- I show how to do a 10 kHz fixed-period loop using
- [my answer] pthread_create not working properly with pthread_attr_setschedparam
- [my answer] How to create
sleep_us()
fromnanosleep()
Output from sleep_nanosleep_minimum_time_interval.c
with SCHED_RR
round-robin real-time scheduler:
Notice that the error averages around 4 us (scroll to the right a little):
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
Well, you'll have to learn to live with it since the man page states, in part: the actual time slept may be longer, due to system latencies and possible limitations in the timer resolution of the hardware
:-)
Now as to the answer to your question, my best guess is that it's because your first loop is running in-process. In other words, there are no context switches involved since you're running the CPU flat out and you will be doing all that work within your 100ms quanta given to you by the scheduler.
However, there's a good chance that nanosleep
will switch you out since you are explicitly asking to be put to sleep. It won't be so inefficient as to just put your process in a tight while
loop until the duration is over :-)
That means you're subject to all the vagaries of the scheduler including the fact that another process may totally use up its quanta, hence your process may be out of there for 100ms at least. On a heavily-enough loaded system, it could be out for quite a while.
// 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;
}
// it is much better than the usleep.
you can use usleep
method to get sleep in microsecond units.
Efficiency - an Os that allowed tasks to be switched in and out with a precision of a few clock cycles would do very little else.
There are specialized OSes that do this - but on regular hardware you pay a lot of overhead for the hypervisor
This is a holding answer - I don't know the relevant linux internals, hopefully an expert can come along and clear it up.
One possibility is that that 69us is simply the raw overhead of descheduling and then rescheduling the thread. Even though the sleep is short, the kernel might do a lot of work to perform a context switch (or half a context switch, if there's nothing to schedule), and then undo it almost immediately. I don't know how long that "should" take on linux on a typical PC.
If that doesn't explain it, a scheduler generally has a concept of a "timeslice", which is how long a scheduled thread will be left to run before the scheduler thinks about switching it, unless it either deschedules itself or else something with higher priority becomes schedulable. The kernel will have low-level timers firing interrupts at the end of a time slice (in addition to interrupts that fire for certain other events such as I/O that could unblock a thread). When a timeslice ends, the scheduler can decide whether to continue with the same thread, or switch to another.
So it looks as though when you sleep, either (a) the scheduler isn't actually setting a timer that will make your thread schedulable at the requested time, it's just waiting for a timeslice, and so the CPU is going idle longer than necessary; or else (b) it is making your thread schedulable at the requested time, but when you gave up execution by sleeping, some other thread of equal priority got in, and the scheduler has no grounds to prefer you over it until it's "your turn" again according to whatever rules the scheduler usually uses to decide what thread to schedule.
69us is pretty short to be an artefact of timeslicing, though.
You seem to have a rudimentary solution - you can delay for very short periods by sitting in a loop checking the time, just like a spinlock. As everyone else is saying, though, in a non-realtime system, more or less by definition you can't demand that the scheduler runs your thread at any specific time. Even in a realtime system, if you're competing with threads of equal priority you may lose, and if you're competing with threads at higher priority you will lose.
Any answer above doesn't mention about PR_SET_TIMERSLACK
parameter of a thread. By default, it's set to 50 us. More about PR_SET_TIMERSLACK here: https://man7.org/linux/man-pages/man2/prctl.2.html.
You can check timerslack for any process with sudo cat /proc/PID/timerslack_ns
(don't forget to replace PID
with real process ID).
Threads with a real-time scheduler policy ignore timerslack value, so the accepted answer works because in SCHED_FIFO
and SCHED_RR
timerslack is ignored. Additionally, it's easier to immediately schedule a thread with a priority higher than any other.
Maybe setting timerslack to 0 can be enough in some cases (when 5 us don't have to be strict every single time). This is the way if you don't want to change the scheduling policy.
精彩评论