Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

Advanced Signal Handling and Inter-Process Communication in Linux

Tech Aug 18 16

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;
}
Tags: Linux

Related Articles

Understanding Strong and Weak References in Java

Strong References Strong reference are the most prevalent type of object referencing in Java. When an object has a strong reference pointing to it, the garbage collector will not reclaim its memory. F...

Comprehensive Guide to SSTI Explained with Payload Bypass Techniques

Introduction Server-Side Template Injection (SSTI) is a vulnerability in web applications where user input is improper handled within the template engine and executed on the server. This exploit can r...

Implement Image Upload Functionality for Django Integrated TinyMCE Editor

Django’s Admin panel is highly user-friendly, and pairing it with TinyMCE, an effective rich text editor, simplifies content management significantly. Combining the two is particular useful for bloggi...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.