Arvid Brodin
2013-03-19 23:20:43 UTC
Hello,
Just out of curiosity, I wrote this program to test the latency using a
vanilla 3.4 kernel's "real-time scheduler". Maybe it is of interest to
someone on this list.
$ uname -srvmp
Linux 3.3.8 #7 SMP PREEMPT Tue Jun 26 03:08:40 CEST 2012 x86_64 Intel(R)
Core(TM) i3-2120T CPU @ 2.60GHz
Tests were made while running a 1080p x264 movie and "shaking" a large
glxgears window.
Sample running as normal user without permission to change to realtime
scheduler, i.e. running under the normal scheduler (SCHED_OTHER):
$ ./rt_test
Setting SCHED priority 1
Could not set scheduler: Operation not permitted
CLOCK_REALTIME resolution: 1 ns
Establishing handler for signal 34
timer ID is 0x1ae6010
...
Min, max diff -686, 694 µs; cumulative diff 1990 µs; overruns 0;
calls 70913
Min, max diff -827, 1257 µs; cumulative diff 4003 µs; overruns 2;
calls 71970
Min, max diff -645, 1193 µs; cumulative diff 4994 µs; overruns 1;
calls 73028
Min, max diff -629, 635 µs; cumulative diff 4996 µs; overruns 0;
calls 74087
Min, max diff -48, 65 µs; cumulative diff 4997 µs; overruns 0;
calls 75146
Min, max diff -29, 34 µs; cumulative diff 4990 µs; overruns 0;
calls 76204
Min, max diff -253, 160 µs; cumulative diff 4995 µs; overruns 0;
calls 77263
Min, max diff -854, 868 µs; cumulative diff 4996 µs; overruns 0;
calls 78323
Min, max diff -812, 1812 µs; cumulative diff 5990 µs; overruns 1;
calls 79381
Min, max diff -635, 371 µs; cumulative diff 5990 µs; overruns 0;
calls 80441
Min, max diff -536, 540 µs; cumulative diff 6000 µs; overruns 0;
calls 81500
Min, max diff -855, 1229 µs; cumulative diff 6988 µs; overruns 1;
calls 82558
Min, max diff -305, 435 µs; cumulative diff 7002 µs; overruns 0;
calls 83618
Min, max diff -293, 304 µs; cumulative diff 6995 µs; overruns 0;
calls 84677
Min, max diff -644, 647 µs; cumulative diff 6993 µs; overruns 0;
calls 85737
Sample running under real-time scheduler (SCHED_FIFO):
# ulimit -r 1
# ./rt_test
Setting SCHED priority 1
CLOCK_REALTIME resolution: 1 ns
Establishing handler for signal 34
timer ID is 0x1236010
...
Min, max diff -24, 36 µs; cumulative diff 2 µs; overruns 0; calls 35173
Min, max diff -15, 20 µs; cumulative diff -3 µs; overruns 0; calls
36178
Min, max diff -30, 24 µs; cumulative diff -3 µs; overruns 0; calls
37183
Min, max diff -17, 14 µs; cumulative diff -1 µs; overruns 0; calls
38187
Min, max diff -27, 22 µs; cumulative diff -4 µs; overruns 0; calls
39192
Min, max diff -68, 80 µs; cumulative diff 2 µs; overruns 0; calls 40199
Min, max diff -26, 32 µs; cumulative diff 0 µs; overruns 0; calls 41204
Min, max diff -23, 29 µs; cumulative diff -3 µs; overruns 0; calls
42209
Min, max diff -29, 28 µs; cumulative diff 3 µs; overruns 0; calls 43215
Min, max diff -24, 25 µs; cumulative diff -3 µs; overruns 0; calls
44220
Min, max diff -26, 28 µs; cumulative diff 2 µs; overruns 0; calls 45225
Min, max diff -19, 25 µs; cumulative diff -3 µs; overruns 0; calls
46231
Min, max diff -47, 57 µs; cumulative diff -1 µs; overruns 0; calls
48242
It looks good enough for the servo thread, at least on my computer!
Here's the program, in case you want to try it yourself:
Compile with
$ gcc -Wall -lrt rt_test.c -o rt_test
---
/*
* Latency/precision test program for POSIX timers under Linux
* real-time scheduling (SCHED_FIFO).
*
* Based on the sample code in 'man 2 timer_create',
* copyright (c) 2009 Linux Foundation, written by Michael Kerrisk
* <mtk.manpages-***@public.gmane.org>
* and released under the "Verbatim" License:
* https://www.kernel.org/doc/man-pages/licenses.html#verbatim
*
* Modifications for this usage by Arvid Brodin <arvidb-***@public.gmane.org>.
*/
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <sched.h>
#include <signal.h>
#include <time.h>
#include <errno.h>
#define CLOCKID CLOCK_REALTIME
#define SIG SIGRTMIN
#define PERIOD 1000000
#define errExit(msg) do { perror(msg); exit(EXIT_FAILURE); \
} while (0)
static inline int min(int a, int b)
{
return (a < b) ? a : b;
}
static inline int max(int a, int b)
{
return (a > b) ? a : b;
}
/* Global variable to keep track of latency statistics etc. */
static struct {
int mindiff; // Maximum negative deviation
int maxdiff; // Maximum positive deviation
int cumdiff; // Cumulative deviation from first call
int calls; // Total number of calls made
int overruns; // Number of completely missed calls
int last_ns;
} period_info;
static void print_clockinfo()
{
struct timespec ts;
if (clock_getres(CLOCK_REALTIME, &ts) != 0) {
printf("Could not get clock info.\n");
return;
}
printf("CLOCK_REALTIME resolution: %ld ns\n", ts.tv_nsec);
}
/*
* Print statistics. Called from main loop.
*/
static void print_diffs()
{
printf("Min, max diff %5d, %5d µs; cumulative diff %d µs; overruns %d;
calls %d\n",
period_info.mindiff / 1000,
period_info.maxdiff / 1000,
period_info.cumdiff / 1000,
period_info.overruns,
period_info.calls);
period_info.mindiff = 0;
period_info.maxdiff = 0;
period_info.overruns = 0;
}
static struct sigaction sa;
/*
* Signal handler. Called every PERIOD ns.
*/
static void handler(int sig, siginfo_t *si, void *uc)
{
timer_t *tidp;
struct timespec ts;
int overruns;
int diff;
period_info.calls += 1;
tidp = si->si_value.sival_ptr;
overruns = timer_getoverrun(*tidp);
if (overruns > 0)
period_info.overruns += overruns;
clock_gettime(CLOCKID, &ts);
if (period_info.last_ns < 0)
// First call
period_info.last_ns = ts.tv_nsec - PERIOD;
else if (ts.tv_nsec < period_info.last_ns)
// Seconds wrapped
period_info.last_ns -= 1000000000;
diff = ts.tv_nsec - period_info.last_ns - PERIOD;
period_info.mindiff = min(period_info.mindiff, diff);
period_info.maxdiff = max(period_info.maxdiff, diff);
period_info.cumdiff += diff;
period_info.last_ns = ts.tv_nsec;
}
/*
* Signal delivery causes system/library calls to return prematurely.
* See 'man 7 signal' for details. This function repeatedly call
* nanosleep() until (at least) 'sec' seconds has passed.
*/
static void sigsafe_sleep(int sec)
{
struct timespec rqtp, rmtp;
struct timespec *ts_req, *ts_rem, *ts_tmp;
int res;
rqtp.tv_sec = sec;
rqtp.tv_nsec = 0;
ts_req = &rqtp;
ts_rem = &rmtp;
res = nanosleep(ts_req, ts_rem);
while (res < 0 && errno == EINTR) {
ts_tmp = ts_req;
ts_req = ts_rem;
ts_rem = ts_tmp;
res = nanosleep(ts_req, ts_rem);
}
}
int main(int argc, char *argv[])
{
struct sched_param sched_param;
timer_t timerid;
struct sigevent sev;
struct itimerspec its;
int res;
const int sched_policy = SCHED_FIFO;
// Scheduler
sched_param.sched_priority = sched_get_priority_min(sched_policy);
printf("Setting SCHED priority %d\n", sched_param.sched_priority);
res = sched_setscheduler(0, sched_policy, &sched_param);
if (res < 0)
perror("Could not set scheduler");
// Some init
print_clockinfo();
period_info.mindiff = 0;
period_info.maxdiff = 0;
period_info.cumdiff = 0;
period_info.last_ns = -1;
// Establish handler for timer signal
printf("Establishing handler for signal %d\n", SIG);
sa.sa_flags = SA_SIGINFO;
sa.sa_sigaction = handler;
sigemptyset(&sa.sa_mask);
if (sigaction(SIG, &sa, NULL) == -1)
errExit("sigaction");
// Create the timer
sev.sigev_notify = SIGEV_SIGNAL;
sev.sigev_signo = SIG;
sev.sigev_value.sival_ptr = &timerid;
if (timer_create(CLOCKID, &sev, &timerid) == -1)
errExit("timer_create");
printf("timer ID is 0x%lx\n", (long) timerid);
// Start the timer
its.it_value.tv_sec = 0;
its.it_value.tv_nsec = PERIOD;
its.it_interval.tv_sec = its.it_value.tv_sec;
its.it_interval.tv_nsec = its.it_value.tv_nsec;
if (timer_settime(timerid, 0, &its, NULL) == -1)
errExit("timer_settime");
while (1) {
sigsafe_sleep(1);
print_diffs();
}
// Not reached
exit(EXIT_SUCCESS);
}
---
Just out of curiosity, I wrote this program to test the latency using a
vanilla 3.4 kernel's "real-time scheduler". Maybe it is of interest to
someone on this list.
$ uname -srvmp
Linux 3.3.8 #7 SMP PREEMPT Tue Jun 26 03:08:40 CEST 2012 x86_64 Intel(R)
Core(TM) i3-2120T CPU @ 2.60GHz
Tests were made while running a 1080p x264 movie and "shaking" a large
glxgears window.
Sample running as normal user without permission to change to realtime
scheduler, i.e. running under the normal scheduler (SCHED_OTHER):
$ ./rt_test
Setting SCHED priority 1
Could not set scheduler: Operation not permitted
CLOCK_REALTIME resolution: 1 ns
Establishing handler for signal 34
timer ID is 0x1ae6010
...
Min, max diff -686, 694 µs; cumulative diff 1990 µs; overruns 0;
calls 70913
Min, max diff -827, 1257 µs; cumulative diff 4003 µs; overruns 2;
calls 71970
Min, max diff -645, 1193 µs; cumulative diff 4994 µs; overruns 1;
calls 73028
Min, max diff -629, 635 µs; cumulative diff 4996 µs; overruns 0;
calls 74087
Min, max diff -48, 65 µs; cumulative diff 4997 µs; overruns 0;
calls 75146
Min, max diff -29, 34 µs; cumulative diff 4990 µs; overruns 0;
calls 76204
Min, max diff -253, 160 µs; cumulative diff 4995 µs; overruns 0;
calls 77263
Min, max diff -854, 868 µs; cumulative diff 4996 µs; overruns 0;
calls 78323
Min, max diff -812, 1812 µs; cumulative diff 5990 µs; overruns 1;
calls 79381
Min, max diff -635, 371 µs; cumulative diff 5990 µs; overruns 0;
calls 80441
Min, max diff -536, 540 µs; cumulative diff 6000 µs; overruns 0;
calls 81500
Min, max diff -855, 1229 µs; cumulative diff 6988 µs; overruns 1;
calls 82558
Min, max diff -305, 435 µs; cumulative diff 7002 µs; overruns 0;
calls 83618
Min, max diff -293, 304 µs; cumulative diff 6995 µs; overruns 0;
calls 84677
Min, max diff -644, 647 µs; cumulative diff 6993 µs; overruns 0;
calls 85737
Sample running under real-time scheduler (SCHED_FIFO):
# ulimit -r 1
# ./rt_test
Setting SCHED priority 1
CLOCK_REALTIME resolution: 1 ns
Establishing handler for signal 34
timer ID is 0x1236010
...
Min, max diff -24, 36 µs; cumulative diff 2 µs; overruns 0; calls 35173
Min, max diff -15, 20 µs; cumulative diff -3 µs; overruns 0; calls
36178
Min, max diff -30, 24 µs; cumulative diff -3 µs; overruns 0; calls
37183
Min, max diff -17, 14 µs; cumulative diff -1 µs; overruns 0; calls
38187
Min, max diff -27, 22 µs; cumulative diff -4 µs; overruns 0; calls
39192
Min, max diff -68, 80 µs; cumulative diff 2 µs; overruns 0; calls 40199
Min, max diff -26, 32 µs; cumulative diff 0 µs; overruns 0; calls 41204
Min, max diff -23, 29 µs; cumulative diff -3 µs; overruns 0; calls
42209
Min, max diff -29, 28 µs; cumulative diff 3 µs; overruns 0; calls 43215
Min, max diff -24, 25 µs; cumulative diff -3 µs; overruns 0; calls
44220
Min, max diff -26, 28 µs; cumulative diff 2 µs; overruns 0; calls 45225
Min, max diff -19, 25 µs; cumulative diff -3 µs; overruns 0; calls
46231
Min, max diff -47, 57 µs; cumulative diff -1 µs; overruns 0; calls
48242
It looks good enough for the servo thread, at least on my computer!
Here's the program, in case you want to try it yourself:
Compile with
$ gcc -Wall -lrt rt_test.c -o rt_test
---
/*
* Latency/precision test program for POSIX timers under Linux
* real-time scheduling (SCHED_FIFO).
*
* Based on the sample code in 'man 2 timer_create',
* copyright (c) 2009 Linux Foundation, written by Michael Kerrisk
* <mtk.manpages-***@public.gmane.org>
* and released under the "Verbatim" License:
* https://www.kernel.org/doc/man-pages/licenses.html#verbatim
*
* Modifications for this usage by Arvid Brodin <arvidb-***@public.gmane.org>.
*/
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <sched.h>
#include <signal.h>
#include <time.h>
#include <errno.h>
#define CLOCKID CLOCK_REALTIME
#define SIG SIGRTMIN
#define PERIOD 1000000
#define errExit(msg) do { perror(msg); exit(EXIT_FAILURE); \
} while (0)
static inline int min(int a, int b)
{
return (a < b) ? a : b;
}
static inline int max(int a, int b)
{
return (a > b) ? a : b;
}
/* Global variable to keep track of latency statistics etc. */
static struct {
int mindiff; // Maximum negative deviation
int maxdiff; // Maximum positive deviation
int cumdiff; // Cumulative deviation from first call
int calls; // Total number of calls made
int overruns; // Number of completely missed calls
int last_ns;
} period_info;
static void print_clockinfo()
{
struct timespec ts;
if (clock_getres(CLOCK_REALTIME, &ts) != 0) {
printf("Could not get clock info.\n");
return;
}
printf("CLOCK_REALTIME resolution: %ld ns\n", ts.tv_nsec);
}
/*
* Print statistics. Called from main loop.
*/
static void print_diffs()
{
printf("Min, max diff %5d, %5d µs; cumulative diff %d µs; overruns %d;
calls %d\n",
period_info.mindiff / 1000,
period_info.maxdiff / 1000,
period_info.cumdiff / 1000,
period_info.overruns,
period_info.calls);
period_info.mindiff = 0;
period_info.maxdiff = 0;
period_info.overruns = 0;
}
static struct sigaction sa;
/*
* Signal handler. Called every PERIOD ns.
*/
static void handler(int sig, siginfo_t *si, void *uc)
{
timer_t *tidp;
struct timespec ts;
int overruns;
int diff;
period_info.calls += 1;
tidp = si->si_value.sival_ptr;
overruns = timer_getoverrun(*tidp);
if (overruns > 0)
period_info.overruns += overruns;
clock_gettime(CLOCKID, &ts);
if (period_info.last_ns < 0)
// First call
period_info.last_ns = ts.tv_nsec - PERIOD;
else if (ts.tv_nsec < period_info.last_ns)
// Seconds wrapped
period_info.last_ns -= 1000000000;
diff = ts.tv_nsec - period_info.last_ns - PERIOD;
period_info.mindiff = min(period_info.mindiff, diff);
period_info.maxdiff = max(period_info.maxdiff, diff);
period_info.cumdiff += diff;
period_info.last_ns = ts.tv_nsec;
}
/*
* Signal delivery causes system/library calls to return prematurely.
* See 'man 7 signal' for details. This function repeatedly call
* nanosleep() until (at least) 'sec' seconds has passed.
*/
static void sigsafe_sleep(int sec)
{
struct timespec rqtp, rmtp;
struct timespec *ts_req, *ts_rem, *ts_tmp;
int res;
rqtp.tv_sec = sec;
rqtp.tv_nsec = 0;
ts_req = &rqtp;
ts_rem = &rmtp;
res = nanosleep(ts_req, ts_rem);
while (res < 0 && errno == EINTR) {
ts_tmp = ts_req;
ts_req = ts_rem;
ts_rem = ts_tmp;
res = nanosleep(ts_req, ts_rem);
}
}
int main(int argc, char *argv[])
{
struct sched_param sched_param;
timer_t timerid;
struct sigevent sev;
struct itimerspec its;
int res;
const int sched_policy = SCHED_FIFO;
// Scheduler
sched_param.sched_priority = sched_get_priority_min(sched_policy);
printf("Setting SCHED priority %d\n", sched_param.sched_priority);
res = sched_setscheduler(0, sched_policy, &sched_param);
if (res < 0)
perror("Could not set scheduler");
// Some init
print_clockinfo();
period_info.mindiff = 0;
period_info.maxdiff = 0;
period_info.cumdiff = 0;
period_info.last_ns = -1;
// Establish handler for timer signal
printf("Establishing handler for signal %d\n", SIG);
sa.sa_flags = SA_SIGINFO;
sa.sa_sigaction = handler;
sigemptyset(&sa.sa_mask);
if (sigaction(SIG, &sa, NULL) == -1)
errExit("sigaction");
// Create the timer
sev.sigev_notify = SIGEV_SIGNAL;
sev.sigev_signo = SIG;
sev.sigev_value.sival_ptr = &timerid;
if (timer_create(CLOCKID, &sev, &timerid) == -1)
errExit("timer_create");
printf("timer ID is 0x%lx\n", (long) timerid);
// Start the timer
its.it_value.tv_sec = 0;
its.it_value.tv_nsec = PERIOD;
its.it_interval.tv_sec = its.it_value.tv_sec;
its.it_interval.tv_nsec = its.it_value.tv_nsec;
if (timer_settime(timerid, 0, &its, NULL) == -1)
errExit("timer_settime");
while (1) {
sigsafe_sleep(1);
print_diffs();
}
// Not reached
exit(EXIT_SUCCESS);
}
---
--
Arvid
Arvid