Review of Signals in Linux C

Signals provide one-way asynchronous notifications. A signal may be sent from the kernel to a process, from a process to another process, or even from a process to itself. The Linux kernel implements around 30 signals, with each signal being represented by a numeric constant and a name.

Signals interrupt a process, causing it to stop mid-execution and perform a predetermined action. A signal may be ignored, with the exceptions of the SIGKILL and SIGSTOP signals, or it may handle the signal.

Signals are defined by including the header file <signal.h>. All of the signal names begin with SIG.

#include <stdio.h>
#include <signal.h>

int main(void){
    
    
    //alarm clock
    printf("SIGALRM %d\n", SIGALRM);
    //hangup
    printf("SIGHUP %d\n", SIGHUP);
    //illegal instruction
    printf("SIGILL %d\n", SIGILL);
    //terminal interrupt
    printf("SIGINT %d\n", SIGINT);
    //write on pipe with no reader
    printf("SIGPIPE %d\n", SIGPIPE);
    //terminal quit
    printf("SIGTERM %d\n", SIGTERM);
    
    return 0;
    
}

Signals can be handled using the signal() library function, which is defined in <signal.h>. The signal() function takes two arguments. The first argument is the signal to be caught or ignored. The second argument contains the function to be called when the signal specified in the first argument is received.

#include <stdio.h>
#include <signal.h>
#include <unistd.h>

void catchSignal(int sig){
    printf("Caught Ctrl-C (Signal %d)\n", SIGINT);
    fflush(stdout);
}

int main(void){
    
    
    signal(SIGINT, catchSignal);
    
    while(1){
        printf("Waiting for Ctrl-C...\n");
        sleep(1);
    }
    
    return 0;
    
}

A process can send a signal to another process, including itself, by calling the kill() function. The kill() function takes two arguments, the first being the process ID of the signal to be sent, the second being the actual signal to be sent.

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <time.h>

void doChild(void);
void doParent(void);

void sigtermHandler(int signal);
void sigchldHandler(int signal);
void sigintHandler(int signal);
void sigioHandler(int signal);
void sigpwrHandler(int signal);
void sigpipeHanlder(int signal);

int main(void){
    
    pid_t returnPID;
    
    if((returnPID = fork())<0){
        printf("Error forking the process!\n");
        exit(EXIT_FAILURE);
    }
    
    if(returnPID==0){
        printf("In child process %u.\n", getpid());
        doChild();
        printf("Child process has finished.\n");
    }
    if(returnPID > 0){
        printf("In parent process %u.\n", getpid());
        doParent();
        printf("Parent process has ended.\n");
    }
    
    return 0;
}

void doChild(void){
    printf("\tIn child process %u.\n", getpid());
    int arrSignals[] = {SIGTERM, SIGCHLD, SIGINT, SIGIO, SIGPWR, SIGPIPE};
    //set random
    srand(time(NULL));
    int i, r;
    //------------------for loop------------------------------------
    for(i = 0; i < 12; i++){
        //randomly select one of the signals from
        //the array
        r = rand() % 6;
        printf("\tSending signal %d to the parent process %u.\n", arrSignals[r], getppid());
        kill(getppid(), arrSignals[r]);
        sleep(2);
    } //end for loop------------------------------------------------
   
} //end doChild(void)--------------


void doParent(void){
    int i = 0;
    signal(SIGTERM, sigtermHandler);
    signal(SIGCHLD, sigchldHandler);
    signal(SIGINT, sigintHandler);
    signal(SIGIO, sigioHandler);
    signal(SIGPWR, sigpwrHandler);
    signal(SIGPIPE, sigpipeHanlder);
    while(i++ < 25){
       printf("In parent process %u.\n", getpid());
       sleep(1);
    }
}

//process was explicitly killed by someone
void sigtermHandler(int signal){
    printf("Received SIGTERM signal %d in process %u.\n", signal, getpid());
    printf("Electing not to terminate.\n");
    fflush(stdout);
}

void sigchldHandler(int signal){
    printf("Received SIGCHLD signal %d in process %u.\n", signal, getpid());
    printf("Apparently a a child of this process has terminated.\n");
    fflush(stdout);
}

//Ctrl-C
void sigintHandler(int signal){
    printf("Received SIGINT signal %d in process %u.\n", signal, getpid());
    printf("The process was interrupted. Continuing now.\n");
    fflush(stdout);
}

void sigioHandler(int signal){
    printf("Received SIGIO signal %d in process %u.\n", signal, getpid());
    printf("The process is ready for I/O.\n");
    fflush(stdout);
}

void sigpwrHandler(int signal){
    printf("Received SIGPWR signal %d in process %u.\n", signal, getpid());
    printf("The power has been switched to a short-term emergency supply.\n");
    fflush(stdout);
}

void sigpipeHanlder(int signal){
    printf("Received SIGPIPE signal %d in process %u.\n", signal, getpid());
    printf("The consumer of this process's pipe has died.\n");
    fflush(stdout);
}

Note that the kill() function will return -1 and set errno if the signal given is invalid, if it doesn’t have permission, or if the specified process does not exist.

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>


void processSignal(int signal);

int main(void){
    
    pid_t returnID;
    int i = 0;
    
    if((returnID = fork())<0){
        printf("Error forking.\n");
        exit(EXIT_FAILURE);
    }
    if(returnID==0){
        while(i++ < 10){
            printf("In child process, %u.\n", getpid());
            if((kill(getppid(), SIGINT))<0){
                printf("***Error sending signal to %u.\n", getppid());
                exit(EXIT_FAILURE);
            }
            sleep(1);
        }
    }
    if(returnID > 0){
        signal(SIGINT, processSignal);
        while(i++ < 5){
            printf("In parent process, %u.\n", getpid());
            sleep(1);
        }
        exit(EXIT_SUCCESS);
    }
    
    return 0;
    
}


void processSignal(int signal){
    printf("\tReceived signal %d.\n", signal);
    fflush(stdin);
}

If you’re interested, I wrote a book on C that is available at http://www.amazon.com/Big-Als-C-Standard-ebook/dp/B00A4JGE0M/

Advertisements

The kill() Function in C

The easiest way to send a signal to a process is via the kill() signal.

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>

int main(void){

    pid_t retVal;

    retVal = fork();

    if(retVal > 0){
        int i = 0;
        while(i++ < 5){
            printf("in the parent process.\n");
            sleep(1);
        }
        //kill the child process
        kill(retVal, SIGKILL);

    } else if (retVal == 0){
        int i = 0;
        //will not ever get to 15, because
        //the parent process will kill it
        while(i++ < 15){
            printf("In the child process.\n");
            sleep(1);
        }
    } else {
        //something bad happened.
        printf("Something bad happened.");
        exit(EXIT_FAILURE);
    }

    return 0;

}

Remember, the fork function returns a process identifier that identifies the context in which the process is running.  If the process ID returned is zero, then the process is the newly created child process. If the return value is greater than zero, then that value is the ID of the child process, and we know that we are in the parent process.

If the kill() function fails, it returns -1, otherwise it returns 0. We can use the return value from the kill() function to determine if the process was successfully terminated.

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>

int main(void){

    int status;
    pid_t killReturnValue, forkReturnValue;

    if((forkReturnValue=fork())<0){
        //error occurred.
        printf("Danger! Danger, Will Robinson!\n");
        exit(EXIT_FAILURE);
    }

    if(forkReturnValue==0){
        sleep(100);
        exit(EXIT_SUCCESS);
    } else {
        killReturnValue = kill(forkReturnValue, SIGKILL);
        if(killReturnValue){
            printf("Unable to kill child process.\n");
            waitpid(forkReturnValue, &status, 0);
        } else {
            printf("Child process killed.\n");
        }
    }
    
    return 0;
}

Although the kill() function is often used to terminate methods, the function can actually send other signals as well. For instance, we can send the SIGSTOP signal to stop a process, and the SIGCONT signal to continue a process.

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>

int main(void){

    int status;
    pid_t killReturnValue, forkReturnValue;

    if((forkReturnValue=fork())<0){
        //error occurred.
        printf("Danger! Danger, Will Robinson!\n");
        exit(EXIT_FAILURE);
    }

    if(forkReturnValue==0){
        sleep(100);
        exit(EXIT_SUCCESS);
    } else {
        killReturnValue = kill(forkReturnValue, SIGKILL);
        if(killReturnValue){
            printf("Unable to kill child process.\n");
            waitpid(forkReturnValue, &status, 0);
        } else {
            printf("Child process killed.\n");
        }
    }
    
    return 0;
}

Next time we will cover signal handling with the signal() function.