Correct way to handle exiting a daemon

So, i am currently trying to learn about Daemon development under Linux.

I have found this "Template": https://programmerwiki.com/article/773078800/

However it does not mention how to correctly handle the SIGTERM for example.

Would it be acceptable to have a While-loop in the core, that runs when an int is 1 and then change that int to 0 in the SIGTERM-handler to cause it to exit the while and then have a chance to close everything up?

Ive tried it and it works. However, it seems a bit crude.

What do you guys think?
However, it seems a bit crude.

This is standard practice, except
- The flag must have a volatile-qualified type; and
- The signal handler must be re-entrant

The C++ standard is much more restrictive than POSIX regarding what can be done in a signal handler.

S.A.
https://en.cppreference.com/w/cpp/utility/program/sig_atomic_t
mbozzi,

Thank you for your help and very informative response.

So, declaring my int as:
volatile int run;

Would meet the first requirement.

However after scouting around a bit on the web, one of the requirements of beeing re-entrant, is to not use global variables.

But my run variable needs to be accessed to both the signal handler and my "core". Does declaring it as volatile makes this OK?

Quote from my book "C primer plus": "The volatile qualifier tells the compiler that a variable can have its value altered by agencies other than the program"

Also, sorry for posting C-code here. Its just that the examples ive found for this is written in C.
So, declaring my int as:
volatile int run;
Would meet the first requirement.

Strictly speaking it needs to be volatile sig_action_t or volatile std::atomic_int as detailed in the link I posted. This is a consequence of the re-entrancy requirement.

However after scouting around a bit on the web, one of the requirements of being re-entrant, is to not use global variables.
You can use global variables, with the understanding that reentrant code can be called at almost any point during its own execution.

For example's sake, imagine that print is atomic; it cannot be interrupted part-way through by a signal. Then
1
2
3
4
5
6
7
8
9
10
11
volatile std::sig_action_t global;

void not_reentrant()
{
  // because we chose std::sig_action_t 
  // this function cannot be re-entered during the assignment operation
  global = 2; 
  // but we may be interrupted here
  print(global); // might print 4!
  global = 4;
}

If we're interrupted immediately before the call to print then our global variable might be clobbered by global.

We can only perform atomic operations on global data:
1
2
3
4
void reentrant()
{
  global = 4; 
}

Does this help?

"The volatile qualifier tells the compiler that a variable can have its value altered by agencies other than the program"

For example: on some small computers (embedded micro-controllers), the real-time status of a connected sensor (a peripheral) can be checked by examining a particular memory location -- whose contents are set directly by the peripheral (a scheme called memory-mapped I/O).

When accessing that memory location, volatile must be used: it prevents the compiler from optimizing under the assumption that the value didn't change "by itself".

With this being said, it is the kernel which calls the signal handler -- not your program. Therefore data modified in the signal handler must be declared volatile.
Last edited on
Thanks again!

However i am having a hard time with the examples to be honest.
I have probably misunderstood some key concepts here.
Also, i don't know how this translates to my while-loop and signal-handler.

You use sig_action_t to meet the re-entrant requirement. But it still cannot be re-entered?

How come that global can print 4? Does it execute global = 4; in another order? Or do you mean that it can get interrupted during the assignment of = 2 and not after?


Lets see here:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
volatile sig_atomic_t run = 1;

void signal_handler(int sig) {
 
    switch(sig) {
        case SIGHUP:
            syslog(LOG_WARNING, "Received SIGHUP signal.");
            break;
        case SIGTERM:
            syslog(LOG_WARNING, "Received SIGTERM signal.");
	    run = 0; 
            break;
        default:
            syslog(LOG_WARNING, "Unhandled signal (%d) %s", strsignal(sig));
            break;
    }
}




int main ()
{


while (run > 0)
{
Main execution here;
}

Close up shop;


}



Can the problem arise when setting run to 0, or does it mess when the while-loop checks the value?

Sorry for being slow with this :)




How come that global can print 4? Does it execute global = 4; in another order? Or do you mean that it can get interrupted during the assignment of = 2 and not after?
A signal handler (generally) can be called at any time, even during its own execution. This is the source of the problem.

First let's make not_reentrant an actual signal handler:
1
2
3
4
5
6
extern "C" void signal_handler(int sig) // note: C linkage
{
  status = sig; 
  print(status); // might print 0!
  status = 0;
}
Imagine SIGTERM was caught. Line 3 is completed, and then another SIGTERM is caught, so an invocation of signal_handler happens between lines 3 and 4.

The last thing the nested handler does is assign status = 0, so when execution resumes in the first signal handler (back at line 4), status has been reassigned to 0. Ouch.

This is similar in many respects to having multiple threads modify the same global variable.

From the manual page on signal safety,
https://man7.org/linux/man-pages/man7/signal-safety.7.html
and the page on syslog,
https://man7.org/linux/man-pages/man3/syslog.3.html
We can verify that syslog is not async-signal-safe, i.e., it can't be used in a signal handler unless you can guarantee no other signals will be raised during its execution.

Generally this means it's easiest to simply assign & return:
1
2
3
4
5
6
7
8
9
10
11
#include <csignal>
#include <cstdio>

volatile std::sig_atomic_t status; 
extern "C" void signal_handler(int sig) { status = sig; }  

int main()
{
  std::signal(SIGTERM, signal_handler);
  do /* nothing */; while(status != SIGTERM);
}
Last edited on
Thank you very much for the great clarification!
I get the concept now. Did forget to think about that the signal handler also (of course) returns to the exact position it got interrupted in. Exactly as a interrupt in a micro controller for example.


Also, a bad idea to have the syslog instruction in the signal handler then. I think he maybe just had that for reference.


Again, thank you for taking the time and explain this to me.

Topic archived. No new replies allowed.