Signals are what the kernel uses to notify processes of an event and severe errors!
They are all constants; no associated data. Examples he gave:
Example
- interrupt (Ctrl-C): SIGINT
- broken pipe: SIGPIPE
- suspend and resume: SIGSTOP, SIGCONT
- child died/suspended/resumed: SIGCHLD etc
The “Signal Life Cycle”
Some event will try to “generate” a signal; the kernel tries to “deliver” the signal. Until a signal is delivered, it’s “pending”. If the process has masked or blocked that kind of signal, then it will stay pending until the signal is unmasked (if ever). “No multiplicity” = If a signal happens one, two, thirty times, it doesn’t matter. It’s either “happening” or “not happening”. A boolean if you will!
The process has default signal actions.
- i.e. you can install “signal handler functions” to override the default signal action.
Generating a signal
kill -SIGKILL 31337
kill -KILL 31337
kill -9 31337
Syscall
int kill(pid_t pid, int sig);
int raise(int sig); (to self)
Setting Signal Actions/Handlers
You can set a signal type (todo it takes an int; can you do more than 1 signal at a time?) in question to a new action! The action is stored in a struct with a function pointer as the handler, as well as other thingys.
struct sigaction {
// This is the handler function. Called when the signal is generated by mr. kernel
void (*sa_handler)(int sig);
// Mask which signals when running handler. So, while the handler is running, signals inside of sa_make will not be delivered to the process while the handler is running!
// By default, the signal being masked is also masked (avoids chicken-egg problem and recursing the handler)
sigset_t sa_mask;
// Options for your handler to do fancier things
int sa_flags;
// "Not for application use". I don't think we're supposed to touch this
void (*sa_restorer)(void);
};sigset_t Operations
Remember, this is the data type of sa_mask, which masks certain signals while the handler is running!
int sigemptyset ( sigset_t *set );
int sigfillset ( sigset_t *set ); // add all signals
int sigaddset ( sigset_t *set , int sig );
int sigdelset ( sigset_t *set , int sig );
int sigismember (const sigset_t *set , int sig );I’m p sure theses are simple binary operations
Some Flags For sa_flags
For your installed “handler”, you can use these flags to make it do fancier things:
SA_NODEFERmakes the signal not maskedSA_RESETHANDresets handler after it is run once (?)- etc. there are more of em
Broken Pipe, SIGPIPE
- If you write to a pipe/socket but the other end has closed, you have a “broken pipe”. Your process gets slapped a
SIGPIPEsignal. By default, process is die. - The simplest way to override it is to set the action to
SIG_IGN(ignore)- Process won’t die, write will return -1, etc.
Handler Limitations
- You can’t call
printfinside a handler, because:- Normal code is running another
printf. If, in the middle of calling it, it gets interrupted (signal arrives and handler is run) it’ll mess up the buffer/bookkeeping vars to update.
- Normal code is running another
"If handler calls
printfnow, toasted" - Albert
- Also means you shouldn’t call
fclose(stdin)either. Shouldn’t callexit(since that includesfclose(stdin)as well)
Handler Strategies
- If there are some non-trivial things you gotta do upon a signal, do it outside the handler!
- Make a global var/pipe (pipe is better though)
- Make the signal handler write to var/pipe to notify normal code that the signal has happened (
writeand many syscalls are safe in handlers!) - Normal code polls var/pipe at convenient times. When change occurs, you do the your thing
For
SIGCHLD,waitandwaitpidare safe in handlers. I guess this is an exception. Yay