Signals
Prev: Process Control
TOC: Exceptional Control Flow
Next: Nonlocal Jumps
- Signal is a small message that notifies a process that an event of some type has occured in the system, below's 30 different types of signals supported in linux system.
- It's the software abstraction of hardware level exception or response to some software event. Lower level hardware exception is not visible to the user, it's processed by the kernel's exception handlerand signals provide a mechanism for exposing it's occurrence to user processes.
- SIGFPE signal is sent if a process attempts to divide by zero, SIGILL if a process executes an illegal instruction, SIGSEGV if a process makes an illegal memory reference,all of these are hardware exceptions that are bubbled up to software level.
- Some software event corresponding signals include Ctrl+C while a process is running in the foreground makes the kernel sends SIGINT to each process in the foreground process group
- We can forcibly terminate another process by sending it a SIGKILL signals, and when a child terminates/ stop, the kernel sends SIGCHLD signal to the parent.

Signal terminology
- Transfer of a signal occurs in 2 different steps:
- Sending a signal: The kernel sends a signal to a destination process by updating some state in context of destination process. The signal is delivered for one of two reasons:
- The kernel has detected a system event like divide by zero error or termination of a child.
- A process has invoked the kill function (just sending signal from one process to another).
- Receiving a signal: A destination process receives a signal when it is forced by the kernel to react in some way to the delivery of the signal. The process can either ignore it, terminate or catch and handle the signal using a signal handler.
- Sending a signal: The kernel sends a signal to a destination process by updating some state in context of destination process. The signal is delivered for one of two reasons:
- Transferred signal that's been sent but not yet received is called a pending signal, and at any point in time there can only be one such pending signal.
- A process can also selectively block signals of certain types, when a signal is blocked, it can be delivered, but the resulting pending signal will not be delivered until the process unblocks it.
- The kernel maintains a pending bit vector and a blocked bit vector for each process. The kernel sets bit k on pending whenever a signal of type k is delivered and clears bit k in pending whenever signal of type k is received.
Sending Signals
- Provides multiple ways to send signals to process:
Process Groups
- Every process belongs to exactly one process group which is identified as a positive int process grp ID.
- getgrp returns the process grp ID of current process
#include <unistd.h>
pid_t getpgrp(void);
Returns process grp ID of calling process
- By default a child grp belongs to the same process grp as it's parent.
- A process can change the process grp of itself or of some other process
#include <unistd.h>
int setpgid(pid_t pid, pid_t pgid);
Return 0 on success, -1 on error
- Usage example:
setpgid(0, pgid);sets current process pgid to given pgid
setpgid(pid, 0);sets pid process's pgid to current process's pgid
setpgid(0, 0);creats a new process grp and adds this process to that process grp
Sending Signals with /bin/kill Program
linux> /bin/kill -9 15213
- Sending SIGKILL to process 15213
linux> /bin/kill -9 -15213
- Sending SIGKILL to process grp 15213
Sending Signals from Keyboard

- Unix shells uses abstraction of a job to represent the processes that are created as a result of evaluating a single command line.
- At any time, there's at most one foreground job and zero or more background jobs
- Example:
linux> ls | sort
- will create foreground job consisting of two processes connected by a unix pipe, one running ls and one running sort.
- the shell fork() itself, creates 2 new process and groups them in a process grp and this grp is basically the foreground job.
- When we do Ctrl+C from the shell, the kernel sends a SIGINT signal to every process int he foreground process grp.
- Ctrl+Z causes the kernel to send SIGSTP to every process in foreground process grp ID.
Sending Signals with kill function
- Process can send signals to other process including themselves
#include <system/types.h>
#include <signal.h>
int kill(pid_t pid, int sig);
Return 0 if OK, -1 on error
- pid 0 then the process grp of current process gets the sig signal
- if pid is positive then pid process gets sig signal
- if pid is positive hen |pid| group gets signal signal
Sending Signals with alarm Function
- Can send SIGALARM signals to itself by calling alarm function
#include <unistd.h>
unsigned int alarm(unsigned int secs);
Returns remaining seconds of previous alarm, or 0 if no previous alarm
- Any previous pending alarm is cancelled as this gets set up and the remaining time of that previous alarm is returned.
Receiving Signals
- When a kernel switches process p from kernel mode to user mode, it checks the set of unblocked pending signals (pending & ~blocked) for process p.
- If empty then the control flow is passed on to next instruction Inext if non empty then the kernel choose some signal k in the set (typically the smallest k) and forces p to receive the signal which triggers some action after which control is passed back to inext.
- Each signal type has predefined actions as following:
- The process terminates
- The process terminates and dumps core
- The suspends until restarted by SIGCONT
- The process ignores the signal - We can use the signal() function to to modify the default action associated with a signal.
#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighander_t handler);
Returns pointer to privious handler if OK, SIG_ERR on error (does NOT set errno)
- It's return value is really interesting, it will give pointer to the previous signal handler it was pointing too, so the first time u use it on a signal of a particular type u get a pointer to the default signal.
- There are three ways we can change the default action associated with provided signum:
- If handler is SIG_IGN, then signals of type signum are ignored
- If handler is SIG_DFL, then signals of type signum revert their actions to the default one
- If handler is the address of a user defined function called signal handler then ti will be called whenever the process receives the signal of type signum with the signed int arg sent to signum's corresponding non negative number.
- Example of a signal handler setup:
void signt_handler(int sig){ printf("Caught SIGINT") exit(0); } int main(){ if(signal(SIGINT, sigint_handler) == SIGERR) unix_error("signal error"); pause(); return 0; } - Now whenever this program gets a SIGINT, our custom signal handler is set off.
- Signal handlers can be interrupted by other signal handlers too, like in figure below, the main program catches signal s which interrupts the main and control flow is transferred to handler S. Then when S is running the program catches signal t != s, which interrupts S and now we have T is running.
- When T returns, S will resume and when S returns, main will perform Inext.

Blocking and Unblocking Signals
- There was two ways signal blocking implicit and explicit:
- Implicit blocking mechanism: By default Linux kernel blocks pending signal of ANY type that is currently being processed by a handler. if more signal of the same type is sent the process's way it just gets discarded.
- Explicit blocking mechanism: Applications can explicitly block/ unblock signals using sigprocmask function and its helpers
#include <signal.h> int sigprocmask(int how, const sigset_t *set, sigset_t *oldset); int sigemptyset(sigset_t *set); int sigfillset(sigset_t *set); int sigaddset(sigset_t *set, int signum); int sigdelset(sigset_t *set, int signum); Returns 0 if OK, -1 on error int sigismember(const sigset_t *set, int sighum); Return 1 if member, 0 if not, -1 on error sigprocmask();function changes the set of currently blocked signals, the "change" depends on what is the value of how:- SIG_BLOCK: Add the signals in set to blocked (blocked = blocked | set).
- SIG_UNBLOCK: Remove the signals in set from blocked (blocked = blocked & ~ set).
- SIG_SETMASK: blocked = set.
- For
sigprocmask()if oldset is not-NULL, then it will now have the value of the old set that just got changed. - The rest of the helper functions are there just to manipulate the sigset_t variable that we initialize:
sigsetempty()initializes the set to empty setsigfillset()adds every signal to the setsigaddset()adds signum to the setsigdeleteset()removes signum from the setsigismember()returns 1 if signum is member of the set, 0 if not.
- Example usage:
sigset_t mask, prev_mask;
sigemptyset(&mask);
sigaddset(&mask, SIGINT);
//Blocking SIGINT
sigprocmask(SIG_BLOCK, &mask, &prev_mask);
//Restoring previous block set
sigprocmask(SIG_SETMASK, &prev_mask, NULL);
Writing Signal Handlers
- Writing signal handlers can be hard due to the following reasons:
- Handlers run concurrently with the main program and share the same global vars so it can interfere with main program and other handlers.
- Rules for how and when signals are received is often counter intuitive.
- Different systems can have different signal handling semantics.
Safe Signal Handling
- Running concurrently with main programs means that the handler and the main program can access the same global data structure, which can create a lot of issues.
- Here are the guidelines for safe signal handling:
- G0:
- Keep the handlers as simple as possible. Like a handler can just set a global flag and return and all other processing is done in main.
- G1:
- Call only async signal safe functions in you handlers (given in pic below). It has properties as being either reentrant i.e accessing only local vars or because it cannot be inturrutbed by a signal handler.
- Note that calling printf, sptinrf, malloc and exit and many other common functions is not safe. Even to print to console, we gotta do write().
- write() kinds sucks we we can make our own signal safe sio (Safe i/O) functions:
- G0:
#include "csapp.h"
ssize_t sio_putl(long v);
ssize_t sio_puts(char s[]);
Returns: numer of bytes transferred if OK, -1 on error
void sio_error(char s[]);
Returns nothing.
- sio_putl and sio_puts emits long and string respectively to the stdout. Likewise sio_error message prints the error message and terminates. - We have implementation of it too:  - Finally below is the image of all the async signal safe function in linux implementation:  - G2: Save and restore errno: - Many linux based functions set errno, calling such functions inside the signal handler might disrupt with the main's error handling. - So we gotta save the errno to a local variable on entry to the handler and restore it before the handler returns - Example:
void sigint_handler(int sig){
int olderrno = errno;
//do sm stuff
errno = olderrno;
return;
}
```
- G3: Protect accesses to shared global data structure by blocking all signals:
- If some data structure is is shared with main program by the handler or other handlers then we should temporarily block all signals while accessing this global structure(read/ writing).
- G4: Declare shared global variables with volatile:
- Like if some global var g never changes in main but can be changed by the handler then the compiler might try to optimize by making the global a constant and never looking up the var even if it changes.
- By making it volatile, the program will be forced to look it up every time it's referenced.
- G5: Declare flags with sig_atomic_t:
- Common signal handler pattern is to have the handler record the receipt signal by writing into the global flag and the main reading the global.
- Having the flag as sig_atomit_t means read/write to it can't be interrupted. (Also make it volatile following G4).
- Note that these guides are conservative, in the sense that they are not always strictly necessary. If you know certain thing are not required and might just become an overhead, u can just remove them.
Correct Signal Handling
- In unix systems, a process can only handle one type of signal at a time, and upon receiving another signal of the same type while handling one, the new signal is put into the pending set.
- If more signal of the same type comes around when pending set already has the same signal type then the rest are simply discarded.
- This can be problematic in many different scenarios, like a parent might want to reap all it's children, but if it has a lot of it, and they all terminate at once and the handling process is relatively complex then the parent might not be able to catch and process all the signals and process it leading to a lot of unhanded SIGCHILD signals and zombie processes.
- Example of such buggy code:

- The above most likely won't be able to reap all of it's terminated children, a better solution might be a handler like this:
void handler2(int sig){
int olderrno = errno;
while(waitpid(-1, NULL, 0) >0){
sio_puts("handler reaped child\n");
}
if(errno != ECHILD)
Sio_error("waitpid_error");
sleep(1);
errno = olderrno;
}
- Here as soon as the first child is reaped, the process does not exit the signal handler until all the children are reaped.
Portable Signal Handling
- Semantics of Unix signal() varies depending on the Unix system version, some older version restores action for signal k to its default action after just one signal gets caught by the handler each time it runs.
- Likewise sys calls can interrupted and in older Unix systems, interrupted sys calls means it returns -1 and errno is set to EINTR, specially for longer sys calls like write or read which might get inturrupted more commonly it might be a problem.
- To deal with these issue, Posix standard defines the sigaction function which allows the users to clearly specify the signal handling semantics they want with their handler.
#include <signal.h>
int sigaction(int signum, struct sigaction *act, struct sigaction *oldact);
Returns 0 if OK, -1 on error
- sigaction is complicated specially to set before every signal set up, so we simply make a Signal wrapper that gives us a portable signal handling on posix-compliant systems:
handler_t *Signal(int signum, handler_t *handler){
struct sigaction action, old_action;
action.sa_handler = handler;
sigemptyset(&action.sa_mask);//only signal currently being handled is blocked
action.sa_flag = SA_RESTART;//all functions if can be restarted will be after an intrrupt
if(sigaction(signum, &action, &old_action) < 0)
unix_error("Signal error");
return (old_action.sa_handler);//returning the previous handler like normal signal function would do
}
- there requirements are met:

Synchronizing Flows to Avoid Nasty Concurrency Bugs
- One of the biggest headache for concurrency is to program the concurrent flow for programs that read and write the same store location. Specially because the potential number of interleaving of the flow rises exponentially with increase in number of instructions.
- The problem is the synchronize this flow while allowing for largest number of interleaving.
- Here's an example showing one such problem, the race condition:

- Here right after fork() what if the processor decides to go ahead and complete the child process it just created? That would mean once the child exits, the signal handler will be called and in turn delete job will be called without add job ever being called.
- Here there' a race between add_job() and delete_job() function, maybe for 90% of the run add_job will happen before delete_job, but for the remaining 10 % of the condition, that might not be a case leading to nasty bugs that only happen once a while, and very hard to debug.
- Here's a potential solution for the above bug:

- here, we block SIGCHILD signal for parent process from the moment it's created to it's the point right after addjob() is executed. This way we guarantee that signal hander is no executed before the addjob() function.
- Also a thing to note is that we have unblocked the SIG_CHILD right before we execute that new program, we did this because our child process inherits the parent's blocked vector bit set, so by doing this we make sure that the child inherit a clean slate instead of having SIGCHILD blocked for it by default.
Explicitly Waiting for Signals
- For some cases (like LInux shell), we need to have the process explicitly wait for it's child die before moving forward, an example implementation for this could be:
volatile sig_atomic_t pid;
void sigchild_handler(int s)
{
int olderrno = errno;
pid = waitpid(-1, NULL, 0);
errno = olderrno;
}
void signint_handler(int s)
{
}
int main(int argc, char **argv)
{
sigset_t mask, prev;
Signal(SIGCHILD, sigchild_handler);
Signal(SIGINT, signint_handler);
Sigemptyset(&mask);
Sigaddset(&mask, SIGCHILD);
while(1){
Sigprocmask(SIG_BLOCK, &mask, &prev);
if(fork() == 0)
exit(0);
pid = 0;
Sigprocmask(SIG_SETMASK, &prev, NULL);
//waiting for pid (which is a global var) to change from 0 to some child pid
while(!pid)
;
//wasteful cuz we just doing infinite loop
printf(".");
}
exit(0);
}
- A better solution is to use sigsusped, that way instead of forcing the process to just stay there and loop infinity, we manually suspend it so cpu can do some other stuff.
#include <signal.h>
int sigsuspend(const sigset_t *mask);
Returns: -1
- It's equivalent to a following, but done atomically!! Here the signal could interrupt between line 1 and line 2 fucking everything up:
sigprocmask(SIGBLOCK, &mask, &prev);
pause();
sigprocmask(SIG_SETMASK, &prev, NULL);
- where mask has everything except SIGCHILD so pause is only interruptible by SIGCHILD.
volatile sig_atomic_t pid;
void sigchild_handler(int s)
{
int olderrno = errno;
pid = waitpid(-1, NULL, 0);
errno = olderrno;
}
void sigint_handler(int s)
{
}
int main(int argc, char **argv){
sigset_t mask, prev;
Signal(SIGCHILD, sigchild_handler);
Signal(SIGINT, sigint_handler);
Sigemptyset(&mask);
Sigaddset(&mask, SIGCHILD);
while(1){
Sigprocmask(SIG_BLOCK, &mask, &prev);
if(fork() == 0)
exit(0);
pid = 0;
while(!pid){
sigsuspend(&prev); //here basically
}
Sigprocmask(SIG_SETMAKS, &prev, NULL);
printf(">");
}
exit(0);
}
Prev: Process Control
TOC: Exceptional Control Flow
Next: Nonlocal Jumps