Skip to content

Signals

benliao1 edited this page Aug 5, 2020 · 4 revisions

What are Signals?

Signals in one sentence: signals are ways for a process to signal to another process that an event has occurred.

There are many, many, many signals defined by the POSIX standard (about 40 of them), but in most everyday programming, you only need to deal with a few of the more common ones.

An example of a common signal is SIGINT (interrupt signal). This signal is generated when you press Ctrl-C on your keyboard in a terminal that is running a process. More precisely speaking, a SIGINT is generated by the terminal process when you press Ctrl-C, which is sent to the process you are running.

Signals can be handled, i.e. a process can indicate to the operating system that it would like to run a certain function when the process receives a particular signal. The function that the process specifies is often called a signal handler for that particular signal. In the systems lingo, we say that a process can install a signal handler for a given process. A signal handler is often used for freeing up memory, killing other processes, closing file descriptors, or other other cleanup work that needs to be done before a process exits.

There are two signals, SIGKILL and SIGSTOP, that cannot be handled or ignored; they always result in the termination of a process. This is necessary because sometimes when a process has become unresponsive, it will no longer respond to other signals which may be potentially handled by the process; in this case the operating system can send one of these two signals to the process to "force quit" it.

Functions Used When Working With Signals

The two most important functions that are used when working with signals (and pretty much the only two signal-handling functions that are used in Runtime) are signal and kill. The signal function is used to install a signal handler, and the kill function (whose name is a very big misnomer) is used to send a particular signal to another process.

The signal Function

The signal function has the following definition:

typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);

Or

void (*signal(int sig, void (*func)(int)))(int)

This is a stupidly complicated definition that doesn't tell you much about how to actually use the function; don't worry, it's not as complicated as it seems. The signal function takes two arguments. The first argument is the signal that you are installing a signal handler for (SIGINT, SIGPIPE, SIGCHLD, etc.). The second argument is the name of a function that returns nothing and takes in a single integer argument that is to be installed as the signal handler for the given signal. That's it!

The kill Function

The kill function has the following definition:

int kill(pid_t pid, int sig);

This is a relatively straightforward function. It takes two arguments. The first argument is the process ID of the process that we wish to send a signal to; the second argument is the signal the we wish to send (SIGINT, SIGPIPE, SIGCHLD, etc.). Remember, though the kill function is often used to terminate processes, the function does not always terminate the process that we're sending the signal to (which is why the function name is such a huge misnomer). Rather, the kill function is simply used to send a particular signal to a particular process.

If you don't know what a process ID is, read the wiki page on processes.

Use in Runtime

net_handler, executor, dev_handler, as well as of their respective CLIs, can in theory run indefinitely. When testing, it is super important that we install signal handlers for SIGINT in them, so that we can stop them cleanly by pressing Ctrl-C in the terminal. You'll find that we have a call to the signal function at the beginning of the main functions in all of these processes to install a signal handler to clean up after the process upon pressing Ctrl-C. Additionally, in the test clients, which spawn the actual Runtime processes on demand, we use the kill function to send SIGINT to the spawned Runtime process to simulate pressing Ctrl-C on that process and stop it on demand. executor also uses kill to terminate the subprocess running the student code in a certain run mode when a mode change is detected.

The only other place that we use signals is in the logger. When you have a pipe or FIFO that has a write end open but no read end, the signal SIGPIPE is generated when a process tries to write into the write end. The default behavior when a process receives this signal is to terminate, but we don't want this to happen! For the logger, this signal being generated simply means that Dawn has disconnected from the robot, and so the FIFO pipe that is ferrying logs from all of the Runtime processes to net_handler has had its read end closed. In this case, we install a signal handler in the logger itself to "reset" the pipe; read more about it at the wiki page on the logger.

Additional Resources

Clone this wiki locally