A signal is an event that has occurred, that your process as a whole or a particular thread in your process might want to know about.

Examples:

  • SIGTERM, your process is told to terminate.
  • SIGSEGV, your thread attempted to access memory it doesn’t have access to.

Each signal (event) has a type (SIGSEGV, SIGTERM, SIGKILL, etc) and is either process directed or thread directed.

Process directed signals are meant to be delivered to the process as a whole. Any thread of your process may end up handling the signal.

Thread directed signals are meant to be delivered to a particular thread. Only that target thread may be able to handle the signal.

Your process can specify that certain signals are ignored. If your process receives an ignored signal, that signal simply disappears.

Your thread (each thread) can specify that certain signals are blocked (not ignored!) at that moment in time. While ignored signals simply disappear, blocked signals will wait in a queue until they can be handled. It is usually assumed that a thread that is currently blocking a signal, will eventually unblock it.

So signal ignoring is on a per process basis, whereas signal blocking is on a per thread basis. Also, signal ignoring is permanent whereas signal blocking is temporary.

If a process or thread directed signal is ignored, it simply disappears.

If a process directed signal is blocked by all threads, it will wait in a general (process level) queue. When any thread unblocks the signal, the signal will be delivered to that thread (i.e. that thread will execute the signal handler).

If a thread directed signal is blocked by its target thread, it will wait in a queue specific to the target thread. When that target thread unblocks it, it is delivered. In other words, a thread directed signal will wait for its target thread to unblock it. No other thread can handle it.

Let’s follow the life of a process directed signal. First, it is raised. If your process has ignored this type of signal, the signal simply disappears. If your process has not ignored this type of signal, and there is a thread that is currently not blocking this type of signal, then the signal is delivered to the first thread that is not blocking it. If all thread are currently blocking it, it will wait in the general queue, until any thread unblocks it. It will then be delivered to that thread.

This is different than a thread directed signal, a thread directed signal will wait for its target thread to unblock it, because it cannot be handled by any other thread!

Some signals cannot be ignored or blocked (e.g. SIGSEGV, SIGKILL).

Signal blocking is a temporary way for particular threads to specify that they cannot handle particular signals at this moment in time (most likely because they are handling another signal).

A signal can come from:

  • outside your process (by another process, say by, the kill command)
    • but these can only be process directed
  • inside your process
    • one of your threads can raises the signal by doing something bad
      • SIGSEGV raised by your thread that tries to access invalid memory
    • can be process or thread directed
    • note: thread directed signals are raised by using the pthreads library
  • a signal can be process or thread directed
    • process directed signals are meant for your process as a whole
    • thread directed signals are meant for a particular thread
  • signals can be blocked (masked) or ignored
    • signal masking is on a per thread basis
    • signal ignoring is on a per process basis
  • masked signals go in a queue
    • thread directed signals go in a queue specific to the target thread
      • when the target thread unblocks the signal, it will be delivered (i.e. signal handler will be executed by that thread)
    • process directed signals go in a process level queue
      • when any thread unblocks the signal, it will be delivered to that thread
  • sending a thread directed signal can only be done via the pthreads library (everything else is done via the linux API)

Some questions you might ask about a particular signal:

  • who raised the signal?
  • was it the kernel (e.g. your thread tried to access memory it doesn’t have access to)?
  • was it another process?
  • what is the type of the signal (SIGINT, SIGSEGV). This is also known as the signal number (e.g. SIGNINT’s number is 2).
  • the type of the signal tells you a little about the event (“why the signal was raised”)
  • is this type of signal currently ignored? (i.e. no thread in your process will handle or even see this signal)
  • is this type of signal currently blocked (masked) by some of your threads?

Common Signals:

  • SIGKILL - can’t be handled (i.e. can’t set your own handler for it), ignored, or masked. This signal is entirely handled by OS code. The OS will ungracefully kill your process.
  • SIGSTOP - can’t be handled, ignored, or masked. This one is handled by OS code as well. The OS will simply stop your process (i.e. it won’t get any CPU time till it is resumed)
  • SIGCONT - same thing as above two (can’t be handled, ignored, or masked), is handled by OS code. Will resume a stopped process.
  • SIGSEGV - your thread tried to access memory it doesn’t have access to
  • SIGINT - when you press ctr+c in a terminal, the foreground process running in the terminal gets this signal. Default handler will exit your process.
  • SIGPIPE - you tried to write to a pipe that has no read end

API:

  • to ignore a signal: signal(SIGINT, SIG_IGN);
  • to block a signal: masking API is a bit complex, just google it :grin:
  • to set a signal handler (i.e. the function that will be executed to handle the signal)
    1. specify a signal handler function, signiture should be like: void sig_handler(int signum)
    2. set this function as the handler for, say SIGINT: signal(SIGINT, sig_handler)

Signal handlers can interrupt the handling thread at any time (the only control you have over this is signal masking). Thus you need to make sure that the code in your signal handler does not step on the code that was being executed by the thread before the thread was forced to jump to the signal handler. In other words, only call reentrant functions in your signal handler.