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:

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_NODEFER makes the signal not masked
  • SA_RESETHAND resets 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 SIGPIPE signal. 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 printf inside 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.

"If handler calls printf now, toasted" - Albert

  • Also means you shouldn’t call fclose(stdin) either. Shouldn’t call exit (since that includes fclose(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 (write and many syscalls are safe in handlers!)
  • Normal code polls var/pipe at convenient times. When change occurs, you do the your thing

For SIGCHLD, wait and waitpid are safe in handlers. I guess this is an exception. Yay