Networking - C Signals and Signal Handling in Linux
Hello it's a me again Drifter Programming! Today we will continue our talk with Signals in Linux using C Language. I will first start out explaining what signals are and how we can send a signal and then we will get into signal handling, sending information using a signal and lastly get into examples! So, let's do it!
Signals:
Signals are interruptions that are generated by the software and give us a way of handling asynchronous events. Those interruptions can be generated by things outside the process (like Ctrl+C from the user in the terminal) or from errors inside the process (like a math error for dividing with 0).
So, a signal follows this life-cycle:
- The Signal gets generated/created
- The Kernel stores the signal until it's ready
- The Kernel handles the signal by ignoring it, performing a default action or user specified function
To send a signal to a process we use kill -Code PID. The Code specifies what kind of signal we want to send to the specific Process with Process ID = PID. You can check the Codes here. The signals SIGUSR1 and SIGUSR2 are special and they behavior can be user-defined.
Signal Handling:
To handle a signal inside of our Programm we have to include the signal.h library. We will also use the pause() function from unistd.h to wait until we receive a signal. Our Code will need to contain a signal_handler function for each signal_code that we want to handle customly and we will use the signal() function to define the handler for a specific Signal. The handler function can also be the same and the behavior can change using a switch case statement that checks for the Code.
So, our Code will contain the following:
A signal handler function
void signal_handler(int sig_id){
// handle signal sig_id
}
Signal-Signal Handler assigment
signal(sig_id, signal_handler);
To sent a signal we do:
kill(pid, sig_id);
To wait for a signal we do:
pause();
The sig_id must be a correct Code from the Linux Signals we talked about before!
So, we can put for example SIGINT to catch the Interrupt Signal and make it terminate our Programm or do something before terminating the Programm. I will have Examples in a second!
Use Signals for IPC:
There is also another way of sending signals where we first change the behavior using sigaction() and then send a signal using sigqueue(). That way we can include information inside of our Signal!
To change the behavior we have to include the following in our Code:
void sig_action_function(int sig, siginfo_t *info, void *ptr);
struct sigaction act;
union sigval value;
value.sival_int;
act.sa_sigaction = sig_action_function;
act.sa_flags= SA_SIGINFO;
sigaction(SIGUSR1, &act, 0);
I will not get in depth, but you just have to know that the value that we want to sent get's stored inside of value.sival_int. Using sival_int we sent integers. We would use sival_ptr if we wanted to sent strings!
To send a signal you then do the following:
value.sival_int = val; // set value to val
sigqueue(pid, SIGUSR1, value); // sent signal with value val to pid
The other process again receives the signal using pause()!
Examples:
Example 1:
I have 2 great Examples for you. First we will use the Signals SIGTSTP, SIGQUIT, SIGUSR1 and count how many times we catched them and use SIGINT to print the stats and also terminate the programm if we wish. To do this we will use the same signal_handler for all of them and use a switch case statement to see which one we catched and increment the specific counter or print stats and terminate our programm if we wish. To run this Code you will have to run the programm in background using ./code & and then you will have to send the specific signals using kill -Code PID!
So, our Code looks like this:
// include stdio, stdlib, string, unistd and signal.h
int SIGTSTP_Count=0, SIGQUIT_Count=0, SIGUSR1_Count=0; // counters
static void signal_handler (int signo);
int main(){
// set handler for each signal
signal(SIGINT, signal_handler);
signal(SIGTSTP, signal_handler);
signal(SIGQUIT, signal_handler);
signal(SIGUSR1, signal_handler);
while(1){
// the code will run as long as we don't stop it using SIGINT
}
}
static void signal_handler (int signo){
char choice;
// behavior depending on signal
if(signo == SIGINT){
printf("SIGTSTP: %d\nSIGQUIT: %d\nSIGUSR1: %d\n", SIGTSTP_Count, SIGQUIT_Count, SIGUSR1_Count);
printf("Do you want to close the program?(y or n)\n");
scanf("%c",&choice);
if((choice=='y')||(choice=='Y')){
exit(0);
}
}
else if(signo == SIGTSTP){
printf("Caught SIGTSTP!\n");
SIGTSTP_Count++;
}
else if(signo == SIGQUIT){
printf("Caught SIGQUIT!\n");
SIGQUIT_Count++;
}
else if(signo == SIGUSR1){
printf("Caught SIGUSR1!\n");
SIGUSR1_Count++;
}
}
Example 2:
In the second example we will transfer information between processes and also use a pipe! The signal we sent will control if we can receive a message through the pipe right now or haven't seen the previous one yet. Payload 0 will mean we haven't received the message yet and 1 means we are ready to receive the next one. We will use a parent-child relationship to make it easier. The parent will read from a file and sent to the child to print it out and finally sent a termination buffer at the end and so both will terminate!
So, our Code looks like this:
// include stdio, stdlib, string, unistd, signal, sys/types and sys/wait.h
void sig_action_function(int sig, siginfo_t *info, void *ptr);
int main(){
pid_t pid, cid;
FILE *fp;
char buffer[20], prev_buffer[20], stop_buffer[20];
int file_pipes[2];
int data_processed;
// change behavior of signal
struct sigaction act;
union sigval value;
value.sival_int;
act.sa_sigaction = sig_action_function;
act.sa_flags= SA_SIGINFO;
sigaction(SIGUSR1, &act, 0);
strcpy(stop_buffer, "\0"); // termination value
pid=getpid(); // parent pid
if(pipe(file_pipes)==0){ // create pipe
cid=fork(); // create child process
if(cid==-1){ // error checking
perror("fork failed");
exit(1);
}
else if(cid==0){ // child code
// read until we receive the termination buffer
do{
// read from pipe
data_processed = read(file_pipes[0], buffer, 19);
if(!strcmp(buffer, stop_buffer)) break;
if(!strcmp(buffer, prev_buffer)){
value.sival_int=0; // payload 0
}
else{
value.sival_int=1; // payload 1
printf("%s\n", buffer);
}
sigqueue(pid, SIGUSR1, value); // sent signal to parent
strcpy(prev_buffer, buffer);
}while(strcmp(buffer, stop_buffer));
exit(0);
}
else{ // parent code
if(!(fp=fopen("test.in","r"))){ // open file
perror("fp");
exit(1);
}
while(fscanf(fp, "%s", buffer)!= EOF){ // read until end o file
// write to pipe
data_processed = write(file_pipes[1], buffer, 19);
printf("Wrote to Pipe!\n");
pause(; // wait to receive signal value 1 from child
printf("Read from Pipe was successfull!\n");
sleep(1);
}
strcpy(buffer, stop_buffer); // sent termination buffer to child
// write to pipe
data_processed = write(file_pipes[1], buffer, 19);
}
}
wait(&cid); // wait for child termination
exit(0);
}
void sig_action_function(int sig, siginfo_t *info, void *ptr){
union sigval value = info->si_value;
// if value is different than 1(so 0) we wait until it becomes 1
while(value.sival_int!=1);
}
I use the following test.in file:
abcdef
gh
ijk
lmnop
qrstu
vwx
yz
123
4567
890
!@#$%^
&*()_
{-;'\
[:+
"|<,
.>/}
?~`
]=
And this is actually it for today my friends!
Next time in Networking we will get into Classic Synchronization Problems!
Bye!