6.3 מתזמן התהליכים פתרון
פתרון תרגול - מתזמן התהליכים¶
1. מצבי תהליכים¶
האותיות במצבי תהליך:
- R - Running (רץ או מוכן לרוץ) - TASK_RUNNING
- S - Sleeping (ישן, ניתן להפסקה) - TASK_INTERRUPTIBLE
- D - Disk sleep (ישן, לא ניתן להפסקה) - TASK_UNINTERRUPTIBLE
- T - Stopped (עצור) - TASK_STOPPED
- Z - Zombie (זומבי) - TASK_ZOMBIE
כמה תהליכים במצב R:
בדרך כלל רואים 1-3 תהליכים במצב R. המספר נמוך כי:
- רוב התהליכים ישנים (מצב S) ומחכים לאירוע - קלט מקלדת, נתונים מרשת, timeout, וכו'
- תהליך שלא מחכה לכלום (CPU-bound) לא נפוץ - רוב התוכנות עושות IO בעיקר
- אחד מהתהליכים במצב R הוא top/htop עצמו
תוכנית שיוצרת זומבי:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main() {
pid_t pid = fork();
if (pid == 0) {
// תהליך הבן - יוצא מיד
printf("child (PID %d) exiting\n", getpid());
_exit(0);
} else if (pid > 0) {
// תהליך האב - ישן בלי wait
printf("parent (PID %d), child was PID %d\n", getpid(), pid);
printf("sleeping 60 seconds without wait...\n");
sleep(60);
}
return 0;
}
בדיקה:
פלט צפוי (דוגמה):
2. השפעת nice¶
הנה התוכנית:
#include <stdio.h>
#include <time.h>
int main() {
long count = 0;
time_t start = time(NULL);
while (1) {
count++;
if (time(NULL) - start >= 1) {
printf("iterations: %ld\n", count);
count = 0;
start = time(NULL);
}
}
return 0;
}
התוצאה תלויה בחומרה ובעומס, אבל בדרך כלל נראה שהתוכנית עם nice=0 מבצעת הרבה יותר איטרציות - בערך פי 5 עד פי 20 יותר מהתוכנית עם nice=19.
ההסבר: הCFS מחלק את זמן הCPU לפי משקלות שנגזרים מערך הnice. תהליך עם nice=0 מקבל משקל גדול יותר מתהליך עם nice=19, ולכן הvruntime שלו עולה לאט יותר והוא מקבל הרבה יותר זמן CPU.
3. מידע מproc¶
מצב:
פלט:
התהליך במצב S (TASK_INTERRUPTIBLE) כי הוא ישן - מחכה שהsleep יסתיים.
מידע תזמון:
יציג vruntime, nr_switches, ועוד. אם נבדוק שוב אחרי כמה שניות, הvruntime לא ישתנה - כי התהליך ישן ולא צורך CPU. הvruntime עולה רק כשהתהליך באמת רץ על CPU.
context switches:
פלט לדוגמה:
הרוב יהיו voluntary כי sleep הוא ויתור רצוני על CPU.
שליחת SIGSTOP:
פלט:
המצב השתנה ל-T (TASK_STOPPED). התהליך עצור לחלוטין - הוא לא יתעורר גם כשהsleep יסתיים, עד שיקבל SIGCONT.
4. ספירת context switch-ים¶
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
void print_switches() {
FILE *f = fopen("/proc/self/status", "r");
if (!f) {
perror("fopen");
return;
}
char line[256];
while (fgets(line, sizeof(line), f)) {
if (strstr(line, "voluntary_ctxt_switches") ||
strstr(line, "nonvoluntary_ctxt_switches")) {
printf("%s", line);
}
}
fclose(f);
}
int main() {
printf("--- before sleeps ---\n");
print_switches();
for (int i = 0; i < 5; i++) {
sleep(1);
}
printf("\n--- after 5 sleeps ---\n");
print_switches();
return 0;
}
קימפול והרצה:
פלט לדוגמה:
--- before sleeps ---
voluntary_ctxt_switches: 3
nonvoluntary_ctxt_switches: 0
--- after 5 sleeps ---
voluntary_ctxt_switches: 8
nonvoluntary_ctxt_switches: 0
הcontext switch-ים הרצוניים עולים בבערך 5 - אחד לכל sleep. כל קריאה ל-sleep גורמת לתהליך לוותר רצונית על הCPU (voluntary context switch). ייתכנו כמה תוספות נוספות בגלל קריאות IO (fopen/fgets) שגם הן יכולות לגרום לcontext switch.
שימו לב שnonvoluntary נשאר 0 - כי התהליך הזה כמעט ולא צורך CPU, אז הscheduler לא צריך להפקיע ממנו את הCPU בכוח.
5. שאלה תיאורטית - vruntime של תהליך חדש¶
מה יקרה אם vruntime=0:
אם תהליך חדש יקבל vruntime=0, הוא יהפוך מיד לתהליך עם הvruntime הנמוך ביותר בעץ. הCFS יבחר בו לרוץ באופן מיידי, ויתן לו לרוץ זמן רב עד שהvruntime שלו יתקרב לזה של התהליכים האחרים.
זה יגרום ל"הרעבה" (starvation) זמנית של כל שאר התהליכים - הם לא יקבלו CPU עד שהתהליך החדש "יתפוס" אותם בvruntime. במערכת עמוסה שבה תהליכים ותיקים צברו vruntime גבוה, ההשפעה תהיה חמורה - התהליך החדש ירוץ כמעט ללא הפרעה דקות ארוכות.
גרוע מזה - אם מישהו כותב תוכנית שעושה fork בלולאה, כל תהליך חדש יקבל vruntime=0 ויירוץ מיד. זו למעשה מתקפת מניעת שירות (DoS) על הscheduler.
מה הCFS באמת עושה:
הCFS מאתחל את הvruntime של תהליך חדש לערך של min_vruntime של התור - כלומר, הvruntime המינימלי מבין כל התהליכים שכבר רצים. ככה התהליך החדש מתחיל מ"אותו מקום" כמו כולם, ולא מקבל יתרון לא הוגן.
בפועל, הCFS אפילו מוסיף קצת לvruntime ההתחלתי (sched_child_runs_first) כדי שהתהליך החדש לא יידחוף את כולם מיד, אלא יתמזג בהדרגה לתחרות ההוגנת.