יפו – המכללה האקדמית תל אביב – מערכות הפעלה 2# תרגיל בית מספר ~1~

‫מערכות הפעלה – המכללה האקדמית תל אביב – יפו‬
‫סמסטר א‪ ,‬תשע"ז‬
‫מרצה‪ :‬כרמי מרימוביץ‪ .‬מתרגל‪ :‬צבי מלמד‬
‫תרגיל בית מספר ‪#2‬‬
‫הוראות כלליות‬
‫בתרגיל הזה שאלה אחת בסביבת ‪ ,XV6‬שכוללת שתי משימות‪:‬‬
‫‪ .1‬מימוש סמפורים ב‪XV6 -‬‬
‫‪ .2‬כתיבת תכנית בדיקה שמשתמשת בסמפורים האלו‪.‬‬
‫הגשה עד עד ליום רביעי ‪ 11/1/17‬בחצות‬
‫הוראות ההגשה המפורטות נמצאות באתר‪.‬‬
‫הסביבה הקובעת להרצת התרגילים היא סביבת ‪ BOCHS +CYGWIN‬במעבדה‬
‫יש לבדוק ולהריץ את התרגיל כאשר האמולטור עובד לפחות עם ‪ 2‬מעבדים‪ .‬מומלץ לבדוק גם עם ‪ 4‬מעבדים‪.‬‬
‫(אתם מגדירים את זה בקובץ ‪ ,bochsrc‬חפשו אחר המילה ”…= ‪.)“cpu: count‬‬
‫‪‬‬
‫הבהרה‪ :‬אתם יכולים לפתח ולפתור את התרגילים בסביבה הנוחה לכם‪ .‬אבל‪ ,‬לפני ההגשה‪ ,‬חובתכם לוודא‬
‫שהתרגילים רצים בסביבה הפורמלית‪-‬סטנדרטית‪.‬‬
‫הנחיות לגבי עבודת צוות‬
‫הגשת התרגיל ביחידים‬
‫היזהרו מהעתקות! ראו הוזהרתם‪.‬‬
‫למרות האמור בסעיף הקודם – מותר ששני תלמידים (לכל היותר!) יעבדו על התרגיל ביחד‪ .‬במקרה כזה‪ ,‬יהיה‬
‫להם קוד זהה (מן הסתם)‪ .‬במקרה כזה חובה עליהם לציין בתחילת קובץ ה ‪ READ_ME‬את השמות של שניהם‬
‫כולל ת‪.‬ז‪ .‬של שניהם!)‪.‬‬
‫~‪~1‬‬
‫שאלה ‪ - #1‬משימה א'‪ :‬מימוש סמפורים ב ‪XV6‬‬
‫עליכם לממש קריאות מערכת שמממשת סמפורים על פי ה ‪ API‬שמוגדר להלן‪ .‬עליכם לתעד את אופן מימוש‬
‫הסמפורים ואת תכנית המשתמש שכתבתם‪ .‬את התיעוד שצריך להיות קצר כתבו בקובץ טקסט שנקרא‬
‫‪ READ_ME.txt‬או ‪.READ_ME.PDF‬‬
‫תיאור כללי‪:‬‬
‫התנהגות הסמפורים כאן‪ ,‬דיי דומה (אבל לא זהה) להתנהגות הסמפורים שמתוארת ב ‪man 7 sem_overview‬‬
‫בסביבת לינוקס‪.‬‬
‫במובנים מסוימים הטיפול בסמפורים יהיה דומה לטיפול ב ‪ – file-descriptors‬כלומר‪ ,‬יהיה לנו מערך של ‪OSEM‬‬
‫(בדומה למערך של ‪ .)OFILE‬בקשה לפתיחת סמפור תחזיר ‪ – SD‬כלומר ‪ ,semaphore-descriptor‬ובהמשך‪,‬‬
‫קריאות מערכת יקבלו את ה ‪ SD‬הזה כארגומנט‪ ,‬ועל פיו הקרנל יודע באיזה סמפור מדובר‪.‬‬
‫לסמפור יש ערך התחלתי וערך מקסימלי שאליו הוא יכול להגיע (כולל) אבל לא לעבור אותו‪.‬‬
‫פתיחת סמפור מתבצעת ע"י ‪ .sem_open‬אם סמפור בשם הזה כבר קיים‪ ,‬אזי מתעלמים מהארגומנטים של ערכי‬
‫האיתחול והערך המקסימלי‪ .‬אם הוא לא קיים עדיין במערכת‪ ,‬אזי יוצרים סמפור כזה‪ ,‬ומאתחלים אותו בערכים‬
‫‪.init and maxVal‬‬
‫הפונקציה ‪ sem_close‬סוגרת את הסמפור‪ ,‬אבל איננה משחררת את המשאב‪ .‬כלומר‪ ,‬גם אם כל התהליכים סגרו‬
‫את הסמפור ‪ sem_x‬שהם פתחו‪ ,‬הסמפור ‪ sem_x‬נשאר במערכת‪ ,‬ואיננו נמחק‪.‬‬
‫הפונקציה ‪ sem_unlink‬מוחקת את הסמפור מהמערכת‪ .‬יתכן מצב שמריצים את הפונקציה הזאת‪ ,‬אבל ישנם‬
‫תהליכים שעדיין מצביעים לסמפור הזה‪ .‬במקרה כזה‪ ,‬הפעולה הזאת תיחסם‪ ,‬עד שאחרון הסמפורים ייסגר ( ‪g”h‬‬
‫הקריאה )(‪ sem_close‬או ע"י ביצוע ‪ .)exit‬במקרה כזה הפעולה הזאת תתבצע כאשר יקרה ה‪ close() -‬האחרון על‬
‫הסמפור המדובר‪( .‬עדכון בדרישה ‪.)1.1.17‬‬
‫הפונקציות )(‪ sem_wait() and sem_post‬פועלות כרגיל וכמצופה‪.‬‬
‫הפונקציה ‪ sem_try_wait‬פועלת בדומה למקבילה לה בלינוקס – היא מנסה לבצע ‪ WAIT‬אבל איננה נחסמת אם‬
‫ערך הסמפור הוא ‪( 0‬כמובן שבאמצעות הערך המוחזר המשתמש יכול לדעת מה קרה למעשה)‪.‬‬
‫הפונקציה ‪ sem_get_value‬מחזירה את ערכו הנוכחי של הסמפור וכן את ה ‪ maxVal‬שלו‪.‬‬
‫פרוטוטייפ הפונקציות‪:‬‬
‫;)‪sem_open(char *name, int init, int maxVal‬‬
‫;)‪sem_close(int sd‬‬
‫;)‪sem_wait(int sd‬‬
‫;)‪sem_try_wait(int sd‬‬
‫;)‪sem_signal(int sd‬‬
‫;)‪sem_get_value(int sd, int *val‬‬
‫‪sem_unlink(char *name); //‬‬
‫שימו לב לתיקון ההגדרה!!‬
‫~‪~2‬‬
‫‪int‬‬
‫‪int‬‬
‫‪int‬‬
‫‪int‬‬
‫‪int‬‬
‫‪int‬‬
‫‪int‬‬
:‫להלן תיאור ההתנהגות הדרושה מכל אחת מהפונקציות‬
int sem_open(char *name, int init, int maxVal)
Open a semaphore and return SD (semaphore-descriptor) to it.
.‫ אליו‬SD ‫ והחזר את ה‬,‫פתח סמפור‬
‫ הסמפור הזה כבר מוגדר (קיים) או שהוא‬:‫כאשר תהליך מבצע קריאה לפונקציה הזאת – יכולים להיות שני מקרים‬
.‫עדיין איננו קיים במערכת‬
,‫ אם סמפור כזה איננו קיים במערכת‬.init and maxVal ‫ מתעלמים מערכי‬,‫אם סמפור כזה קיים כבר במערכת‬
.‫ ומאתחלים את הערכים שלו בהתאם לארגומנטים של הפונקציה‬,‫אזי יוצרים אותו‬
-‫ הכניסה המתאימה במערך ה‬,‫ המתאים (כלומר‬SD ‫ כאשר הפונקציה מצליחה היא מחזירה את ה‬:‫ערך מוחזר‬
‫ ואין אפשרות ליצור‬,‫ הסתיימה מכסת הסמפורים במערכת‬,‫ אם הפונקציה נכשלה מסיבה כלשהי (למשל‬.)OSEM
.-1 ‫עוד סמפור) אזי היא תחזיר‬
:‫בהקשר למגבלת המשאבים שימו לב להערה הבאה‬
a) A kernel constant NOSEM (Number of Open Sem) limits the number of open semaphores per
process, similar to the NOFILE. Of course, if the call might result in exceeding NOSEM (per this
process), the call fails and returns -1.
b) A kernel constant NSEM limits the total number of active semaphores in the system. This is similar
to NFILE.
c) The length of semaphore-name <= 6 .
int sem_close(int sd);
a) This call closes an open semaphore. Error code of -1 is returned if sd is index of non-valid entry in
the process semaphore table.
b) If this is the last reference to the semaphore, then we check if there’s a pending sem_unlink wanting
to destroy the semaphore. Note: the semaphore as a system wide resource is not destroyed, just
like when you close a file, it is not erased from the disk. (1.1.17 notice, there was a slight change to
the requirement here from the original one).
int sem_wait(int sd);
This is standard wait operation of semaphores.
a) if sem > 0 then this call decrements the value of the semaphore. Otherwise the process is put to
sleep until this condition is true.
b) The process wakeups when the decrement is possible, and checks again the condition in (a).
int sem_trywait(int sd);
~3~
This call tries to decrement the value of the semaphore, provided it does not become negative. If this is
possible, the call returns 0 (success).
If this is not possible, the call returns -1 (failure), but the process is NOT suspended.
‫ אזי סימן‬,0 ‫ אם הקריאה החזירה‬.‫ הקריאה הזאת איננה גורמת לתהליך להיחסם‬,‫ בכל מקרה‬,‫במלים אחרות‬
‫ אזי זה סימן‬-1 ‫ אם הקריאה מחזירה‬.)‫שהסמפור הוקטן באחד (כלומר היה בערך גדול מאפס בזמן שערכון נדגם‬
.)‫שהסמפור לא הוקטן באחד (כלומר היה בערך אפס בזמן שערכו נדגם‬
int sem_signal(int sd);
This is standard ‘signal’ (or ‘post’) operation of semaphores.
a) The value of the semaphore is incremented.
b) wakeup other processes who might be waiting on this semaphore.
c) If sd is not an open semaphore entry, the call returns -1.
d) If the value of the semaphore is already at maxVal (as set in sem_open) then the value is not
incremented, and the call returns failure code of -2.
int sem_get_value(int sd, int *val, int* maxVal);
.‫ את ערכו המקסימלי‬maxVal ‫ ובמשתנה‬,‫ את ערכו הנוכחי של הסמפור‬val ‫הקריאה הזאת מחזירה במשתנה‬
‫ מצביע על כניסה מאופסת‬sd ‫ אם משהו לא תקין (למשל‬-1 -‫ ו‬,‫ אם הכל תקין‬0 ‫הערך המוחזר של הקריאה הוא‬
.)OSEM ‫בטבלת ה‬
int sem_unlink(char *name);
.‫שימו לב לשינוי ההגדרה‬
!‫הקריאה הזאת מוחקת את הסמפור מהמערכת‬
‫ אם תהליכים כלשהם מצביעים אל‬,‫הסמפור נמחק מהקרנל רק כאשר אין עוד מצביעים אל הסמפור! כלומר‬
‫ זהו תיקון לדרישה‬:1.1.2017( !‫ אזי הקריאה הזאת נחסמת עד שכולם סגרו את הסמפורים שלהם‬.‫הסמפור‬
‫ בזמן‬,‫ על הסמפור‬OPEN ‫ אין למנוע מתהליכים לבצע פעולות‬.)‫ בגירסה הראשונה של הדרישות‬,‫הקודמת‬
.sem_unlink ‫שתהליך אחד (או יותר) ממתינים לביצוע‬
init and ‫ יוצר סמפור חדש במערכת מאתחל את ערכי‬,‫ מיד לאחר הפעולה הזאת‬sem_open ‫תהליך שיבצע‬
.‫ שלו‬maxVal
Suggestions
 Don’t invent the wheel, and don’t invent the semaphore. Refer to handling
of files (ofile and ftable) to get ideas on ways to implement things.
 Add a global variable stable (semaphore table) to the system, defined as
follows:
struct {
struct spinlock gslock; //global semaphore lock
struct {
~4~
‫‪struct spinlock sslock; // single semaphore lock‬‬
‫‪.....‬‬
‫‪.....‬‬
‫;]‪} sem[NSEM‬‬
‫;‪} stable‬‬
‫‪Use gslock for sem_open and sem_close and sem_unlink.‬‬
‫‪Use sslock for sem_wait, sem_trywait and sem_signal.‬‬
‫‪Consider the fork() – child should inherit all opened semaphores from the‬‬
‫‪parent.‬‬
‫‪Consider the implications of exit().‬‬
‫‪Consider the implication of exec() – unlike fork() - the new program does‬‬
‫‪NOT get the semaphores.‬‬
‫‪‬‬
‫‪‬‬
‫‪‬‬
‫‪‬‬
‫‪‬‬
‫(הערה‪ :‬זאת איננה ההתנהגות המצופה "בחיים האמיתיים"‪ .‬כלומר‪ ,‬בד"כ כשעושים ‪ .EXEC‬ניתן לצפות שהתכנית‬
‫החדשה תקבל את הסמפורים של התכנית הישנה‪ ,‬בדיוק כשם שהיא מקבלת את הקבצים הפתוחים‪ .‬ההגדרה‬
‫השונה כאן היא למטרות התרגיל הזה)‪.‬‬
‫הערות ודגשים‬
‫א‪.‬‬
‫ב‪.‬‬
‫שני הערכים ‪ NSEM‬ו‪ NOSEM -‬יוגדרו בקובץ ‪ .param.h‬התכנית שלכם צריכה לעבוד עם ערכים כלשהם‬
‫– כלומר‪ ,‬יתכן שהבודק יריץ את התרגיל שלכם עם קובץ ‪ param.h‬משלו ‪ -‬אותם שמות‪ ,‬אבל ערכים‬
‫אחרים‪ .‬התרגיל שלכם צריך להתקמפל ולרוץ באופן תקין‪ .‬כמובן‪ ,‬שאי הקפדה על דיוק בכתיבת השמות‬
‫תגרום לכך שהקומפילציה תיפול‪.‬‬
‫קובץ ה ‪ Makefile‬שלכם צריך להכיל כניסה בהגדרה של ‪ UPROGS‬בשם ‪ test‬שמתאימה לקובץ עם‬
‫תכנית משתמש ‪ .test.c‬הבודק יריץ את התרגיל שלכם עם קובץ ‪ test.c‬משלו‪ ,‬והתרגיל צריך להתקמפל‬
‫"חלק"‪.‬‬
‫שאלה ‪ - #1‬משימה ב' – תכנית משתמש שעובדת עם סמפורים‪.‬‬
‫בתכנית זאת נדמה פעולת יצרן‪-‬צרכן‪ ,‬שכותבים לחוצץ משותף‪ .‬מכיוון שאין לנו חוצץ משותף‪ ,‬לא באמת נכתוב‬
‫הודעות לחוצץ‪ ,‬ובמקום זאת נכתוב אותן לפלט הסטנדרטי‪.‬‬
‫המספר המקסימלי של ההודעות כביכול בחוצץ הוא ‪ .MAX_MSG‬כלומר‪ ,‬אם למשל ‪ MAX_MSG ==3‬אזי אנחנו‬
‫לא מצפים לראות בפלט יותר משלוש הודעות על "הוספה" (כלומר הודעות מ ‪ ,PROD‬היצרן) מבלי שיש הודעות‬
‫על "הסרה" (הדעות שיגיעו מ‪ – CONS -‬הצרכן)‪.‬‬
‫א‪.‬‬
‫תהליך בשם ‪( prod‬קיצור של ‪ )producer‬מייצר הודעות‪ .‬כל הודעה נוצרת ע"י קריאה לפונקציה‬
‫‪ write_prod_msg‬שהפרוטוטייפ שלה הוא‪:‬‬
‫;()‪void write_prod_msg‬‬
‫~‪~5‬‬
‫ב‪.‬‬
‫תהליך בשם ‪( cons‬קיצור של ‪ )consumer‬קורא הודעות‪ .‬אנו מדמים קריאת הודעה ע"י קריאה לפונקציה‬
‫‪ write_cons_msg‬שהפרוטוטייפ שלה הוא‪:‬‬
‫;()‪void write_cons_msg‬‬
‫ג‪.‬‬
‫תכנית בשם ‪( pc‬קיצור של ‪ )producer-consumer‬מקבלת ‪ 3‬עד ‪ 4‬ארגומנטים‪:‬‬
‫‪)1‬‬
‫‪)2‬‬
‫‪)3‬‬
‫‪)4‬‬
‫כמה תהליכים ‪ PROD‬לייצר‬
‫כמה תהליכים ‪ CONS‬לייצר‬
‫כמה הודעות מייצר כ"א מה ‪PROD‬‬
‫הארגומנט הרביעי הוא אופציונלי‪ :‬אם הוא ניתן‪ ,‬אז זה שם קובץ‪ ,‬שצריך לעשות אליו‬
‫‪ REDIRECTION‬לפלט הסטנדרטי של כל התהליכים ‪ PROD‬ו‪.CONS -‬‬
‫התכנית ‪ PC‬מתנהגת באופן הבא‪:‬‬
‫א‪ .‬היא יוצרת תהליכים ‪ PROD‬ו‪ CONS -‬במספר הדרוש‬
‫ב‪ .‬כותבת למסך הודעה בסגנון‬
‫******************* ‪PARENT pid = 5 created all children‬‬
‫ההודעה הזאת נוצרת ע"י קריאה לפונקציה )(‪.write_parent_msg‬‬
‫הפעולה של התהליכים ‪ cons and prod‬מתחילה רק לאחר שהתהליך ההורה סיים להדפיס את ההודעה שלו‪.‬‬
‫כמובן‪ ,‬שעליכם לוודא שדרישות הסנכרון מתקיימות‪:‬‬
‫‪‬‬
‫‪‬‬
‫‪‬‬
‫‪‬‬
‫הבנים לא מתחילים לפעול לפני שתהליך האב יצר את כל הבנים‪.‬‬
‫תהליכי ‪ PROD‬לא מייצרים יותר מאשר ‪ MAX_MSG‬הודעות שלא נצרכו‬
‫תהליכים ‪ CONS‬לא צורכים הודעה לפני שהיא נוצרה‬
‫כל תהליך ‪ PROD‬יודע כמה הודעות הוא צריך לייצר – זה הארגומנט השלישי בהפעלת ה ‪.PC‬‬
‫תוספות בעקבות שאלות שנשאלתי‪:‬‬
‫‪‬‬
‫‪‬‬
‫‪‬‬
‫תהליכי ‪ CONS‬לא יודעים כמה הודעות הם צריכים לצרוך (וזה גם יכול להתחלק ביניהם בצורה לא שווה!!‬
‫צריך מנגנון סנכרון שיאפשר לתהליכי ‪ CONS‬להסתיים כשכל ההודעות נצרכו‪ .‬עדיף לא להשתמש ב ‪KILL‬‬
‫למטרה הזאת‪.‬‬
‫כל ההודעות צריכות להיות מודפסות בשלמותן! זה חלק מהסנכרון הדרוש (הדפסת הודעה מדמה גישה‬
‫למשאב משותף)‪.‬‬
‫לצורך התרגיל עליכם לייצר קובץ שנקרא ‪ pc.c‬ולעדכן את ה ‪ MAKEFILE‬בהתאם‪ .‬הקובץ הזה יעשה ‪INCLUDE‬‬
‫כזה‪( :‬קצת לא יפה‪ ..‬אבל זה עובד)‪:‬‬
‫"‪# include "pc_def.c‬‬
‫הקובץ הזה כבר כתוב‪ .‬הוא כולל את הפונקציות שהוזכרו כאן (אין לשנות אותן)‪ .‬אתם יכולים לשנות בו את‬
‫הקבועים כרצונכם‪ .‬הבודק גם הוא יכול לשנות אותם‪ .‬עליכם להגיש את התכנית כולל הקובץ הזה כמובן‪.‬‬
‫~‪~6‬‬
‫תורידו את הקובץ הזה מהקישור הבא‪http://tzvimelamed.com/lab/files/pc_def.c :‬‬
‫הערה‪ :‬בנוסף לתכנית הנ"ל ‪ -‬כמו בתרגיל מספר ‪ – #1‬גם בתרגיל הזה‪ ,‬עליכם ליצור כניסה ב ‪MAKEFILE‬‬
‫לתכנית משתמש בשם ‪( .test‬שנמצאת בקובץ ‪ .)test.c‬הבודק ישתמש בקובץ שלו באותו שם‪ ,‬בכדי לבדוק את‬
‫התכנית שלכם‪ .‬כמובן‪ ,‬שההגשה שלכם צריכה לכלול קובץ בשם ‪ test.c‬שמכיל תכנית משתמש כלשהי‪( .‬אפילו‬
‫‪ ..hello-world‬אבל עוברת קומפילציה!‬
‫בהצלחה!!‬
‫~‪~7‬‬