hn-classics/_stories/2009/10919277.md

10 KiB
Raw Permalink Blame History

created_at title url author points story_text comment_text num_comments story_id story_title story_url parent_id created_at_i _tags objectID year
2016-01-17T12:29:15.000Z All about Linux signals (2009) http://www.linuxprogrammingblog.com/all-about-linux-signals?page=show ingve 124 8 1453033755
story
author_ingve
story_10919277
10919277 2009

Source

All about Linux signals | Linux Programming Blog

Jump to Navigation

Home

** Linux Programming Blog **

const char s[] = "Linux Programming Blog";

Main menu

Home Blogs daper's blog

All about Linux signals

Submitted by daper on Tue, 02/03/2009 - 00:33

In most cases if you want to handle a signal in your application you write a simple signal handler like:

void handler (int sig)

and use the signal(2) system function to run it when a signal is delivered to the process. This is the simplest case, but signals are more interesting than that! Information contained in this article is useful for example when you are writing a daemon and must handle interrupting your program properly without interrupting the current operation or the whole program.

What is covered here

The article describes how signals work in Linux and how to handle signals using POSIX API. I will cover functions working on every modern Linux system, but it should also apply to most POSIX systems unless explicitly stated otherwise. Legacy functions are not covered. A basic knowledge about signals is required.

What is signaled in Linux

Your process may receive a signal when:

  • From user space from some other process when someone calls a function like kill(2).
  • When you send the signal from the process itself using a function like abort(3).
  • When a child process exits the operating system sends the SIGCHLD signal.
  • When the parent process dies or hangup is detected on the controlling terminal SIGHUP is sent.
  • When user interrupts program from the keyboard SIGINT is sent.
  • When the program behaves incorrectly one of SIGILL, SIGFPE, SIGSEGV is delivered.
  • When a program accesses memory that is mapped using mmap(2) but is not available (for example when the file was truncated by another process) - really nasty situation when using mmap() to access files. There is no good way to handle this case.
  • When a profiler like gprof is used the program occasionally receives SIGPROF. This is sometimes problematic when you forgot to handle interrupting system functions like read(2) properly (errno == EINTR).
  • When you use the write(2) or similar data sending functions and there is nobody to receive your data SIGPIPE is delivered. This is a very common case and you must remember that those functions may not only exit with error and setting the errno variable but also cause the SIGPIPE to be delivered to the program. An example is the case when you write to the standard output and the user uses the pipeline sequence to redirect your output to another program. If the program exits while you are trying to send data SIGPIPE is sent to your process. A signal is used in addition to the normal function return with error because this event is asynchronous and you can't actually tell how much data has been successfully sent. This can also happen when you are sending data to a socket. This is because data are buffered and/or send over a wire so are not delivered to the target immediately and the OS can realize that can't be delivered after the sending function exits.

For a complete list of signals see the signal(7) manual page.

Signal handlers

Traditional signal() is deprecated

The signal(2) function is the oldest and simplest way to install a signal handler but it's deprecated. There are few reasons and most important is that the original Unix implementation would reset the signal handler to it's default value after signal is received. If you need to handle every signal delivered to your program separately like handling SIGCHLD to catch a dying process there is a race here. To do so you would need to set to signal handler again in the signal handler itself and another signal may arrive before you cal the signal(2) function. This behavior varies across different systems. Moreover, it lacks features present in sigaction(2) you will sometimes need.

The sigaction(2) function is a better way to set the signal action. It has the prototype:

    1. int sigaction (int signum, const struct sigaction *act, struct sigaction *oldact);

As you can see you don't pass the pointer to the signal handler directly, but instead a struct sigaction object. It's defined as:

    1. struct sigaction {

2.         void     (*sa_handler)(int);

3.         void     (*sa_sigaction)(int, siginfo_t *, void *);

4.         sigset_t   sa_mask;

5.         int        sa_flags;

6.         void     (*sa_restorer)(void);

7. };

For a detailed description of this structure's fields see the sigaction(2) manual page. Most important fields are:

  • sa_handler - This is the pointer to your handler function that has the same prototype as a handler for signal(2).
  • sa_sigaction - This is an alternative way to run the signal handler. It has two additional arguments beside the signal number where the siginfo_t * is the more interesting. It provides more information about the received signal, I will describe it later.
  • sa_mask allows you to explicitly set signals that are blocked during the execution of the handler. In addition if you don't use the SA_NODEFER flag the signal which triggered will be also blocked.
  • sa_flags allow to modify the behavior of the signal handling process. For the detailed description of this field, see the manual page. To use the sa_sigaction handler you must use SA_SIGINFO flag here.

What is the difference between signal(2) and sigaction(2) if you don't use any additional feature the later one provides? The answer is: portability and no race conditions. The issue with resetting the signal handler after it's called doesn't affect sigaction(2), because the default behavior is not to reset the handler and blocking the signal during it's execution. So there is no race and this behavior is documented in the POSIX specification. Another difference is that with signal(2) some system calls are automatically restarted and with sigaction(2) they're not by default.

Example use of sigaction()

See example of using sigaction() to set a signal handler with additional parameters.

In this example we use the three arguments version of signal handler for SIGTERM. Without setting the SA_SIGINFO flag we would use a traditional one argument version of the handler and pass the pointer to it by the sa_handler field. It would be a replacement for signal(2). You can try to run it and do kill PID to see what happens.

In the signal handler we read two fields from the siginfo_t *siginfo parameter to read the sender's PID and UID. This structure has more fields, I'll describe them later.

The sleep(3) function is used in a loop because it's interrupted when the signal arrives and must be called again.

SA_SIGINFO handler

In the previous example SA_SIGINFO is used to pass more information to the signal handler as arguments. We've seen that the siginfo_t structure contains si_pid and si_uid fields (PID and UID of the process that sends the signal), but there are many more. They are all described in sigaction(2) manual page. On Linux only si_signo (signal number) and si_code (signal code) are available for all signals. Presence of other fields depends on the signal type. Some other fields are:

  • si_code - Reason why the signal was sent. It may be SI_USER if it was delivered due to kill(2) or raise(3), SI_KERNEL if kernel sent it and few more. For some signals there are special values like ILL_ILLADR telling you that SIGILL was sent due to illegal addressing mode.
  • For SIGCHLD fields si_status, si_utime, si_stime are filled and contain information about the exit status or the signal of the dying process, user and system time consumed.
  • In case of SIGILL, SIGFPE, SIGSEGV, SIGBUS si_addr contains the memory address that caused the fault.

We'll see more examples of use of siginfo_t later.

Compiler optimization and data in signal handler

Let's see the following example:

    1. #include <stdio.h>

2. #include <unistd.h>

3. #include <signal.h>

4. #include <string.h>

5.  

6. static int exit_flag = 0;

7.  

8. static void hdl (int sig)

9. {

10. 	exit_flag = 1;

11. }

12.  

13. int main (int argc, char *argv[])

14. {

15. 	struct sigaction act;

16.  

17. 	memset (&act, '