Often we need a daemon process to respond to some external events. We might want to notify a running process of some external event and make that process do some work corresponding to that event or we may simply want to modify the behavior of a process corresponding to some external events.
Now one way to achieve this will be to use IPC (inter process communication) using sockets or pipes or dbus. All of these requires the running process to constantly poll the IPC elements used like socket files, pipe files etc or expose some methods on dbus. The daemon and the notifier process are tied by a thin bridge of socket files or pipe files or the dbus methods. Is there a native OS way to achieve this?
Real time signals
Linux already has the functionality that allows us to send a variety of signals to our running processes and we use them on a daily basis to terminate, kill or HUP a process. But linux also has an additional set of signals called real time signals that allows the daemons to respond to arbitrary events. On its own, these signals are meaningless to the kernel or the process. The definition and behavior for these signals are left for the process/daemons to define.
There are a total of 33 real time signals that the linux kernel supports, numbered between 32 to 64. Internally, glibc threading implementation might use 2 or 3 real time signals. Due to this, the actual range available for the use might not begin with 32. Hence we should refrain from using these hard-coded numbers for the signals and instead the recommended way to use these signals is to use notation SIGRTMIN+n or SIGRTMAX-n, where n starts from 1. We should ensure that the value of these signals doesn't go outside SIGRTMIN - SIGRTMAX. Here SIGRTMIN and SIGRTMAX are defined by the kernel depending upon the glibc threading implementation and we don't need to worry about their exact values.
We can actually list all the available signals in bash for our system:
$ bash -c "kill -l"
1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP
6) SIGABRT 7) SIGBUS 8) SIGFPE 9) SIGKILL 10) SIGUSR1
11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM
16) SIGSTKFLT 17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP
21) SIGTTIN 22) SIGTTOU 23) SIGURG 24) SIGXCPU 25) SIGXFSZ
26) SIGVTALRM 27) SIGPROF 28) SIGWINCH 29) SIGIO 30) SIGPWR
31) SIGSYS 34) SIGRTMIN 35) SIGRTMIN+1 36) SIGRTMIN+2 37) SIGRTMIN+3
38) SIGRTMIN+4 39) SIGRTMIN+5 40) SIGRTMIN+6 41) SIGRTMIN+7 42) SIGRTMIN+8
43) SIGRTMIN+9 44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12
53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9 56) SIGRTMAX-8 57) SIGRTMAX-7
58) SIGRTMAX-6 59) SIGRTMAX-5 60) SIGRTMAX-4 61) SIGRTMAX-3 62) SIGRTMAX-2
63) SIGRTMAX-1 64) SIGRTMAX
All the signals which starts with SIGRTMIN or SIGRTMAX are the real time signals and their corresponding numeric value is shown before them.
Using Real Time Signals in apps
Here I will demo the use of these signals using a simple bash script but the same approch can be taken with any language. First we need to decide which real time signals our app will be using and what function those signals would correspond to. Then we need to implement below two steps in our program for each of the real time signals we have decided to use:
depending upon the signal function we have decided, define separate handler function for each signal
assign these handler functions to their respective real time signals we have decided.
Sample program
#!/usr/bin/env bash
#### Section 1: define all handler functions ######################
# handler function to send email SIGRTMIN+1
send_email_notification() {
echo "Sending email notification"
# code for ending email goes here
}
# handler function to enable execution with set -x
enable_debug_logs() {
set -x
echo "enabling debug logs"
}
# handler function to disable -x
disable_debug_logs() {
set +x
echo "disabling debug logs"
}
######################################################33333
#### Section 2: adding handler to signals #########################
## Now we will lay down our traps to handle these signals
## In bash 'trap' is how we define the signal handler
trap send_email_notification 'SIGRTMIN+1'
trap enable_debug_logs 'SIGRTMIN+2'
trap disable_debug_logs 'SIGRTMIN+3'
########################################################
### the main loop which will do some work
while true; do
echo "Doing some work"
sleep 10
done
This is very simple bash program that I have written to demo the concept and the possible use case. Here corresponding to the steps mentioned previously, I have divided the program into two sections. The real time signals and their function that our program will be using are as follows:
SIGRTMIN+1 : to send email notification on demand
SIGRTMIN+2: to enable printing of debug output
SIGRTMIN+3: to disable printing of debug output
Its not mandatory to go serially or pick these real time signals from start. You can pick any real time signal in the range allowed by the OS . Let’s now look at what the two sections defined in the program do.
Section 1: defining the handlers
# handler function to send email SIGRTMIN+1
send_email_notification() {
echo "Sending email notification"
# code for ending email goes here
}
# handler function to enable execution with set -x
enable_debug_logs() {
set -x
echo "enabling debug logs"
}
# handler function to disable -x
disable_debug_logs() {
set +x
echo "disabling debug logs"
}
Here we are defining three handler functions:
send email notification handler (send_email_notification): This function is supposed to send some email notification to some defined set of email IDs when the signal is received.
enable debug logs (enable_debug_logs): This function will run
set -x
which will cause the bash script to print the lines it is executingdisable debug logs (disable_debug_logs): This function will run
set +x
which will cause the bash script to stop printing the lines it is executing
Section 2: assigning the handlers to the signals
trap send_email_notification 'SIGRTMIN+1'
trap enable_debug_logs 'SIGRTMIN+2'
trap disable_debug_logs 'SIGRTMIN+3'
This is where the actual magic happens. In bash we assign the handlers to the signal using trap
command. The syntax is quite simple:
trap <handler function name> <signal name>
In the program above using trap
command, we have assigned each of our handler function to their respective real time signals we had decided to use.
Seeing signals in action
Time to see this in action now. Lets run our bash script in one terminal and open a new terminal to run our command to send the real time signals. Using the ps
command, note the PID of our bash script (in my case it is 692560). Now to send the signal to our program, we will be using the bash kill command. The syntax is like this:
kill -s <signal name> <pid>
Let's now see each of the three scenarios in action.
Note: below demo uses bash kill command. If you are using any other shell then please check the respective documentation on how to send the real time signals in that shell.
Experiment 1: tell our bash process to send email notification
Let's run the below command in the second terminal:
kill -s SIGRTMIN+1 692560
Now in the first terminal, after few seconds (utmost 10), you should see the the following sequence of lines (the delay will happen because sleep command will be running):
Doing some work
Doing some work
Sending email notification
Doing some work
This shows that our send_email_notification
handler was executed successfully.
Experiment 2: enable printing of debug output
Run the below command in the second terminal now:
kill -s SIGRTMIN+2 692560
Observe the output in the first terminal for next 20 secs. You will see output like below. The program will start printing the lines it is executing.
Doing some work
++ echo 'enabling debug logs'
enabling debug logs
+ true
+ echo 'Doing some work'
Doing some work
+ sleep 10
+ true
+ echo 'Doing some work'
Doing some work
+ sleep 10
Thus we can see that our enable_debug_logs
handler was also executed successfully.
Experiment 3: disable printing of debug logs
Now final experiment. Lets disable the debug logs printing.
kill -s SIGRTMIN+3 692560
Again lets observe the output on the first terminal for next 20 secs. You will see output similar to below. The program will stop printing the lines it is executing and go back to normal output sequence.
Doing some work
+ sleep 10
++ disable_debug_logs
++ set +x
disabling debug logs
Doing some work
Doing some work
This show that our disable_debug_logs
was also successfully executed.
Conclusion
Thus we saw how we can easily implement these real time signal handling in our program and notify our running process using native linux signalling. This is the simplest way of notifying a running process without requiring expensive socket, pipe or dbus setup. Of course this doesn't allows you to send some data along if your process requires that but it is quite good for scenarios where the process just needs to be notified of some event to do some work.
Bonus
As a bonus, I leave you with a simple python program that implements the above idea using python. Try to run it and send the same real time signals to it as above and see the output.
#!/usr/bin/env python
import signal
import logging
import sys
import time
import types
# setup logger
logger = logging.getLogger('my_logger')
logger.setLevel(logging.DEBUG) # Set the log level
stdout_handler = logging.StreamHandler(sys.stdout)
stdout_handler.setLevel(logging.INFO)
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
stdout_handler.setFormatter(formatter)
logger.addHandler(stdout_handler)
def send_email_notification(signum: int, frame: types.FrameType | None) -> None:
logger.info("Sending email notification")
def enable_debug_logs(signum: int, frame: types.FrameType | None) -> None:
stdout_handler.setLevel(logging.DEBUG)
logger.info("enabling debug log")
def disable_debug_log(signum: int, frame: types.FrameType | None) -> None:
stdout_handler.setLevel(logging.INFO)
logger.info("disabling debug log")
if __name__ == '__main__':
signal.signal(signal.SIGRTMIN+1, send_email_notification)
signal.signal(signal.SIGRTMIN+2, enable_debug_logs)
signal.signal(signal.SIGRTMIN+3, disable_debug_log)
while True:
time.sleep(5)
logger.info("Doing some work")
logger.debug("this is debug log for that work")
References
For further reading:
Top comments (1)
Thanks for sharing :)