Linux Application Development (paperback) (2nd Edition)

   

12.7. Learning About a Signal

The signals we have discussed so far carry no data with them; the arrival of the signal is the only information the application gets. In some cases it would be nice to know what caused the signal to be sent (such as the illegal memory address that caused a SIGSEGV) or to be able to include data with application-generated signals. The Real Time Signal extensions address both of these needs.

12.7.1. Getting a Signal's Context

The information about how and why a signal was generated is called the signal's context.[21] Applications that wish to view a signal's context use a different signal handler than the normal one. It includes two more parameters, a pointer to a siginfo_t, which provides the signal's context and a pointer to void *, which can be used by some low-level system libraries.[22] Here is what the full handler prototype looks like:

[21] Before the POSIX standards, applications could access a struct sigcontext for the same type of information now provided by siginfo_t, and the term context has stuck from this older implementation.

[22] This third parameter is actually a pointer to a struct ucontext, which allows processes to do full context switching in user space. Doing this is beyond the scope of this book, but it is well documented in the Single Unix Specification.

void handler(int signum, siginfo_t * siginfo, void * context);

Applications need to tell the kernel to pass full context information by setting the SA_SIGINFO flag in the sa_mask member of the struct sigaction used to register the signal handler. The sa_handler member is also not used, as it is a pointer to a function with a different prototype. Instead, a new member, sa_sigaction, is set to point to the signal handler with the proper prototype.

To help reduce memory usage, sa_handler and sa_sigaction are allowed to use the same region of memory, so only one should be used at a time. To make this transparent, the C library defines struct sigaction like this:

#include <signal.h> struct sigaction { union { __sighandler_t sa_handler; __sigaction_t sa_sigaction; } __sigaction_handler; sigset_t sa_mask; unsigned long sa_flags; }; #define sa_handler __sigaction_handler.sa_handler #define sa_sigaction __sigaction_handler.sa_sigaction

Using this combination of a union and macros allows the two members to overlap in memory without the data structure getting overly complicated from the application's view.

The siginfo_t structure contains information about where and why a signal was generated. Two members are available for all signals: si_signo and si_code. Which other members are available depends on the signal being delivered, and those members overlap in memory in a way similar to sa_handler and sa_sigaction in struct sigaction. The si_signo member contains the signal number that is being delivered and is the same as the first parameter passed to the signal handler, while si_code specifies why the signal was generated and changes depending on the signal number. For most signals, it is one of the following:

SI_USER

A user-space application used kill() to send the signal.[23]

SI_QUEUE

Auser-space application used sigqueue() to send the signal, which is discussed on page 237.

SI_TKILL

The signal was sent by a user-space application using the tkill() system call. While the Linux kernel uses SI_TKILL, its value is not specified in current versions of the C library. If you need to check for SI_TKILL, use the following code segment to define its value:

#ifndef SI_TKILL #define SI_TKILL -6 #endif

 

SI_TKILL is not specified by any standard (but it is allowed by them), so it needs to be used with care in portable programs.

SI_KERNEL

The signal was generated by the kernel.[24]

[23] The sigsend() function, which Linux includes for compatibility with some other Unix systems, also causes SI_USER.

[24] There are more values for si_code than we talk about here, related to features like asynchronous I/O, message queues, and real-time timers, which are outside the scope of this book.

When SIGILL, SIGFPE, SIGSEGV, SIGBUS, and SIGCHLD are sent by the kernel, si_code takes on the values shown in Table 12.3 instead of SI_KERNEL.[25]

[25] It also takes on special values for SIGTRAP, which is used by debuggers, and SIGPOLL, which is used by an unreliable asynchronous I/O mechanism. Neither of these topics is covered in this book, so the details on these signals have been omitted from Table 12.3.

Table 12.3. Values of si_code for Special Signals

Signal

si_code

Description

SIGILL

ILL_ILLOPC

Illegal opcode

 

ILL_ILLOPC

Illegal operand

 

ILL_ILLOPC

Illegal addressing mode

 

ILL_ILLOPC

Illegal trap

 

ILL_ILLOPC

Privileged opcode

 

ILL_ILLOPC

Privileged register

 

ILL_ILLOPC

Internal stack error

 

ILL_ILLOPC

Coprocessor error

SIGFPE

FPE_INTDIV

Integer divide by zero

 

FPE_INTOVF

Integer overflow

 

FPE_FLTDIV

Floating point divide by zero

 

FPE_FLTOVF

Floating point overflow

 

FPE_FLTUND

Floating point underflow

 

FPE_FLTRES

Floating point inexact result

 

FPE_FLTINV

Floating point invalid operation

 

FPE_FLTSUB

Floating point subscript out of range

SIGSEGV

SEGV_MAPERR

Address not mapped in an object

 

SEGV_ACCERR

Invalid permissions for address

SIGBUS

BUS_ADRALN

Invalid address alignment

 

BUS_ADRERR

Nonexistent physical address

 

BUS_OBJERR

Object specific hardware error

SIGCHLD

CLD_EXITED

Child has exited

 

CLD_KILLED

Child was killed without a core file

 

CLD_DUMPED

Child was killed and a core file was created

 

CLD_TRAPPED

Child has hit a breakpoint

 

CLD_STOPPED

Child has stopped

To help clarify the various values si_code can take, here is an example that generates SIGCHLD in four different ways: kill(), sigqueue(), raise() (which uses the tkill() system call), and by creating a child that immediately terminates.

1: /* sicode.c */ 2: 3: #include <sys/signal.h> 4: #include <stdlib.h> 5: #include <stdio.h> 6: #include <unistd.h> 7: 8: #ifndef SI_TKILL 9: #define SI_TKILL -6 10: #endif 11: 12: void handler(int signo, siginfo_t * info, void * f) { 13: static int count = 0; 14: 15: printf("caught signal sent by "); 16: switch (info->si_code) { 17: case SI_USER: 18: printf("kill()\n"); break; 19: case SI_QUEUE: 20: printf("sigqueue()\n"); break; 21: case SI_TKILL: 22: printf("tkill() or raise()\n"); break; 23: case CLD_EXITED: 24: printf("kernel telling us child exited\n"); exit(0); 25: } 26: 27: if (++count == 4) exit(1); 28: } 29: 30: int main() { 31: struct sigaction act; 32: union sigval val; 33: pid_t pid = getpid(); 34: 35: val.sival_int = 1234; 36: 37: act.sa_sigaction = handler; 38: sigemptyset(&act.sa_mask); 39: act.sa_flags = SA_SIGINFO; 40: sigaction(SIGCHLD, &act, NULL); 41: 42: kill(pid, SIGCHLD); 43: sigqueue(pid, SIGCHLD, val); 44: raise(SIGCHLD); 45: 46: /* To get a SIGCHLD from the kernel we create a child and 47: have it exit immediately. The signal handler exits after 48: receiving the signal from the kernel, so we just sleep for 49: a while and let the program terminate that way. */ 50: 51: if (!fork()) exit(0); 52: sleep(60); 53: 54: return 0; 55: }

If si_code is SI_USER, SI_QUEUE, or SI_TKILL, two additional members of siginfo_t are available, si_pid and si_uid, which provide the process ID that sent the signal and the real user ID of that process.

When a SIGCHLD is sent by the kernel, the si_pid, si_status, si_utime, and si_stime members are available. The first, si_pid, gives the pid of the process whose status has changed.[26] Information on the new status is available in both si_code (as specified by Table 12.3) and si_status, which is identical to the status integer returned by the wait() family of functions. The final two members, si_utime and si_stime, specify the amount of the time the child application has spent in user space and kernel space, respectively (this is similar to measures wait3() and wait4() return in struct rusage). They are measured in clock ticks, which is an integer. The number of clock ticks per second is defined by the _SC_CLK_TCK macro, defined in <sysconf.h>.

[26] Recall that SIGCHLD is sent not only when a child has exited, but also when a child has stopped or resumed.

SIGSEGV, SIGBUS, SIGILL, and SIGFPE all provide si_addr, which specifies the address that caused the fault described by si_code.

Here is a simple example of examining a signal's context. It installs a signal handler for SIGSEGV that prints out the context for that signal and then terminates the process. A segmentation violation is generated by trying to dereference NULL.

1: /* catch-segv.c */ 2: 3: #include <sys/signal.h> 4: #include <stdlib.h> 5: #include <stdio.h> 6: 7: void handler(int signo, siginfo_t * info, void * f) { 8: printf("caught "); 9: if (info->si_signo == SIGSEGV) 10: printf("segv accessing %p", info->si_addr); 11: if (info->si_code == SEGV_MAPERR) 12: printf(" SEGV_MAPERR"); 13: printf("\n"); 14: 15: exit(1); 16: } 17: 18: int main() { 19: struct sigaction act; 20: 21: act.sa_sigaction = handler; 22: sigemptyset(&act.sa_mask); 23: act.sa_flags = SA_SIGINFO; 24: sigaction(SIGSEGV, &act, NULL); 25: 26: *((int *) NULL) = 1; 27: 28: return 0; 29: }

12.7.2. Sending Data with a Signal

The siginfo_t mechanism also allows signals sent from programs to attach a single data element to the signal (this element may be a pointer, allowing an arbitrary amount of data to be passed indirectly). To send data, a union sigval is used.

#include <signal.h> union sigval { int sival_int; void * sival_ptr; };

Either sival_int or sival_ptr may be set to an arbitrary value that is included in the siginfo_t delivered with the signal. To generate a signal with a union sigval, sigqueue() must be used.

#include <signal.h> void * sigqueue(pid_t pid, int signum, const union sigval value);

Unlike kill(), pid must be a valid process ID number (no negative values are allowed). The signum specifies the signal number to send. Like kill(), sigqueue() allows signum to be zero to check whether the calling process is allowed to send the target pid a signal without actually sending one. The final parameter, value, provides the datum that is delivered along with the signal.

To receive the union sigval, the process catching the signal must use SA_SIGINFO when registering its signal handler with sigaction(). When the si_code member of siginfo_t is SI_QUEUE, siginfo_t provides a si_value member that is the same as the value passed to sigqueue.

Here is an example of sending data elements with a signal. It queues three SIGRTMIN signals with different data elements. It demonstrates that the signals were delivered in the same order they were sent, which is what we would expect for queued real-time signals.[27] A more involved example uses signals to monitor changes in directories and is presented on page 319.

[27] For more examples of signal handling, look at the sample programs for file leases (page 287), tty handling (page 355), and interval timers (page 493).

1: /* sigval.c */ 2: 3: #include <sys/signal.h> 4: #include <stdlib.h> 5: #include <stdio.h> 6: #include <string.h> 7: #include <unistd.h> 8: 9: /* Catch a signal and record that it was handled. */ 10: void handler(int signo, siginfo_t * si, void * context) { 11: printf("%d\n", si->si_value.sival_int); 12: } 13: 14: int main() { 15: sigset_t mask; 16: sigset_t oldMask; 17: struct sigaction act; 18: int me = getpid(); 19: union sigval val; 20: 21: /* Send signals to handler() and keep all signals blocked 22: that handler() has been configured to catch to avoid 23: races in manipulating the global variables. */ 24: act.sa_sigaction = handler; 25: act.sa_mask = mask; 26: act.sa_flags = SA_SIGINFO; 27: 28: sigaction(SIGRTMIN, &act, NULL); 29: 30: /* Block SIGRTMIN so we can see the queueing and ordering */ 31: sigemptyset(&mask); 32: sigaddset(&mask, SIGRTMIN); 33: 34: sigprocmask(SIG_BLOCK, &mask, &oldMask); 35: 36: /* Generate signals */ 37: val.sival_int = 1; 38: sigqueue(me, SIGRTMIN, val); 39: val.sival_int++; 40: sigqueue(me, SIGRTMIN, val); 41: val.sival_int++; 42: sigqueue(me, SIGRTMIN, val); 43: 44: /* Enable delivery of the signals. */ 45: sigprocmask(SIG_SETMASK, &oldMask, NULL); 46: 47: return 0; 48: }


       
     

    Категории