C Programming

Sigaction() Function in C Language

Signals allow interaction between two or more processes that run on the same system. This interaction takes place between a sending process and one or more receiving processes. The sender sends a predefined signal to the PID of the receiver. Once it’s received, the receiver interrupts its task, performs the action, and resumes the execution at the point where it was interrupted.

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 the signals, create the 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 will explain the errors and possible solutions that the use of sigaction() can cause.

Syntax of the Sigaction() Function in C language

int sigaction(int  sig, const struct sigaction *act,
              struct sigaction * oldact);

 

Description of the Sigaction() Function

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. Later, you’ll find a section that describes 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. We will see the three ways to handle a signal in the following:

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:

void handler (int sig);

 

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:

void handler (int sig, siginfo_t * info, void * context);

 

When SA_SIGINFO is sent, the “capture” function takes three input arguments: “sig” which specifies the signal, a structure of type “signinfo_t” which contains the detailed information about the process that sent the signal and context, and 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 0 and the action is associated with the signal that is specified in “sig”. If an error occurrs during execution, the result is -1 and the identification code can be retrieved via the global variable “errno”.

The sigaction() function and its predefined constants and variables are declared in the “signal.h” header. To use them, we must include this file as follows:

#include <signal.h>

 

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 the signal 36 to a controller. To do this, we first create the sig_handler function which is 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. Next, we look at the code for the handler that we’re linking the signal to:

void sig_handler (int sig)
    {
        printf("\nSignal received\n", sig );
        sleep(10);
    }

 

In the main()function, we define the “act” structure 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 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 two 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:

//Step 1
//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 the terminal to the second terminal with the following command syntax:

kill -36 PID

 

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:

act.sa_handler = SIG_DFL
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:

act.sa_handler = SIG_IGN;
sigaction (36, &act, NULL);

 

In this way, the specified signal is ignored every time it arrives at the process.

Sigaction Type Structures in the C Language

The “sigaction” type structure is meant to contain the data that configures an action. We can see such a structure in the following:

struct sigaction {
                  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 the 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 children stop.
SA_NOCLDWAIT Don’t create zombies on child death.
SA_SIGINFO Invoke the 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 that is 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:

int n;
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 global variable “errno” that is defined in the “errno.h” header.

In the following image, 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.

int n;
n = sigaction (sig, act , oldact);
if ( n == -1){
     
   switch(errno){
    case EFAULT:{
                  printf("Incorrect pointer to handler. Error: %i\n", errno);
                  break;}
    case EINVAL:{
                   printf("Invalid signal. Error: %i\n", errno);
                   break;}

     }

 

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 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().

About the author

Julio Cesar

Julio Cesar is a 42 years old programmer with 8 years of experience in embedded systems development, 6 years developing firmware for user interfaces in C and C++. Additionally he has 2 years of experience developing scripts for network devices and 3 years as developer of high frequency PCB (Printed Circuit Board).