Simple Linux Signal Handling

It'sPete picture It'sPete · Jul 30, 2013 · Viewed 20.2k times · Source

I have a program that creates many threads and runs until either power is shutdown to the embedded computer, or the user uses kill or ctrlc to terminate the process.

Here's some code and how the main() looks.

static int terminate = 0;  // does this need to be volatile?

static void sighandler(int signum) { terminate = 1; }

int main() {
  signal(SIGINT, sighandler);
  // ...
  // create objects, spawn threads + allocate dynamic memory
  // ...
  while (!terminate) sleep(2);
  // ...
  // clean up memory, close threads, etc.
  // ...
  signal(SIGINT, SIG_DFL);  // is this necessary?
}

I'm wondering a few things:

  1. Is any signal handling necessary?
    I read in this thread "Linux C catching kill signal for graceful termination", that apparently the OS will handle cleanup for me. Therefore, can I just replace the signal handler with just an infinite loop and let the OS gracefully exit the threads, de-allocate the memory, etc?

  2. Are there any other signals that I need to be concerned with regarding clean termination? This thread "How does SIGINT relate to the other termination signals?", was useful to list all the signals I may be concerned with, but how many actually required handling?

  3. Does the terminate variable in my example have to be volatile? I've seen many examples where this variable is volatile, and others where it is not.

  4. I've read that signal() is now deprecated, and to use sigaction(). Are there any really good examples to show how to convert from the previous signal() call? I'm having trouble with the new structure that I have to create/pass and how it all fits together.

  5. Is the second call to signal() necessary?
    Is there something similar that I need to be concerned with for sigaction()?

To be clear, all I'm trying to accomplish to to have my: main loop run until either ctrlc or power is disconnected or something really bad happens.

Answer

Grijesh Chauhan picture Grijesh Chauhan · Jul 30, 2013

[Q-3] Does the terminate variable in my example have to be volatile? I've seen many examples where this variable is volatile, and others where it is not.

The flag terminate should be volatile sig_atomic_t:

Because handler functions can be called asynchronously. That is, a handler might be called at any point in the program, unpredictably. If two signals arrive during a very short interval, one handler can run within another. And it is considered better practice to declare volatile sig_atomic_t, this type are always accessed atomically, avoid uncertainty about interrupting access to a variable. volatile tells the compiler not to optimize and put it into register. (read: Atomic Data Access and Signal Handling for detail expiation).
One more reference: 24.4.7 Atomic Data Access and Signal Handling. Furthermore, the C11 standard in 7.14.1.1-5 indicates that only objects of volatile sig_atomic_t can be accessed from a signal handler (accessing others has undefined behavior).

[Q-4] I've read that signal() is now deprecated, and to use sigaction(). Are there any really good examples to show how to convert from the previous signal() call? I'm having trouble with the new structure that I have to create/pass and how it all fits together.

The example below (and the link in the comments) can be helpful:

// 1. Prepare struct 
struct sigaction sa;
sa.sa_handler =  sighandler;

// 2. To restart functions if interrupted by handler (as handlers called asynchronously)
sa.sa_flags = SA_RESTART; 

// 3. Set zero 
sigemptyset(&sa.sa_mask);

/* 3b. 
 // uncomment if you wants to block 
 // some signals while one is executing. 
sigaddset( &sa.sa_mask, SIGINT );
*/ 

// 4. Register signals 
sigaction( SIGINT, &sa, NULL );

references:

  1. Beginning Linux Programming, 4th Edition: in this book, exactly your code is explained with sigaction() nicely in "Chapter 11: Processes and Signals".
  2. The sigaction documentation, including an example (quick learning).
  3. The GNU C Library: Signal Handling
    *I started from 1, Presently I am reading 3 GNU-library

[Q-5] Is the second call to signal() necessary? Is there something similar that I need to be concerned with for sigaction()?

Why you set it to default-action before program termination is unclear to me. I think the following paragraph will give you an answer:

Handling Signals

The call to signal establishes signal handling for only one occurrence of a signal. Before the signal-handling function is called, the library resets the signal so that the default action is performed if the same signal occurs again. Resetting signal handling helps to prevent an infinite loop if, for example, an action performed in the signal handler raises the same signal again. If you want your handler to be used for a signal each time it occurs, you must call signal within the handler to reinstate it. You should be cautious in reinstating signal handling. For example, if you continually reinstate SIGINT handling, you may lose the ability to interrupt and terminate your program.

The signal() function defines the handler of the next received signal only, after which the default handler is reinstated. So it is necessary for the signal handler to call signal() if the program needs to continue handling signals using a non-default handler.

Read a discussion for further reference: When to re-enable signal handlers.

[Q-1a] Is any signal handling necessary?

Yes, Linux will do cleanup for you. For example if you don't close a file or a socket, Linux will do the cleanup after the process terminates. But Linux may not necessary perform the clean up immediately and it may take some time (may be to keep system performance high or some other issues). For example if you don't close a tcp-socket and the program terminates the kernel will not close the socket immediately to ensure all data has been transmitted, TCP guarantees delivery if possible.

[Q-1b] Therefore, can I just replace the signal handler with just an infinite loop and let the OS gracefully exit the threads, de-allocate the memory, etc?

No, operating system performs do clean-up only after program terminates. While a process executes, resources that are allocated to that process don't get claimed by the OS. (The OS can't know whether your process is in an infinite loop or not - this is an unsolvable problem). If you want that after process termination the OS performs the clean-up operations for you, then you don't need to handle signals (even in case your process abnormally terminated by a signal).

[Q] All I'm trying to accomplish to to have my: main loop run until either ctrlc or power is disconnected or something really bad happens.

No, there is a limitation! You can't catch all signals. Some signals are not catchable e.g. SIGKILL and SIGSTOP and both are termination signals. Quoting one:

— Macro: int SIGKILL

The SIGKILL signal is used to cause immediate program termination. It cannot be handled or ignored, and is therefore always fatal. It is also not possible to block this signal.

So you can't make a program that cannot be interrupted (an uninterrupted program)!

I am not sure but may be you can do something like this in Windows systems: by writing TSRs (some sort of kernel-mode hooking). I remember from my thesis time that some viruses couldn't be terminated even from task manager but I also believe that they trick user by admin permissions.

I hope this answer will help you.