Several system calls allow User Mode processes to read and modify the time and date and to create timers. Let’s briefly review these and discuss how the kernel handles them.
Processes in User Mode can get the current time and date by means of several system calls:
time( )
Returns the number of elapsed seconds since midnight at the start of January 1, 1970 (UTC).
ftime( )
Returns, in a data structure of type timeb
, the
number of elapsed seconds since midnight of January 1, 1970 (UTC) and
the number of elapsed milliseconds in the last second.
gettimeofday( )
Returns, in a data structure named timeval
, the
number of elapsed seconds since midnight of January 1, 1970 (UTC) (a
second data structure named timezone
is not
currently used).
The former system calls are superseded by gettimeofday( )
, but they are still included in Linux for backward
compatibility. We don’t discuss them further.
The gettimeofday( )
system call is implemented by
the sys_gettimeofday( )
function. To compute the
current date and time of the day, this function invokes
do_gettimeofday( )
, which executes the following
actions:
Disables the interrupts and acquires the
xtime_lock
read/write spin lock for reading.
Gets the number of microseconds elapsed in the last second by using
the function whose address is stored in
do_gettimeoffset
:
usec = do_gettimeoffset( );
If the CPU has a Time Stamp Counter, the
do_fast_gettimeoffset( )
function is executed. It
reads the TSC register by using the rdtsc
assembly
language instruction; it then subtracts the value stored in
last_tsc_low
to obtain the number of CPU cycles
elapsed since the last timer interrupt was handled. The function
converts that number to microseconds and adds in the delay that
elapsed before the activation of the timer interrupt handler (stored
in the delay_at_last_interrupt
variable mentioned
earlier in Section 6.2.1.1).
If the CPU does not have a TSC register,
do_gettimeoffset
points to the
do_slow_gettimeoffset( )
function. It reads the
state of the 8254 chip device internal oscillator and then computes
the time length elapsed since the last timer interrupt. Using that
value and the contents of jiffies
, it can derive
the number of microseconds elapsed in the last second.
Further increases the number of microseconds in order to take into account all timer interrupts whose bottom halves have not yet been executed:
usec += (jiffies - wall_jiffies) * (1000000/HZ);
Copies the contents of xtime
into the user-space
buffer specified by the system call parameter tv
,
adding to the following fields:
tv->tv_sec = xtime->tv_sec; tv->tv_usec = xtime->tv_usec + usec;
Releases the xtime_lock
spin lock and reenables
the interrupts.
Checks for an overflow in the microseconds field, adjusting both that field and the second field if necessary:
while (tv->tv_usec >= 1000000) { tv->tv_usec -= 1000000; tv->tv_sec++; }
Processes in User Mode with root privilege may modify the current
date and time by using either the obsolete stime( )
or the settimeofday( )
system call.
The sys_settimeofday( )
function invokes
do_settimeofday( )
, which executes operations
complementary to those of do_gettimeofday( )
.
Notice that both system calls modify the value of
xtime
while leaving the RTC registers unchanged.
Therefore, the new time is lost when the system shuts down, unless
the user executes the clock
program to change
the RTC value.
Although clock drift ensures that all systems eventually move away
from the correct time, changing the time abruptly is both an
administrative nuisance and risky behavior. Imagine, for instance,
programmers trying to build a large program and depending on filetime
stamps to make sure that out-of-date object files are recompiled. A
large change in the system’s time could confuse the
make
program and lead to an incorrect build.
Keeping the clocks tuned is also important when implementing a
distributed filesystem on a network of computers. In this case, it is
wise to adjust the clocks of the interconnected PCs so that the
timestamp values associated with the inodes of the accessed files are
coherent. Thus, systems are often configured to run a time
synchronization protocol such as Network Time Protocol (NTP) on a
regular basis to change the time gradually at each tick. This utility
depends on the adjtimex( )
system call in Linux.
This system call is present in several Unix variants, although it
should not be used in programs intended to be portable. It receives
as its parameter a pointer to a timex
structure,
updates kernel parameters from the values in the
timex
fields, and returns the same structure with
current kernel values. Such kernel values are used by
update_wall_time_one_tick( )
to slightly adjust
the number of microseconds added to xtime.tv_usec
at each tick.
Linux allows User Mode processes to activate special timers called interval timers.[52]
The timers cause Unix signals (see Chapter 10) to be sent periodically to the process. It is also possible to activate an interval timer so that it sends just one signal after a specified delay. Each interval timer is therefore characterized by:
The frequency at which the signals must be emitted, or a null value if just one signal has to be generated
The time remaining until the next signal is to be generated
The earlier warning about accuracy applies to these timers. They are guaranteed to execute after the requested time has elapsed, but it is impossible to predict exactly when they will be delivered.
Interval timers are activated by means of the POSIX
setitimer( )
system call. The first parameter
specifies which of the following policies should be adopted:
ITIMER_REAL
The actual elapsed time; the process receives
SIGALRM
signals.
ITIMER_VIRTUAL
The time spent by the process in User Mode; the process receives
SIGVTALRM
signals.
ITIMER_PROF
The time spent by the process both in User and in Kernel Mode; the
process receives SIGPROF
signals.
To implement an interval timer for each of the preceding policies, the process descriptor includes three pairs of fields:
it_real_incr
and it_real_value
it_virt_incr
and it_virt_value
it_prof_incr
and it_prof_value
The first field of each pair stores the interval in ticks between two signals; the other field stores the current value of the timer.
The ITIMER_REAL
interval timer is implemented by
using dynamic timers because the kernel must send signals to the
process even when it is not running on the CPU. Therefore, each
process descriptor includes a dynamic timer object called
real_timer
. The setitimer( )
system call initializes the real_timer
fields and
then invokes add_timer( )
to insert the dynamic
timer in the proper list. When the timer expires, the kernel executes
the it_real_fn( )
timer function. In turn, the
it_real_fn( )
function sends a
SIGALRM
signal to the process; if
it_real_incr
is not null, it sets the
expires
field again, reactivating the timer.
The ITIMER_VIRTUAL
and
ITIMER_PROF
interval timers do not require dynamic
timers, since they can be updated while the process is running. The
do_it_virt( )
and do_it_prof( )
functions are invoked by update_one_ process( )
,
which is called either by the PIT’s timer interrupt
handler (UP) or by the local timer interrupt handlers (SMP).
Therefore, the two interval timers are updated once every tick, and
if they are expired, the proper signal is sent to the current
process.
The alarm( )
system call sends a
SIGALRM
signal to the calling process when a
specified time interval has elapsed. It is very similar to
setitimer( )
when invoked with the
ITIMER_REAL
parameter, since it uses the
real_timer
dynamic timer included in the process
descriptor. Therefore, alarm( )
and
setitimer( )
with parameter
ITIMER_REAL
cannot be used at the same
time.
[52] These software constructs have nothing in common with the Programmable Interval Timer chips described earlier in this chapter.