- תכנית (program) היא אוסף פקודות.
- תהליך (process) הוא מופע (instance) של תכנית הנמצאת בשלבי ביצוע.
- תהליך הוא ביצוע סדרתי של תכנית, כלומר מופע של קובץ הרצה.
- יכולים להיות מספר תהליכים עצמאיים לאותו קובץ הרצה.
- מערכת ההפעלה מחליטה בכל רגע איזה תהליך ירוץ על כל ליבת מעבד.
- כדי להקל על פיתוח ולספק הגנה בין תהליכים, מערכת ההפעלה יוצרת עבור כל תהליך את האשליה שהוא לבדו במערכת.
- מספר תהליכים רצים "בו-זמנית" על המעבד: מערכת ההפעלה מחליפה בין התהליכים במהירות ויוצרת אשליה שהם רצים יחד.
- כל תהליך צורך משאבים, למשל זמן מעבד וזיכרון.
- מרחב הכתובות הווירטואלי של תהליך הוא רציף מבחינה לוגית, גם אם המיפוי לזיכרון פיזי מפוזר.
- חלוקה טיפוסית למקטעים:
- text / code segment - קוד התכנית ונתונים לקריאה בלבד.
- data segment - נתונים סטטיים מאותחלים ו-
bss(לא מאותחלים). - heap - זיכרון דינמי שמוקצה בזמן ריצה (
malloc/free). - stack - מסגרות קריאה, משתנים אוטומטיים וכתובות חזרה.
- כיוון גדילה: ה-
stackגדל כלפי מטה, וה-heapגדלה כלפי מעלה.
- הגרעין אינו תהליך ולא ניתן לתזמון:
- זה אוסף שגרות המבוצע בתגובה לאירועים (למשל פסיקות).
- לעיתים מערכת ההפעלה כן תריץ קוד גרעין בתוך ישות הניתנת לזימון, הנקראת "תהליכון גרעין".
-
לכל תהליך בלינוקס יש מזהה הקרוי PID.
- זהו מספר שלם בן 32 ביט, ייחודי לתהליך.
- ברוב מערכות לינוקס משתמשים רק ב-15 הביטים התחתונים, ולכן ניתן ליצור עד 32K תהליכים. מנהל המערכת יכול להגדיר מספר גבוה יותר.
- ערכי PID ממוחזרים מתהליכים שסיימו לתהליכים חדשים.
-
עם עליית המערכת, הגרעין יוצר את התהליך
idleשמספרוpid=0.- הוא נקרא לריצה כאשר אין תהליכים מוכנים ומבצע את פקודת המכונה
HLT, המכניסה את המעבד למצב שינה. - זה כדאי לחיסכון באנרגיה.
- בנוסף,
idleמאגד תחתיו תהליכונים שונים של הגרעין.
- בנוסף,
- הוא נקרא לריצה כאשר אין תהליכים מוכנים ומבצע את פקודת המכונה
-
בארכיטקטורת
x86, פקודתHLTהיא פקודת אסמבלי שעוצרת את ה-CPUעד שתתקבל פסיקה חיצונית.- קוד המכונה של
HLTב-x86הוא0xF4.
- קוד המכונה של
-
התהליך
idleיוצר את התהליךinitשמספרוpid=1.- התהליך
initיוצר את כל שאר התהליכים.
- התהליך
pid_t fork(void);-
מה עושה? יוצרת תהליך בן ע"י העתקת תהליך האב. שני התהליכים חוזרים מאותה נקודה בקוד.
-
מה משתכפל?
- קוד זהה ומיקום ריצה זהה.
- מרחב זיכרון זהה (משתנים וערכיהם, גם במחסנית וגם בערימה).
- סביבה זהה (קבצים פתוחים, ספריית עבודה נוכחית).
-
ייחוד: תהליך הבן נפרד מהאב ולכן מקבל
PIDמשלו. -
פרמטרים: אין.
-
ערך מוחזר:
מצב ערך מוחזר למי כישלון -1אב הצלחה 0בן הצלחה PIDשל הבןאב -
שאלה: איך נבדיל בין אב לבן אם ה-
PIDשל הבן יוצא במקרה 0? -
תשובה: במקרה של הצלחה תמיד יוחזרו ערכים שונים לאב ולבן, כי ה-
PIDשל הבן שונה מ-0 (המזהה הזה שמור ל-idle).
- לאחר פעולת
forkמוצלחת יש לאב ולבן אותם משתנים בזיכרון, אך בעותקים נפרדים. - לכן שינוי ערכי המשתנים אצל האב לא ייראה אצל הבן, ולהיפך.
pid_t wait(int *wstatus);-
תפקיד:
waitמנקה את המשאב שהוקצה בקריאת המערכתforkומונעת מצבzombieלבנים שסיימו. -
מה עושה? ממתינה עד שאחד מתהליכי הבן יסיים.
-
פרמטרים:
wstatus- מצביע למשתנה שבו יאוחסנו פרטי הבן שהסתיים.wstatusכולל את ערך הסיום של הבן (הערך שהועבר כארגומנט ל-exit).- ערך הסיום מופיע בבית השני מתוך ארבעת בתי
wstatus. - חילוץ ערך הסיום מתבצע עם המאקרו
WEXITSTATUS(*wstatus)שמחזיר(*wstatus >> 8) & 0xff. - מאקרואים שימושיים נוספים:
WIFSIGNALED(*wstatus)— בודק האם הבן הסתיים עקב סיגנל.WTERMSIG(*wstatus)— מחזיר את מספר הסיגנל שהרג את הבן.
- אם אין צורך בסטטוס, אפשר להעביר
NULL.
-
ערך מוחזר:
מצב ערך מוחזר אין בנים או שכל הבנים כבר סיימו ובוצע להם wait-1יש בנים שסיימו ועדיין לא בוצע להם wait(מצבzombie)pidשל אחד הבניםאחרת המתנה עד שבן כלשהו יסיים -
קריאות נוספות במשפחה:
waitid,wait3,wait4(מספקות מידע/שליטה מתקדמים). -
טריק:
fork≈malloc,wait≈free. -
שאלה: איך תהליך אב יכול לחכות לסיום כל תהליכי הבן?
-
תשובה: לקרוא ל-
waitבלולאה עד שמתקבל-1(כל הבנים נאספו).
pid_t pid;
while ((pid = wait(NULL)) > 0) {
}pid_t waitpid(pid_t pid, int *wstatus, int options);
-
מה עושה? ממתינה לסיום בן ספציפי שמספרו
pid. -
חסימה:
waitו-waitpidהן קריאות מערכת חוסמות.- הן עוצרות את התקדמות התהליך עד שמתקיים תנאי מסוים.
-
התנהגות לא חוסמת: הפרמטר
optionsמאפשר לשנות את ההתנהגות.- אם
options == WNOHANGהקריאה חוזרת מיד.
- אם
-
ערך מוחזר (כאשר
WNOHANGפעיל):מצב ערך מוחזר אף בן עדיין לא סיים 0בן סיים והוא עדיין zombiepidחיובי של בן
void exit(int status);
-
מה עושה? מסיימת את ביצוע התהליך הקורא ומשחררת את משאביו.
- התהליך עובר למצב
zombieעד שתהליך האב בודק את סיומו, ואז הוא מפונה לחלוטין.
- התהליך עובר למצב
-
פרמטרים:
status- ערך סיום המוחזר לאב אם יבדוק את סיום התהליך.- בפועל ניתן להעביר להורה רק 8 ביטים, ולכן מתקבל
(status & 0xff).
-
ערך מוחזר: הקריאה אינה חוזרת, ולפי ה-
manאינה יכולה להיכשל. -
מטרת מצב
zombie: לאפשר לאב לבדוק את סיבת הסיום ומידע בסיסי על הבן לפני פינוי מלא. -
למה לא מספיק
returnבסוףmain?mainאינה הפונקציה הראשית האמיתית; היא נקראת ע"י__libc_start_mainשמפעילהexit.
int __libc_start_main(...) {
exit(main(...));
}- מסקנה:
exitתמיד נקראת בסיום סטנדרטי של תוכנית. - למה חייבים את מערכת ההפעלה? לדוגמה, כדי להעיר את האב שממתין לסיום הבן.
- יתרון נוסף:
exitמאפשר לסיים תהליך מיד מתוך עומק שרשרת קריאות בלי להחזיר שגיאות עדmain.
תהליך יתום: אם תהליך מסתיים אחרי שאביו הסתיים בלי
wait, הוא מאומץ ע"יinit, שממשיך לרוץ ומבצעwaitלבניו כדי לפנות את נתוניהם.
int execv(const char *filename, char *const argv[]);
-
מה עושה? טוענת תכנית חדשה לביצוע במקום התהליך הקורא.
-
פרמטרים:
filename- מסלול אל הקובץ המכיל את התכנית.argv- מערך מצביעים למחרוזות של הארגומנטים.argv[0] == filename(שם התכנית).- אחרי הארגומנט האחרון מופיע
NULL.
-
ערך מוחזר:
- כישלון:
-1. - הצלחה: הקריאה אינה חוזרת. אזורי הזיכרון (קוד, מחסנית, וכו') מאותחלים עבור התכנית החדשה שמתחילה מהתחלה.
- כישלון:
-
למה צריך
NULL? כדי לסמן את סוף רשימת הארגומנטים, כי מספרם אינו קבוע. -
הערה:
execvהיא אחת ממשפחת פונקציותexec*. -
דוגמה:
int main {
char *argv[] = {"date", NULL};
execv("/bin/date", argv);
printf("hello");
return 0;
}pid_t getpid(void);
pid_t getppid(void);
-
getpidמחזירה לתהליך הקורא את ה-PIDשל עצמו. -
getppidמחזירה את ה-PIDשל תהליך האב. -
שאלה: מה המשמעות של
getppid == 1עבור תהליך משתמש טיפוסי? -
תשובה: תהליך האב הוא
init(לדוגמה כשבן הופך ליתום).
- ברוב קריאות המערכת, כישלון מסומן ע"י ערך מוחזר
-1והגרעין מציב אתerrno. errnoהוא משתנה גלובלי (Thread-Local) שמכיל את קוד השגיאה האחרון.- יש לקרוא את
errnoמיד לאחר הכישלון ולפני קריאה למערכת אחרת. perror("prefix")מדפיסה את הודעת השגיאה עם קידומת.strerror(errno)מחזירה מחרוזת תיאורית לקוד השגיאה.- כדי להשתמש ב-
errno/strerrorיש לכלול<errno.h>ו-<string.h>.
if (read(fd, buf, n) == -1) {
perror("read");
/* or: fprintf(stderr, "read: %s\n", strerror(errno)); */
}- קריאות מערכת מתבססות על:
- מתאר תהליך (Process Descriptor /
task_struct, נקרא גםPCB). - רשימת תהליכים (Process List).
- טבלת ערבול
PID -> PCB. - תורי ריצה (Run Queue) ו-תורי המתנה (Waiting Queue).
- מתאר תהליך (Process Descriptor /
- שומר את המידע המרכזי על תהליך:
PID, מצב ריצה, עדיפות.- קשרי משפחה (אב/בנים/אחים).
- מצביעים לזיכרון ולקבצים פתוחים.
- קישורי רשימות ומסוף.
real_parent- האב המקורי.parent- האב בפועל (למשל תחת debugger).children- רשימת הבנים.siblings- רשימת האחים.- שימושים טיפוסיים:
getppidלאיתור אב,waitלאיתור בנים.
תרשים - קשרי משפחה (PCB):
flowchart TB
P0((P0));
P1((P1));
P2((P2));
P3((P3));
P4((P4));
P1 -->|real_parent| P0;
P2 -->|real_parent| P0;
P3 -->|real_parent| P0;
P0 -->|children| P1;
P0 -->|children| P2;
P0 -->|children| P3;
P3 -->|children| P4;
P1 -.->|siblings| P2;
P2 -.->|siblings| P3;
linkStyle 3,4,5,6 stroke-dasharray: 6 3;
- רשימה מקושרת כפולה מעגלית דרך
prev_taskו-next_task. - הראש הוא
idle(מוצבע ע"יinit_task). - מוסיפה איבר:
fork. - אינה מוסיפה:
execv(לא יוצר תהליך חדש). - מוחקת איבר:
wait;exitמשאירzombieעד לאיסוף.
- חיפוש לפי
PIDברשימה הוא$O(n)$ . - טבלת ערבול מאפשרת איתור בממוצע
$O(1)$ (למשל עבורwaitpid). - גודל הטבלה הוא
PIDHASH_SZ(לרוב 1024 כניסות). - לרוב מספר התהליכים קטן בהרבה מ-32K, לכן לא נדרשות כניסות לכל
PIDאפשרי. - התנגשויות נפתרות ב-
separate chaining:- לכל תא בטבלה יש רשימה מקושרת של מתארי תהליך.
- השרשרת היא רשימה מקושרת דו-כיוונית.
תרשים - טבלת ערבול PID -> PCB:
flowchart LR
subgraph HashTable["Size = 1,024"]
direction TB
B0["0"];
B1["1"];
B2["2"];
BDot["..."];
BLast["1023"];
end
B1 --> PID57["PID 57"];
PID57 --> PID20777["PID 20777"];
B2 --> PID22100["PID 22100"];
PID22100 --> PID18044["PID 18044"];
- מצב התהליך נשמר בשדה
stateבמתאר התהליך (מערך ביטים בגודל 32). בכל רגע ביט אחד פעיל.
| מצב | תיאור קצר |
|---|---|
TASK_RUNNING |
רץ או מוכן לריצה (זימון "בטווח הקצר"). |
TASK_ZOMBIE |
הסתיים וממתין ל-wait; נשאר רק המתאר. |
TASK_INTERRUPTIBLE |
שינה שניתן להעיר באמצעות סיגנל (מצב נפוץ). |
TASK_UNINTERRUPTIBLE |
שינה עמוקה; מתעורר רק מהאירוע (לרוב I/O/page fault). |
TASK_STOPPED |
נעצר בצורה מבוקרת (לרוב debugger/tracer). |
בתיאוריה, תהליך עובר בין המצבים ready ל-running, אך בלינוקס שני המצבים הללו מאוגדים תחת אותו דגל: TASK_RUNNING.
הערה: תהליך שנמצא במצב TASK_INTERRUPTIBLE ניתן להעיר בסיגנל (הקריאה מצפה לכך ולעיתים תמשיך אוטומטית).
TASK_UNINTERRUPTIBLE מתעורר רק מהאירוע עצמו, לרוב I/O אחרי page fault, ולכן אינו מגיב לסיגנלים בזמן ההמתנה.
- תהליכים במצב
TASK_RUNNINGנמצאים ב-runqueue. - לכל ליבה תור ריצה משלה:
struct rq runqueues[NR_CPUS];. - תהליך יכול להופיע בתור ריצה אחד בלבד, כדי למנוע ריצה כפולה במקביל.
- מבנה התור: תורי זמן-אמת + עץ אדום-שחור לתהליכים רגילים.
- פעולות עיקריות:
activate_task,deactivate_task.wake_up_processמעבירה תהליך ממתין ל-TASK_RUNNINGומוסיפה אותו לתור של ה-CPU הנוכחי (ועשויה לסמן צורך בהחלפת הקשר).
- תהליכים ב-
TASK_INTERRUPTIBLEאוTASK_UNINTERRUPTIBLEנמצאים בתורי המתנה, לא ב-runqueue. - לכל סוג אירוע יש תור נפרד (פסיקות חומרה, משאבים, סיום בן וכו').
- כניסה לתור המתנה מתבצעת דרך קריאה חוסמת (
read,wait, ...). - תור ההמתנה לסיום בנים נשמר בשדה
wait_queue_head_t wait_chldexitבמתאר האב.
- החלפת הקשר מתרחשת בפסיקה, קריאת מערכת או כאשר המתזמן מחליט על החלפת תהליך.
- שלבים עיקריים בגרעין:
- כניסה למצב גרעין עקב פסיקה/קריאת מערכת.
- שמירת מצב הריצה של התהליך הנוכחי (registers, PC, SP, flags) אל ה-PCB שלו.
- בחירת תהליך חדש לריצה ע"י המתזמן.
- טעינת מצב הריצה של התהליך החדש מה-PCB שלו.
- עדכון מרחב הכתובות (page tables) וחזרה למצב משתמש.
-
"Everything is a file" (לא באמת, אבל נתעלם מזה בינתיים): עקרון ב-Unix שבו משאבים רבים מיוצגים כ-
file descriptorsונגישים דרךopen/read/write(למשל קבצים רגילים, התקני קלט/פלט,pipes,sockets, ואף קבצים וירטואליים כמו/proc). זה לא מילולית "הכול", אלא איחוד ממשק הגישה. -
סיגנלים: אותות מספריים קצרים (בתחום 1-31) המיועדים להודעה על אירועים.
-
חסרונות תקשורת מבוססת קבצים: גישה לדיסק איטית ולעיתים נוצר קובץ זמני שצריך למחוק.
המונח IPC (Inter-Process Communication) הוא שם כולל למנגנוני תקשורת בין תהליכים.
| מנגנון | מי מתקשר | אופי ההעברה | שימוש נפוץ |
|---|---|---|---|
signals |
בין תהליכים באותה מכונה, או ממערכת ההפעלה לתהליך | אסינכרוני, מטא־מידע קצר | הודעה/עצירה/המשך |
pipes, FIFOs |
בין תהליכים באותה מכונה | זרם נתונים חד-כיווני | יצרן-צרכן |
sockets |
בין תהליכים באותה מכונה או במכונות שונות | דו-כיווני | תקשורת רשת |
סיגנל הוא מנגנון לשליחת הודעה אסינכרונית לתהליך:
- בין תהליך לתהליך, וגם ממערכת ההפעלה לתהליך.
- ממומש בתוכנה בלבד (ללא תמיכת חומרה).
- מגיע בכל רגע, בלי שהתהליך צריך "להאזין" לו.
- לא קיים סיגנל 0, שליחה עם
sig == 0משמשת לבדיקה בלבד: האם התהליך קיים והאם יש הרשאה לשלוח לו סיגנל (ללא שליחה בפועל).
אירוע אסינכרוני הוא אירוע חיצוני לקוד המשתמש שקוטע את הריצה וגורם לביצוע שגרת טיפול.
הערה: סיגנלים אינם פסיקות. פסיקות מטופלות במצב גרעין, בעוד שסיגנלים מטופלים במצב משתמש בעת החזרה מהגרעין.
#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int sig);פעולה: שולחת את הסיגנל שמספרו sig לתהליך המזוהה על-ידי pid.
- אם
sig == 0— מתבצעת בדיקת קיום התהליך ללא שליחת סיגנל. - ערך מוחזר:
0בהצלחה,-1בכישלון (למשל אם אין תהליך בעלpid).
השם
killמטעה: הקריאה משמשת לשליחת כל סוגי הסיגנלים, לא רקSIGKILL. פקודתkillבלינוקס קוראת לקריאת המערכת הזו.
סיגנל עובר שני שלבים: רישום (generation) ואז טיפול (delivery).
| שלב | מה קורה בפועל |
|---|---|
| רישום | מערכת ההפעלה מסמנת ב-PCB של התהליך שיש סיגנל ממתין (pending) באמצעות מערך ביטים. לכל מספר סיגנל יכול להיות לכל היותר סיגנל ממתין אחד. |
| טיפול | בכל חזרה ממצב גרעין למצב משתמש, מערכת ההפעלה בודקת את הביטים ומטפלת בסיגנלים לפי הסדר (מהנמוך לגבוה). |
- לכל תהליך נשמרים ב-PCB שני מערכי ביטים:
pending— סיגנלים שממתינים לטיפול.blocked— מסכת הסיגנלים החסומים (Signal Mask).
- סיגנל יטופל רק אם הוא ממתין ואינו חסום; סיגנלים חסומים נשארים במצב pending עד להסרתם מהמסכה.
- בנוסף נשמרת טבלת פעולות/מצביעים ל-handlers עבור כל סיגנל (ברירת מחדל
SIG_DFL).
בסיום טיפול בסיגנל, הביט המתאים מתאפס.
הערה: כאשר תהליך נמצא במצב
TASK_INTERRUPTIBLEעקב קריאה חוסמת, רישום סיגנל עשוי לשבור את ההמתנה ולגרום לקריאה להחזיר את השגיאה-EINTR.
לתהליך יש כמה דרכי תגובה לסיגנל:
| אופן טיפול | משמעות |
|---|---|
| terminate | סיום התהליך |
| ignore | התעלמות והמשך עבודה |
| stop | עצירה למצב TASK_STOPPED (לרוב בשליטת debugger) |
| continue | המשך תהליך שהיה עצור |
| catching | הפעלת שגרת משתמש (signal handler) |
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);פעולה: משנה את אופן הטיפול בסיגנל שמספרו signum.
signumבתחום 1-31, פרט ל-SIGKILLול-SIGSTOP(לא ניתנים ללכידה/התעלמות).handlerיכול להיות מצביע לפונקציית משתמש, אוSIG_DFL, אוSIG_IGN.- ערך מוחזר: בהצלחה — ה-handler הקודם; בכישלון —
SIG_ERR.
חשוב: לא כל פונקציה בטוחה לקריאה מתוך handler (למשל printf, malloc). טכניקה נפוצה היא להגדיר דגל בתוך ה-handler ולבדוק אותו בקוד הראשי.
ב-PCB שמור מבנה עם פעולות הטיפול לכל סיגנל. לכל סיגנל נשמר אחד מאלה:
SIG_DFL— טיפול ברירת מחדל.SIG_IGN— התעלמות.- מצביע ל-signal handler שהוגדר ע"י המשתמש.
דוגמה: ברירת המחדל של SIGFPE היא סיום עם dump. אפשר להחליף זאת ב-handler, אך המשך ריצה אחרי SIGFPE הוא undefined behavior ולכן כמעט תמיד לא רצוי.
שגרת טיפול רצה במצב משתמש ובהקשר של התהליך שקיבל את הסיגנל.
- ההקשר (registers וכו') נשמר לפני ההרצה ומשוחזר אחריה.
- אין החלפת הקשר תהליך — רק "קפיצה" זמנית ל-handler.
- בזמן ההרצה נחסם זמנית הסיגנל שגרם ל-handler, כדי למנוע בעיות
reentrancy.
- ניתן לחסום סיגנלים (למעט
SIGKILLו-SIGSTOP) באמצעותsigprocmask.SIG_BLOCKלהוספה למסכה,SIG_UNBLOCKלהסרה,SIG_SETMASKלדריסה מלאה.
- עבודה עם סטים של סיגנלים נעשית עם הטיפוס
sigset_tוהפונקציות:sigemptyset,sigfillset,sigaddset,sigismember. sigactionהיא חלופה מתקדמת ל-signalומספקת שליטה מדויקת יותר (למשל מסכת חסימה בזמן handler ודגלים כמוSA_RESTART/SA_SIGINFO).
שימוש נפוץ בסיגנלים הוא שליטה בהרצת תהליך דרך ה-shell.
| פעולה | סיגנל | טיפול ברירת מחדל |
|---|---|---|
Ctrl+C |
SIGINT |
סיום התהליך |
Ctrl+Z |
SIGTSTP |
עצירת התהליך |
השולח בפועל הוא ה-shell.
תרחיש טיפוסי:
- תהליך מבצע פעולה לא חוקית (למשל גישה לכתובת
NULL). - המעבד יוצר חריגה ועובר לשגרת טיפול במצב גרעין.
- מערכת ההפעלה מטפלת בחריגה ורושמת סיגנל לתהליך.
- בחזרה למצב משתמש, ה-signal handler (או טיפול ברירת מחדל) מופעל.
- קריאה חוסמת שנקטעת ע"י סיגנל עשויה להחזיר
-1ו-errno == EINTR. - התבנית המקובלת היא לנסות מחדש בלולאה:
ssize_t n;
do {
n = read(fd, buf, count);
} while (n == -1 && errno == EINTR);
if (n == -1) {
perror("read");
}| סיגנל | משמעות |
|---|---|
SIGBUS |
חריגת חומרה: גישה לכתובת לא תקינה (bus error). |
SIGILL |
פקודה לא חוקית. |
SIGFPE |
שגיאה אריתמטית (כמו חלוקה באפס), למרות השם שמצביע על נקודה צפה. |
SIGCHLD |
נשלח לאב כאשר בן מסתיים או נעצר. |
SIGALRM |
נשלח לאחר זמן שהוגדר (alarm, setitimer). |
SIGTRAP |
משמש לדיבוג והרצה צעד-אחר-צעד. |
SIGUSR1, SIGUSR2 |
סיגנלים לשימוש חופשי של האפליקציה (למשל daemon). |
SIGXCPU |
חריגה ממגבלת CPU "רכה" (setrlimit). |
SIGPIPE |
כתיבה ל-pipe כשאין קורא. |
SIGIO |
קלט/פלט מוכן; נפוץ ב-I/O אסינכרוני (ב-non-blocking לרוב מתקבלים EAGAIN/EWOULDBLOCK). |
| מאפיין | פסיקות (Interrupts) | סיגנלים (Signals) |
|---|---|---|
| מי מעורר | חומרה | תוכנה/OS או תהליך |
| יעד | CPU/ליבה | תהליך |
| תזמון | לרוב אסינכרוני | אסינכרוני או סינכרוני (למשל בעקבות שגיאה בתהליך) |
| מי מגדיר משמעות | חומרה/OS | ברירת מחדל של OS, ניתן להחליף ע"י handler |
| היכן מטופל | תמיד במצב גרעין | במצב משתמש בעת חזרה מהגרעין |
| דוגמאות | שעון מערכת, I/O | SIGSEGV, SIGCHLD, SIGINT |
- סיגנל הוא הודעה אסינכרונית קצרה, אין צורך "להאזין" לה.
- שליחה מתבצעת בשני שלבים: רישום ואז טיפול בעת חזרה למצב משתמש.
killשולחת סיגנל, ו-sig == 0משמש רק לבדיקה.- אפשר להתעלם, לעצור, להמשיך, לסיים או לטפל באמצעות handler;
SIGKILLו-SIGSTOPאינם ניתנים ללכידה. - ניתן לחסום סיגנלים באמצעות מסכה (
sigprocmask), ולנהל handlers באופן מדויק עםsigaction. - קריאות חוסמות עשויות להיקטע ולהחזיר
EINTR, ולכן לעיתים עוטפים בלולאת retry. - בתוך handler עובדים בזהירות: לשמור אותו קצר ולהימנע מפונקציות לא בטוחות.
| מקור הסיגנל | זרימת האירועים בקצרה |
|---|---|
| OS → תהליך | חריגה → רישום ב-PCB → טיפול בעת חזרה למצב משתמש |
| תהליך→תהליך | kill → רישום ב-PCB → טיפול בעת חזרה למצב משתמש |