Download as pdf or txt
Download as pdf or txt
You are on page 1of 39

‫מערכות הפעלה‬

‫תרגול ‪ – 3‬תהליכים ב‪Linux-‬‬


‫תוכן התרגול‬
‫‪ ‬מבוא לתהליכים ב‪Linux-‬‬
‫‪ API ‬לעבודה עם תהליכים‬
‫‪ ‬מבוא לניהול תהליכים בתוך הגרעין‬
‫‪ ‬מתאר התהליך (‪)process descriptor‬‬
‫‪ ‬רשימת התהליכים (‪)process list‬‬
‫‪ ‬מאגר התהליכים המוכנים לריצה ("הטווח הקצר")‬
‫‪ ‬תורי המתנה ("הטווח הבינוני ‪ /‬ארוך")‬

‫(‪ ) c‬ארז חדד ‪2003‬‬ ‫מערכות הפעלה ‪ -‬תרגול ‪3‬‬ ‫‪2‬‬


‫מבוא לתהליכים ב‪)1( Linux-‬‬
‫תהליך (‪ ) process‬הוא ביצוע סדרתי של משימה‪ ,‬המוגדרת על‪-‬ידי‬ ‫‪‬‬
‫תכנית (‪)program‬‬
‫תהליך = מופע (‪ ) instance‬של ביצוע תכנית‬ ‫‪‬‬
‫תהליך נקרא גם ‪ task‬במקומות שונים‬ ‫‪‬‬
‫מספר תהליכים מתבצעים "בו זמנית" על המעבד במחשב‬ ‫‪‬‬
‫למעשה – המעבד "ממתג" בין התהליכים בתדירות גבוהה באמצעות מנגנון‬ ‫‪‬‬
‫החלפת הקשר‪ .‬פרטים בתרגול הבא‬
‫עבור מערכת ההפעלה‪ ,‬תהליך הינו יישות עצמאית הצורכת‬ ‫‪‬‬
‫משאבים‬
‫זיכרון‪ ,‬זמן מעבד‪ ,‬שטח דיסק וכו'‬ ‫‪‬‬
‫לכל תהליך ב‪ Linux-‬יש מזהה הקרוי ‪PID – Process IDentifier‬‬ ‫‪‬‬
‫מספר שלם בן ‪ 32‬ביט ייחודי לתהליך (עד ‪ 32K‬תהליכים מטעמי תאימות‬ ‫‪‬‬
‫הסטורית)‬
‫ערכי ה‪ pid-‬ממוחזרים מתהליכים שסיימו לתהליכים חדשים שנוצרים‬ ‫‪‬‬

‫(‪ ) c‬ארז חדד ‪2003‬‬ ‫מערכות הפעלה ‪ -‬תרגול ‪3‬‬ ‫‪3‬‬


‫מבוא לתהליכים ב‪)2( Linux-‬‬
‫באיתחול ‪ Linux‬גרעין מערכת ההפעלה יוצר שני‬ ‫‪‬‬
‫תהליכים בלבד‬
‫‪ – swapper \ idle (pid=0) ‬משמש לניהול זיכרון ; מורץ כאשר‬
‫אין אף תהליך אחר מוכן לריצה ; ממנו נוצר התהליך ‪init‬‬
‫‪ – init (pid=1) ‬ממנו נוצרים כל שאר התהליכים במערכת‬
‫כל תהליך נוסף נוצר ב‪ Linux-‬כעותק של תהליך קיים‬ ‫‪‬‬
‫‪ ‬התהליך המקורי נקרא תהליך אב (או הורה )‬
‫‪ ‬התהליך החדש נקרא תהליך בן‬
‫‪ ‬תהליך הבן נוצר בעקבות ביצוע קריאת מערכת כדוגמת )(‪fork‬‬
‫על‪-‬ידי תהליך האב‬
‫‪ ‬תהליך אב יכול ליצור יותר מתהליך בן אחד‬
‫(‪ ) c‬ארז חדד ‪2003‬‬ ‫מערכות הפעלה ‪ -‬תרגול ‪3‬‬ ‫‪4‬‬
‫מבוא לתהליכים ב‪)3( Linux-‬‬
‫תהליך יכול לאחר היווצרו לבצע משימה שונה מאביו‬ ‫‪‬‬
‫‪ ‬על‪-‬ידי הסתעפות בקוד התכנית הקיימת‪ ,‬לאחר ההתפצלות‬
‫מהאב‬
‫‪ ‬על‪-‬ידי טעינת משימה חדשה (תכנית חדשה) לביצוע‬
‫למשל‪ ,‬על‪-‬ידי קריאת המערכת )(‪execv‬‬ ‫‪‬‬

‫תהליך אב יכול לבדוק סיום של כל תהליך בן שלו‬ ‫‪‬‬


‫‪ ‬אך לא של "נכדים"‪" ,‬נינים"‪" ,‬אחים" וכדומה‬
‫‪ ‬אב יכול להמתין לסיום בן לפני המשך פעולתו‬
‫למשל‪ ,‬על‪-‬ידי קריאת המערכת )(‪wait‬‬ ‫‪‬‬

‫(‪ ) c‬ארז חדד ‪2003‬‬ ‫מערכות הפעלה ‪ -‬תרגול ‪3‬‬ ‫‪5‬‬


‫מבוא לתהליכים ב‪)4( Linux-‬‬
‫כדי לאפשר לאב לקבל מידע על סיום הבן‪ ,‬לאחר שתהליך‬ ‫‪‬‬
‫מסיים את פעולתו הוא עובר למצב מיוחד – ‪– zombie‬‬
‫שבו התהליך קיים כרשומת נתונים בלבד ללא שום ביצוע‬
‫משימה‬
‫‪ ‬הרשומה נמחקת לאחר שהאב קיבל את המידע על סיום הבן‬
‫מה קורה לתהליך שהופך ל"יתום" (‪ )orphan‬לאחר‬ ‫‪‬‬
‫שאביו כבר סיים?‬
‫‪ ‬התהליך הופך להיות בן של ‪init‬‬
‫התהליך ‪ init‬ממשיך להתקיים לאורך כל פעולתה של‬ ‫‪‬‬
‫מערכת ההפעלה‬
‫‪ ‬אחד מתפקידיו העיקריים ‪ -‬המתנה לכל בניו‪ ,‬כדי לפנות את‬
‫נתוניהם לאחר סיום‬
‫(‪ ) c‬ארז חדד ‪2003‬‬ ‫מערכות הפעלה ‪ -‬תרגול ‪3‬‬ ‫‪6‬‬
‫‪ API‬לתהליכים ב‪)1( Linux-‬‬
‫קריאת המערכת )(‪fork‬‬ ‫‪‬‬
‫‪ ‬תחביר ‪:‬‬
‫;)(‪pid_t fork‬‬
‫‪ ‬פעולה‪ :‬מעתיקה את תהליך האב לתהליך הבן וחוזרת בשני‬
‫התהליכים‬
‫קוד זהה (ומיקום בקוד)‬ ‫‪‬‬

‫זיכרון זהה (משתנים וערכיהם‪ ,‬חוצצים)‬ ‫‪‬‬

‫סביבה זהה (קבצים פתוחים‪ , file descriptors ,‬ספרית עבודה נוכחית)‬ ‫‪‬‬

‫‪ ‬פרמטרים‪ :‬אין‬
‫‪ ‬ערך מוחזר‪:‬‬
‫במקרה של כישלון‪ -1 :‬לאב (אין בן)‬ ‫‪‬‬

‫במקרה של הצלחה‪ :‬לבן מוחזר ‪ 0‬ולאב מוחזר ה‪ pid-‬של הבן‬ ‫‪‬‬

‫(‪ ) c‬ארז חדד ‪2003‬‬ ‫מערכות הפעלה ‪ -‬תרגול ‪3‬‬ ‫‪7‬‬


‫‪ API‬לתהליכים ב‪)2( Linux-‬‬
‫לאחר פעולת )(‪ fork‬מוצלחת‪ ,‬אמנם יש לאב ולבן את אותם‬ ‫‪‬‬
‫משתנים בזיכרון‪ ,‬אך בעותקים נפרדים‬
‫כלומר‪ ,‬שינוי ערכי המשתנים אצל האב לא ייראה אצל הבן‪ ,‬וההיפך‬ ‫‪‬‬
‫כמו כן‪ ,‬תהליך הבן הוא תהליך נפרד מתהליך האב לכל דבר‪.‬‬ ‫‪‬‬
‫בפרט‪ ,‬יש לו ‪ pid‬משלו‬
‫מה מדפיס הקוד הבא ?‬ ‫‪‬‬
‫{ )(‪main‬‬
‫;)(‪fork‬‬
‫;)”‪printf(“hello‬‬
‫}‬

‫(‪ ) c‬ארז חדד ‪2003‬‬ ‫מערכות הפעלה ‪ -‬תרגול ‪3‬‬ ‫‪8‬‬


)3( Linux-‫ לתהליכים ב‬API
:)‫ הצליחה‬fork()-‫תשובות אפשריות (בהנחה ש‬ 
hellohello
hheellollo

‫ שני תהליכים כותבים פלט בצורה לא מתואמת‬:‫הסיבה‬ 


:fork()-‫מבנה תכנית אופייני המשתמש ב‬ 
status = fork();
if (status < 0)
// fork() failed – handle error (e.g. message & exit)
if (status == 0)
// son process – do son code
else
// father process – do father code
2003 ‫ ) ארז חדד‬c( 3 ‫ תרגול‬- ‫מערכות הפעלה‬ 9
‫‪ API‬לתהליכים ב‪)4( Linux-‬‬
‫קריאת המערכת )(‪execv‬‬ ‫‪‬‬
‫‪ ‬תחביר ‪:‬‬
‫;)][‪int execv(const char *filename, char *const argv‬‬
‫‪ ‬פעולה‪ :‬טוענת תכנית חדשה לביצוע על‪-‬ידי התהליך הקורא‬
‫‪ ‬פרמטרים‪:‬‬
‫‪ – filename‬מסלול אל הקובץ המכיל את התכנית לטעינה‬ ‫‪‬‬

‫‪ – argv‬מערך מצביעים למחרוזות המכיל את הפרמטרים עבור התכנית‪.‬‬ ‫‪‬‬


‫האיבר הראשון מקיים ‪ argv[0] == filename‬או רק מכיל את שם קובץ‬
‫התכנית‪ .‬האיבר שאחרי הפרמטר האחרון מכיל ‪NULL‬‬
‫‪ ‬ערך מוחזר‪:‬‬
‫במקרה של כישלון‪-1 :‬‬ ‫‪‬‬

‫במקרה של הצלחה‪ :‬הקריאה אינה חוזרת‪ .‬איזורי הזיכרון של התהליך‬ ‫‪‬‬


‫מאותחלים לתכנית החדשה שמתחילה להתבצע מההתחלה‪.‬‬
‫(‪ ) c‬ארז חדד ‪2003‬‬ ‫מערכות הפעלה ‪ -‬תרגול ‪3‬‬ ‫‪10‬‬
‫‪ API‬לתהליכים ב‪)5( Linux-‬‬
‫מה ידפיס הקוד הבא ?‬ ‫‪‬‬
‫{ )(‪main‬‬
‫;}‪char *argv[] = {“date”, NULL‬‬
‫;)‪execv(“/bin/date”, argv‬‬
‫;)”‪printf(“hello‬‬
‫}‬
‫התשובה‪:‬‬ ‫‪‬‬
‫‪ ‬אם )(‪ execv‬מצליחה‪ :‬את התאריך והשעה‬
‫‪ ‬אם )(‪ execv‬נכשלת‪hello :‬‬

‫(‪ ) c‬ארז חדד ‪2003‬‬ ‫מערכות הפעלה ‪ -‬תרגול ‪3‬‬ ‫‪11‬‬


)6( Linux-‫ לתהליכים ב‬API
:execv() ‫ עם‬fork() ‫דוגמה אופיינית לשילוב‬ 
pid = fork();
if (pid < 0) {
// handle fork() error
}
else if (pid == 0) {
execv(“son_prog”, argv_son);
//handle execv() error
}
else
// do father code

2003 ‫ ) ארז חדד‬c( 3 ‫ תרגול‬- ‫מערכות הפעלה‬ 12


‫‪ API‬לתהליכים ב‪)7( Linux-‬‬
‫קריאת המערכת )(‪exit‬‬ ‫‪‬‬
‫‪ ‬תחביר ‪:‬‬
‫;)‪void exit(int status‬‬
‫‪ ‬פעולה‪ :‬מסיימת את ביצוע התהליך הקורא ומשחררת את כל‬
‫המשאבים שברשותו‪ .‬התהליך עובר למצב ‪ zombie‬עד‬
‫שתהליך האב יבקש לבדוק את סיומו ואז יפונה לחלוטין‬
‫‪ ‬פרמטרים‪:‬‬
‫‪ – status‬ערך סיום המוחזר לאב אם יבדוק את סיום התהליך‬ ‫‪‬‬

‫‪ ‬ערך מוחזר‪ :‬הקריאה אינה חוזרת‬

‫(‪ ) c‬ארז חדד ‪2003‬‬ ‫מערכות הפעלה ‪ -‬תרגול ‪3‬‬ ‫‪13‬‬


‫‪ API‬לתהליכים ב‪)8( Linux-‬‬
‫קריאת המערכת )(‪wait‬‬ ‫‪‬‬
‫‪ ‬תחביר ‪:‬‬
‫;)‪pid_t wait(int *status‬‬
‫‪ ‬פעולה‪ :‬גורמת לתהליך הקורא להמתין עד אשר אחד מתהליכי‬
‫הבן שלו יסיים‬
‫‪ ‬פרמטרים‪:‬‬
‫‪ – status‬מצביע למשתנה בו יאוחסן סטטוס הבן שסיים‬ ‫‪‬‬

‫‪ ‬ערך מוחזר‪:‬‬
‫אם אין בנים‪ ,‬או שכל הבנים כבר סיימו וכבר בוצע להם )(‪ - wait‬חזרה‬ ‫‪‬‬
‫מיד עם ערך ‪-1‬‬
‫אם יש בן שסיים ועדיין לא בוצע לו )(‪ – ) zombie( wait‬חזרה מייד עם‬ ‫‪‬‬
‫ה‪ pid-‬של הבן הנ" ל ועם סטטוס הסיום שלו‪ .‬מאקרו‪-‬ים שונים מאפשרים‬
‫לקבל מתוך הסטטוס מידע על הבן‪ .‬למשל )‪WEXITSTATUS (status‬‬
‫יתן את ערך הסיום של בן שסיים (הערך שהעביר כארגומנט ל‪.) exit()-‬‬
‫אחרת – המתנה עד שבן כלשהו יסיים‬ ‫‪‬‬

‫(‪ ) c‬ארז חדד ‪2003‬‬ ‫מערכות הפעלה ‪ -‬תרגול ‪3‬‬ ‫‪14‬‬


‫‪ API‬לתהליכים ב‪)9( Linux-‬‬
‫דוגמת קוד אופיינית בה האב מחכה לסיום כל תהליכי‬ ‫‪‬‬
‫הבן‪:‬‬
‫;)‪while (wait(&status) != -1‬‬
‫קריאה שימושית נוספת‪ – waitpid() :‬המתנה לסיום בן‬ ‫‪‬‬
‫מסוים‬
‫;)‪pid_t waitpid(pid_t pid, int *status, int options‬‬

‫ניתן למשל‪ ,‬באמצעות ‪ ,options‬לבחור רק לבדוק אם הבן‬ ‫‪‬‬


‫סיים (ערך ‪ )WNOHANG‬או להמתין לסיום הבן (ערך ‪) 0‬‬

‫(‪ ) c‬ארז חדד ‪2003‬‬ ‫מערכות הפעלה ‪ -‬תרגול ‪3‬‬ ‫‪15‬‬


‫‪ API‬לתהליכים ב‪)10( Linux-‬‬
‫קריאת המערכת )(‪getpid‬‬ ‫‪‬‬
‫‪ ‬תחביר ‪:‬‬
‫)(‪pid_t getpid‬‬
‫‪ ‬פעולה‪ :‬מחזירה לתהליך הקורא את ה‪ pid-‬של עצמו‬
‫‪ ‬פרמטרים‪ :‬אין‬
‫‪ ‬ערך מוחזר‪ :‬ה‪ pid-‬של התהליך הקורא‬
‫קריאה שימושית דומה‪ getppid() :‬מחזירה את ה‪ pid-‬של‬ ‫‪‬‬
‫תהליך האב של התהליך הקורא‬
‫‪ ‬מה המשמעות של ‪ getppid() == 1‬עבור תהליך משתמש‬
‫טיפוסי ?‬
‫תשובה‪ :‬תהליך האב שיצר את התהליך הנוכחי סיים‬ ‫‪‬‬

‫(‪ ) c‬ארז חדד ‪2003‬‬ ‫מערכות הפעלה ‪ -‬תרגול ‪3‬‬ ‫‪16‬‬


‫אתחול תהליכים ב‪Linux-‬‬
‫משתמשים מתחברים לעבודה ב‪ Linux-‬דרך מסופים‬ ‫‪‬‬
‫מסוף = מסך ‪ +‬מקלדת (מקומי או מרוחק )‬ ‫‪‬‬
‫התהליך ‪ init‬יוצר עבור כל מסוף של ‪ Linux‬תהליך בן‬ ‫‪‬‬
‫הטוען ומבצע את המשימות הבאות לפי הסדר‪:‬‬
‫איתחול של המסוף בתכנית ‪getty‬‬ ‫‪.1‬‬
‫תכנית ‪ login‬המאפשרת למשתמש להיכנס למערכת‬ ‫‪.2‬‬
‫לאחר שאושרה כניסת המשתמש‪ :‬תכנית ‪( shell‬כמו ‪ tcsh‬או‬ ‫‪.3‬‬
‫‪ ) bash‬המאפשרת למשתמש להעביר פקודות למערכת‬
‫ההפעלה‬
‫כאשר ה‪ shell-‬מקבל פקודה‪ ,‬הוא מייצר תהליך בן‬ ‫‪‬‬
‫שמבצע אותה‪ ,‬ממתין לסיום הבן ואז קורא את הפקודה‬
‫הבאה‬
‫(‪ ) c‬ארז חדד ‪2003‬‬ ‫מערכות הפעלה ‪ -‬תרגול ‪3‬‬ ‫‪17‬‬
)2( Linux-‫אתחול תהליכים ב‬
Pid=1
init fork() fork() fork() wait()
Pid=7223 Pid=30498 Pid=8837

exec(getty) exec(getty) exec(getty) exit()

exec(login) exec(login)

exec(shell)

shell fork() wait()


Pid=5562

exec(command)
exit()
2003 ‫ ) ארז חדד‬c( 3 ‫ תרגול‬- ‫מערכות הפעלה‬ 18
‫ניהול תהליכים בגרעין (‪)1‬‬
‫לכל תהליך ב‪ Linux-‬קיים בגרעין מתאר תהליך‬ ‫‪‬‬
‫(‪ ,)process descriptor‬שהוא רשומה מסוג‬
‫‪( task_struct‬קובץ גרעין ‪)include/linux/sched.h‬‬
‫המכילה‪:‬‬
‫‪ ‬מצב התהליך‬
‫‪ ‬עדיפות התהליך‬
‫‪ ‬מזהה התהליך (‪) pid‬‬
‫‪ ‬מצביע לטבלת איזורי הזיכרון של התהליך‬
‫‪ ‬מצביע לטבלת הקבצים הפתוחים של התהליך‬
‫‪ ‬מצביעים למתארי תהליכים נוספים (רשימה מקושרת )‬
‫‪ ‬מצביעים למתאר תהליך האב ו"קרובי משפחה" נוספים‬
‫‪ ‬מסוף איתו התהליך מתקשר‬
‫‪ ‬ועוד‪..‬‬
‫(‪ ) c‬ארז חדד ‪2003‬‬ ‫מערכות הפעלה ‪ -‬תרגול ‪3‬‬ ‫‪19‬‬
‫ניהול תהליכים בגרעין (‪)2‬‬
‫מצב התהליך נמצא בשדה ‪ , state‬שהוא משתנה בגודל‬ ‫‪‬‬
‫‪ 32‬ביט המתפקד כמערך ביטים‬
‫‪ ‬בכל זמן שהוא‪ ,‬בדיוק אחד מהביטים ב‪ state-‬דלוק בהתאם‬
‫למצב התהליך באותו זמן‬
‫‪ Linux‬מגדירה את המצבים הבאים לכל תהליך‪:‬‬ ‫‪‬‬
‫‪ – TASK_RUNNING ‬התהליך רץ או מוכן לריצה‪ ,‬כלומר נמצא‬
‫בטווח הקצר‬
‫‪ – TASK_INTERRUPTIBLE ‬התהליך ממתין לאירוע כלשהו‬
‫(טווח בינוני‪/‬ארוך) אך ניתן להפסיק את המתנת התהליך‬
‫ולהחזירו למצב ‪ TASK_RUNNING‬באמצעות שליחת אות‬
‫(‪ ) Signal‬כלשהו לתהליך‪ .‬זהו מצב ההמתנה הנפוץ‪.‬‬
‫(‪ ) c‬ארז חדד ‪2003‬‬ ‫מערכות הפעלה ‪ -‬תרגול ‪3‬‬ ‫‪20‬‬
‫ניהול תהליכים בגרעין (‪)3‬‬
‫‪ – TASK_UNINTERRUPTIBLE ‬התהליך ממתין לאירוע‬
‫כלשהו (בדומה ל‪ ) TASK_INTERRUPTIBLE -‬אך פרט‬
‫לאירוע לו הוא ממתין‪ ,‬לא ניתן "להעיר" את התהליך‬
‫מצב המתנה נדיר לשימוש – למשל כאשר התהליך מבקש לגשת‬ ‫‪‬‬
‫לחומרה ומערכת ההפעלה צריכה לסרוק אחר החומרה ללא הפרעה‬
‫‪ – TASK_STOPPED ‬ריצת התהליך נעצרה בצורה מבוקרת‬
‫על‪-‬ידי תהליך אחר (בדרך‪-‬כלל ‪ debugger‬או ‪) tracer‬‬
‫‪ – TASK_ZOMBIE ‬ריצת התהליך הסתיימה‪ ,‬אך תהליך האב‬
‫של התהליך שסיים עדיין לא ביקש מידע על סיום התהליך‬
‫באמצעות קריאה כדוגמת )(‪ . wait‬התהליך קיים כמתאר בלבד‬
‫את ערך השדה ‪ state‬ניתן לשנות בהצבה ישירה או על‪-‬‬ ‫‪‬‬
‫ידי המאקרו ‪ set_task_state‬או ‪set_current_state‬‬
‫(קובץ גרעין ‪) include/linux/sched.h‬‬
‫(‪ ) c‬ארז חדד ‪2003‬‬ ‫מערכות הפעלה ‪ -‬תרגול ‪3‬‬ ‫‪21‬‬
‫ניהול תהליכים בגרעין (‪)4‬‬
‫לכל תהליך יש מחסנית נוספת הקרויה ‪, kernel mode stack‬‬ ‫‪‬‬
‫כלומר "מחסנית גרעין"‬
‫מחסנית זו משמשת את גרעין מערכת ההפעלה בטיפול באירועים‬ ‫‪‬‬
‫במהלך ריצת התהליך‬
‫פסיקות בכלל‬ ‫‪‬‬
‫קריאות מערכת בפרט‬ ‫‪‬‬
‫מחסנית הגרעין של כל תהליך מאוחסנת באיזור הזיכרון של הגרעין‬ ‫‪‬‬
‫כאשר במהלך ריצת התהליך מתבצע מעבר בין ‪ user mode‬ו‪-‬‬ ‫‪‬‬
‫‪ , kernel mode‬מתבצעת החלפת מחסניות (שינוי ערכי ‪ ss‬ו‪) esp-‬‬
‫בין המחסנית הרגילה של התהליך ומחסנית הגרעין‬
‫ערכי ‪ ss:esp‬המצביעים למחסנית הרגילה נשמרים על‪-‬ידי המעבד במחסנית‬ ‫‪‬‬
‫הגרעין מיד עם המעבר ל‪ kernel mode-‬ומשוחזרים במעבר החוזר ל‪user -‬‬
‫‪mode‬‬
‫(‪ ) c‬ארז חדד ‪2003‬‬ ‫מערכות הפעלה ‪ -‬תרגול ‪3‬‬ ‫‪22‬‬
‫ניהול תהליכים בגרעין (‪)5‬‬
‫מחסנית הגרעין מאוחסנת יחד עם מתאר התהליך‬ ‫‪‬‬
‫בקטע זיכרון אחד בגודל ‪ ,8KB‬המתחיל בכתובת‬
‫שהיא כפולה של ‪)213( 8KB‬‬
‫‪0x015fbfff‬‬ ‫{ ‪union task_union‬‬
‫‪Kernel Mode‬‬
‫;‪struct task_struct task‬‬
‫‪Stack‬‬
‫;]‪unsigned long stack[2048‬‬
‫;}‬

‫‪esp‬‬ ‫‪0x015fa878‬‬ ‫המחסנית לא דורסת את מתאר‬


‫‪0x015fa3cb‬‬ ‫התהליך מפני שאיננה גדלה‬
‫‪Process Descriptor‬‬
‫מעבר ל‪ 7200-‬בתים‪ ,‬וגודל מתאר‬
‫‪current‬‬ ‫‪0x015fa000‬‬ ‫התהליך קטן מ‪ 1000-‬בתים‬

‫(‪ ) c‬ארז חדד ‪2003‬‬ ‫מערכות הפעלה ‪ -‬תרגול ‪3‬‬ ‫‪23‬‬


‫ניהול תהליכים בגרעין (‪)6‬‬
‫מצורת האחסון הנ"ל נובעת דרך פשוטה "לשלוף" את כתובת מתאר‬ ‫‪‬‬
‫התהליך מתוך ‪ esp‬כאשר המעבד ב‪:kernel mode-‬‬
‫לאפס את ‪ 13‬הביטים הנמוכים של ‪esp‬‬
‫בהתאם לדוגמה בשקף הקודם‪:‬‬ ‫‪‬‬
‫‪esp = 0x15fa878‬‬
‫כתובת מתאר התהליך – ‪esp & 0xffffe000 = 0x15fa000‬‬
‫המאקרו ‪( current‬קובץ גרעין ‪) include/asm-i386/current.h‬‬ ‫‪‬‬
‫משתמש בשיטה זו על מנת לאחסן את כתובת מתאר התהליך‬
‫בערך מוחזר ‪: p‬‬
‫‪movl $0xffffe000, %ecx‬‬
‫‪movl %esp, p‬‬
‫‪andl %ecx, p‬‬

‫(‪ ) c‬ארז חדד ‪2003‬‬ ‫מערכות הפעלה ‪ -‬תרגול ‪3‬‬ ‫‪24‬‬


‫ניהול תהליכים בגרעין‬

‫רשימת התהליכים (‪)1‬‬


‫מתארי כל התהליכים מחוברים ברשימה מקושרת כפולה‬ ‫‪‬‬
‫מעגלית הקרויה רשימת התהליכים (‪)process list‬‬
‫באמצעות השדות ‪ prev_task‬ו‪next_task-‬‬
‫‪ ‬רשימה זו מקבילה ל"טבלת התהליכים" הקיימת במערכות‬
‫ההפעלה אחרות‬
‫‪ ‬ראש הרשימה הוא המתאר של התהליך ‪( swapper‬מוצבע ע"י‬
‫‪init_task‬‬ ‫‪) init_task‬‬

‫‪prev_task‬‬ ‫‪next_task‬‬ ‫‪prev_task‬‬ ‫‪next_task‬‬ ‫‪prev_task‬‬ ‫‪next_task‬‬

‫‪swapper‬‬ ‫‪init‬‬

‫(‪ ) c‬ארז חדד ‪2003‬‬ ‫מערכות הפעלה ‪ -‬תרגול ‪3‬‬ ‫‪25‬‬


‫ניהול תהליכים בגרעין‬

‫רשימת התהליכים (‪)2‬‬


‫המאקרו ‪ SET_LINKS‬ו‪( REMOVE_LINKS-‬קובץ‬ ‫‪‬‬
‫גרעין ‪ )include/linux/sched.h‬משמשים להוספה‬
‫והסרה של מתאר תהליך ברשימת התהליכים‬
‫‪ ‬מטפלים גם ב"קשרי משפחה" בין תהליכים‪ .‬פרטים בהמשך‬
‫המאקרו ‪( for_each_task‬אותו קובץ גרעין) מאפשר‬ ‫‪‬‬
‫לעבור על כל התהליכים ברשימה בסריקה דרך השדה‬
‫‪:next_task‬‬
‫)‪#define for_each_task(p‬‬
‫) ;‪for (p = &init_task; (p = p->next_task) != &init_task‬‬

‫(‪ ) c‬ארז חדד ‪2003‬‬ ‫מערכות הפעלה ‪ -‬תרגול ‪3‬‬ ‫‪26‬‬


‫ניהול תהליכים בגרעין‬

‫מיפוי ‪ PID‬למתאר תהליך (‪)1‬‬


‫אמנם קריאות מערכת המתייחסות לתהליך מציינות את‬ ‫‪‬‬
‫ה‪ pid-‬של התהליך‪ ,‬אך הגרעין עובד עם מתאר התהליך‬
‫לפיכך‪ ,‬הוגדר בגרעין מנגנון המאתר את מתאר התהליך‬ ‫‪‬‬
‫לפי ה‪ pid-‬של התהליך‬
‫המנגנון מבוסס על ‪ hash-table‬בגודל ‪PIDHASH_SZ‬‬ ‫‪‬‬
‫(בד"כ ‪ ) 1024‬כניסות‬
‫‪ ‬בדרך‪ -‬כלל מספר התהליכים במערכת קטן בהרבה מ‪ 32K-‬ולכן‬
‫אין צורך להחזיק כניסות עבור כל ה‪ pid-‬האפשריים‬
‫התנגשויות בפונקצית ה‪ hash-‬נפתרות על‪-‬ידי קישור‬ ‫‪‬‬
‫מתארי התהליך‪ ,‬המתמפים לאותה כניסה בטבלה‪,‬‬
‫ברשימה מקושרת כפולה דרך השדות ‪ pidhash_next‬ו‪-‬‬
‫‪pidhash_pprev‬‬
‫(‪ ) c‬ארז חדד ‪2003‬‬ ‫מערכות הפעלה ‪ -‬תרגול ‪3‬‬ ‫‪27‬‬
‫ניהול תהליכים בגרעין‬

‫מיפוי ‪ PID‬למתאר התהליך (‪)2‬‬


‫הפונקציות )(‪ hash_pid‬ו‪ unhash_pid()-‬מאפשרות‬ ‫‪‬‬
‫להוסיף ולהסיר מתאר תהליך לטבלה‬
‫הפונקציה )(‪ find_task_by_pid‬מבצעת את איתור‬ ‫‪‬‬
‫‪pidhash‬‬ ‫מתאר התהליך לפי ה‪ pid-‬הנתון‬
‫…‬

‫‪0‬‬

‫‪199‬‬ ‫‪PID‬‬ ‫‪PID‬‬


‫‪199‬‬ ‫‪26799‬‬
‫…‬

‫‪PID‬‬
‫‪216‬‬ ‫‪26800‬‬ ‫‪pidhash_next‬‬
‫‪pidhash_pprev‬‬
‫…‬

‫‪1023‬‬

‫(‪ ) c‬ארז חדד ‪2003‬‬ ‫מערכות הפעלה ‪ -‬תרגול ‪3‬‬ ‫‪28‬‬


‫ניהול תהליכים בגרעין‬

‫ניהול קשרי משפחה בגרעין (‪)1‬‬


‫"קשרי המשפחה" בין תהליכים מיוצגים בגרעין באמצעות‬ ‫‪‬‬
‫מצביעים בין מתארי תהליכים‬
‫‪ ‬מתאר תהליך אב מצביע למתאר תהליך הבן הצעיר ביותר שלו‬
‫(שנוצר אחרון) באמצעות השדה ‪ p_cptr‬במתאר התהליך‬
‫‪ ‬מתאר תהליך מצביע למתאר תהליך האב שלו באמצעות השדה‬
‫‪p_opptr‬‬
‫במתאר התהליך קיים שדה מצביע נוסף הקרוי ‪ p_pptr‬המצביע למתאר‬ ‫‪‬‬
‫תהליך האב בפועל‪ .‬ערך זה שונה מתהליך האב כאשר התהליך נמצא‬
‫בריצה מבוקרת ע"י ‪ debugger‬או ‪tracer‬‬
‫‪ ‬מתאר תהליך מצביע למתאר תהליך ה"אח הבוגר" ( ‪older‬‬
‫‪ ,) sibling‬כלומר מתאר התהליך שאביו יצר לפניו‪ ,‬באמצעות‬
‫השדה ‪p_osptr‬‬
‫‪ ‬מתאר תהליך מצביע למתאר תהליך ה"אח הצעיר" ( ‪younger‬‬
‫‪ ,) sibling‬כלומר מתאר התהליך שאביו יצר אחריו‪ ,‬באמצעות‬
‫השדה ‪p_ysptr‬‬
‫(‪ ) c‬ארז חדד ‪2003‬‬ ‫מערכות הפעלה ‪ -‬תרגול ‪3‬‬ ‫‪29‬‬
‫ניהול תהליכים בגרעין‬

‫ניהול קשרי משפחה בגרעין (‪)2‬‬


‫באמצעות "קשרי המשפחה"‬ ‫‪‬‬
‫‪ ‬תהליך יכול לאתר את אביו‬
‫למשל‪ ,‬עבור )(‪getppid‬‬ ‫‪‬‬

‫‪ ‬תהליך יכול לאתר את בניו לפי סדר יצירתם‬


‫למשל‪ ,‬עבור )(‪wait‬‬ ‫‪‬‬
‫‪P0‬‬

‫‪p_(o)pptr‬‬
‫‪P1‬‬ ‫‪P2‬‬ ‫‪P3‬‬
‫‪p_ysptr‬‬
‫‪p_osptr‬‬
‫‪P4‬‬ ‫‪p_cptr‬‬

‫(‪ ) c‬ארז חדד ‪2003‬‬ ‫מערכות הפעלה ‪ -‬תרגול ‪3‬‬ ‫‪30‬‬


‫ניהול תהליכים בגרעין‬

‫רשימות מקושרות בגרעין (‪)1‬‬


‫לצורך ניהול תורים ומבני נתונים אחרים הגרעין משתמש ברשימות מקושרות כפולות‬ ‫‪‬‬
‫מעגליות‬
‫‪ ‬הגדרת מבנה הרשימה בקובץ הגרעין ‪include/linux/list.h‬‬
‫כל איבר ברשימה הוא מסוג ‪list_t‬‬ ‫‪‬‬
‫{ ‪struct list_head‬‬
‫;‪struct list_head *next, *prev‬‬
‫;}‬
‫;‪typedef struct list_head list_t‬‬
‫‪data structure 1‬‬ ‫‪data structure 2‬‬ ‫‪data structure 3‬‬

‫‪list_head‬‬

‫‪next‬‬ ‫‪next‬‬ ‫‪next‬‬ ‫‪next‬‬

‫‪prev‬‬ ‫‪prev‬‬ ‫‪prev‬‬ ‫‪prev‬‬

‫(‪ ) c‬ארז חדד ‪2003‬‬ ‫מערכות הפעלה ‪ -‬תרגול ‪3‬‬ ‫‪31‬‬


‫ניהול תהליכים בגרעין‬

‫רשימות מקושרות בגרעין (‪)2‬‬


‫האיברים ברשימה הם שדות המוכלים ברשומות מבני נתונים‬ ‫‪‬‬
‫מבני הנתונים המכילים את אברי הרשימה מקושרים זה לזה באמצעות הרשימה‬ ‫‪‬‬
‫הפעולות על הרשימה כוללות‪ ,‬בין השאר‪:‬‬ ‫‪‬‬
‫יצירת (ראש) הרשימה‪LIST_HEAD :‬‬ ‫‪‬‬
‫הוספת איבר במקום נתון (‪ ) list_add‬ובסוף הרשימה (‪) list_add_tail‬‬ ‫‪‬‬
‫הסרת איבר נתון (‪) list_del‬‬ ‫‪‬‬
‫בדיקה האם הרשימה ריקה (‪) list_empty‬‬ ‫‪‬‬
‫גישה לרשומה המכילה איבר נתון (‪) list_entry‬‬ ‫‪‬‬
‫\ )‪#define list_entry(ptr, type, member‬‬
‫)))‪((type *)((char *)(ptr) – (unsigned long)(&((type *)0)->member‬‬
‫לולאת מעבר על איברים ברשימה (‪)list_for_each‬‬ ‫‪‬‬

‫(‪ ) c‬ארז חדד ‪2003‬‬ ‫מערכות הפעלה ‪ -‬תרגול ‪3‬‬ ‫‪32‬‬


‫ניהול תהליכים בגרעין‬

‫הטווח הקצר (‪)1‬‬


‫מתארי התהליכים המוכנים לריצה ב‪( Linux-‬מצב‬ ‫‪‬‬
‫‪ )TASK_RUNNING‬נגישים מתוך מבנה נתונים הקרוי‬
‫‪( runqueue‬קובץ גרעין ‪)kernel/sched.c‬‬
‫‪ ‬לכל מעבד יש ‪ runqueue‬משלו‬
‫‪ ‬כל ‪ runqueue‬מכיל מספר תורים של מתארי תהליכים‪ ,‬אחד‬
‫לכל עדיפות של תהליך‬
‫‪ ‬כל תור ממומש כרשימה מעגלית כפולה שתוארה קודם‬
‫‪ ‬השדה ‪ run_list‬במתאר התהליך הוא איבר הקישור ברשימה‬
‫(מסוג ‪) list_head‬‬

‫(‪ ) c‬ארז חדד ‪2003‬‬ ‫מערכות הפעלה ‪ -‬תרגול ‪3‬‬ ‫‪33‬‬


‫ניהול תהליכים בגרעין‬

‫הטווח הקצר (‪)2‬‬


‫‪ ‬הפונקציות )(‪ enqueue_task‬ו‪dequeue_task()-‬‬
‫מכניסות ומוציאות [מתאר] תהליך ב‪runqueue-‬‬
‫‪ ‬הפונקציה )(‪ wake_up_process‬הופכת תהליך‬
‫ממתין למוכן לריצה (‪ ,)TASK_RUNNING‬מוסיפה‬
‫את התהליך ל‪ runqueue-‬באמצעות‬
‫)(‪ enqueue_task‬ומסמנת צורך בהחלפת הקשר אם‬
‫התהליך החדש מועדף לריצה על‪-‬פני האחרים‬

‫(‪ ) c‬ארז חדד ‪2003‬‬ ‫מערכות הפעלה ‪ -‬תרגול ‪3‬‬ ‫‪34‬‬


‫ניהול תהליכים בגרעין‬

‫הטווח הבינוני‪/‬ארוך (‪)1‬‬


‫תהליך שצריך להמתין לאירוע כלשהו לפני המשך ריצתו‬ ‫‪‬‬
‫(‪ )TASK_(UN)INTERRUPTIBLE‬נכנס לתור המתנה‬
‫(‪)wait queue‬‬
‫‪ ‬כמו כן‪ ,‬התהליך יוצא מה‪ runqueue-‬ומוותר על המעבד‬
‫כל תור המתנה משויך לאירוע או סוג אירוע כלשהו‪ ,‬כגון‬ ‫‪‬‬
‫‪ ‬פסיקת חומרה‪ ,‬למשל דיסק או שעון‬
‫‪ ‬התפנות משאב מערכת לשימוש‪ .‬לדוגמה‪ :‬ערוץ תקשורת‬
‫שהתפנה וניתן לשלוח דרכו נתונים‬
‫‪ ‬אירועים אחרים כלשהם‪ ,‬כמו סיום תהליך‬
‫כאשר קורה האירוע אליו מקושר תור ההמתנה‪ ,‬מערכת‬ ‫‪‬‬
‫ההפעלה "מעירה" תהליכים מתוך התור‪ ,‬כלומר מחזירה‬
‫אותם למצב ריצה (‪)TASK_RUNNING‬‬
‫(‪ ) c‬ארז חדד ‪2003‬‬ ‫מערכות הפעלה ‪ -‬תרגול ‪3‬‬ ‫‪35‬‬
‫ניהול תהליכים בגרעין‬

‫הטווח הבינוני‪/‬ארוך (‪)2‬‬


‫תהליך ממתין בתור יכול להיות באחד משני מצבים‪:‬‬ ‫‪‬‬
‫‪( exclusive ‬בלעדי) – כאשר האירוע המעורר קורה‪ ,‬מעירים‬
‫אחד מהתהליכים שממתינים עם סימון "בלעדי"‪ .‬למשל‪ :‬כאשר‬
‫האירוע הוא שחרור של משאב שניתן לשימוש רק על‪-‬ידי תהליך‬
‫יחיד ב‪-‬זמנית‬
‫‪( non-execlusive ‬משותף) – כאשר האירוע המעורר קורה‪,‬‬
‫מעירים את כל התהליכים שממתינים עם סימון "משותף"‪.‬‬
‫למשל‪ :‬כאשר האירוע הוא פסיקת שעון שיכולה לסמן סוף‬
‫המתנה עבור תהליכים שונים הממתינים למשך זמן קצוב‬
‫בדרך‪-‬כלל אין באותו תור המתנה ממתינים בלעדיים‬ ‫‪‬‬
‫ומשותפים יחד‬
‫(‪ ) c‬ארז חדד ‪2003‬‬ ‫מערכות הפעלה ‪ -‬תרגול ‪3‬‬ ‫‪36‬‬
‫ניהול תהליכים בגרעין‬

)3( ‫ארוך‬/‫הטווח הבינוני‬


‫תור המתנה ממומש כרשימה מקושרת כפולה מעגלית שתוארה‬ 
)include/linux/wait.h ‫קודם (קובץ גרעין‬
struct __wait_queue_head {
spinlock_t lock; ‫ מיועד להגן על התור‬lock ‫השדה‬
‫ידי שני‬-‫מפני גישה במקביל על‬
struct list_head task_list; ‫תהליכים או יותר‬
};
typedef __wait_queue_head wait_queue_head_t;
:‫כל תהליך בתור מוצבע מאיבר ברשימה המוגדר כדלהלן‬ 
struct __wait_queue { ‫ מציין האם ההמתנה‬flags ‫השדה‬
unsigned int flags; non- ‫ ) או‬1( exclusive ‫היא‬
) 0( exclusive
struct task_struct *task;
struct list_head task_list;
};
typedef struct __wait_queue wait_queue_t;

2003 ‫ ) ארז חדד‬c( 3 ‫ תרגול‬- ‫מערכות הפעלה‬ 37


‫ניהול תהליכים בגרעין‬

)4( ‫ארוך‬/‫הטווח הבינוני‬


sleep_on() - ‫פונקציה להכנסת תהליך להמתנה בתור‬ 
void sleep_on(wait_queue_head_t *q) {
unsigned long flags;
wait_queue_t wait;
wait.flags = 0;
wait.task = current;
current->state = TASK_UNINTERRUPTIBLE;
add_wait_queue(q, &wait);
schedule();
remove_wait_queue(q, &wait);
}
-‫ ו‬add_wait_queue[_exclusive]() ‫הפונקציות‬
‫ מכניסות ומוציאות תהליך מהתור‬remove_wait_queue()
2003 ‫ ) ארז חדד‬c( 3 ‫ תרגול‬- ‫מערכות הפעלה‬ 38
‫ניהול תהליכים בגרעין‬

‫הטווח הבינוני‪/‬ארוך (‪)5‬‬


‫פונקציות נוספות מאפשרות הכנסת תהליך לתור כשהוא‬ ‫‪‬‬
‫ממתין במצב ‪ interruptible‬ו‪/‬או כשהממתין בלעדי‬
‫במקביל‪ ,‬פונקציות המשמשות "להעיר" תהליכים‪:‬‬ ‫‪‬‬
‫‪ wake_up ‬מעירה את כל הממתינים המשותפים ואחד‬
‫מהבלעדיים‬
‫‪ ‬גרסאות נוספות של ‪: wake_up‬‬
‫להעיר מספר מוגבל של תהליכים ממתינים‬ ‫‪‬‬

‫להעיר רק ממתינים שהם ‪interruptible‬‬ ‫‪‬‬

‫לבצע החלפת הקשר אם התהליך המועדף לריצה משתנה לאחר‬ ‫‪‬‬


‫שמעירים תהליכים‬

‫(‪ ) c‬ארז חדד ‪2003‬‬ ‫מערכות הפעלה ‪ -‬תרגול ‪3‬‬ ‫‪39‬‬

You might also like