Section 2.11. Signals


2.11. Signals

Signals are a means by which a process or thread can be notified of a particular event. Signals are often compared with hardware interrupts, when a hardware subsystem, such as a disk I/O interface (for example, a SCSI host adapter), generates an interrupt to a processor when an I/O is completed. The interrupt causes the processor to enter an interrupt handler, so subsequent processing, based on the source and cause of the interrupt, can be done in the operating system. The hardware interrupt analogy is close to what signals are all about. Similarly, when a signal is sent to a process or thread, a signal handler may be entered (depending on the current disposition of the signal), analogous to the system entering an interrupt handler as the result of receiving an interrupt.

The occurrence of a signal may be synchronous or asynchronous to the process or thread, depending on the source of the signal and the underlying reason or cause. Synchronous signals occur as a direct result of the executing instruction stream, where an unrecoverable error such as an illegal instruction or illegal address reference requires an immediate termination of the process. Such signals are directed to the thread whose execution stream caused the error. Because an error of this type causes a trap into a kernel trap handler, synchronous signals are sometimes referred to as traps.

Asynchronous signals are, as the term implies, external (and in some cases unrelated) to the current execution context. An obvious example is a process or thread sending a signal to another process by means of a kill(2), _lwp_kill(2), or sigsend(2) system call or by invocation of the tHR_kill(3T), pthread_kill(3T), or sigqueue(3R) interfaces. Asynchronous signals are also referred to as interrupts.

Every signal has a unique signal name: an abbreviation that begins with SIG, such as SIGINT (interrupt signal), SIGILL (illegal instruction signal), etc., and a corresponding signal number. For all possible signals, the system defines four possible default dispositions, or an action to take, when a signal occurs:

  • Exit. Terminate the process.

  • Core. Create a core image of the process and terminate.

  • Stop. Suspend process execution (typically, job control or debug).

  • Ignore. Discard the signal and take no action, even if the signal is blocked.

A signal's disposition within a process's context defines what action the system will take on behalf of the process when a signal is delivered. All threads and LWPs within a process share the signal dispositionit is processwide and cannot be unique among threads within the same process. The process uarea maintains a u_signal[MAXSIG] array, with an entry for every possible signal that defines the signal's disposition for the process. The array contains a 0, indicating a default disposition; a 1, which means ignore the signal; or a function pointer, if a user-defined handler has been installed. Table 2.7 describes all signals and their default action.

Table 2.7. Signals

Name

Number

Default Action

Description

SIGHUP

1

Exit

Hang up (see termio(7))

SIGINT

2

Exit

Interrupt (see termio(7))

SIGQUIT

3

Core

Quit (see termio(7))

SIGILL

4

Core

Illegal instruction

SIGTRAP

5

Core

Trace or breakpoint trap

SIGABRT

6

Core

Abort

SIGEMT

7

Core

Emulation trap

SIGFPE

8

Core

Floating-point arithmetic exception

SIGKILL

9

Exit

Kill. Cannot be caught or ignored

SIGBUS

10

Core

Bus error; misaligned address error

SIGSEGV

11

Core

Segmentation faulttypically, a reference to an illegal memory address

SIGSYS

12

Core

Bad system call

SIGPIPE

13

Exit

Broken pipe

SIGALRM

14

Exit

Alarm clock (setitimer(2), alarm(2))

SIGTERM

15

Exit

Termination

SIGUSR1

16

Exit

User-defined signal 1

SIGUSR2

17

Exit

User-defined signal 2

SIGCHLD

18

Ignore

Child process status change

SIGPWR

19

Ignore

Power fail or restart

SIGWINCH

20

Ignore

Window size change

SIGURG

21

Ignore

Urgent socket condition

SIGPOLL

22

Exit

Pollable event (see streamio(7))

SIGIO

22

Exit

aioread/aiowrite completion

SIGSTOP

23

Stop

Stop (cannot be caught or ignored)

SIGTSTP

24

Stop

Stop (job control)

SIGCONT

25

Ignore

Continue

SIGTTIN

26

Stop

Stoppedtty input (see termio(7))

SIGTTOU

27

Stop

Stoppedtty output (see termio(7))

SIGVTALRM

28

Exit

Alarm clocksetitimer(2) ITIMER_VIRTUAL alarm

SIGPROF

29

Exit

Profiling alarmsetitimer(2) ITIMER_PROF, ITIMER_REALPROF

SIGXCPU

30

Core

CPU time limit exceeded

SIGXFSZ

31

Core

File size limit exceeded

SIGWAITING

32

Ignore

Concurrency signal, used by the thread's library before Solaris 10

SIGLWP

33

Ignore

Inter-LWP signal used by the thread's library before Solaris 10

SIGFREEZE

34

Ignore

Checkpoint suspend

SIGTHAW

35

Ignore

Checkpoint resume

SIGCANCEL

36

Ignore

Cancellation

SIGLOST

37

Ignore

Resource lost

SIGRTMIN

38

Exit

Lowest-priority real-time signal

SIGRTMAX

45

Exit

Highest-priority real-time signal


The SIGWAITING and SIGLWP signals were implemented in the original thread model for managing concurrency and are no longer used in Solaris 10. SIGPOLL and SIGIO are both defined as signal number 22. SIGIO is generated as a result of a process issuing an asynchronous read or write through aioread(3) or aiowrite(3) (or the POSIX equivalent aio_read(3R) or aio_write(3R)), to notify the process that the I/O completed or that an error occurred. SIGPOLL is a more generic indicator that a pollable event has occurred.

The disposition of a signal can be changed from its default, and a process can arrange to catch a signal and invoke a signal handling routine of its own or can ignore a signal that may not have a default disposition of ignore. The only exceptions to this are SIGKILL and SIGSTOPthe default disposition of these two signals cannot be changed. The interfaces for defining and changing signal disposition are the signal(3C) and sigset(3C) libraries and the sigaction(2) system call.

Signals can also be blocked, which means that the process or thread has temporarily prevented delivery of a signal. The generation of a signal that has been blocked results in the signal remaining pending to the process until it is explicitly unblocked or until the disposition is changed to ignore. Signal masks for blocking signals exist within the kernel thread. The sigprocmask(2) system call sets or gets a signal mask for a thread within a processeach thread has its own signal masks, and different threads in the same process can mask different signals (though all threads share the disposition). A call to sigprocmask(2) affects the signal mask of the calling thread, providing the same behavior as a call to pthread_sigmask(3C).

The psig(1) command lists the signal actions for a process. The example below dumps the signal actions for our ksh process.

sol10$ psig $$ 1097:   -ksh HUP     blocked,caught  sig_sh_done      RESTART INT     blocked,caught  sh_fault         RESTART QUIT    blocked,caught  sh_fault         RESTART ILL     blocked,caught  sig_sh_done      RESTART TRAP    blocked,caught  sig_sh_done      RESTART ABRT    blocked,caught  sig_sh_done      RESTART EMT     blocked,caught  sig_sh_done      RESTART FPE     blocked,caught  sig_sh_done      RESTART KILL    default BUS     blocked,caught  sig_sh_done      RESTART SEGV    blocked,default SYS     blocked,caught  sig_sh_done      RESTART PIPE    blocked,caught  sig_sh_done      RESTART ALRM    blocked,caught  sh_fault         RESTART TERM    blocked,ignored USR1    blocked,caught  sig_sh_done      RESTART USR2    blocked,caught  sig_sh_done      RESTART CLD     blocked,caught  sh_fault        NOCLDSTOP PWR     blocked,default WINCH   blocked,default URG     blocked,default POLL    blocked,default STOP    default . . . 


Recall that a signal can originate from several different places, for a variety of reasons. SIGHUP, SIGINT, and SIGQUIT, are typically generated by a keyboard entry from the controlling terminal (SIGINT and SIGQUIT) or if the control terminal is disconnected, which generates a SIGHUP. Note that use of the nohup(1) command makes processes "immune" from hangups by setting the disposition of SIGHUP to ignore. Other terminal I/O-related signals are SIGSTOP, SIGTTIN, SIGTTOU, and SIGTSTP. For those signals that originate from a keyboard command, the actual key sequence that results in the generation of these signals is defined within the parameters of the terminal session, typically, by stty(1). For example, ^c [Control-C] is usually the interrupt key sequence and results in a SIGINT being sent to a process, which has a default disposition of forcing the process to exit.

Signals generated as a direct result of an error encountered during instruction execution start with a hardware trap on the system. Different processor architectures define various traps that result in an immediate vectored transfer of control to a kernel trap-handling function. The Solaris kernel builds a trap table and inserts trap handling routines in the appropriate locations, based on the architecture specification of the processors that the Solaris environment supports. In Intel parlance the routines are called interrupt descriptor tables, or IDTs. On SPARC, they are called trap tables. The kernel-installed trap handler ultimately generates a signal to the thread that caused the trap. The signals that result from hardware traps are SIGILL, SIGFPE, SIGSEGV, SIGTRAP, SIGBUS, and SIGEMT. Table 2.8 lists traps and signals for UltraSPARC.

Table 2.8. UltraSPARC Traps and Resulting Signals

Trap Name

Signal

instruction_access_exception

SIGSEGV, SIGBUS

instruction_access_MMU_miss

SIGSEGV

instruction_access_error

SIGBUS

illegal_instruction

SIGILL

privileged_opcode

SIGILL

fp_disabled

SIGILL

fp_exception_ieee_754

SIGFPE

fp_exception_other

SIGFPE

tag_overflow

SIGEMT

division_by_zero

SIGFPE

data_access_exception

SIGSEGV, SIGBUS

data_access_MMU_miss

SIGSEGV

data_access_error

SIGBUS

data_access_protection

SIGSEGV

mem_address_not_aligned

SIGBUS

privileged_action

SIGILL

async_data_error

SIGBUS


Signals can originate from sources other than terminal I/O and error trap conditions; process-induced (for example, SIGXFSZ) and external events (kill()) can also generate signals. Examples include the following:

  • Applications can create user-defined signals as a somewhat crude form of interprocess communication by defining handlers for SIGUSR1 or SIGUSR2 and sending those signals between processes.

  • The kernel sends SIGXCPU if a process exceeds its processor time resource limit or sends SIGXFSZ if a file write exceeds the file size resource limit.

  • A SIGABRT is sent as a result of an invocation of the abort(3C) library.

  • If a process is writing to a pipe and the reader has terminated, SIGPIPE is generated.

  • kill(2), sigsend(2), or pthread_kill(3C) does an explicit, program-matic send.

  • The kill(1) command sends a signal to a process from the command line.

  • sigsend(2) and sigsendset(2) programmatically send signals to processes or groups of processes.

  • The kernel notifies parent processes of a status change in a child process by SIGCHLD.

  • The alarm(2) system call sends a SIGALRM when the timer expires.

These are just a few examples of how and where signals may originate. Refer to the Solaris Software Developer Collection on http://docs.sun.com for additional information on signal types and managing signals in software.

2.11.1. Signals Implementation

A signal is represented as a bit (binary digit) in a data structure (several data structures actually, as you'll see shortly). More precisely, the posting of a signal by the kernel results in a bit getting set in a structure member at either the process or thread level. Because each signal has a unique signal number, we use a structure member of sufficient width, such that we can represent every signal by simply setting the bit that corresponds to the signal number of the signal we want to post. For example, set the 17th bit to post signal 17, SIGUSR1 (which is actually bit number 16 because the bit numbers start with 0 and the signal numbers start with 1).

Signals traditionally go through two well-defined stages: generation and delivery. Signal generation is the point of origin of the signalthe sending phase. A signal is said to be delivered when whatever disposition has been established for the signal is invoked, even if it is to be ignored. If a signal is being blocked, thus postponing delivery, it is considered pending.

Signal disposition in Solaris is processwide, but each thread has its own signal mask. Threads can choose to block signals independently of other threads executing in the same process; thus, different threads may be available to take delivery of different signals at various times during process execution. An interface, pthread_sigmask(3C), establishes per-thread signal masks. Since the disposition and handlers for all signals are shared by all threads in a process, a SIGINT (for example) with the default disposition in place causes the entire process to exit. Synchronous signals, generated as a result of a trap (SIGFPE, SIGILL, etc.) are sent to the thread that caused the trap. Asynchronous signals, which are all signals not defined as traps, are delivered to the first thread that is found not blocking the signal.

Before we delve into the mechanics of signal delivery, let's look at the data types that support signals in the processes and threads.

The primary data fields at the process and thread level are the signal set fields, and the siginfo structure.

typedef struct {                /* signal set type */         unsigned int     __sigbits[4]; } sigset_t; . . . typedef struct {         unsigned int     __sigbits[2]; } k_sigset_t;                                                     See usr/src/uts/common/sys/signal.h 


The sigset_t field is 128 bits wide; the k_sigset_t field is 64 bits wide. Fewer than 50 signals are defined, so a 64-bit-wide field is sufficient. However, the System V application binary interface (ABI) specification defines a 128-bit-wide field for signals, so the signal fields in the user thread (ulwp_t) must comply, and they use the sigset_t. In the kernel, k_sigset_t is used.

A siginfo structure stores various bits of information for many different types of signals. The siginfo structure fields are summarized in Table 2.9. The source code for the structure can be found in usr/src/uts/common/sys/siginfo.h. The structure definition includes several unionsa data union with nested unions, meaning the actual datum will vary according to the variable references in the source code. Table 2.9 shows all possible variables.

Table 2.9. siginfo Structure

struct or union

Variable Name

Data Type

Description

siginfo

si_signo

integer

Signal number

 

si_code

integer

Code from signal

 

si_errno

integer

Error number from sys/errno.h

_proc

  

proc union instantiated in kill(2), SIGCLD, and sigqueue()

 

pid

pid_t

PID

 

ctid

ctid_t

Contact ID

 

zoneid

zoneid_T

Zone ID

_kill

  

Data for SIGKILL

 

uid

uid_t

UID

 

value

sigval

Signal value (check this)

_cld

utime

clock_t

Child process user time

 

stime

clock_t

Child process system time

 

status

int

Child process exit status

_fault

  

Fault union for SIGSEGV, SIGBUS, SIGILL, SIGTRAP, SIGFPE

 

addr

void *

Fault address

 

trapno

int

Illegal trap number

 

pc

caddr_t

Program counteraddress of faulting instruction

_file

  

File union for SIGPOLL and SIGXFZ

 

fs

int

File descriptor

 

band

long

 

_prof

  

Profiling (SIGPROF) signal information

 

faddr

caddr_t

Last fault address

 

tstamp

timestruct

Timestamp

 

syscall

short

Current system call

 

nsysarg

char

Number of arguments

 

fault

char

Last fault type

 

sysarg

long[]

Array of system call arguments

 

mstate

int[]

Array of microstates

_rctl

  

Resource control information

 

entity

int32_t

Resource type exceeded


References in the code to specific fields of a target siginfo structure can be tricky to read given the number, and nesting, of unions. A set of preprocessor definitions in siginfo.h makes it a little easier and also illustrates the union and variable relationship in a siginfo_t.

siginfo structure members #define si_pid          __data.__proc.__pid #define si_ctid         __data.__proc.__ctid #define si_zoneid       __data.__proc.__zoneid #define si_status       __data.__proc.__pdata.__cld.__status #define si_stime        __data.__proc.__pdata.__cld.__stime #define si_utime        __data.__proc.__pdata.__cld.__utime #define si_uid          __data.__proc.__pdata.__kill.__uid #define si_value        __data.__proc.__pdata.__kill.__value #define si_addr         __data.__fault.__addr #define si_trapno       __data.__fault.__trapno #define si_trapafter    __data.__fault.__trapno #define si_pc           __data.__fault.__pc #define si_fd           __data.__file.__fd #define si_band         __data.__file.__band #define si_tstamp       __data.__prof.__tstamp #define si_syscall      __data.__prof.__syscall #define si_nsysarg      __data.__prof.__nsysarg #define si_sysarg       __data.__prof.__sysarg #define si_fault        __data.__prof.__fault #define si_faddr        __data.__prof.__faddr #define si_mstate       __data.__prof.__mstate #define si_entity       __data.__rctl.__entity                                                    See usr/src/uts/common/sys/siginfo.h 


The siginfo data is available to programs that need to know more about the reason a signal was generated. The sigaction(2) system call programmatically provides this information. An optional SA_SIGINFO flag (in the sa_flags field of the sigaction structure) results in two additional arguments being passed to the signal handler (assuming of course that the signal disposition has been set up to be caught). The first argument is always the signal number. A non-NULL second argument is a pointer to a siginfo structure (described in Table 2.9), and a third argument is a pointer to a ucontext_t data structure that contains hardware context information (stack pointer, signal mask, and general register contents) about the receiving process when the signal was delivered. The siginfo data can be useful for debugging when a trap signal is generated; for example, in the case of a SIGILL or SIGFPE, more specific information about the underlying reason for the trap can be gleaned from the data the kernel plugs into siginfo when getting ready to send a signal. See the sigaction(2), siginfo(5), ucontext(5), and siginfo.h(3HEAD) man pages for more information on using siginfo data.

The threads model in Solaris requires per-thread signal masks and signal support at several points in the objects that make up the process model.

ulwp_t . . . sigset_t        ul_sigmask;     /* thread's current signal mask */ sigset_t        ul_tmpmask;     /* signal mask for sigsuspend/pollsys */ siginfo_t       ul_siginfo;     /* deferred siginfo */ . . .                                                See usr/src/lib/libc/inc/thr_uberdata.h klwp_t . . . /* * signal handling and debugger (/proc) interface */ uchar_t lwp_cursig;             /* current signal */ uchar_t lwp_curflt;             /* current fault */ uchar_t lwp_sysabort;           /* if set, abort syscall */ uchar_t lwp_asleep;             /* lwp asleep in syscall */ uchar_t lwp_extsig;             /* cursig sent from another contract */ stack_t lwp_sigaltstack;        /* alternate signal stack */ struct sigqueue *lwp_curinfo;   /* siginfo for current signal */ k_siginfo_t     lwp_siginfo;    /* siginfo for stop-on-fault */ k_sigset_t      lwp_sigoldmask; /* for sigsuspend */ . . .                                                      See usr/src/uts/common/sys/klwp.h kthread_t . . . struct sigqueue *t_sigqueue;    /* queue of siginfo structs */ k_sigset_t      t_sig;          /* signals pending to this process */ k_sigset_t      t_extsig;       /* signals sent from another contract */ k_sigset_t      t_hold;         /* hold signal bit mask */ . . .                                                    See usr/src/uts/common/sys/thread.h proc_t . . . k_sigset_t p_sig;               /* signals pending to this process */ k_sigset_t p_extsig;            /* signals sent from another contract */ k_sigset_t p_ignore;            /* ignore when generated */ k_sigset_t p_siginfo;           /* gets signal info with signal */ struct sigqueue *p_sigqueue;    /* queued siginfo structures */ struct sigqhdr *p_sigqhdr;      /* hdr to sigqueue structure pool */ struct sigqhdr *p_signhdr;      /* hdr to signotify structure pool */ uchar_t p_stopsig;              /* jobcontrol stop signal */ . . .                                                       See usr/src/uts/common/sys/proc.h uarea in proc_t k_sysset_t u_entrymask;         /* /proc syscall stop-on-entry mask */ k_sysset_t u_exitmask;          /* /proc syscall stop-on-exit mask */ k_sigset_t u_signodefer;        /* signals deferred when caught */ k_sigset_t u_sigonstack;        /* signals taken on alternate stack */ k_sigset_t u_sigresethand;      /* signals reset when caught */ k_sigset_t u_sigrestart;        /* signals that restart system calls */ k_sigset_t u_sigmask[MAXSIG];   /* signals held while in catcher */ void     (*u_signal[MAXSIG])(); /* Disposition of signals */ . . .                                                       See usr/src/uts/common/sys/user.h 


The ulwp_t, which represents the user component of a thread, maintains a signal mask for per-thread pending signals. A signal is deferred if it cannot be delivered because the target thread is in a critical code section; ul_siginfo stores the siginfo_t if signal delivery needs to be deferred. The kernel LWP stores the current signal in lwp_cursig and stores a pointer to a sigqueue struct with the siginfo data for the current signal in lwp_curinfo, which is used in the signal delivery phase. Other fields include a stack pointer if an alternative signal stack, lwp_sigaltstack, is set up by a call to sigaltstack(2). It is sometimes desirable for programs that do their own stack management to handle signals on an alternative stack, as opposed to the default use of the thread's runtime stack (SA_ONSTACK through sigaction(2) when the handler is set). The kernel thread maintains two k_sigset_t members: t_sig and t_hold. t_sig has the same meaning as p_sig at the process level, that is, a mask of pending signals; t_hold is a bit mask of signals to block.

In addition, t_sigqueue points to a sigqueue for siginfo data. A signal's siginfo structure will be placed either on the process p_sigqueue or the kthread's t_sigqueue. The kthread t_sigqueue is used when a non-NULL kthread pointer has been passed in the kernel signal code, indicating a directed signal targeting a specific thread.

In the embedded uarea, several bit maps are maintained for flagging various signal-related events or forcing a particular behavior, settable by the sigaction(2) system call. An array of pointers signals dispositions: u_signal[MAXSIG], which contains one array entry per signal. The entries in the array may indicate the signal is to be ignored (SIG_IGN) or the default action is set (SIG_DEF). If the signal is to be caught, with a handler installed by signal(3C) or sigaction(2), the array location for the signal points to the function to be invoked when the signal is delivered. The other uarea signal fields are described in the following list. The described behavior occurs when a signal corresponding to a bit that is set in the field is posted.

  • u_sigonstack. Flags an alternative signal stack for handling the signal. Assumes sigaltstack(2) has been called to set up an alternative stack. If one has not been set up, the signal is handled on the default stack. Set by sigaction(2) with the SA_ONSTACK flag in sa_flags field of the sigaction structure.

  • u_sigresethand. Resets the disposition to default (SIG_DEF) when the handler is entered for the signal. The signal is not blocked when the handler is entered. As above, set by SA_RESETHAND in sa_flags.

  • u_sigrestart. If inside a system call when the signal is received, restarts the system call. This behavior does not work for all system calls, only those that are potentially "slow" (for example, I/O-oriented system callsread(2), write(2)). Interrupted system calls typically result in an error, with the errno being set to EINTR. Set by SA_RESTART in sa_flags.

  • u_signodefer. Does not block subsequent occurrences of the signal when it is caught. Normally, a signal is blocked when it has been delivered and a handler is executed. Set by SA_NODEFER in sa_flags.

  • u_sigmask[]. Signals that have been caught and are being held while a handler is executing.

  • u_signal[]. Signal dispositions.

Clearly, there appears to be more than a little redundancy in the signal support structure members spread throughout the various entities that exist within the context of a process. This redundancy is due to the requirements for support of the multithreaded model and the fact that different signals get posted to different places, depending on the signal itself and the source. Earlier in the discussion, we provided several examples of why some signals are sent and where they originate. Asynchronous signals could originate from a user or from various places in the kernel. Signals that are sent from userland (for example, kill(1), kill(2), sigqueue(2)) are sent to the process. Some signals that originate in the kernel are directed to a particular thread. For example, the kernel clock interrupt handler may send a SIGPROF or SIGVTALRM directly to a thread. The pthread_kill(3C) and tHR_kill(3C) library interfaces provide for sending asynchronous signals to a specific thread. The STREAMS subsystem sends SIGPOLL and SIGURG to the process when appropriate (for example, a polled event occurs or an urgent out-of-band message is received).

2.11.1.1. Synchronous Signals

Synchronous signals, or trap signals, originate from within the kernel trap handler. When an executing instruction stream causes one of the events described in (missingCRef), the event is detected by hardware and execution is redirected to a kernel trap handler. The trap handler code populates a siginfo structure with the appropriate information about the trap and invokes the TRap_cleanup() function, which determines whether to stop the thread because of a debugger "stop on fault" flag. The entry point into the kernel signal subsystem is through the trapsig() function, which is executed next. If the signal is masked or if the disposition has been set to ignore, then trapsig() unmasks the signal and sets the disposition to default. The siginfo structure is placed on the kthread's t_sigqueue list, and sigtoproc() is called to post the signal.

The kernel sigtoproc() function takes three arguments: a process pointer, a kernel thread pointer, and the signal number. Signals that should be directed to the thread call sigtoproc() with a valid kthread pointer. A NULL kthread pointer signifies that the signal should be posted to the process. Let's look at the code flow of sigtoproc().

/*  * Post a signal.  * If a non-null thread pointer is passed, then post the signal  * to the thread/lwp, otherwise post the signal to the process.  */ void sigtoproc(proc_t *p, kthread_t *t, int sig) . . . if (signal == SIGKILL)         post it to proc else if (signal == SIGCONT) /* job control continue */         remove SIGSTOP, SIGTSTP, SIGTTOU, SIGTTIN from signal queue         clear p_stopsig         if (process is multithreaded)                 remove SIGSTOP, SIGTSTP, SIGTTOU, SIGTTIN from each thread                 start all stopped threads else if (signal is SIGSTOP | SIGTDTP | SIGTTOU | SIGTTIN)         clear SIGCONT in process and threads if (signal is discardable)         return if (signal is directed to a thread)         add the signal to t_sig in the kthread         eat_signal() else if (process is threaded, but signal is not directed)         find a thread to take the signal                                                         See usr/src/uts/common/os/sig.c 


A lot of the up-front work in sigtoproc() deals with job control and the terminal I/O signals. In compliance with the POSIX specifications, this behavior is documented in the signal(5) man page, which we summarize here: Any pending SIGCONT signals are discarded upon receipt of a SIGSTOP, SIGTSTP, SIGTTIN, or SIGTTOU signal, regardless of the disposition. The inverse is also true; if any of those four signals are pending when a SIGCONT is received, they are discarded, again regardless of the disposition.

Two areas in the signal-posting pseudocode above require expanding: discardable signals and eat_signal. The kernel sig_discardable() function determines whether a signal can be discarded.

/*  * Return true if the signal can safely be discarded on generation.  * That is, if there is no need for the signal on the receiving end.  * The answer is true if the process is a zombie or  * if all of these conditions are true:  *      the signal is being ignored  *      the process is single-threaded  *      the signal is not being traced by /proc  *      the signal is not blocked by the process  */                                                         See usr/src/uts/common/os/sig.c 


eat_signal() ensures that the thread is not blocking the signal by testing two fields in the kernel thread: the t_hold field and a scheduler control field that can be set to signify that the thread is blocking all signals. A sleeping thread is made runnable, taken off the sleep queue, and put on a dispatch queue, and the t_astflag in the kthread structure is set.

The t_astflag forces the thread to check for a signal when execution resumes. With the signal now posted, sigtoproc() is done and the code returns to trap_cleanup(). The cleanup code invokes the ISSIG_PENDING macro, which determines from the bit set in the t_sig field that a signal has been posted. Once the macro establishes the presence of the signal, it invokes the kernel psig() function to handle actual delivery.

trap_cleanup() . . . if (ISSIG_PENDING(curthread, lwp, p)) {                         if (issig(FORREAL))                                 psig();                         curthread->t_sig_check = 1;                 } . . .                                                          See usr/src/uts/sun4/os/trap.c 


ISSIG_PENDING is one of several macros the system defines for speeding up the examination of the process p_sig and kthread t_sig fields for the presence of a posted signal.

/* Macro to reduce unnecessary calls to issig() */ #define ISSIG(t, why)   ISSIG_FAST(t, ttolwp(t), ttoproc(t), why) /*  * Fast version of ISSIG.  *      1. uses register pointers to lwp and proc instead of reloading them.  *      2. uses bit-wise OR of tests, since the usual case is that none of them  *         are true; this saves orcc's and branches.  *      3. loads the signal flags instead of using sigisempty() macro which does  *         a branch to convert to boolean.  */ #define ISSIG_FAST(t, lwp, p, why)               \         (ISSIG_PENDING(t, lwp, p) && issig(why)) #define ISSIG_PENDING(t, lwp, p)                 \         ((lwp)->lwp_cursig |                     \             sigcheck((p), (t)) |                 \             (p)->p_stopsig |                     \             (t)->t_dtrace_stop |                 \             (t)->t_dtrace_sig |                  \             ((t)->t_proc_flag & (TP_PRSTOP|TP_HOLDLWP|TP_CHKPT|TP_PAUSE)) | \             ((p)->p_flag & (SEXITLWPS|SKILLED|SHOLDFORK1|SHOLDWATCH)))                                                       See usr/src/uts/common/sys/proc.h 


ISSIG and ISSIG_FAST resolve to ISSIG_PENDING, which performs a logical OR on the p_sig and t_sig fields (through sigcheck()), logically ANDing that result with the return value of issig(why).

The issig() function is the last bit of work the kernel does before actual signal delivery. The "why" argument passed to issig is one of JUSTLOOKING or FORREAL. The JUSTLOOKING flag causes issig() to return if a signal is pending, but the flag does not stop the process if a debugger requests a stop. In the case of a trap signal, issig() is passed FORREAL, which causes the process to be stopped if a stop has been requested or a traced signal is pending. Assuming no special debugger flags or signal tracing, the kernel invokes the psig() signal-delivery function to carry out the delivery phase according to the current disposition of the signal.

Once a signal has been posted, the existence of a signal must be made known to the process/thread so that action can be taken. When you consider that a signal is represented by the setting of a bit in a data structure, it seems intuitive that the kernel must periodically check for set bits (that is, pending signals). This is, in fact, precisely how delivery is done. The kernel checks for posted signals at several points during the typical execution flow of a process:

  • Return from a system call

  • Return from a trap

  • Wake up from a sleep

In essence, the determination of the existence of a signal is a polling process by which the signal fields in the process p_sig and kthread t_sig fields are examined frequently for the presence of a set bit. Once it is determined that a signal is posted, the kernel can take appropriate action, based on the signal's current disposition in the context of the process that received it. In this instance (synchronous signals), a trap event occurred, and with the signal posted, the detection of the posted signal will happen on the return to TRap_cleanup() (shown on the previous page). The kernel psig() code takes over to continue signal delivery.

When psig() is entered, the signal bit set when the signal was posted has been cleared, lwp_cursig contains the current signal number, and lwp_curinfo points to the siginfo structure for the signal. psig() does some checking to ensure that things have not changed since the signal was posted, and sets an internal variable, func, to the disposition of the signal from the process u_signal[] array. psig() also does some additional testing, to determine if the signal disposition has changed since the signal was posted or if the signal has been deferred, and updates various fields in the process object according to the signal type. psig() handles all cases in which the signal is ignored, or the default signal disposition is set, which typically involves the process exiting, exiting with a core file, stopping the process (job control), or ignoring the signal (see Table 2.7). psig() also increments the LWP resource usage nsignal counter. If a user signal handler has been defined, the kernel sendsig() function is called to set up the thread for running the user's signal handler.

Because sendsig() requires intimate knowledge of the process and thread internal organization, which vary according to processor type (SPARC or x64), sendsig() is defined in the platform-specific directories of the source tree. sendsig() essentially hand-crafts the execution environment for the user's signal handler, setting up the thread state such that control is passed to the signal handler when the execution context changes from kernel back up to user. sendsig() handles the setting up of an alternative thread stack if that option has been specified and manages the low-level hardware context setup (registers) for handler execution. When the thread context returns through the kernel call stack back to user mode, the point of execution is the user's installed signal handler.

The event flow for synchronous signals can be summarized as follows:

  • An executing thread generates a fault condition as a direct result of the current instruction stream.

  • The fault condition is detected and a system trap occurs. A trap is a vectored transfer of control to a kernel trap handler.

  • The kernel trap handler determines the type of fault, gathers some information based on the fault type, and calls sigtoproc() to post a signal to the target thread.

  • The thread is set up to check for signals, and, with a signal posted, psig() is called to complete signal delivery.

  • psig() handles default signal dispositions. If a user-defined signal handler has been installed, sendsig() is called.

  • sendsig() constructs the execution environment for the user's signal handler in the thread.

  • When the thread returns to user mode, the user's signal handler executes.

2.11.1.2. Asynchronous Signals

Asynchronously generated (interrupt) signals can originate from a user command, program, or from somewhere inside the kernel. The kill(1) command can send a signal to a target process, the kill(2) system call or pthread_kill(3C) inter-face can send signals programatically, and signals can come from keyboard events for process termination (Control-C), job control (Control-Z), etc. When an asynchronous signal is generated, it is delivered to the first thread the kernel finds that is not masking the signal.

Using dtrace(1), we can trace the execution flow through the kernel when a kill(2) system call is executed. The D script to do this is very simple.

#!/usr/sbin/dtrace -s #pragma D option flowindent syscall::kill:entry / pid == $target / {         self->t = 1; } fbt::: / self->t / { } syscall::kill:return / self->t / {         self->t = 0;         exit(0); } 


The D script uses a predicate on the kill:entry probewe run our kill(1) command under control of this dtrace script, which sets the $target variable used in the predicate. Running a target process in the background, we execute the kill command,

sol10$ ./kill.d -c "kill -USR1 2667" 


where kill.d is the D script name, and 2667 is the PID of a target process that was previously started and that has a signal handler installed for a SIGUSR1. Once the script is executed, we get a trace of the code path through the kernel to complete sending the signal.

CPU FUNCTION   0  -> kill   0    -> sigqkill   0      -> prfind   0        -> prfind_zone   0          -> pid_lookup   0          <- pid_lookup   0        <- prfind_zone   0      <- prfind   0      -> sigsendproc   0        -> prochasprocperm   0          -> secpolicy_basic_proc   0            -> priv_policy   0            <- priv_policy   0          <- secpolicy_basic_proc   0          -> hasprocperm   0          <- hasprocperm   0        <- prochasprocperm   0        -> getzoneid   0        <- getzoneid   0        -> crgetruid   0        <- crgetruid   0        -> sigaddq   0          -> sig_discardable   0          <- sig_discardable   0          -> kmem_alloc   0            -> kmem_cache_alloc   0            <- kmem_cache_alloc   0          <- kmem_alloc   0          -> sigaddqins   0          <- sigaddqins   0          -> sigtoproc   0            -> sig_discardable   0            <- sig_discardable   0            -> thread_lock   0              -> uppc_setspl   0              <- uppc_setspl   0            <- thread_lock   0            -> eat_signal   0              -> signal_is_blocked   0                -> schedctl_sigblock   0                <- schedctl_sigblock   0              <- signal_is_blocked   0            <- eat_signal   0            -> disp_lock_exit   0              -> uppc_setspl   0              <- uppc_setspl   0            <- disp_lock_exit   0          <- sigtoproc   0        <- sigaddq   0      <- sigsendproc   0    <- sigqkill   0  <- kill 


kill(2) simply sets up a sigsend_t structure, setting the signal number and code value to signify that the signal originated from a user.

int kill(pid_t pid, int sig) {         sigsend_t v;         bzero(&v, sizeof (v));         v.sig = sig;         v.checkperm = 1;         v.sicode = SI_USER;         return (sigqkill(pid, sig, &v)); } static int sigqkill(pid_t pid, int signo, sigsend_t *sigsend) {         register proc_t *p;         int error;         if (signo < 0 || signo >= NSIG)                 return (set_errno(EINVAL));         if (pid == -1) {                 procset_t set;                 setprocset(&set, POP_AND, P_ALL, P_MYID, P_ALL, P_MYID);                 error = sigsendset(&set, sigsend);         } else if (pid > 0) {                  mutex_enter(&pidlock);                 if ((p = prfind(pid)) == NULL || p->p_stat == SIDL)                          error = ESRCH;                 else {                         error = sigsendproc(p, sigsend);                         if (error == 0 && sigsend->perm == 0)                                 error = EPERM;                 }                 mutex_exit(&pidlock); . . .                                               See usr/src/uts/common/syscall/sigqueue.c 


sigqkill() is called from kill(2) and does some basic checking for a valid signal number, does a lookup on the PID, and calls sigsendproc(); which checks credentials, updates the siginfo structure fields, and calls sigaddq(); which allocates a signal queue structure and calls sigaddqins(); which inserts the signal information into the queue. When sigaddqins() returns, sigtoproc() is called to post the signal.

We looked at sigtoproc() in the previous section. For a directed signal, the signal is posted to the target thread. Otherwise, the code loops through the list of threads in the process and posts the signal to the first thread that is not blocking it.

The eat_signal() function connects the dots between an external source (like the kill(1) command) posting a signal and forcing the target process (or thread) to do one of two thingsacknowledge the signal and execute the handler or take the default disposition if a handler has not been installed.

eat_signal(kthread_t *t, int sig) . . . /*          * Do not do anything if the target thread has the signal blocked.          */          if (!signal_is_blocked(t, sig)) {                 t->t_sig_check = 1;     /* have thread do an issig */                 if (t->t_state == TS_SLEEP && (t->t_flag & T_WAKEABLE)) {                          setrun_locked(t);                         rval = 1;                 } else if (t->t_state == TS_STOPPED && sig == SIGKILL) {                          ttoproc(t)->p_stopsig = 0;                         t->t_dtrace_stop = 0;                         t->t_schedflag |= TS_XSTART | TS_PSTART;                          setrun_locked(t);                 } else if (t != curthread && t->t_state == TS_ONPROC) {                         if ((t != curthread) && (t->t_cpu != CPU))                                 poke_cpu(t->t_cpu->cpu_id);                         rval = 1;                 } else if (t->t_state == TS_RUN) {                         rval = 1;                 }         } . . .                                                         See usr/src/uts/common/os/sig.c 


The target thread's t_sig_check flag is set, which forces the thread to run issig(). If the target thread is sleeping, setrun_locked() forces a wakeup; the thread checks for a signal on wakeup and enters psig() for delivery and handling of the signal. A stopped thread receiving a SIGKILL signal also gets nudged with setrun_locked(). If the thread is currently running (TS_ONPROC), poke_cpu() interrupts the processor the thread is running on, and the subsequent handler falls through the trap code where the thread checks for a signal and enters psig(). Finally, if the thread is runnable (TS_RUN), eat_signal() returns to sigtoproc(). If the t_sig_check bit is set in the thread, then when the thread is selected by the dispatcher to run, it returns from the kernel, sees the t_sig_check flag is set, and calls psig().

2.11.2. Observing Signal Activity

For the adventurous among you, using the dtrace fbt provider and tracking the various signal-related functions in the kernel is always an option for drilling down on signal support. All Solaris 10 systems ship with a wonderful set of dtrace scripts in /usr/demo/dtrace, and the sig.d script tracks signal senders, recipients, signal types, and counts.

# dtrace -s ./sig.d ^C               SENDER            RECIPIENT          SIG COUNT                  ksh                mysig           16 1                 sshd               dtrace            2 1                 sshd                   sh            2 1                sched                 Xorg           14 67                                                              See /usr/demo/dtrace/sig.d 


The output of sig.d is self-explanatory, and of course the script can be modified to focus on a specific process or thread.

The prstat(1) command can track the number of signals received for processes and threads.

 PID USERNAME USR SYS TRP TFL DFL LCK SLP LAT VCX ICX SCL SIG PROCESS/NLWP   2883 mauroj   0.0 0.1 0.0 0.0 0.0 0.0 100 0.0  43   0 242   0 prstat/1    664 mauroj   0.0 0.0 0.0 0.0 0.0 0.0 100 0.0  25   0  63   0 gnome-netsta/1    473 mauroj   0.0 0.0 0.0 0.0 0.0 0.0 100 0.0  20   0  80  10 Xorg/1    704 mauroj   0.0 0.0 0.0 0.0 0.0 0.0 100 0.0  21   0  51   0 gnome-termin/2 . . . 


The SIG column represents a count of the number of signals received in the last sampling period (5 seconds, by default).

2.11.3. Summary

Signals are a process and thread-notification mechanism, providing a framework used by the kernel when an executing thread generates a fault condition stemming from its instruction flow (synchronous signals) or from allowing external events to force a process or thread into a specific routine (asynchronous signals). Signals are commonly used in application code, wherein custom signal handlers are developed to manage events that may occur during the execution of the application code. Signals also provide the infrastructure for process control (start/stop), and various job control functions available in the system command-line interpreters (shells).

The implementation of signals in the kernel and support libraries is nontrivial, especially with support of multiple threads within a single process. Signal management is a delicate dancea significant number of conditional tests and operations throughout the code are left unexplored in this text. A line-by-line exploration is an exercise for the reader.

On a related note, the signal flow explains why zombie processes cannot be killed (any horror movie fan knows you can't kill a zombie). A process must be executing in order to take delivery of a signal. A zombie process is, by definition, a process that has terminated. It exists only as a process table entry, with all of its execution state having been freed by the kernel (see preap(1) for cleaning up zombie processes).




SolarisT Internals. Solaris 10 and OpenSolaris Kernel Architecture
Solaris Internals: Solaris 10 and OpenSolaris Kernel Architecture (2nd Edition)
ISBN: 0131482092
EAN: 2147483647
Year: 2004
Pages: 244

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net