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 the SCHED_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 of 1
:
- Minimum nanosleep time possible: ~4000 ns (~4 us)
SCHED_RR
round-robin soft real-time scheduler with the highest priority of 99
:
- 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 of 1
:
- 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 of 99
:
- 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:
# 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
.
- 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, and clock_nanosleep()
with the absolute timing flag on.
- [my answer] pthread_create not working properly with pthread_attr_setschedparam
- [my answer] How to create
sleep_us()
from nanosleep()
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