Chapter 5. Signalling Events

Processes can receive signals in order to respond to asynchronous requests from software or to unexpected hardware events. There are three different programming interfaces for receiving signals; you must select one and use it consistently throughout a program.

Many programs need access to time data for one of two purposes: to produce timestamps so that data can be ordered by its time of origin, and to define intervals so the program can take action at regular times. (Intervals are presented to the program as signals.)

These two issues are covered in the following topics:

Signals

A signal is a notification of an event, sent asynchronously to a process. Some signals originate from the kernel in response to hardware traps; for example, the SIGFPE signal that notifies of an arithmetic overflow, or the SIGALRM that notifies of the expiration of a timer interval. Other signals are issued by software. For a detailed, formal discussion of signals, read the signal(5) reference page.

A process can block all signals or selected signals, ignore some signals, or request a default system handling for some signals. When a signal that has been sent to a process is blocked by the process, the signal remains pending. When a signal is not blocked, the process receives the signal. In a multithreaded process, signals can be blocked or received by individual threads.

When receiving a signal, a process or thread can handle the signal by an asynchronous call into a signal-handling function. Alternatively, using the POSIX interface, a process or thread can handle signals synchronously, as a stream of event objects.

Signal Numbers

IRIX supports the following 64 signal numbers:

1-31

Same meanings as SVR4 and BSD; see Table 5-1.

32

Reserved by IRIX kernel.

33-48

Reserved by the POSIX standard for system use.

49-64

Reserved by POSIX for real-time programming.

Signals with smaller numbers have priority for delivery. The low-numbered BSD-compatible signals, which include all kernel-produced signals, are delivered ahead of real-time signals, and signal 49 takes precedence over signal 64.

Table 5-1 is reproduced from the signal(5) reference page for convenience.

Table 5-1. Signal Numbers and Default Actions

Symbolic Name

Numeric Value


Default Action


Normal Meaning

SIGHUP

1

Terminate

Controlling terminal disconnect; see termio(7).

SIGINT

2

Terminate

Interrupt key signal from controlling terminal; see termio(7).

SIGQUIT

3

Terminate and dump

Quit key signal from controlling terminal; see termio(7).

SIGILL

4

Terminate and dump

Attempt to execute illegal instruction.

SIGTRAP

5

Terminate and dump

Trace/breakpoint reached; see proc(4).

SIGABRT

6

Terminate and dump

Abort.

SIGEMT

7

Terminate and dump

Emulation trap.

SIGFPE

8

Terminate and dump

Arithmetic exception; see math(3M), sigfpe(3C), and matherr(3M).

SIGKILL

9

Terminate

Kill request from software or user.

SIGBUS

10

Terminate and dump

Bus error (hardware exception).

SIGSEGV

11

Terminate and dump

Segmentation fault (illegal address).

SIGSYS

12

Terminate and dump

Invalid system call.

SIGPIPE

13

Terminate

Read or write to broken pipe; see pipe(2), read(2), write(2).

SIGALRM

14

Terminate

Interval timer elapsed; see “Timer Facilities”.

SIGTERM

15

Terminate

Process terminated.

SIGUSR1

16

Terminate

Programmer-defined; see also text below.

SIGUSR2

17

Terminate

Programmer-defined.

SIGCHLD or SIGCLD

18

Terminate

Child process status change; see wait(2) and “Process “Reaping””.

SIGPWR

19

Ignore

Power fail/restart.

SIGWINCH

20

Ignore

Change in size of window; see xterm(1).

SIGURG

21

Ignore

Urgent socket condition; see socket(2).

SIGPOLL

22

Terminate

Pollable event from a STREAMS device, see streamio(7).

SIGIO

22

Terminate

Input/output possible.

SIGSTOP

23

Suspend

Stopped.

SIGTSTP

24

Suspend

Stop key signal from controlling terminal; see termio(7).

SIGCONT

25

Ignore

Continued.

SIGTTIN

26

Suspend

Attempt to read terminal from background process; see termio(7).

SIGTTOU

27

Suspend

Attempt to write terminal from background process; see termio(7).

SIGVTALRM

28

Terminate

Virtual timer expired; see getitimer(2).

SIGPROF

29

Terminate

Profiling timer expired; see getitimer(2).

SIGXCPU

30

Terminate and dump

CPU time limit exceeded; see getrlimit(2).

SIGXFSZ

31

Terminate and dump

File size limit exceeded; see getrlimit(2) and write(2).

(no symbol)

32-48

Terminate

Unassigned; do not use.

SIGRTMIN - SIGRTMAX

49-64

Terminate

POSIX real-time signal range.

Although SIGUSR1 and SIGUSR2 are nominally defined by the you for your program's purposes, they are also used by different application packages for special signals. For example, if you set a file lock on an NFS mounted file, the NFS lock daemon may send SIGUSR1—see “NFS File Locking”.  

Signal Implementations

There are three UNIX traditions for signals, and IRIX supports all three. They differ in the library calls used, in the range of signals allowed, and in the details of signal delivery. The basic signal operations and the implementing functions are summarized in Table 5-2.(You can click on the reference pages in this table to display them.)

Table 5-2. Signal Handling Interfaces

Function

POSIX Functions

SVR4 Functions

BSD 4.2 Functions

Set and query signal handler

sigaction(2)
sigsetops(3)
sigaltstack(2)

sigset(2)
signal(2)

sigvec(3)
signal(3)

Send a signal

sigqueue(2)
kill(2)
pthread_kill(3P)

sigsend(2)
kill(2)

kill(3)
killpg(3)

Temporarily block specified signals

sigprocmask(2)
pthread_sigmask(3P)

sighold(2)
sigrelse(2)

sigblock(3)
sigsetmask(3)

Query pending signals

sigpending(2)

n.a.

n.a.

Wait for a signal handler to be invoked.

sigsuspend(2)

sigpause(2)

sigpause(3)

Wait for a signal and receive synchronously

sigwait(2)
sigwaitinfo(2)
sigtimedwait(2)

n.a.

n.a.

It is important to not mix these signal facilities. Your program should use functions from only one column of Table 5-2; otherwise unexpected results can occur.

Signal Blocking and Signal Masks

Certain ideas are basic to the use of signals. One basic idea is that a program can block the delivery of any signal. When a signal that is sent to a program is blocked, the signal is queued and remains pending until the program unblocks the signal, or terminates. Certain urgent signals—SIGKILL, SIGSTOP, SIGCONT—cannot be blocked.

You specify which signals are blocked using a signal mask, a set of bits in which each bit corresponds to one signal number. When a bit in the mask is set on, the signal is blocked (if it is a signal that can be blocked).

Each process has a signal mask, inherited from its parent process. All three interfaces provide ways to set and clear bits in the current signal mask. The BSD interface, however, only lets you mask the first 32 signal numbers listed in Table 5-1.

Each POSIX thread has a signal mask also (see “Setting Signal Masks”). A multithreaded program (defined as a program that is linked with libpthread, so it uses the pthreads version of the standard library) should use the POSIX interface for signal handling.

Multiple Signals

In most cases, if a signal of a certain number is pending for a process, and another signal of the same number arrives, the second signal is discarded. In other words, at most one signal of a given number can normally be pending for a process.

In the POSIX interface you can use one particular function, sigqueue(), to send a signal that is queued regardless of how many signals of the same number are already pending.

Signal Handling Policies

You can specify one of three policies for handling an unblocked signal. You set the policy for each signal number individually.

Default Handling

Initially, all signals receive default handling. This means that when a signal arrives and is not blocked, it causes the default action listed in Table 5-1. In many cases the default action is to ignore the signal, that is, to silently discard it. In other cases, the default action is to terminate the program, or to terminate it with a dump.

Each signal interface gives you a way to specify non-default handling or a specified signal, or to return a signal to default handling.

Ignoring Signals

You can request that a specified signal be ignored. You would do this when the signal is not meaningful to your program and the default action is not what you wish. For example, in a noninteractive program, you might set Ignore handling for SIGHUP (the default action is to terminate).

Catching Signals

You can request that a signal be caught and handled asynchronously, at the moment it arrives. You specify that a signal should be caught by specifying the address of a function to be called when the signal is received.

The signal-handling function is entered asynchronously, without regard for what the process was doing at the time the signal was delivered. You cannot be sure what code was executing when the signal handler is called; it could have been any function in your own code, or it could have been code in the C library or in any layer of the X-Windows or Motif support libraries.

All three interfaces provide for passing the signal number as the first argument of the signal-handling function. Other arguments to the handler function depend on the interface used and the options you specify when establishing the handler.

You can create an alternate memory area to be used as a stack when executing the signal handler. Typically a signal handler does not require a great deal of stack space. On the other hand, each POSIX thread has limited stack space, and when you provide an alternate signal-handling stack, you do not have to allow for possible signals in allocating thread stack space (see “Setting Signal Actions”).

Synchronous Signal Handling

Using the POSIX signal interface you can process signals in a synchronous way, as a stream of input items to your program. This allows you to design your program so that signals are received when the process is in a known state, without the uncertainties of asynchronous delivery.

Signal Latency

The time that elapses from the moment a signal is generated until a signal handler begins to execute is the signal latency. Signal latency can be long (as real-time programs measure time) and signal latency has a high variability.

The IRIX kernel normally delivers a pending, unblocked signal the next time the process returns to user code from the kernel domain. In most cases, this occurs

  • when the process is dispatched after a wait or preemption

  • upon return from a system function

  • upon return from the kernel's usual 10-millisecond “tick” (dispatch) interrupt

SIGALRM, which signals the expiration of a real-time timer (see “Timer Facilities”), is given special treatment. It is delivered as soon as the kernel is ready to return to a user process after the timer interrupt, in order to preserve timer accuracy.

When a process is ready to run and is not preempted by a process of higher priority, and is executing in user code, not calling a system function, the latency for other than SIGALRM can be as much as 10 milliseconds. However, when the process is suspended (for example, waiting on a semaphore), or when there are competing processes having higher priorities, the delivery of a signal is delayed until the next time the receiving process is scheduled. This can be many milliseconds.

In general, you should use signals to deliver infrequent messages of high priority. You should not use the exchange of signals as the basis for real-time scheduling.

Signals Under X-Windows

If you plan to handle signals asynchronously in a program that uses X intrinsics, you must take special steps. Before establishing a signal handler with the operating system, you establish one or more signal callback procedures using XtAppAddSignal(). Then, in the asynchronous signal handling function, you call XtNoticeSignal(). This function ensures that the established signal callback will be invoked like other callback functions, when it is safe to do so. This process is documented in the XtAppAddSignal(3Xt) reference page.

The only X-windows function that can safely be called from a signal handler is XtNoticeSignal().

POSIX Signal Facility

The POSIX interface to signals is the most functionally complete and robust of the three. It is the recommended interface for all new programs. The functions used in POSIX style signal handling are summarized in Table 5-3.

Table 5-3. Functions for POSIX Signal Handling

Function

Purpose

kill(2)

Send a signal to a process or process group. (Discards multiple signals of the same number.)

sigqueue(3)

Queue a signal to a specified process, including a sigval for added information about the signal. (Queues multiple signals of the same number.)

pthread_kill(3P)

Send a signal to a specified thread.

sigprocmask(2)
pthread_sigmask(3P)

Examine or change the mask of signals allowed and blocked. You must use pthread_sigmask() in a program that is linked with libpthread.

sigaction(2)

Specify or query the signal handling policy for a specified signal.

sigaltstack(2)

Specify or query an alternate stack area to be used by a signal handler.

sigpending(2)

Return the set of signals pending for the calling process or thread.

sigsetops(3)

Manipulate signal mask objects in memory.

sigsuspend(2)

Unblock selected signals for the calling process or thread, and wait for a signal to be received asynchronously.

sigwait(3)
sigtimedwait(3)
sigwaitinfo(3)

Wait for and receive specified signals in a synchronous manner.

In addition to the reference pages listed in Table 5-3, the following have important information about signal handling:

signal(5)

Detailed overview of signals and signal handling.

siginfo(5)

Description of the information structure passed to a POSIX signal handler.

ucontext(5)

Description of machine context structure passed to a POSIX signal handler.


Signal Masking

Each process and thread has an active signal mask. A single-thread program sets or queries its signal mask using sigprocmask(). A multithreaded program (any program that linked libpthread, which provides the pthread version of the standard library) should use pthread_sigmask().

Besides the active signal mask, you may have other signal mask objects (type sigset_t) in memory. The sigsetops(3) reference page documents a number of utility functions for setting, clearing, and testing the bits in a signal mask object. Several POSIX signal functions take a signal mask as an argument. For example, sigsuspend() takes a new signal mask and swaps it for the current signal mask, establishing which pending signals will be accepted while the process is suspended.

Using Synchronous Handling

You can design your program so that it treats arriving signals as a stream of event records to be processed in sequence. For example, you could use one or more signal numbers in the POSIX real-time range to signify events that are meaningful to your application. Your application, or one thread in your application, can receive each signal in turn and act upon it.

To implement this design approach, follow these steps:

  1. Block the expected signal numbers in all processes or threads using sigprocmask() or pthread_sigmask().

  2. Send the signals using sigqueue(). This function permits you to augment the signal number with a union sigval (in effect creating an open-ended set of sub-signals), and also assures that multiple signals will be retained until you process them.

  3. In the signal-processing loop, wait for the next signal with sigwaitinfo() or sigtimedwait(). When the signal arrives, act accordingly and wait again.

The sigwaitinfo() and sigtimedwait() functions accept a new signal mask. They unblock the specified signal or signals and suspend until one such signal arrives. They accept that signal, restore the original signal mask, and return the signal information.

You could construct a very similar work-handling application using a message queue (see Chapter 6, “Message Queues”). However, this design approach allows you to integrate the handling of unplanned signals such as SIGPIPE, and interval-timer signals such as SIGALRM, into the same scheme as planned application events.

Using Asynchronous Handling

Using sigaction(), you specify a function to be called when a particular signal is received. You have a choice of function prototypes. In each case the signal handler is passed the signal number, additional information about the signal, and information about the machine context at the time the signal was delivered.

Your signal handler can have the POSIX prototype, as follows:

void name(int sig, siginfo_t *sip, ucontext_t *up)

The second argument, a POSIX information structure siginfo_t, contains these fields:

si_signo

The signal number (again).

si_errno

Either 0 or an error code from errno.h.

si_code

An indication of the source of the signal.

si_value

When si_code is SI_QUEUE, the union sigval passed to sigqueue().

si_pid

When si_code is SI_USER, the process ID that called kill().

When the signal is an error reported by the kernel or hardware, si_code is an explanatory number. These values are spelled out in detail in the siginfo(5) reference page. The third argument, a pointer to a ucontext_t object, gives the machine state at the time the signal was delivered. The ucontext_t is detailed in the ucontext(5) reference page.

Alternatively, your signal handler can have this prototype:

void name(int sig, int code, struct sigcontext *sc);

The second argument gives some added information about the signal (see signal(5) for a list of codes). The third argument, a pointer to a sigcontext_t object, gives the machine state at the time the signal was delivered (in slightly different form from the ucontext_t).

When you use sigaction() to set up a signal handler, you pass an argument structure containing option flags that affect the treatment of the signal:

SA_SIGINFO

When set, you are specifying asynchronous handling and your handler uses the POSIX prototype. Its address is passed in the sa_sigaction structure field. When not set, a handler uses the older prototype and its address is passed in sa_handler.

SA_ONSTACK

When set, your handler is called using alternate stack memory you have previously assigned with sigaltstack(). Otherwise the handler uses the stack of the process or thread stack executing at the time of the signal.

SA_RESETHAND

When set, the policy for this signal is reset to the default when your handler is called. Your handler is expected to reestablish the action if that is desired.

SA_NODEFER

When not set, the signal is automatically blocked while your handler executes, and unblocked when your handler returns. When set, the same signal could be taken while your handler executes, resulting in multiple entries to the handler.

SA_RESTART

When not set, if this signal interrupts a blocked system function the system function returns EINTR. When set, the system function is restarted.

System V Signal Facility

The System V signal interface is compatible with code ported from UNIX System V. It includes compatibility for release 3 (SVR3) and release 4 (SVR4). Table 5-4 summarizes the functions you use to manage signals through this interface.

Table 5-4. Functions for SVR4 Signal Handling

Function

Purpose

kill(2)

Send a signal to a process or process group. (A duplicate of a pending signal is discarded.)

sigsend(2)

Send a signal to a set of processes or process groups, specified in a variety of ways, for example by user ID.

signal(2)

SVR3 call to establish handling policy of default, ignore, or catch for a specified signal.

sigset(2)

SVR4 call to establish handling policy of default, ignore, or catch for a specified signal.

sighold(2)

Hold (block) a specified signal.

sigignore(2)

Set the handling for a specified signal to Ignore.

sigrelse(2)

Release (unblock) a specified signal.

sigpause(2)

Suspend the calling process until a specified signal arrives.

Only asynchronous signal handling is supported by the System V interface. Also, you must block and unblock signals individually; there is no support for setting the entire signal mask in one operation.

The semantics of SVR3-compatible signal established with signal() are not desirable for most programs. When control enters a signal handler you established using signal(), the handling of that same signal is set to default, and that signal remains unblocked. Your signal handler can use signal() to reestablish itself as the handler, or it can use sighold() to block the signal. However, even if these actions are the first statements of the handler function, there is a period of time at the beginning of the handler during which a second signal of the same type could be received. If this occurs, the second signal receives default handling and is not seen by your handler.

You can avoid this problem by using the SVR4 function sigset() instead of signal() to establish a handler. Before a handler established by sigset() is called, that signal is blocked until the handler returns, and the signal disposition is not reset to default.

BSD Signal Facility

The BSD signal facility is compatible with code ported from the BSD 4.2 distribution. Table 5-5 summarizes the functions you use to manage signals with this interface.


Note: In order to use any of the functions in Table 5-5 you must define one of the compiler variables _BSD_SIGNALS or _BSD_COMPAT prior to the inclusion of the header file signal.h. You can do this directly in the source file with #define. More commonly you will include -D_BSD_COMPAT as one of the compiler flags you define in your Makefile.


Table 5-5. Functions for BSD Signal Handling

Function Name

Purpose and Operation

kill(3B)

Send a signal to a specified process, or broadcast a signal to a process group or to all processes with the same effective user ID. (A duplicate of a pending signal is discarded.)

killpg(3B)

Send a signal to all members of a process group. (A duplicate of a pending signal is discarded.)

sigvec(3)

Establish a policy of default, ignore, or catch for a specified signal.

signal(3B)

Simplified interface to sigvec().

sigstack(2B)

Establish an alternate stack for the use of signal-handling functions.

sigsetmask(3)

Set the active signal mask.

sigblock(3)

Add blocked signals to the active signal mask.

sigpause(3B)

Wait for specified signals to arrive.

Only asynchronous signal handling is supported by the BSD interface. It is possible to set and interrogate the signal mask in a single operation; however, the signal mask type is the integer, so only signal numbers 1-32 can be blocked. The BSD interface does not recognize higher-numbered signals.

Timer Facilities

You use timer facilities for a number of purposes: to get information about program performance; to make a program pause for a certain time; to program an interval of time; and to create a timestamp value to store with other data.

Timed Pauses and Schedule Cession

In many instances a program, or a process within a multiprocess program, needs to suspend execution for a period of time. IRIX contains a variety of functions that provide this capability. The functions differ in their precision and in their portability. Table 5-6 contains a summary.

Table 5-6. Functions for Timed Suspensions

Reference Page

Precision

Compatibility

Operation

sched_yield(2)

n.a.

POSIX

Defer to any processes eligible to run.

sginap(2)

dispatching interval (10ms)

IRIX

Defer to other processes for the specified number of dispatching cycles.

sleep(3C)

second

POSIX

Suspend for a number of seconds or until a signal arrives.

usleep(3C)

microsecond

IRIX

Suspend for a number of microseconds or until a signal arrives.

nanosleep(2)

nanosecond

POSIX

Suspend for a number of seconds and nanoseconds or until a signal arrives.

Sometimes you do not want to suspend for any particular amount of time, but simply want to make the current process defer to other processes, so that any waiting processes receive a chance to run. You can achieve this in two ways. The IRIX unique function sginap() accepts an argument of 0, meaning to defer for the minimum amount of time. However, sched_yield() is a POSIX compliant function for this purpose.

Time Data Structures

The include files time.h and sys/time.h define several data types and data structures related to time. Some of these are used in POSIX time functions and others in BSD-based functions; and there are somewhat confusing similarities between them. Features of these structures are summarized in Table 5-7.

Table 5-7. Time Data Structures and Usage

Data Type

Declared In

Contains

Some Functions Using This Type

time_t

time.h

long int with time in seconds since 00:00:00 UTC, January 1, 1970

time(2), ctime(3C), cftime(3C), difftime(3C)

timeval

sys/time.h

structure of time_t giving seconds and a long int giving microseconds

adjtime(2), getitimer(2), getrusage(3C), gettimeofday(3C), select(2), utimes(3B)

itimerval

sys/time.h

structure of two timeval fields for first interval and repeat interval

getitimer(2) and setitimer(2)

timespec_t

time.h

structure of time_t giving seconds and a long int giving nanoseconds

clock_gettime(2), nanosleep(2), aio_suspend(3), sigtimedwait(3C)

itimerspec

time.h

structure of two timespec_t fields for first interval and repeat interval

timer_settime(3C), timer_gettime(3C)

tm

time.h

structure of int fields for seconds, minutes, hours, day, month, etc.

localtime(2), gmtime(2), strftime(3C)


Time Signal Latency

It takes time for the kernel to deliver the SIGALRM that notifies your program at the end of an interval. (The issue of signal latency in general is discussed under “Signal Latency”.) The signal latency is less for SIGALRM than for other signals, since the kernel initiates a scheduling cycle immediately after the timer interrupt, without waiting for the end of a fixed time slice. When the receiving process or thread is running or ready to run, the latency is fairly short and consistent from one signal to the next. (Even so, it is not advisable to use a repeating itimer as the time base for a real-time program). Under less favorable conditions, signal latency can be variable and sometimes lengthy (tens of milliseconds) relative to a fast timer frequency.

How Timers Are Managed

The IRIX kernel can be asked to implement itimers for many processes at once, each interval having a different length and starting at a different time. The kernel's method differs depending on the hardware architecture (this issue is discussed at length in the timers(5) reference page).

  • Some obsolete Silicon Graphics systems have no hardware support for interval timers, so the kernel had to rely on frequent, periodic interrupts as a time base.

    In those systems, the precision of timer interrupts was controlled by a kernel tuning variable, fasthz, which determined the rate at which the kernel was interrupted to poll for an expired timer.

  • In all current architectures, each CPU has a clock comparator that the kernel can program to cause an interrupt after a specific interval has elapsed.

    In these systems, timer interrupts have sub-microsecond precision and do not impose overhead for timer-polling interrupts.

In earlier versions of IRIX, in order to minimize the overhead of polling for elapsed timers, the kernel did not allow normal processes to ask for timer intervals with fine granularity (sub-millisecond precision). Only processes that executed under real-time scheduling priority could ask for precise timer intervals.

Starting with IRIX 6.2, any process can request a timer interval with any precision. If this support is misused, it is possible to cause performance problems. For example, a process can set up a repeating timer at an interval so short that one CPU is monopolized by setting and handling that timer.

POSIX Timers

IRIX supports the time and timer facilities specified by IEEE standard 1003.1b-1993, commonly called POSIX timers. This timer interface is the most complete, robust, and portable, and is recommended for all new applications. The functions it includes for time measurement are summarized in Table 5-8.

Table 5-8. POSIX Time Management Functions

Function Name

Purpose and Operation

time(2)

Return a time_t value containing the count of seconds elapsed since 00:00:00 UTC, January 1, 1970.

times(2)

Return user and system execution time consumption for the calling process and its terminated child processes.

clock_gettime(2)

Return the instantaneous reading of one of two clocks: the system time (CLOCK_REALTIME), or the hardware cycle counter (CLOCK_SGI_CYCLE).

clock_getres(2)

Return the precision of the system time (CLOCK_REALTIME), the hardware cycle counter in this system (CLOCK_SGI_CYCLE) or the high-resolution timer base (CLOCK_SGI_FAST).

The POSIX functions for interval timers are summarized in Table 5-9.

Table 5-9. POSIX Time Management Functions

Function Name

Purpose and Operation

alarm(2)

Cause a SIGALRM signal after a specified number of whole seconds.

timer_create(3C)

Create a POSIX timer and specify its time base (CLOCK_REALTIME or CLOCK_SGI_FAST) and the signal number it can generate.

timer_delete(3C)

Remove a timer created with timer_create().

timer_settime(3C)

Set expiration and reload times of a timer, or disarm it.

timer_gettime(3C)

Query the time remaining in a timer.

timer_getoverrun(3C)

Query the number of overrun events generated by a timer.


Getting Program Execution Time

The times() function returns counts of accumulated user-process and system execution time. These counts have a resolution of the system dispatching interval, 10 milliseconds.

Creating Timestamps

The time() function returns a timestamp with a resolution of 1 second. A timestamp with a resolution this coarse can be used only for infrequent events.

You can use the clock_gettime() function to sample the system time with a resolution of 0.01 second, or you can use it to read the hardware cycle counter—a free-running binary counter with an update frequency near the machine clock rate. The clock_getres() function returns the resolution of either of these clocks.

The program in Example 5-1 demonstrates the use of clock_gettime() and clock_getres(). The following is an example of the output of this program, ptime, as executed on an Indy workstation:

$ ptime
CLOCK_REALTIME value: sec 835660711, ns 465330000 [8.35661e+08 sec]
CLOCK_REALTIME units: sec 0, ns 10000000 [0.01 sec]
CLOCK_SGI_CYCLE value: sec 83, ns 449744360 [83.4497 sec]
CLOCK_SGI_CYCLE units: sec 0, ns 40 [4e-08 sec]
CLOCK_SGI_FAST units: sec 0, ns 1000000 [0.001 sec]

 

Example 5-1. Example of POSIX Time Functions

/*
|| Program to exercise POSIX clock_gettime() and clock_getres() functions.
||
||  ptime [-r -c -R -C -F]
||      -r  display CLOCK_REALTIME value
||      -R  display CLOCK_REALTIME resolution
||      -c  display CLOCK_SGI_CYCLE value
||      -C  display CLOCK_SGI_CYCLE resolution
||      -F  display CLOCK_SGI_FAST resolution (cannot get time from this)
|| Default is display everything (-rRcC).
*/
#include <time.h>
#include <unistd.h>     /* for getopt() */
#include <errno.h>      /* errno and perror */
#include <stdio.h>
void showtime(const timespec_t tm, const char *caption)
{
    printf("%s: sec %ld, ns %ld [%g sec]\n",
        caption, tm.tv_sec, tm.tv_nsec,
        ((double)tm.tv_sec) + ((double)tm.tv_nsec / 1e9));
}
main(int argc, char **argv)
{
    int opta = 1;
    int optr = 0;
    int optR = 0;
    int optc = 0;
    int optC = 0;
    int optF = 0;
    timespec_t sample, res;
    int c;
    while ( -1 != (c = getopt(argc,argv,"arRcCF")) )
    {
        switch (c)
        {
        case 'a': opta=1; break;
        case 'r': optr=1; opta=0; break;
        case 'R': optR=1; opta=0; break;
        case 'c': optc=1; opta=0; break;
        case 'C': optC=1; opta=0; break;
        case 'F': optF=1; opta=0; break;
        default: return -1;
        }
    }
    if (opta || optr)
    {
        if (!clock_gettime(CLOCK_REALTIME,&sample))
            showtime(sample,"CLOCK_REALTIME value");
        else
            perror("clock_gettime(CLOCK_REALTIME)");
    }
    if (opta || optR)
    {
        if (!clock_getres(CLOCK_REALTIME,&res))
            showtime(res,"CLOCK_REALTIME units");
        else
            perror("clock_getres(CLOCK_REALTIME)");
    }
    if (opta || optc)
    {
        if (!clock_gettime(CLOCK_SGI_CYCLE,&sample))
            showtime(sample,"CLOCK_SGI_CYCLE value");
        else
            perror("clock_gettime(CLOCK_SGI_CYCLE)");
    }
    if (opta || optC)
    {
        if (!clock_getres(CLOCK_SGI_CYCLE,&res))
            showtime(res,"CLOCK_SGI_CYCLE units");
        else
            perror("clock_getres(CLOCK_SGI_CYCLE)");
    }
    if (opta || optF)
    {
        if (!clock_getres(CLOCK_SGI_FAST,&res))
            showtime(res,"CLOCK_SGI_FAST units");
        else
            perror("clock_getres(CLOCK_SGI_FAST)");
    }
}

The real-time clock (CLOCK_REALTIME) can shift backward or jump forward under the influence of adjustments to the system time by a time daemon. The Silicon Graphics hardware cycle counter always increases at a steady rate. However, the cycle counter has a limited precision that depends on the hardware. You can use the syssgi() system function to find out the precision of the cycle counter (see syssgi(2) and look for the SGI_CYCLECNTR_SIZE option).

Using Interval Timers

You create an interval timer object by calling timer_create(). To this function you pass codes that specify the time base to use and the signal to send upon timer expiration. It returns an ID value to identify the timer to other functions.

The time base for a timer is either CLOCK_REALTIME or CLOCK_SGI_FAST (the latter is a nonportable request). Typically CLOCK_SGI_FAST has finer resolution, but you can verify that using the clock_getres() function, as shown in Example 5-1.

You also pass a sigevent_t object to timer_create(). In it you would normally set the following values:

sigev_notify

SIGEV_SIGNAL to have the timer generate a signal on expiration.

sigev_signo

The signal number you want sent, possibly selected from the POSIX real-time range, for example, SIGRTMIN+1.

sigev_value.sival_int
sigev_value.sival_ptr

An extra value to be passed to the signal-handling function or to sigwait() when the signal is delivered.

You can pass a NULL instead of the address of a sigevent_t. In that case, the timer signals with a SIGALRM.

Initially, a timer is disarmed (inactive). You start a timer by calling timer_settime(). The principal argument to this function is an itimerspec_t object, which contains two times. One, it_value, specifies when the timer next expires. The other, it_interval, is the value to be loaded into the timer when it expires. You can call timer_settime() to accomplish any of three different operations:

  • With it_value nonzero and it_interval zero, arm the timer and initiate a one-time interval.

  • With it_value nonzero and it_interval nonzero, arm and initiate a repeating timer.

  • With it_value zero, disarm the timer, preventing it from expiring (if it has not expired already).

You can also use timer_settime() to reprogram the intervals in a timer while it runs.

A timer can be programmed in terms of relative time (you pass an it_value that represents increments past the present time) or absolute time (you pass an it_value that represents actual future times when the timer should expire).

You can interrogate the time remaining in a timer by calling timer_gettime(). After a timer has expired—for example, in the signal handling function—you can call timer_getoverrun() to find out how many additional intervals it would have signalled, but could not signal because the first signal was pending.

BSD Timers

IRIX supports the BSD UNIX feature of interval timers or “itimers.” Table 5-10 summarizes the functions you use to manage itimers.

Table 5-10. BSD Functions for Interval Timers

Function Name

Purpose and Operation

setitimer(2)

Set the expiration and repeat interval of a timer.

getitimer(2)

Return the current value of a timer.

Each process has three itimers available to it, as summarized in Table 5-11.

Table 5-11. Types of itimer

Kind of itimer

Interval Measured

Resolution

Signal Sent

ITIMER_REAL

Elapsed clock time

1 millisecond or less

SIGALRM

ITIMER_VIRTUAL

User time (process execution time)

1 second

SIGVTALRM

ITIMER_PROF

User+system time

1 second

SIGPROF

The ITIMER_VIRTUAL and ITIMER_PROF have a relatively coarse precision. Their intervals vary depending on when and how often the process is dispatched. The ITIMER_REAL timer is comparable to the POSIX time base CLOCK_SGI_FAST.

In order to use an itimer, you establish a signal handler for the appropriate signal as shown in Table 5-11, then issue the setitimer() call. The principal argument to this function is a struct itimerval, an object containing two incremental time values. The it_value field specifies the time until the timer should expire. The it_interval field, when nonzero, gives the time that should be loaded into the timer after it expires.


Tip: One excellent reason not to mix BSD and POSIX timer support in the same program is that the POSIX struct itimerspec, used to set a POSIX timer, and the BSD struct itermval, used to set a BSD itimer, have fields with identical names, but these fields have different data types and precisions.

You can use setitimer() for any of three operations:

  • With it_value nonzero and it_interval zero, initiate a one-time interval.

  • With it_value nonzero and it_interval nonzero, initiate a repeating timer.

  • With it_value zero, disarm the timer, preventing it from expiring (if it has not expired already).

Hardware Cycle Counter

All current Silicon Graphics systems have a hardware “cycle counter,” a free-running binary counter that is incremented at a high, regular frequency. You can use the cycle counter as a high-precision timestamp.

The precision of the cycle counter is different in different system types; for example, it is a 24-bit counter in the Indy workstation, but a 64-bit counter in CHALLENGE and Onyx systems. The rate at which the timer increments is its resolution, and this also varies with the hardware type.

The cycle counter is an addressable hardware device that you can map into the address space of your process (see “Mapping Physical Memory”). When this is done you can sample the cycle counter as if it were a program variable. The code to do this mapping is discussed in the syssgi(2) reference page under SGI_QUERY_CYCLECNTR.

However, the use of the hardware cycle counter has been integrated into the POSIX timer support beginning in IRIX 6.2, and this makes access to the cycle counter much simpler than before:

  • In order to sample the cycle counter, call clock_gettime() passing CLOCK_SGI_CYCLE.

  • In order to find out the resolution (update frequency) of the cycle counter, call clock_getres() passing CLOCK_SGI_CYCLE.

  • In order to find out the precision of the cycle counter, call syssgi() passing SGI_CYCLECNTR_SIZE. The returned value is the number of bits in the counter.

The first two operations are illustrated in Example 5-1.