Although the signals have a predefined action, they can be modified or processed by actions that are programmed by the programmer in capture functions or handlers. In this Linuxhint article, you’ll learn how to use the sigaction() function to replace, query, or restore the action of a particular signal. We’ll look at the syntax of this function and explain how it works in theory. Then, we’ll apply what we learned in practical examples with code snippets and images where we use signals, create catch functions, and modify or restore the actions with the sigaction() function.
To give you a complete overview of this function, we included a special section that explains how the structures that are used by this function to define the actions are composed. In another section, we explained the errors and possible solutions that the use of sigaction() can cause.
Syntax of the Sigaction() Function in C Language
struct sigaction * oldact);
Description of the Sigaction() Function in C Language
The sigaction() function associates an action with a signal. This function is used to change, query, or reset the default action of a signal. The “sig” input argument specifies the signal to which the new action is to be assigned. This argument is of type int and can send the identifier of the signal or its constant value.
The action to be associated with the signal is specified in the “act” input argument. This input is a structure of type sigaction which stores the information about the action. The “oldact” argument is a structure of the same type that stores the information about the action to be replaced. The data that a sigaction structure stores determines how the signal executes its action, stores the pointer to the controller, and sets the locks during execution. In the further sections, you’ll find out about the sigaction structure in detail.
When a signal reaches a process, we can treat it in three different ways. The mode is specified by constants or pointers in the “sa_handler” member of the sigaction structure which we send with the “act” argument. Let’s see the three ways to handle a signal:
SIG _DFL: When “sa_handler” is sent with this predefined constant, the signal performs or resets its default action.
SIG_IGN: If “sa_handler” is sent with this constant, the signal is ignored.
*handler: In cases where a handler is used to process the signal, the handler pointer should be sent in the “sa_handler” member.
When using handlers, the signal handler can be used in two different ways depending on the SA_SIGINFO flag in the “sa_flags” member of the sigaction structure. When this flag is disabled or defaulted, the “capture” function has the following prototype:
In this mode, the controller uses only the received signal as an input argument. When the SA_SIGINFO flag is set, the prototype handler looks like this:
When SA_SIGINFO is sent, the “capture” function takes three “sig” input arguments which specifies the signal, a structure of type “signinfo_t” which contains a detailed information about the process that sends the signal and context, a pointer to the “ucontext_t” structure that stores the context information on the kernel stack. This argument isn’t used, so the NULL pointer is sent.
If the sigaction() function is successful, it returns a 0 and the action is associated with the specified signal in sig. If an error occurs during execution, the result is -1 and the identification code can be retrieved via the “errno” global variable.
The sigaction() function, its predefined constants, and variables are declared in the “signal.h” header. To use them, we must include this file as follows:
How to Use the Sigaction() Function to Associate a Signal with a Handler
In this example, we create a simple process and use the sigaction() function to bind signal 36 to a controller. To do this, we first create the “sig_handler” function which will be the handler to which we bind the signal. In this function, we use printf() to display the “Signal received” message on the command console. Then, we put the process to sleep for 10 seconds using the sleep() function. Let’s look at the code for the handler that we’re linking the signal to:
In the main() function, we define the structure act of type sigaction and set the values of its members to 0. In the “sa_handler” member of this structure, we store the pointer to the “sig_handler” function. When we call sigaction(), the signal that is specified in it will be associated with the action of this function.
After specifying the action in the “act” structure, we call the sigaction() function and pass the constant 36 as the first input argument which is the signal that we want to associate the handler with. As the second argument, we pass the pointer to the “act” structure and pass a NULL pointer to oldact as the last parameter. After the return, the “sig_handler” handler is linked to signal 36.
Then, the program falls into an infinite loop where we use the printf() function to display the “The PID process has been active for: n seconds” message. Then, we put the program to sleep for 2 seconds, increment the seconds counter, and clear the screen to repeat the infinite loop. The following is the complete code for this example including the main() function and the signal handler:
//include headers
#include <unistd.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
//Step 2
//handlers statement.
void sig_handler (int sig);
void main()
{
int seconds =0;
//Step 3
//declaration of structures and zeroing.
struct sigaction act = { 0 };
//Step 4
//sets sig_handler as a new action.
act.sa_handler = &sig_handler;
//Step 5
//Call sigaction()
sigaction (36,&act, NULL);
//Step 6
//Infinite loop
while (1){
printf("The %i process has been active for:\n%i seconds\n", getpid(), seconds );
sleep(2);
seconds = seconds+2;
system ("clear");
}
}
void sig_handler (int sig)
{
printf("\nSignal received %i\n", sig );
sleep(10);
}
Now, we copy this code into a file with a C extension, compile it, and run it in the command console. As we can see in the following image, the process in Terminal 1 runs normally and displays the elapsed time:
Next, we send signal 36 from terminal from a second terminal with the following command syntax:
As we can see in the following figure, after receiving the signal, the program enters the handler and outputs the message using the printf() function:
How to Restore the Default Action of a Signal with the Sigaction() Function in C Language
In this example, we will show you how to restore the default action of a signal. To do this, we specify the signal in the “sig” input argument and set the SIG_DFL constant in the “sa_handler” member of the “act” structure. Then, we call the sigaction() function as shown in the following:
sigaction (36, &act, NULL);
Upon return, the signal is associated with its default action.
How to Ignore a Signal with the Sigaction() Function in C Language
In this example, we will show you how to ignore a signal using the sigaction() function. To do this, we specify the signal in the “sig” input argument and set the SIG_IGN constant in the “sa_handler” member of the “act” structure. Then, we call the sigaction() function as shown in the following:
sigaction (36, &act, NULL);
In this way, every time the specified signal arrives at the process, it’s ignored.
Sigaction Type Structures in the C Language
The sigaction type structure is meant to contain the data that configures an action. Let’s see such a structure in the following:
void (*sa_handler)(int);
void (*sa_sigaction)(int, siginfo_t *, void *);
sigset_t sa_mask;
int sa_flags;
void (*sa_restorer)(void);
};
Next, we see a description of the structure of the sigaction type and the function that each member performs in the action of a signal.
sa_handler: This member determines how the signal should be handled. It restores the default action, ignores the signal, or binds it to the handler. This member is of type void and accepts the predefined constants or cast pointers of type int.
- SIG_DFL restores the default action of the signal.
- SIG_ IGN ignores the signal.
- *handler specifies the pointer of the handler to be linked to the signal.
sa_sigaction: This member specifies the pointer to the handler address if SA_SIGINFO is enabled.
sa_mask: This member is a signal blocking mask that specifies which signals are blocked during the execution of the action.
sa_flags: This member is an integer that groups the flags that indicate the modes or properties of the action. Next, we see a table with an excerpt from the “sigacion.h” header with the definition of the flags and a brief description of each one.
Flag | Description |
SA_NOCLDSTOP | Don’t send SIGCHLD when the children stop. |
SA_NOCLDWAIT | Don’t create zombies on child death. |
SA_SIGINFO | Invoke a signal-catching function with three arguments instead of one. |
SA_RESTART | Restart syscall on signal return. |
SA_NODEFER | Don’t automatically block the signal when
its handler is being executed. |
SA_RESETHAND | Reset to SIG_DFL upon entry to the handler. |
SA_INTERRUPT | Historical no-op. |
How to Detect and Identify the Possible Errors that the Sigaction() Function can Generate
Using sigaction() can cause various errors. When this happens, the function returns a result equal to -1.
The easiest way to determine if an error has occurred is to use an “if” condition where the condition is the return value of -1. Now, let’s see how you can use this method to determine if an error has occurred:
n = sigaction (sig, act , oldact);
if ( n == -1){
printf ("An error occurred while calling the sigaction function.");
}
If the sigaction() function returns with an error, it goes to the “if” statement and prints the “An error occurred while calling the sigaction function” message.
When an error occurs, a numeric code is automatically stored in the “errno” global variable that is defined in the “errno.h” header.
In the following, you’ll find an excerpt with the errors that the sigaction() function can generate and that are defined in the “errno.h” header, as well as a short description of each error and the corresponding integer value.
Definition | Value in errno | Error |
EINVAL | 22 | Invalid pointer to handler |
EFAULT | 14 | Incorrect address |
The easiest way to identify an error is to open a switch where the “errno” variable is the jump condition and each case is the constant of an error.
Next, let’s look at an example to identify an error. To do this, we use the “if” condition from the previous snippet and open a switch where each case is an error definition.
In this way, you can detect and identify the errors with the sigaction() function at runtime.
Conclusion
In this Linuxhint article, we showed you how to use the sigaction() function to change or restore the action of a signal. We learned how sigaction() works in theory, what input arguments it uses, and what data types it uses in its input arguments. Also, in a special section, we described the sigaction structure, its members, and the predefined constants that are used to set the action.
To help you get along with this function, we also showed you how to spot the errors that can occur when using sigaction().