|
|
2.11. SignalsSignals 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:
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.
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.
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:
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 ImplementationA 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.
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.
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 SignalsSynchronous 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:
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:
2.11.1.2. Asynchronous SignalsAsynchronously 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 ActivityFor 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. SummarySignals 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). |
|
|