6.7 System Calls Related to Timing Measurements

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.

6.7.1 The time( ), ftime( ), and gettimeofday( ) System Calls

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:

1.       Disables the interrupts and acquires the xtime_lock read/write spin lock for reading.

2.       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.

3.       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);

4.       Copies the contents of xtime into the user-space buffer specified by the system call parameter tv, adding to the following fields:

5.              tv->tv_sec = xtime->tv_sec;
    tv->tv_usec = xtime->tv_usec + usec;

6.       Releases the xtime_lock spin lock and reenables the interrupts.

7.       Checks for an overflow in the microseconds field, adjusting both that field and the second field if necessary:

8.              while (tv->tv_usec >= 1000000) { 
9.                  tv->tv_usec -= 1000000; 
10.             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.

6.7.2 The adjtimex( ) System Call

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.

6.7.3 The setitimer( ) and alarm( ) System Calls

Linux allows User Mode processes to activate special timers called interval timers.[8]

[8] These software constructs have nothing in common with the Programmable Interval Timer chips described earlier in this chapter.

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.