אוניברסיטת בן-גוריון בנגב, המחלקה למדעי המחשב בוחן אמצע במערכות הפעלה מרצים: איתי דינור, דני הנדלר ורוברט יעקבשוילי. מתרגלים: אור דינרי, אחמד דרובי, מתן דרורי, צחי ספורטה, רועי עוזיאל ואריאל תלמי. ענו על כל השאלות: סה"כ 100 נקודות. תאריך הבחינה: 25 במאי, 2018 שם הקורס: מערכות הפעלה מספר הקורס: 20213031 שנה: 2018 סמסטר: ב', בחינת אמצע משך הבחינה: שעתיים חומר עזר: אסור תלמידים שאינם מעוניינים שבחינתם תיבדק צריכים לכתוב באופן ברור בעמוד הראשון: לא לבדיקה 1. תהליכים וחוטים )33 נקודות( )9 נק'( במערכת הפעלה מסוג,UNIX מה צפוי לקרות כאשר אחד החוטים בתהליך multi-threaded קורא לפונקציה execvp על מנת להריץ תוכנית חדשה? הניחו שמדובר בthreads.kernel-level א. כתבו במחברת איזו תשובה מבין שלוש האפשרויות למטה נכונה ונמקו בקצרה. החוט שקרא לexecvp יריץ את התוכנית החדשה ושאר החוטים ימשיכו להריץ את התוכנית המקורית. החוט שקרא לexecvp יריץ את התוכנית החדשה ושאר החוטים יהרגו ע"י הkernel. שתי התשובות אפשריות זה תלוי במימוש..1.2.3 אפשרות 2 היא היחידה הנכונה חוטים באותו ה process מריצים כולם קוד של אותה תכנית, לפיכך אחרי טעינת התוכנית החדשה חוטים שהריצו קודם לכן תכנית אחרת אינם יכולים להמשיך לרוץ. )15 נק'( קיראו את הקוד המצורף וכיתבו מה יודפס למסך בהרצה אחת של התוכנית. נמקו את.ב 100 ושאר התהליכים מקבלים ( pid )תשובתכם בקצרה. הניחו כי התהליך הראשון מקבל מזהה מחזירה את המזהה של () getpidמזהים עוקבים, החל מ- 101, לפי סדר יצירתם. הפונקציה התהליך הקורא לה. int main(){ int c = 0; if(fork() > 0){ c++; printf("%d, %d \n",c, getpid()); if(fork() > 0){ c++; printf("%d, %d \n",c, getpid()); עמ' 1 מתוך 7
101,1 102, 2 101, 2 100, 1 103, 2 100, 2 הסבר: פקודת הwait תמיד תגרום לאב להמתין לסיום ריצת הבן. הערך של c משוכפל כאשר נקרא fork )עותק של זיכרון האב( )9 נק'( מערכת ההפעלה MYUNIX זהה ל UNIX פרט לכך שאין בה את קריאת המערכת.fork() במקום זאת, יש קריאת מערכת myfork() שפעולתה זהה ל() fork, פרט לכך שהיא מחזירה ערך 0 גם לתהליך האב וגם לתהליך הבן במקרה של הצלחה. ג. כתבו במחברת קוד סכמתי המממש shell מערכת UNIX המצורף למטה. על מערכת MYUNIX בהתבסס על הקוד עבור while (TRUE){ type_prompt(); read_command (command, parameters); int ret = fork(); if (ret > 0){ else if(ret < 0){ exit(1); else{ execvp (command, parameters); type_prompt(); read_command (command, parameters); int saved_pid = getpid(); int ret = myfork(); if(ret < 0){ exit(1); int curr_pid = getpid(); if(curr_pid == saved_pid){ else{ execvp (command, parameters); עמ' 2 מתוך 7
2. סינכרוניזציה )34 נקודות( בכל הסעיפים בשאלה זו, על מנת להוכיח שתכונה אינה מתקיימת, יש לכתוב תסריט מדויק המדגים זאת. על מנת להוכיח שתכונה מתקיימת, יש לכתוב נימוק קצר ומדויק )אין צורך בהוכחות פורמליות(. א. )11 נק'( זרובבל ערך שינוי קל באלגוריתם פטרסון ויצר גירסה שלו לשלושה תהליכים: shared boolean b[0..2] initially {false,false,false integer turn Program for process i ε {0,1,2 1 b[i]=true 2 turn=i 3 await (b[(i+1) % 3]== false AND b[(i+2) % 3]==false) OR (turn i) 4 CS 5 b[i]=false האם האלגוריתם מקיים מניעה הדדית? האלגוריתם אינו מקיים מניעה הדדית, להלן תסריט: תהליכים 0-2, כל אחד בתורו, מבצעים את שורות 1-2. כעת turn=2 שניהם להיכנס לקטע הקריטי. ולפיכך תהליכים 0,1 יכולים ב. בכיתה ראינו אלגוריתם למניעה הדדית עבור 2 n תהליכים המתבסס על עץ טורניר tree) (tournament ובו n עלים. לנוחותכם, קוד האלגוריתם מופיע בעמוד האחרון של הבחינה. בכל אחד מן הצמתים הפנימיים של העץ יש מנעול פטרסון לשני תהליכים. על מנת לתפוס את המנעול הכללי )עבור n תהליכים(, תהליךi מנסה בקטע הכניסה לתפוס את כל המנעולים )לשני תהליכים( בצמתים הפנימיים, במסלול המוביל מן העלה שלו עד לשורש. כאשר תהליך עולה לצומת מתת העץ השמאלי הוא "משחק" את התפקיד של p0 באלגוריתם פטרסון בצומת, ואם הוא עולה מתת העץ הימני הוא "משחק" את התפקיד של p1. אחרי תפיסת המנעול בשורש, התהליך נכנס לקטע הקריטי. בקטע היציאה, התהליך משחרר את כל המנעולים שתפס בכניסה מלמעלה למטה החל מן השורש וכלה בצומת הנמוך ביותר במסלול. )12 נק'( כתבו במדויק את ההגדרה של תכונת (FIFO).First in first out עבור אילו ערכי n )אם בכלל( מקיים האלגוריתם הנ"ל תכונה זו? )לשם פשטות, ניתן להניח בסעיף זה כי n הוא חזקה שלמה של 2.(.i הגדרת :FIFO אם תהליך p מגיע לקוד ההמתנה בקטע הכניסה לפני שתהליך q מתחיל לבצע את ה doorway אזי q אינו יכול להיכנס לפני p. אלגוריתם הטורניר מקיים תכונה זו רק עבור 2=n. במקרה זה, מדובר באלגוריתם של פטרסון לשני תהליכים שקל לראות כי הוא מקיים.FIFO אם 2<n אזי האלגוריתם אינו מקיים את התכונה, להלן תסריט עבור 4=n. - תהליך p0 נכנס לקטע הקריטי של העץ - תהליך p1 מבצע את קטע הכניסה ומגיע להמתנה בשורה 7 ברמה הנמוכה ביותר )0( של העץ, שם המנעול תפוס ע"י p0 - תהליך p2 מתחיל רק כעת לבצע את קטע הכניסה ומגיע להמתנה בשורש )רמה 1( לאחר שעלה מתת-העץ הימני בשורה 7 - כעת, תהליך p0 מבצע את קטע היציאה והתהליך הבא להיכנס הוא p2 ולא p1 הפרה עמ' 3 מתוך 7
של.FIFO )11 נק'( שלומציונה ערכה שינוי קל בקטע היציאה של האלגוריתם: במקום לשחרר את המנעולים מלמעלה למטה )החל מן השורש(, האלגוריתם החדש שלה משחרר את המנעולים בקטע היציאה בסדר הפוך מלמטה למעלה. זרובבל טען כי האלגוריתם של שלומציונה שגוי הוא אינו מקיים את תכונת המניעה ההדדית, אבל שלומציונה לא הסכימה אתו. מי מהם צודק? האם האלגוריתם החדש מקיים את תכונת המניעה ההדדית?.ii זרובבל צודק, האלגוריתם החדש מפר את תכונת המניעה ההדדית, להלן תסריט עבור עץ ל- 4 תהליכים. - p0 תופס את המנעול בצומת שלו ברמה 0, תופס את המנעול בשורש ונכנס לקטע הקריטי מבצע את קטע הכניסה, ממתין על הצומת ברמה 0 התפוס ע"י p0 p1 - - p0 מתחיל לבצע את קטע היציאה, אבל ראשית משחרר את המנעול שתפס ברמה 0 )בגלל השינוי באלגוריתם( ובכך מאפשר ל p1 לעלות לשורש. מאחר ו b[1,1]=false )כי שום תהליך לא ניסה עדיין לעלות לשורש מתת העץ הימני(, p1 מצליח לתפוס את המנעול בשורש ונכנס לקטע הקריטי - p2 מבצע את קטע הכניסה, עולה מתת העץ הימני של השורש וממתין על השורש )ברמה 1( משום ש b[1,0]=true ובכך מאפשר גם ל p2 b[1,0]:=false מבצע את קטע היציאה על השורש וכותב p0 - להיכנס לקטע הקריטי הפרה של מניעה הדדית 3. תזמון )33 נקודות( א. )17 נק'( חמש משימות (jobs) מגיעות למערכת בזמן 0 על פי הסדר בו הן מופיעות בטבלה להלן: Job Burst Time A 10 B 20 C 5 D 15 E 25 עבור כל אחד מן האלגוריתמים הבאים, ציירו טבלת Gantt המייצגת את הריצה וחשבו את ה- time average turnaround ואת ה- time.average waiting הניחו כי הזמן לביצוע context switch הוא :0 First come, first served.i עמ' 4 מתוך 7
average turnaround time: (10+30+35+50+75)/5 = 40 average waiting time: (0+10+30+35+50)/5 = 25 A B C D E 0 10 30 35 50 75 Shortest job first.ii average turnaround time: (5+15+30+50+75)/5 = 35 average waiting time: (0+5+15+30+50)/5 = 20 C A D B E 0 5 15 30 50 75 Round robin עם time quantum של 10 יחידות זמן..iii A B C D E B D E 0 10 20 25 35 45 55 60 75 average turnaround time: (10+25+55+60+75)/5 = 45 average waiting time: (0+35+20+45+50)/5 = 30 )8 נק'( במערכת XV6 ראינו כי context switch בין תהליכים מתבצע באמצעות הפונקציה swtch שהקוד שלה מופיע בסוף שאלה זו. בקוד אנו רואים גיבוי של 4 רגיסטרים )מתוך החמישה הקיימים במבנה הנתונים )context אל המחסנית של התהליך הישן, ושיחזור של 4 רגיסטרים אלו מן המחסנית של התהליך החדש. כיצד מגבים/משחזרים את הכתובת אליה הביצוע צריך לחזור בתום הקריאה ל?swtch.i ב. הקריאה לפונקציה swtch אנו נמצאים. מכניסה את כתובת החזרה למחסנית והקריאה לret משחזרת אותה לפי המחסנית שבה )8 ii נק'( בעבודה מספר 2 ביטלתם את השימוש במנעול.ptable.lock בכל מקום בו נתפס קודם לכן המנעול, בוצעה במימוש העבודה חסימה של ה- interrupts, וכן נעשה שימוש בפעולת compare-and-swap על מנת לשנות את המצב של תהליך באופן אטומי. שינוי נוסף שנדרש הוא הוספת מצבים חדשים )עם ערכים שליליים( עבור התהליכים. שאלה זו עוסקת בצורך במצבים חדשים אלו. תארו בקצרה אך במדויק תסריט של תקלה שעלולה הייתה להתרחש במערכת אם לא היינו מוסיפים מצבים אלו. כיצד הוספת המצבים החדשים מונעת תקלה אפשרית זו? עמ' 5 מתוך 7
CPU 1 עובר למצב RUNNABLE ולא מסיים לבצע את CPU 2,swtch מוצא את התהליך ובוחר בו )כיוון שהוא במצב )RUNNABLE ומנסה לשחזר בswtch את הגיבוי של הרגיסטרים אך משום שהגיבוי לא הסתיים כותב לרגיסטרים ערכים שגויים. # Context switch # # void swtch(struct context **old, struct context *new); # # Save the current registers on the stack, creating # a struct context, and save its address in *old. # Switch stacks to new and pop previously-saved registers..globl swtch swtch: movl 4(%esp), %eax movl 8(%esp), %edx # Save old callee-save registers pushl %ebp pushl %ebx pushl %esi pushl %edi # Switch stacks movl %esp, (%eax) movl %edx, %esp # Load new callee-save registers popl %edi popl %esi popl %ebx popl %ebp ret ב ה צ ל ח ה! עמ' 6 מתוך 7
עמ' 7 מתוך 7