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;
}
קימפול והרצה:
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;
}
קימפול והרצה:
פלט צפוי:
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;
}
קימפול והרצה:
פלט צפוי:
האב (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;
}
קימפול והרצה:
5. שאלה תיאורטית - למה אי אפשר לתפוס SIGKILL¶
הסבירו: למה אי אפשר לתפוס את SIGKILL?
אם תהליך יכול היה לתפוס SIGKILL ולהתעלם ממנו, לא הייתה למערכת ההפעלה שום דרך להרוג תהליך בכוח. תהליך שנתקע בלולאה אינסופית, או תוכנה זדונית, יכולים פשוט לרשום handler שמתעלם מSIGKILL - ואז לא היינו יכולים לעצור אותם בשום צורה.
מערכת ההפעלה חייבת לשמור לעצמה "מילה אחרונה" - היכולת להרוג כל תהליך, תמיד, ללא תנאי. לכן SIGKILL (ובאותו אופן SIGSTOP) מטופלים ישירות בקרנל ולא עוברים דרך הhandlers של התהליך בכלל.
זו הסיבה שהעצה הנפוצה היא: תמיד תנסו קודם SIGTERM (שנותן לתהליך הזדמנות לנקות ולצאת בכבוד), ורק אם זה לא עובד - תשתמשו ב-SIGKILL כמוצא אחרון.