לדלג לתוכן

5.4 - סיגנלים - פתרון

פתרון תרגול - סיגנלים

1. תפיסת SIGINT עם מונה

כתבו תוכנית שתופסת SIGINT ואחרי 3 פעמים יוצאת.

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

volatile sig_atomic_t sigint_count = 0;

void sigint_handler(int signum) {
    sigint_count++;
}

int main() {
    struct sigaction sa;
    memset(&sa, 0, sizeof(sa));
    sa.sa_handler = sigint_handler;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = 0;

    sigaction(SIGINT, &sa, NULL);

    printf("Press Ctrl+C three times to exit...\n");

    while (sigint_count < 3) {
        sleep(1);
        if (sigint_count > 0) {
            printf("SIGINT #%d\n", sigint_count);
        }
    }

    printf("Received 3 SIGINT, exiting.\n");
    return 0;
}

קימפול והרצה:

gcc -o ex1 ex1.c
./ex1
# press Ctrl+C three times


2. טיימר עם alarm

כתבו תוכנית שמגדירה טיימר ל-5 שניות ומדפיסה ספירה כל שניה.

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

void alarm_handler(int signum) {
    printf("Time's up!\n");
    _exit(0);
}

int main() {
    struct sigaction sa;
    memset(&sa, 0, sizeof(sa));
    sa.sa_handler = alarm_handler;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = 0;

    sigaction(SIGALRM, &sa, NULL);

    alarm(5);
    printf("Timer set for 5 seconds\n");

    int sec = 1;
    while (1) {
        sleep(1);
        printf("Second %d\n", sec++);
    }

    return 0;
}

קימפול והרצה:

gcc -o ex2 ex2.c
./ex2

פלט צפוי:

Timer set for 5 seconds
Second 1
Second 2
Second 3
Second 4
Time's up!


3. תקשורת בין אב לבן עם SIGUSR1

כתבו תוכנית שבה האב שולח SIGUSR1 לבן.

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

void sigusr1_handler(int signum) {
    const char *msg = "Child received SIGUSR1 from parent!\n";
    write(STDOUT_FILENO, msg, strlen(msg));
}

int main() {
    pid_t pid = fork();

    if (pid < 0) {
        perror("fork");
        return 1;
    }

    if (pid == 0) {
        // child process
        struct sigaction sa;
        memset(&sa, 0, sizeof(sa));
        sa.sa_handler = sigusr1_handler;
        sigemptyset(&sa.sa_mask);
        sa.sa_flags = 0;

        sigaction(SIGUSR1, &sa, NULL);

        printf("Child (PID %d) waiting for signal...\n", getpid());
        pause();  // wait for any signal

        printf("Child finishing.\n");
        _exit(0);
    } else {
        // parent process
        printf("Parent (PID %d) did fork, child is PID %d\n", getpid(), pid);

        sleep(1);  // wait for the child to register the handler

        printf("Parent sending SIGUSR1 to child...\n");
        kill(pid, SIGUSR1);

        int status;
        waitpid(pid, &status, 0);
        printf("Parent finished. Child exited with code %d\n", WEXITSTATUS(status));
    }

    return 0;
}

קימפול והרצה:

gcc -o ex3 ex3.c
./ex3

פלט צפוי:

Parent (PID 1000) did fork, child is PID 1001
Child (PID 1001) waiting for signal...
Parent sending SIGUSR1 to child...
Child received SIGUSR1 from parent!
Child finishing.
Parent finished. Child exited with code 0


4. כיבוי נקי עם SIGTERM

כתבו תוכנית שרושמת handler לSIGTERM, סוגרת קבצים ויוצאת בצורה נקייה.

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

volatile sig_atomic_t running = 1;

void sigterm_handler(int signum) {
    running = 0;
}

int main() {
    struct sigaction sa;
    memset(&sa, 0, sizeof(sa));
    sa.sa_handler = sigterm_handler;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = 0;

    sigaction(SIGTERM, &sa, NULL);

    FILE *logfile = fopen("server.log", "w");
    if (!logfile) {
        perror("fopen");
        return 1;
    }

    printf("Server running (PID %d). Send SIGTERM for clean shutdown.\n", getpid());

    int counter = 0;
    while (running) {
        fprintf(logfile, "Action number %d\n", counter);
        fflush(logfile);
        printf("Action %d\n", counter);
        counter++;
        sleep(1);
    }

    printf("Performing clean shutdown...\n");
    fprintf(logfile, "Clean shutdown after %d actions\n", counter);
    fclose(logfile);
    printf("File server.log closed. Exiting.\n");

    return 0;
}

קימפול והרצה:

gcc -o ex4 ex4.c
./ex4 &
# wait a few seconds
kill $!
# or: kill -TERM <PID>


5. שאלה תיאורטית - למה אי אפשר לתפוס SIGKILL

הסבירו: למה אי אפשר לתפוס את SIGKILL?

אם process יכול היה לתפוס SIGKILL ולהתעלם ממנו, לא הייתה למערכת ההפעלה שום דרך להרוג process בכוח. הprocess שנתקע בלולאה אינסופית, או תוכנה זדונית, יכולים פשוט לרשום handler שמתעלם מSIGKILL - ואז לא היינו יכולים לעצור אותם בשום צורה.

מערכת ההפעלה חייבת לשמור לעצמה "מילה אחרונה" - היכולת להרוג כל process, תמיד, ללא תנאי. לכן SIGKILL (ובאותו אופן SIGSTOP) מטופלים ישירות בקרנל ולא עוברים דרך הhandlers של הprocess בכלל.

זו הסיבה שהעצה הנפוצה היא: תמיד תנסו קודם SIGTERM (שנותן לprocess הזדמנות לנקות ולצאת בכבוד), ורק אם זה לא עובד - תשתמשו ב-SIGKILL כמוצא אחרון.