Advanced Signal Handling and Inter-Process Communication in Linux
Implementing Signal Handlers
Signals are asynchronous notifications sent to a process to notify it of an event. The signal() function allows a program to define how it responds to specific signals, whether by ignoring them, using the default behavior, or executing a custom handler.
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <stdlib.h>
void on_signal_received(int signal_num) {
if (signal_num == SIGINT) {
printf("\nInterrupt signal (Ctrl+C) captured. Execution continues...\n");
}
}
int main() {
// 1. Ignore the signal
if (signal(SIGINT, SIG_IGN) == SIG_ERR) {
perror("Failed to ignore SIGINT");
return EXIT_FAILURE;
}
// 2. Restore default behavior
if (signal(SIGINT, SIG_DFL) == SIG_ERR) {
perror("Failed to reset SIGINT");
return EXIT_FAILURE;
}
// 3. Register a custom callback
if (signal(SIGINT, on_signal_received) == SIG_ERR) {
perror("Failed to register custom handler");
return EXIT_FAILURE;
}
while (1) {
printf("Process %d is active...\n", getpid());
sleep(2);
}
return 0;
}
Constraints of Non-catchable Signals
Certain signals in Linux, specifically SIGKILL and SIGSTOP, cannot be caught, blocked, or ignored. This ensures that the system administrator can always terminate or suspend a process.
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
void attempt_handler(int sig) {
printf("This message will never appear for SIGKILL: %d\n", sig);
}
int main() {
// Attempting to ignore SIGKILL will fail
if (signal(SIGKILL, SIG_IGN) == SIG_ERR) {
perror("Expected error: Cannot ignore SIGKILL");
}
// Attempting to catch SIGKILL will fail
if (signal(SIGKILL, attempt_handler) == SIG_ERR) {
perror("Expected error: Cannot catch SIGKILL");
}
return 0;
}
Asynchronous Zombie Process Reclamation
When a child process terminates, it becomes a zombie until the parent collects its exit status. By handling SIGCHLD, a parent process can clean up terminated children without blocking its primary execution path.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>
void reap_zombies(int sig) {
// Use WNOHANG to avoid blocking if no children are ready
while (waitpid(-1, NULL, WNOHANG) > 0);
}
int main() {
struct sigaction sa;
sa.sa_handler = reap_zombies;
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_RESTART | SA_NOCLDSTOP;
if (sigaction(SIGCHLD, &sa, NULL) == -1) {
perror("sigaction failed");
exit(1);
}
for (int i = 0; i < 5; i++) {
if (fork() == 0) {
printf("Child %d started\n", getpid());
sleep(2);
exit(0);
}
}
while (1) {
pause(); // Wait for signals
}
return 0;
}
Implementing Timeouts with ALRM
The alarm() function sets a timer that delivers a SIGALRM signal to the calling process after a specified number of seconds. This is useful for implementing timeouts for user input.
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
void input_timeout_handler(int sig) {
printf("\nTime limit reached! System performing default action...\n");
alarm(5); // Reset timer
}
int main() {
signal(SIGALRM, input_timeout_handler);
alarm(5);
char input_buffer[64];
while (1) {
printf("Enter command (5s limit): ");
if (fgets(input_buffer, sizeof(input_buffer), stdin) != NULL) {
printf("Command received: %s", input_buffer);
alarm(5); // Reset timer after valid input
}
}
return 0;
}
Inter-Process Signaling via kill and raise
A process can send signals to itself using raise() or to other processes using kill(). This is fundamental for process coordination.
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
void termination_logic(int sig) {
printf("Child process received SIGUSR1. Self-destructing...\n");
raise(SIGKILL);
}
int main() {
pid_t target_pid = fork();
if (target_pid == 0) {
signal(SIGUSR1, termination_logic);
while (1) {
printf("Child process running...\n");
sleep(1);
}
} else {
sleep(3);
printf("Parent sending SIGUSR1 to child %d\n", target_pid);
kill(target_pid, SIGUSR1);
wait(NULL);
}
return 0;
}
System V Message Queues for IPC
Message queues provide a way to send formatted data blocks between processes. Unlike pipes, mesages in a queue can have types, allowing receivers to prioritize or filter messages.
Message Sender
#include <stdio.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/msg.h>
struct message_packet {
long priority;
char payload[256];
};
int main() {
key_t token = ftok(".", 'A');
int qid = msgget(token, IPC_CREAT | 0666);
struct message_packet packet;
while (1) {
printf("Enter priority (long): ");
scanf("%ld", &packet.priority);
getchar();
printf("Message content: ");
fgets(packet.payload, 256, stdin);
packet.payload[strcspn(packet.payload, "\n")] = 0;
msgsnd(qid, &packet, sizeof(packet.payload), 0);
if (strcmp(packet.payload, "exit") == 0) break;
}
return 0;
}
Message Receiver
#include <stdio.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/msg.h>
struct message_packet {
long priority;
char payload[256];
};
int main() {
key_t token = ftok(".", 'A');
int qid = msgget(token, IPC_CREAT | 0666);
struct message_packet packet;
while (1) {
// Receive messages with priority 1
if (msgrcv(qid, &packet, sizeof(packet.payload), 1, 0) == -1) break;
printf("Received [P:%ld]: %s\n", packet.priority, packet.payload);
if (strcmp(packet.payload, "exit") == 0) break;
}
return 0;
}
Bidirectional Communication with Message Queues
By utilizing different message types, two processes can communicate bidirectionally using a single message queue. The following example demonstrates a multi-mode IPC where the parent and child switch roles based on a selection.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/msg.h>
struct comm_msg {
long mtype;
char content[512];
};
void transmit(int qid, long type) {
struct comm_msg msg;
msg.mtype = type;
while (1) {
printf("Send: ");
fgets(msg.content, 512, stdin);
msg.content[strcspn(msg.content, "\n")] = 0;
msgsnd(qid, &msg, sizeof(msg.content), 0);
if (strcmp(msg.content, "quit") == 0) break;
}
}
void listen(int qid, long type) {
struct comm_msg msg;
while (1) {
msgrcv(qid, &msg, sizeof(msg.content), type, 0);
printf("\nReceived: %s\n", msg.content);
if (strcmp(msg.content, "quit") == 0) break;
}
}
int main() {
key_t k = ftok("/tmp", 'z');
int qid = msgget(k, IPC_CREAT | 0664);
int choice;
printf("Select Mode (1: Parent Sends, 2: Parent Receives): ");
scanf("%d", &choice);
getchar();
pid_t pid = fork();
if (pid > 0) {
(choice == 1) ? transmit(qid, 100) : listen(qid, 200);
} else {
(choice == 1) ? listen(qid, 100) : transmit(qid, 200);
}
msgctl(qid, IPC_RMID, NULL);
return 0;
}