לדלג לתוכן

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("לוחצים Ctrl+C שלוש פעמים ליציאה...\n");

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

    printf("קיבלתי 3 SIGINT, יוצא.\n");
    return 0;
}

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

gcc -o ex1 ex1.c
./ex1
# לוחצים Ctrl+C שלוש פעמים


2. טיימר עם alarm

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

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

void alarm_handler(int signum) {
    printf("הזמן נגמר!\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("טיימר הופעל ל-5 שניות\n");

    int sec = 1;
    while (1) {
        sleep(1);
        printf("שניה %d\n", sec++);
    }

    return 0;
}

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

gcc -o ex2 ex2.c
./ex2

פלט צפוי:

טיימר הופעל ל-5 שניות
שניה 1
שניה 2
שניה 3
שניה 4
הזמן נגמר!


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 = "הבן קיבל SIGUSR1 מהאב!\n";
    write(STDOUT_FILENO, msg, strlen(msg));
}

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

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

    if (pid == 0) {
        // תהליך הבן
        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("הבן (PID %d) ממתין לסיגנל...\n", getpid());
        pause();  // ממתין לסיגנל כלשהו

        printf("הבן מסיים.\n");
        _exit(0);
    } else {
        // תהליך האב
        printf("האב (PID %d) שלח fork, הבן הוא PID %d\n", getpid(), pid);

        sleep(1);  // ממתינים שהבן ירשום את הhandler

        printf("האב שולח SIGUSR1 לבן...\n");
        kill(pid, SIGUSR1);

        int status;
        waitpid(pid, &status, 0);
        printf("האב סיים. הבן יצא עם קוד %d\n", WEXITSTATUS(status));
    }

    return 0;
}

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

gcc -o ex3 ex3.c
./ex3

פלט צפוי:

האב (PID 1000) שלח fork, הבן הוא PID 1001
הבן (PID 1001) ממתין לסיגנל...
האב שולח SIGUSR1 לבן...
הבן קיבל SIGUSR1 מהאב!
הבן מסיים.
האב סיים. הבן יצא עם קוד 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("שרת רץ (PID %d). שלחו SIGTERM לכיבוי נקי.\n", getpid());

    int counter = 0;
    while (running) {
        fprintf(logfile, "פעולה מספר %d\n", counter);
        fflush(logfile);
        printf("פעולה %d\n", counter);
        counter++;
        sleep(1);
    }

    printf("מבצע כיבוי נקי...\n");
    fprintf(logfile, "כיבוי נקי אחרי %d פעולות\n", counter);
    fclose(logfile);
    printf("הקובץ server.log נסגר. יוצא.\n");

    return 0;
}

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

gcc -o ex4 ex4.c
./ex4 &
# ממתינים כמה שניות
kill $!
# או: kill -TERM <PID>


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

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

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

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

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