From 35e662d810290b43e98e436f8128eddc72b5123d Mon Sep 17 00:00:00 2001 From: John Hasler Date: Tue, 10 Feb 2009 17:59:57 +0100 Subject: [PATCH] Add mlockall and SCHED_FIFO support The attached patch adds support for mlockall() as well as the SCHED_FIFO real-time scheduler. It should result in reduced (and more consistent) latency. Usage is documented in all the documents. --- chrony.texi | 56 +++++++++++++++++++++++++++++++- chronyd.8 | 9 ++++++ conf.c | 36 +++++++++++++++++++++ conf.h | 8 +++++ configure | 2 +- examples/chrony.conf.example | 18 +++++++++++ main.c | 44 +++++++++++++++++++++++++ sys.c | 17 ++++++++++ sys.h | 3 ++ sys_linux.c | 63 ++++++++++++++++++++++++++++++++++++ sys_linux.h | 4 +++ 11 files changed, 258 insertions(+), 2 deletions(-) diff --git a/chrony.texi b/chrony.texi index ce29f7a..13a914d 100644 --- a/chrony.texi +++ b/chrony.texi @@ -1088,13 +1088,19 @@ useful for dial-up systems that are shut down when not in use. For this to work well, it relies on @code{chronyd} having been able to determine accurate statistics for the difference between the real time clock and system clock last time the computer was on. - @item -u When this option is used, chronyd will drop root privileges to the specified user. So far, it works only on Linux when compiled with capabilities support. @item -v This option displays @code{chronyd's} version number to the terminal and exits. +@item -P +This option will select the SCHED_FIFO real-time scheduler at the +specified priority (which must be between 0 and 100). This mode is +supported only on Linux. +@item -m +This option will lock chronyd into RAM so that it will never be paged +out. This mode is only supported on Linux. @end table On systems that support an @file{/etc/rc.local} file for starting @@ -1187,6 +1193,9 @@ directives can occur in any order in the file. * rtcfile directive:: Specify the file where real-time clock data is stored * rtconutc directive:: Specify that the real time clock keeps UTC not local time * server directive:: Specify an NTP server +* sched_priority directive:: Require real-time scheduling and specify a priority for it. +* lock_all directive:: Require that chronyd be locked into RAM. + @end menu @c }}} @c {{{ comments in config file @@ -2177,6 +2186,37 @@ If the @code{rtconutc} directive appears, it means the RTC is required to keep UTC. The directive takes no arguments. It is equivalent to specifying the @code{-u} switch to the Linux @file{/sbin/clock} program. @c }}} +@c {{{ sched_priority +@node sched_priority directive +@subsection sched_priority + +The @code{sched_priority} directive will select the SCHED_FIFO real-time +scheduler at the specified priority (which must be between 0 and 100). +This mode is supported only on Linux. + +This directive uses the Linux sched_setscheduler() system call to +instruct the kernel to use the SCHED_FIFO first-in, first-out +real-time scheduling policy for Chronyd with the specified priority. +This means that whenever Chronyd is ready to run it will run, +interrupting whatever else is running unless it is a higher priority +real-time process. This should not impact performance as Chronyd's +resource requirements are modest, but it should result in lower and +more consistent latency since Chronyd will not need to wait for the +scheduler to get around to running it. You should not use this unless +you really need it. The sched_setscheduler man page has more details. +@c }}} +@c {{{ lock_all +@node lock_all directive +@subsection lock_all + +The @code{lock_all} directive will lock chronyd into RAM so that it +will never be paged out. This mode is only supported on Linux. This +directive uses the Linux mlockall() system call to prevent Chronyd +from ever being swapped out. This should result in lower and more +consistent latency. It should not have significant impact on +performance as Chronyd's memory usage is modest. The mlockall man +page has more details. +@c }}} @c {{{ server @node server directive @subsection server @@ -2287,6 +2327,20 @@ chrony when disconnecting the dial-up link. (It will still be necessary to use chronyc's @code{online} (@pxref{online command}) command when the link has been established, to enable measurements to start.) +@item sched_priority +This directive tells chronyd to use the real-time FIFO scheduler with the +specified priority (which must be between 0 and 100). This should result +in reduced latency. You don't need it unless you really have a requirement +for extreme clock stability. Works only on Linux. Note that the "-P" +command-line switch will override this. + +@item lock_all +This directive tells chronyd to use the mlockall() syscall to lock itself +into RAM so that it will never be paged out. This should result in reduced +latency. You don't need it unless you really have a requirement +for extreme clock stability. Works only on Linux. Note that the "-m" +command-line switch will also enable this feature. + @end table @c }}} @c }}} diff --git a/chronyd.8 b/chronyd.8 index dfc4004..52e06e2 100644 --- a/chronyd.8 +++ b/chronyd.8 @@ -35,6 +35,15 @@ Information messages and warnings will be logged to syslog. .SH OPTIONS A summary of the options supported by \fBchronyd\fR is included below. +.TP +\fB\-P\fR \fIpriority\fR +This option will select the SCHED_FIFO real-time scheduler at the specified +priority (which must be between 0 and 100). This mode is supported only on +Linux. +.TP +.B \-m +This option will lock chronyd into RAM so that it will never be paged out. +This mode is only supported on Linux. .TP .B \-d When run in this mode, the program will not detach itself from the diff --git a/conf.c b/conf.c index 8e6c1d9..06ff104 100644 --- a/conf.c +++ b/conf.c @@ -93,6 +93,14 @@ static void parse_broadcast(const char *); static void parse_linux_hz(const char *); static void parse_linux_freq_scale(const char *); +#if defined(HAVE_SCHED_SETSCHEDULER) +static void parse_sched_priority(const char *); +#endif + +#if defined(HAVE_MLOCKALL) +static void parse_lockall(const char *); +#endif + /* ================================================== */ /* Configuration variables */ @@ -209,6 +217,14 @@ static const Command commands[] = { {"broadcast", 9, parse_broadcast}, {"linux_hz", 8, parse_linux_hz}, {"linux_freq_scale", 16, parse_linux_freq_scale} +#if defined(HAVE_SCHED_SETSCHEDULER) + ,{"sched_priority", 14, parse_sched_priority} +#endif + +#if defined(HAVE_MLOCKALL) + ,{"lock_all", 8, parse_lockall} +#endif + }; static int n_commands = (sizeof(commands) / sizeof(commands[0])); @@ -365,6 +381,26 @@ parse_source(const char *line, NTP_Source_Type type) /* ================================================== */ +#if defined(HAVE_SCHED_SETSCHEDULER) +static void +parse_sched_priority(const char *line) +{ + if (SchedPriority == 0) { /* Command-line switch must have priority */ + sscanf(line, "%d", &SchedPriority); + } +} +#endif + +#if defined(HAVE_MLOCKALL) +static void +parse_lockall(const char *line) +{ + LockAll = 1; +} +#endif + +/* ================================================== */ + static void parse_server(const char *line) { diff --git a/conf.h b/conf.h index 166cd1b..fa967ae 100644 --- a/conf.h +++ b/conf.h @@ -71,4 +71,12 @@ extern int CNF_AllowLocalReference(int *stratum); extern void CNF_SetupAccessRestrictions(void); +#if defined(HAVE_SCHED_SETSCHEDULER) +extern int SchedPriority; +#endif + +#if defined(HAVE_MLOCKALL) +extern int LockAll; +#endif + #endif /* GOT_CONF_H */ diff --git a/configure b/configure index 9027b85..1ff2bbf 100755 --- a/configure +++ b/configure @@ -257,7 +257,7 @@ case $SYSTEM in EXTRA_DEFS+=" -DFEAT_LINUXCAPS=1" EXTRA_LIBS="-lcap" fi - SYSDEFS="-DLINUX" + SYSDEFS="-DLINUX -DHAVE_SCHED_SETSCHEDULER -DHAVE_MLOCKALL" echo "Configuring for " $SYSTEM if [ "${MACHINE}" = "alpha" ]; then echo "Enabling -mieee" diff --git a/examples/chrony.conf.example b/examples/chrony.conf.example index 5488e72..fbf8e47 100644 --- a/examples/chrony.conf.example +++ b/examples/chrony.conf.example @@ -287,3 +287,21 @@ cmdallow 127.0.0.1 ! rtcdevice /dev/misc/rtc ####################################################################### +### REAL TIME SCHEDULER +# This directive tells chronyd to use the real-time FIFO scheduler with the +# specified priority (which must be between 0 and 100). This should result +# in reduced latency. You don't need it unless you really have a requirement +# for extreme clock stability. Works only on Linux. Note that the "-P" +# command-line switch will override this. + +! sched_priority 1 + +####################################################################### +### LOCKING CHRONYD INTO RAM +# This directive tells chronyd to use the mlockall() syscall to lock itself +# into RAM so that it will never be paged out. This should result in reduced +# latency. You don't need it unless you really have a requirement +# for extreme clock stability. Works only on Linux. Note that the "-m" +# command-line switch will also enable this feature. + +! lock_all diff --git a/main.c b/main.c index ba6e4a9..65942c1 100644 --- a/main.c +++ b/main.c @@ -25,6 +25,16 @@ ======================================================================= +2009-1-28 John G. Hasler + + Added real-time support (Linux only) using sched_setscheduler() and + mlockall(). Files affected: main.c, conf.c, sys.c, sys_linux.c, + conf.h, configure, chronyd.8, chrony.texi, and + examples/chrony.conf.example. The changes are licensed under + version 2 of the GPL as described above. + + ======================================================================= + The main program */ @@ -211,6 +221,10 @@ int main int do_init_rtc = 0; int other_pid; +#if defined(HAVE_SCHED_SETSCHEDULER) + int return_value = 0; +#endif + LOG_Initialise(); /* Parse command line options */ @@ -219,6 +233,24 @@ int main if (!strcmp("-f", *argv)) { ++argv, --argc; conf_file = *argv; + +#if defined(HAVE_SCHED_SETSCHEDULER) + /* Get real-time scheduler priority */ + } else if (!strcmp("-P", *argv)) { + ++argv, --argc; + return_value = sscanf(*argv, "%d", &SchedPriority); + if (return_value != 1 || SchedPriority < 1 || SchedPriority > 99) { + SchedPriority = 0; + LOG(LOGS_WARN, LOGF_Main, "Bad scheduler priority: [%s]", *argv); + } +#endif /* HAVE_SCHED_SETCHEDULER */ + +#if defined(HAVE_MLOCKALL) + /* Detect lockall switch */ + } else if (!strcmp("-m", *argv)) { + LockAll = 1; +#endif /* HAVE_MLOCKALL */ + } else if (!strcmp("-r", *argv)) { reload = 1; } else if (!strcmp("-u", *argv)) { @@ -307,6 +339,18 @@ int main signal(SIGHUP, signal_cleanup); #endif /* WINNT */ +#if defined(HAVE_SCHED_SETSCHEDULER) + if (SchedPriority > 0) { + SYS_SetScheduler(SchedPriority); + } +#endif + +#if defined(HAVE_MLOCKALL) + if (LockAll == 1 ) { + SYS_MemLockAll(LockAll); + } +#endif + /* The program normally runs under control of the main loop in the scheduler. */ SCH_MainLoop(); diff --git a/sys.c b/sys.c index 048ba4d..714fd4f 100644 --- a/sys.c +++ b/sys.c @@ -106,6 +106,23 @@ void SYS_DropRoot(char *user) } /* ================================================== */ + +void SYS_SetScheduler(int SchedPriority) +{ +#if defined(LINUX) && defined(HAVE_SCHED_SETSCHEDULER) + SYS_Linux_SetScheduler(SchedPriority); +#endif + ;; +} + +void SYS_MemLockAll(int LockAll) +{ +#if defined(LINUX) && defined(HAVE_MLOCKALL) + SYS_Linux_MemLockAll(LockAll); +#endif + ;; +} + /* ================================================== */ diff --git a/sys.h b/sys.h index 50b8e46..2efd19c 100644 --- a/sys.h +++ b/sys.h @@ -42,4 +42,7 @@ extern void SYS_Finalise(void); /* Drop root privileges to the specified user */ extern void SYS_DropRoot(char *user); +extern void SYS_SetScheduler(int SchedPriority); +extern void SYS_MemLockAll(int LockAll); + #endif /* GOT_SYS_H */ diff --git a/sys_linux.c b/sys_linux.c index d734b5e..969229f 100644 --- a/sys_linux.c +++ b/sys_linux.c @@ -39,6 +39,17 @@ #include #include +#if defined(HAVE_SCHED_SETSCHEDULER) +# include +int SchedPriority = 0; +#endif + +#if defined(HAVE_MLOCKALL) +# include +#include +int LockAll = 0; +#endif + #ifdef FEAT_LINUXCAPS #include #include @@ -898,6 +909,58 @@ SYS_Linux_DropRoot(char *user) /* ================================================== */ +#if defined(HAVE_SCHED_SETSCHEDULER) + /* Install SCHED_FIFO real-time scheduler with specified priority */ +void SYS_Linux_SetScheduler(int SchedPriority) +{ + int pmax, pmin; + struct sched_param sched; + + if (SchedPriority > 0) { + sched.sched_priority = SchedPriority; + pmax = sched_get_priority_max(SCHED_FIFO); + pmin = sched_get_priority_min(SCHED_FIFO); + if ( SchedPriority > pmax ) { + sched.sched_priority = pmax; + } + else if ( SchedPriority < pmin ) { + sched.sched_priority = pmin; + } + if ( sched_setscheduler(0, SCHED_FIFO, &sched) == -1 ) { + LOG(LOGS_ERR, LOGF_SysLinux, "sched_setscheduler() failed"); + } + else { + LOG(LOGS_INFO, LOGF_SysLinux, "Enabled SCHED_FIFO with priority %d", sched.sched_priority); + } + } +} +#endif /* HAVE_SCHED_SETSCHEDULER */ + +#if defined(HAVE_MLOCKALL) +/* Lock the process into RAM so that it will never be swapped out */ +void SYS_Linux_MemLockAll(int LockAll) +{ + struct rlimit rlim; + if (LockAll == 1 ) { + /* Make sure that we will be able to lock all the memory we need */ + /* even after dropping privileges. This does not actually reaerve any memory */ + rlim.rlim_max = RLIM_INFINITY; + rlim.rlim_cur = RLIM_INFINITY; + if (setrlimit(RLIMIT_MEMLOCK, &rlim) < 0) { + LOG(LOGS_ERR, LOGF_SysLinux, "setrlimit() failed: not locking into RAM"); + } + else { + if (mlockall(MCL_CURRENT|MCL_FUTURE) < 0) { + LOG(LOGS_ERR, LOGF_SysLinux, "mlockall() failed"); + } + else { + LOG(LOGS_INFO, LOGF_SysLinux, "Successfully locked into RAM"); + } + } + } +} +#endif /* HAVE_MLOCKALL */ + #endif /* LINUX */ /* vim:ts=8 diff --git a/sys_linux.h b/sys_linux.h index 53639a5..d4bc2f6 100644 --- a/sys_linux.h +++ b/sys_linux.h @@ -39,4 +39,8 @@ extern void SYS_Linux_GetKernelVersion(int *major, int *minor, int *patchlevel); extern void SYS_Linux_DropRoot(char *user); +extern void SYS_Linux_MemLockAll(int LockAll); + +extern void SYS_linux_SetScheduler(int SchedPriority); + #endif /* GOT_SYS_LINUX_H */