הרצאה שבוע #1 + #2 – שיתוף בין תהליכים

‫שבוע ‪#01‬‬
‫פרק‪Process Synchronization :‬‬
‫סינכרון בין תהליכים‬
‫קורס מערכות הפעלה א'‬
‫מכללת הדסה ‪ /‬מכללה חרדית‬
‫צבי מלמד‬
‫‪[email protected]‬‬
‫הרצאות הקורס מבוססות במידה רבה ביותר על ההרצאות של ד"ר יורם ביברמן‬
‫© כל הזכויות שמורות לד"ר יורם ביברמן ולצבי מלמד‬
‫©צבי מלמד‬
‫‪1‬‬
‫‪a cooperating process‬‬
‫• תהליך משתף‪-‬פעולה )‪(a cooperating process‬‬
‫– תהליך שעשוי להשפיע או להיות מושפע מתהליכים אחרים‬
‫במערכת‪.‬‬
‫– עשוי לחלוק מרחב כתובות )בפרט נתונים( עם אחרים‬
‫– עשוי להעביר מידע דרך ערוצי תקשורת עם עמיתים‪.‬‬
‫• בפרק זה‪ ,‬כשנדבר על תהליך אנו נתכוון גם לפתיל‬
‫– שיתוף נתונים עם פתילים אחרים רלבנטי במיוחד לגבי פתילים‬
‫©צבי מלמד‬
‫‪2‬‬
‫רקע ‪Background‬‬
‫•‬
‫תיפעול זוג תהליכים‪ :‬יצרן וצרכן‬
‫– מעבירים זה לזה הודעות‬
‫– באמצעות זיכרון משותף המנוהל כתור‬
‫– מימוש במערך מעגלי בגודל ‪ BUFFER_SIZE‬תאים‬
‫•‬
‫אופן הפעולה‪:‬‬
‫– היצרן מוסיף איברים לתור‪ ,‬והצרכן גורע‪.‬‬
‫•‬
‫משתני עזר‪:‬‬
‫– מונה )‪ (counter‬המציין כמה איברים מצויים בתור בכל נקודת זמן‪ .‬היצרן‬
‫מגדיל את המונה‪ ,‬הצרכן מקטין‪.‬‬
‫– המשתנה ‪ in‬מציין לאן מכניס היצרן את האיבר הבא‪,‬‬
‫– המשתנה ‪ out‬יציין מהיכן יצרוך הצרכן את האיבר הבא‪.‬‬
‫– ‪ in, out‬מאותחלים לאפס‪.‬‬
‫©צבי מלמד‬
‫‪3‬‬
‫מבוא )דוגמא( – קוד היצרן‬
‫•‬
‫תיאום‪:‬‬
‫– כאשר התור מלא על היצרן להמתין‪ ,‬כאשר התור ריק על הצרכן להמתין‪.‬‬
‫•‬
‫קוד היצרן‪:‬‬
‫{ )‪while (1‬‬
‫‪while (counter == BUFFER_SIZE) ; // busy wait‬‬
‫; ‪buffer[in] = new_item‬‬
‫; ‪in = (in +1) % BUFFER_SIZE‬‬
‫; ‪counter++‬‬
‫}‬
‫©צבי מלמד‬
‫‪4‬‬
‫מבוא )דוגמא( – קוד הצרכן‬
:‫• קוד הצרכן סימטרי‬
while (1) {
while (counter == 0) ; //busy wait
new_item
= buffer[out] ;
out = (out +1) % BUFFER_SIZE ;
counter-- ;
}
5
‫©צבי מלמד‬
‫דוגמת מבוא – תיזמון אפשרי ותוצאתו‬
‫•‬
‫עתה נניח שהתור מכיל ‪ 17‬פריטים‪.‬‬
‫היצרן והצרכן פונים אליו במקביל‪,‬‬
‫בפרט מתפעלים במקביל את ‪:counter‬‬
‫האחד מגדיל את ערכו‪ ,‬השני מקטינו‪.‬‬
‫•‬
‫נניח שמערכת ההפעלה מתזמנת את‬
‫הפעולות באופן הבא )תוך שהמעבד‬
‫מבצע לסירוגין כל תהליך(‬
‫התוצאה‪ :‬ערכו של המונה הוא ‪16‬‬
‫)ולא ‪ 17‬כפי שראוי(‪.‬‬
‫הסיבה‪ :‬שני התהליכים פנו למשתנה‬
‫המשותף במקביל‪ ,‬ותפעלו אותו‬
‫באופן לא מתואם\מסונכרן‬
‫צרכן‬
‫יצרן‬
‫‪reg1 = counter‬‬
‫)‪(reg117‬‬
‫‪reg2 = counter‬‬
‫)‪(reg217‬‬
‫‪reg1++‬‬
‫)‪(reg118‬‬
‫‪reg2-‬‬‫)‪(reg216‬‬
‫‪counter = reg1‬‬
‫)‪(counter18‬‬
‫‪counter = reg2‬‬
‫)‪(counter16‬‬
‫©צבי מלמד‬
‫‪6‬‬
‫מבוא )המשך(‬
‫• תקלה דומה אך שונה‪:‬‬
‫– עדכונו של המשתנה )על‪-‬פי ערכו של אוגר מתאים( ע"י תהליך‬
‫א' לוקח יותר ממחזור זיכרון יחיד‬
‫– בין תחילת העדכון לסיומו מתעניין תהליך ב' בערכו של‬
‫המשתנה‪ ,‬ועל כן מקבל ערך שגוי‪.‬‬
‫©צבי מלמד‬
‫‪7‬‬
‫מצבי מירוץ ‪Race Condition -‬‬
‫• מצבי מרוץ )‪ :(race condition‬מצב שבו התוצאה תלויה בתזמון‬
‫המקרי בו המחשב הריץ את התהליכים )ולכן התוצאה עלולה‬
‫להיות שגויה(‪.‬‬
‫• הפתרון‪ :‬מנגנון שיאפשר רק לתהליך יחיד לפנות למשתנה ‪counter‬‬
‫בפרק זמן כלשהו‪.‬‬
‫• הנושא הזה באופן כללי נקרא‪:‬‬
‫תיאום בין תהליכים או ‪process synchronization‬‬
‫©צבי מלמד‬
‫‪8‬‬
‫מנעול ‪lock‬‬
‫•‬
‫•‬
‫•‬
‫פתרון אפשרי לבעיה‪ :‬מנעול )‪.(lock‬‬
‫– רק תהליך יחיד יכול לנעול את המנעול בכל יחידת זמן‪.‬‬
‫– על המנעול מוגדרות פעולות אטומיות של נעילה ושחרור‬
‫נעילה\רכישה )‪:(lock/acquire‬‬
‫– תהליך המבצע פעולה זו נחסם עד שהמנעול מתפנה‪ ,‬ואז הוא יכול להתקדם‬
‫– מובטח שהוא התהליך היחיד שרכש את המנעול )ועל כן היחיד שפונה‬
‫למשתנה(‬
‫פתיחה\שחרור )‪:(unlock/release‬‬
‫– שחרור המנעול‪ ,‬כך שתהליך אחר יוכל לרכוש )לנעול ( אותו‪.‬‬
‫– הטיפול במשתנים המשותפים‪ ,‬אליהם נרצה לאפשר גישה בלבדית‬
‫)‪ (exclusive‬יחייב ראשית רכישת המנעול‪.‬‬
‫– בתום הטיפול במשתנים ישוחרר המנעול‪.‬‬
‫©צבי מלמד‬
‫‪9‬‬
‫מנעול ‪lock‬‬
‫• עבודה עם המנעול באופן הזה תבטיח שלא ייווצר מצב מרוץ‪.‬‬
‫• רעיון אפשרי‪:‬‬
‫– נגדיר משתנה בולאני שיהווה את המנעול‪.‬‬
‫– המשתנה עשוי להיות פתוח או סגור )‪(true or false‬‬
‫©צבי מלמד‬
‫‪10‬‬
‫בעיה ביישום פתרון המנעול‬
‫•‬
‫בכדי לרכוש את המנעול מבצע‬
‫התהליך שתי פעולות‪:‬‬
‫תהליך ב'‬
‫קרא את מצב‬
‫המנעול‬
‫– )א( בדיקת ערכו של המנעול‬
‫– )ב( עדכון ערכו‬
‫קרא את מצב‬
‫המנעול‬
‫– יש חשש למצב הבא )נניח‬
‫שהמנעול פתוח(‪:‬‬
‫– ‪‬ולא השגנו בלבדיות בפניה‬
‫למשתנים המשותפים‪.‬‬
‫תהליך א'‬
‫אם ערכו פתוח אזי‬
‫עדכן את ערכו‬
‫לסגור‬
‫אם ערכו פתוח אזי‬
‫עדכן את ערכו‬
‫לסגור‬
‫טפל במשתנים‬
‫המשותפים‬
‫טפל במשתנים‬
‫המשותפים‬
‫©צבי מלמד‬
‫‪11‬‬
‫בעיה ביישום פתרון המנעול‬
‫מסקנה‪:‬‬
‫• מנעול הוא רעיון יפה‪ ,‬אבל צריך דרך )יותר( מעודנת‪ ,‬מתוחכמת‬
‫לממשו מאשר משתנה בוליאני פשוט שערכו נבדק ובמידת האפשר‬
‫מעודכן‬
‫©צבי מלמד‬
‫‪12‬‬
‫פתרונות לבעית מימוש המנעול‬
‫• שלושה סוגי פתרונות לבעיה כיצד לממש מנעול ולהשיג בלבדיות בטיפול‬
‫במשתנים‪:‬‬
‫– פתרונות תכנה‪:‬‬
‫• פרוטוקולים )=אוסף כללים( שיקבעו כיצד על התהליכים לנהוג ע"מ‬
‫שתושג הבלבדיות‪.‬‬
‫– פתרונות חומרה‬
‫• מסייעים להשיג את התוצאה הרצויה‪.‬‬
‫– פתרונות תכנה מרמה גבוה‬
‫• מבני נתונים ופעולות מרמה גבוהה‪,‬שית ַ מכו ע"י הקומפיילר‪ ,‬ויאפשרו‬
‫להשיג את הבלבדיות בנוחות‬
‫• מימוש הפעולות על ידי הקומפיילר‪) ,‬בד"כ תוך שימוש במנגנוני חומרה(‬
‫©צבי מלמד‬
‫‪13‬‬
‫בעיית הקטע הקריטי‬
‫‪The Critical-Section Problem‬‬
‫•‬
‫•‬
‫•‬
‫נניח מערכת בת ‪ n‬תהליכים‪ .‬לכל אחד מהתהליכים יש קטע קריטי שבו הוא פונה‬
‫למשתנים או קבצים משותפים ועליו לעשות זאת באופן בלבדי‪.‬‬
‫משמעות המונח קטע קריטי‪:‬‬
‫– לכל אחד מ‪ n-‬התהליכים יש "קטע קריטי"‪ .‬זהו קטע שבו הם ניגשים‬
‫למשאבים משותפים )למשל משתנים או קבצים(‪.‬‬
‫– בגלל בעיות – כגון בעיות מירוץ‪ ,‬אסור שיותר מתהליך אחד ימצא בקטע‬
‫הקריטי שלו בו זמנית‪.‬‬
‫– כלומר‪ ,‬לכל היותר אחד מתוך ‪ n‬התהליכים מבצע בכל נקודת זמן נתונה את‬
‫הקטע הקריטי שלו‬
‫– התהליכים האחרים יבצעו וימצאו באזורים אחרים של התוכנית‪ .‬כלומר או‬
‫שהם ימתינו להכנס לקטע הקריטי או שהם יבצעו את "מה שנשאר" – קטע‬
‫שיורי )‪(remainder section‬‬
‫בעיית הקטע הקריטי‪:‬‬
‫– מה יהיה הפרוטוקול )כללי ההתנהגות( באמצעותו נממש קטע קריטי‪.‬‬
‫©צבי מלמד‬
‫‪14‬‬
‫בעיית הקטע הקריטי‬
‫‪The Critical-Section Problem‬‬
‫• הפרוטוקול יכלול‪:‬‬
‫• קטע כניסה )‪(entry section‬‬
‫– סדרת פעולות אותן צריך לבצע תהליך המעוניין להיכנס‬
‫לקטע הקריטי שלו‪.‬‬
‫• קטע יציאה )‪(exit section‬‬
‫– סדרת פעולות אותן יבצע התהליך בסיום ביצוע הקטע‬
‫הקריטי שלו‬
‫• קטע שיורי )‪(remainder section‬‬
‫– קוד שאינו מטפל ב ַ משתנים המשותפים‬
‫©צבי מלמד‬
‫‪15‬‬
‫בעיית הקטע הקריטי‬
The Critical-Section Problem
16
‫©צבי מלמד‬
‫שלוש הדרישות לבעיית הקטע הקריטי‬
‫•‬
‫בלבדיות )‪:(mutually exclusiveness‬‬
‫– רק תהליך יחיד יוכל לבצע את הקטע הקריטי שלו בכל נקודת זמן‪.‬‬
‫•‬
‫התקדמות )‪:(progress‬‬
‫– אם ‪ m‬תהליכים כלשהם מעוניינים להיכנס לקטע קריטי )כל תהליך לקטע‬
‫קריטי של עצמו(‪ ,‬ואף תהליך אינו מצוי בקטע קריטי‪ .‬אזי תוך זמן סופי‬
‫אחד המעוניינים יזכה להיכנס לקטע קריטי = אין קיפאון‪.‬‬
‫•‬
‫המתנה חסומה )‪ ,(bounded waiting‬אי הרעבה‬
‫– יש חסם על מספר הפעמים שתהליכים אחרים רשאים להיכנס לקטע קריטי‪.‬‬
‫שלהם אחרי שתהליך ‪ P‬ביקש להיכנס לקטע קריטי‪ .‬שלו‪ ,‬ועד שבקשתו‬
‫מסופקת= הוגנות‬
‫©צבי מלמד‬
‫‪17‬‬
‫פתרונות תכנה לזוג תהליכים בלבד‬
‫• ‪Two-Processes Solutions‬‬
‫• נניח זוג תהליכים‪ P0, P1 :‬בלבד‪.‬‬
‫• נניח ‪ -‬כי כל פעולת מכונה‪ ,‬בפרט השמה‪ ,‬היא אטומית‬
‫– )כלומר מתבצעת בשלמותה בלא שניתן יהיה לראות 'תוצאת‬
‫ביניים' של הפעולה(‪.‬‬
‫– הנחה זאת איננה לגמרי מוצדקת‬
‫• הערה‪ :‬במחשבים מודרניים פעולת השמה‪/‬פקודת מחשב עשויה‬
‫להימשך מספר מחזורי זיכרון‪ ,‬ועל‪-‬כן עלול לקרות מצב בו‪ :‬תהליך‬
‫א' מתחיל לעדכן משתנה‪ ,‬תהליך ב' קורא ערך לא תקין של‬
‫המשתנה‪ ,‬תהליך א' מסיים את עדכון ערך המשתנה‬
‫©צבי מלמד‬
‫‪18‬‬
‫פרוטוקול א'‬
‫• התהליכים יחלקו משתנה משותף‪turn :‬‬
‫– כאשר ערכו אפס רשאי ‪ P0‬להיכנס לקטע הקריטי‬
‫– כאשר ערכו אחד‪ ,‬רשאי ‪ P1‬להיכנס לקטע הקריטי‬
‫• המשתנה יאותחל לערך כלשהו‪.‬‬
‫{ )‪while (1‬‬
‫‪while (turn != 0) ; // busy wait‬‬
‫הקטע הקריטי‬
‫; ‪turn = 1‬‬
‫הקוד של‬
‫תהליך ‪P0‬‬
‫הקטע השיורי‬
‫}‬
‫©צבי מלמד‬
‫‪19‬‬
‫בדיקת תכונות פרוטוקול א'‬
‫• נבדוק אילו משלוש הדרישות שהצגנו הפרוטוקול משיג‪:‬‬
‫– בלבדיות ‪‬‬
‫מושגת‪.‬‬
‫אינה בהכרח מושגת‪ P0 :‬לא יוכל להיכנס‬
‫– התקדמות ‪‬‬
‫לקטע הקריטי שלו שוב‪ ,‬עד ש‪ P1 -‬ייכנס לקטע הקריטי שלו‪.‬‬
‫– המתנה חסומה ‪ ‬מושגת‪ :‬אם שני התהליכים רוצים להיכנס‬
‫לקטע הקריטי אזי הם ייכנסו לסירוגין‪.‬‬
‫©צבי מלמד‬
‫‪20‬‬
‫הבעיה של פרוטוקול א'‬
‫• פרוטוקול א' לא התעניין בשאלה‪ :‬האם התהליך האחר בכלל‬
‫מעוניין להיכנס לקטע הקריטי‬
‫• ההנחה‪ :‬הקטע הקריטי מבוצע לסירוגין‬
‫• התור הועבר לתהליך ‪ P0‬או ‪ P1‬בכל מקרה‬
‫• על כן עתה נציע שיפור‪ ,‬נגדיר את המערך‪:‬‬
‫• ; }‪bool want[2] = {false, false‬‬
‫• ‪ want[i] == true‬משמע התהליך ‪ #i‬מעוניין להיכנס לק‪.‬ק‪.‬‬
‫©צבי מלמד‬
‫‪21‬‬
‫הבעיה של פרוטוקול א'‬
‫• פרוטוקול א' לא התעניין בשאלה‪" :‬האם התהליך האחר בכלל‬
‫מעוניין להיכנס לקטע הקריטי"‬
‫• ההנחה‪ :‬הקטע הקריטי מבוצע לסירוגין‬
‫• התור הועבר לתהליך ‪ P0‬או ‪ P1‬בכל מקרה‬
‫©צבי מלמד‬
‫‪22‬‬
‫פרוטוקול ב'‬
‫• נגדיר את המערך‪:‬‬
‫; }‪bool want[2] = {false, false‬‬
‫• ‪ want[i] == true‬משמע התהליך ‪ #i‬מעוניין להיכנס‬
‫לקטע הקריטי‬
‫{ )‪while (1‬‬
‫‪// I want to enter‬‬
‫; ‪want[0] = true‬‬
‫‪// I wait to my pal‬‬
‫;)]‪while (want[1‬‬
‫הקטע הקריטי‬
‫; ‪want[0] = false‬‬
‫הקוד של תהליך ‪P0‬‬
‫הקטע השיורי‬
‫}‬
‫©צבי מלמד‬
‫‪23‬‬
‫בדיקת תכונות פרוטוקול ב'‬
‫• בלבדיות נשמרת‪ :‬אם חברי רוצה להיכנס אני ממתין‪.‬‬
‫• התקדמות‪ :‬עלולה שלא להתקיים‪) .‬ראה הדוגמא(‬
‫‪P0‬‬
‫‪P1‬‬
‫‪want[0]=true‬‬
‫התוצאה‪/‬מסקנה‪:‬‬
‫התהליכים בחסימה הדדית‬
‫‪want[1]=true‬‬
‫;]‪while (want[1‬‬
‫מתקיים‬
‫;]‪while (want[0‬‬
‫מתקיים‬
‫©צבי מלמד‬
‫‪24‬‬
'‫פרוטוקול ג‬
:‫משתנים‬
bool want[2]={false, false};
int turn = 0;
while (1) {
want[0] = true ;
turn = 1 ;
// I want to enter as a gentlemen
// I allow my pal 2 be the 1st
while(want[1] && turn == 1) ;
// I wait while it is his turn
// AND he also wants to enter
‫הקטע הקריטי‬
want[0] = false ;
P0 ‫הקוד של תהליך‬
‫הקטע השיורי‬
{
25
‫©צבי מלמד‬
•
‫פרוטוקול ג' – בדיקת בלבדיות ‪ +‬התקדמות‬
‫• נניח ששני התהליכים מעוניינים להיכנס‬
‫– שניהם מבצעים‪want[i] = true:‬‬
‫– השני מביניהם שיבצע‪ turn = other-process :‬יציב אחרון ערך‬
‫ל‪ turn -‬לפני לולאת ההמתנה‪ ,‬וזה יהיה הערך שיוותר במשתנה‬
‫– ולכן תהליך זה לא יכנס לקטע הקריטי‬
‫– אבל משנהו כן ייכנס‪ ,‬וייכנס לבד‪.‬‬
‫– אם רק אחד מעוניין להיכנס )זאת אומרת שהשני נמצא בקטע‬
‫השיורי( רואים שהוא יוכל להיכנס‪ ,‬וברור שהוא נכנס לבד‬
‫©צבי מלמד‬
‫‪26‬‬
‫פרוטוקול ג' – בדיקת הוגנות‬
‫• השני שהזין ערך למשתנה הוא האיטי יותר‬
‫• עמיתו המהיר ייכנס לקטע הקריטי‪ ,‬יצא‪ ,‬יסמן שאינו מעוניין‬
‫להיכנס‪ ,‬ונניח שאף יספיק לסמן שהוא שוב מעוניין להיכנס‪.‬‬
‫• מיד לאחר מכן‪ ,‬הזריז יעניק את התור ל ַאיטי‪ ,‬ובכך ייתקע את‬
‫עצמו‪ .‬עד מתי?‬
‫• עד שהאיטי ייכנס‪ ,‬יצא‪ ,‬ויסמן שאינו מעוניין;‬
‫• לחילופין‪ ,‬אם האיטי פתאום הפך נורא זריז אזי הוא‪ :‬יסמן שאינו‬
‫מעוניין‪ ,‬שהוא שוב מעוניין‪ ,‬ושעתה תורו של חברו‪.‬‬
‫• בקיצור‪ :‬מי שיצא בהכרח מעניק התור למשנהו‪.‬‬
‫©צבי מלמד‬
‫‪27‬‬
‫• מצאנו פתרון לבעית הקטע‬
‫הקריטי של שני תהליכים‬
‫• בדקנו וראינו שהוא מקיים את‬
‫שלושת הדרישות‪ :‬בלבדיות‪,‬‬
‫התקדמות‪ ,‬ואי‪-‬חסימה‪/‬הוגנות‬
‫פרוטוקול ג' ‪ -‬סיכום‬
‫•‬
‫משתנים‪:‬‬
‫;}‪bool want[2]={false, false‬‬
‫;‪int turn = 0‬‬
‫{ )‪while (1‬‬
‫‪// I want to enter as a gentlemen‬‬
‫‪// I allow my pal 2 be the 1st‬‬
‫; ‪want[0] = true‬‬
‫; ‪turn = 1‬‬
‫; )‪while(want[1] && turn == 1‬‬
‫‪// I wait while it is his turn‬‬
‫‪// AND he also wants to enter‬‬
‫הקטע הקריטי‬
‫; ‪want[0] = false‬‬
‫הקטע השיורי‬
‫הקוד של תהליך ‪P0‬‬
‫{‬
‫©צבי מלמד‬
‫‪28‬‬
‫פתרונות תכנה לתהליכים רבים‬
‫‪Multi Process Solutions‬‬
‫•‬
‫מעודדים מהצלחתינו למצוא פתרון תוכנה לבעית הקטע הקריטי‬
‫לשני תהליכים – ניגש להתמודד עם האתגר הבא‪ :‬פתרון תוכנה‬
‫לאותה בעיה – אבל הפעם‪ ,‬עבור תהליכים מרובים – ‪ n‬תהליכים‬
‫•‬
‫נכיר שני פתרונות‪:‬‬
‫‪ (1‬אלגוריתם )פרוטוקול( המאפיה‬
‫‪ (2‬מימוש מנעולים באמצעות תכנה‬
‫©צבי מלמד‬
‫‪29‬‬
‫אלג' המאפיה ‪Bakery Algorithm‬‬
‫• מקור השם‪ :‬מדוכני מזון מהיר בהם מופעל האלגוריתם‪:‬‬
‫– מי שמגיע מקבל מספר – המספר הבא בתור )פחות או יותר‪( ...‬‬
‫– למה פחות או יותר?‬
‫כי במציאות זה עובד בקלות ופשטות‪...‬‬
‫אבל בתוכנה‪ ,‬אני בודק מה המספר הכי גבוה עד כה‪ ,‬ומוסיף אחד –‬
‫זהו המספר שלי‬
‫אבל במקביל אלי‪ ,‬אולי עוד מישהו עשה תהליך כזה‪ ,‬ואז לשנינו יש‬
‫אותו מספר‬
‫©צבי מלמד‬
‫‪30‬‬
‫אלג' המאפיה ‪Bakery Algorithm‬‬
‫• האלגורתים מניח‪:‬‬
‫– לכל תהליך קיים מזהה ייחודי בתחום ‪.0..n-1‬‬
‫• האלג' עשוי להיות מורץ במערכת מקבילית )בת כמה מעבדים(‪.‬‬
‫• סימונים‪:‬‬
‫– )‪ (a, b) < (c, d‬על‪-‬פי יחס הסדר הלקסיקוגרפי 'הרגיל'‪.‬‬
‫– )‪ max(a0, … ,an-1‬מחזירה ערך הגדול או שווה מכל ה‪ai -‬‬
‫)‪.(i=0, …, n-1‬‬
‫©צבי מלמד‬
‫‪31‬‬
‫אלגוריתם המאפיה ‪Bakery Algorithm‬‬
‫• מבני נתונים‪:‬‬
‫; } ‪bool choosing[n] = { false‬‬
‫• ‪ -‬מציין האם התהליך נמצא כרגע בתהליך בחירת מספר לתור‬
‫)לביצוע הקטע הקריטי(‬
‫; }‪int number[n] = {0‬‬
‫• ‪ -‬מערך של "המספר בתור" של כל תהליך‬
‫©צבי מלמד‬
‫‪32‬‬
‫המספר אינו בהכרח‬
‫ייחודי‪ ,‬בגלל שתהליך‬
‫אחר במעבד )הזה או‬
‫מעבד אחר( עשוי‬
‫לבקש מספר במקביל‬
‫'אלי'‬
‫אלגוריתם המאפיה‬
‫{ )‪while (1‬‬
‫אני כרגע בוחר מספר ‪choosing[me] = true ; //‬‬
‫= ]‪number[me‬‬
‫; ‪max(number[0],...,number[n-1]) + 1‬‬
‫סיימתי לבחור ‪choosing[me] = false ; //‬‬
‫עבור "כל‬
‫האחרים"‬
‫אם מישהו כרגע בוחר‬
‫מספר‪ ,‬אני מחכה‬
‫שיסיים לבחור‬
‫כל עוד האחר רוצה‬
‫להיכנס‪ ,‬והוא בעדיפות‬
‫על‪-‬פני אני ממתין‬
‫{ )‪for (other = 0; other < n; other++‬‬
‫; )]‪while (choosing[other‬‬
‫&& ‪while (number[other] != 0‬‬
‫< )‪(number[other], other‬‬
‫; ) )‪(number[me], me‬‬
‫}‬
‫הקטע הקריטי‬
‫; ‪number[me] = 0‬‬
‫קטע שיורי‬
‫}‬
‫©צבי מלמד‬
‫‪33‬‬
‫פתרון שני באמצעות תכנה‬
‫• פתרון שני לבעיית התיאום )באמצעות תכנה(‪:‬‬
‫– מבוסס על ההנחה שהתוכנית יכולה לחסום פסיקות‪ ,‬ועל ידי‬
‫כך להבטיח שהמעבד לא ייגזל ממנה בין בדיקת תנאי להשמה‪.‬‬
‫– הערה‪ :‬אם הקוד מבוצע ע"י מערכת ההפעלה‪ ,‬למשל קריאת‬
‫מערכת שתבצע את זה – ההנחה הזאת איננה בלתי סבירה‪....‬‬
‫• )בתנאי ש‪ - ...‬איזה תנאי? – כלומר‪ ,‬באיזה מקרה‪ ,‬גם אם ניתן לחסום פסיקות‪,‬‬
‫זה עדיין לא יבטיח לנו את קיום התנאים הדרושים מהקטע הקריטי(‬
‫• על‪-‬סמך ההנחה נוכל לממש מנעולים‪ ,‬כפי שתוארו קודם לכן‪ ,‬באופן‬
‫הבא‪:‬‬
‫©צבי מלמד‬
‫‪34‬‬
‫חסימת פסיקות )פתרון שני באמצעות תכנה(‬
‫בדיקה‪ :‬כאשר המנעול פנוי‬
‫‪ .1‬חוסמים את הפסיקות‬
‫‪ .2‬בודקים ומגלים שהמנעול פנוי‬
‫)לא נכנסים ללולאה(‬
‫{ ) ‪lock_acquire( L‬‬
‫; ‪disable_interrupts‬‬
‫{ )‪while (L != free‬‬
‫; ‪enable_interrupts‬‬
‫; ‪disable_interrupts‬‬
‫}‬
‫‪ .3‬נועלים את המנעול‬
‫‪ .4‬ומאפשרים פסיקות‪.‬‬
‫•‬
‫; ‪L = busy‬‬
‫; ‪enable_interrupts‬‬
‫כלומר בין הבדיקה לנעילה‬
‫הפסיקות חסומות ולכן המעבד‬
‫לא ייגזל מאתנו‪.‬‬
‫}‬
‫©צבי מלמד‬
‫‪35‬‬
‫חסימת פסיקות )פתרון שני באמצעות תכנה(‬
‫בדיקה‪ :‬כאשר המנעול תפוס‬
‫‪ .1‬נכנסים ללולאה )שוב ושוב(‬
‫‪ .2‬חוסמים הפסיקות בסוף כל סיבוב )לפני‬
‫בדיקת התנאי בכניסה לסיבוב הבא(‬
‫{ ) ‪lock_acquire( L‬‬
‫; ‪disable_interrupts‬‬
‫{ )‪while (L != free‬‬
‫; ‪enable_interrupts‬‬
‫; ‪disable_interrupts‬‬
‫‪ .3‬לכן כאשר נבדק התנאי אחרי שהמנעול‬
‫שוחרר ‪ -‬הפסיקות תהיינה חסומות‪.‬‬
‫•‬
‫זהו התרחיש שאוזכר בשקף הקודם‬
‫•‬
‫בכל מקרה‪ :‬בין בדיקת ערך המנעול‪,‬‬
‫לנעילתו ‪ -‬הפסיקות חסומות‪.‬‬
‫}‬
‫; ‪L = busy‬‬
‫; ‪enable_interrupts‬‬
‫}‬
‫©צבי מלמד‬
‫‪36‬‬
‫חסימת פסיקות ‪ -‬מגבלות השיטה‬
‫• אם התכנית עפה כאשר הפסיקות חסומות צריך לדאוג לאפשר‬
‫אותן‬
‫• חסימת פסיקות משמעותה עיכוב הטיפול בהן‪.‬‬
‫– לכל הפחות‪ ,‬החסימה כאן היא לפרק זמן קצר‪.‬‬
‫• הפתרון אינו ישים למערכת בת מספר מעבדים!!‬
‫– אין הגנה שתמנע ממעבד אחר לגשת למנעול )בזמן הקריטי בין‬
‫הנקודה שהוא נבדק ועד הנקודה שהוא ננעל(‬
‫©צבי מלמד‬
‫‪37‬‬
‫חומרת סינכרון ‪Synchronization Hardware‬‬
‫• רוב מערכות החומרה מספקות תמיכה לבעיית הסנכרון‬
‫• החומרה מספקת פקודת אסמבלר בודדת אשר מתבצעת באופן‬
‫אטומי!‬
‫• פקודה זאת לא יכולה להיקטע באמצעה‬
‫• הפקודה תבצע‪:‬‬
‫– בדיקת ערך של משתנה וגם‪...‬‬
‫– השמת ערך חדש למשתנה או פעולה שקולה אחרת‪.‬‬
‫• הרעיון דומה למה שראינו קודם – בפתרון חסימה של הפסיקות‪:‬‬
‫דואגים לכך ששום דבר לא יכול להתבצע בזמן שבין בדיקת המנעול‬
‫לבין שינוי ערכו לערך "נעול"‪.‬‬
‫©צבי מלמד‬
‫‪38‬‬
‫חומרת סינכרון ‪Synchronization Hardware‬‬
‫• הפקודה המוכרת ביותר בנושא זה נקראת ‪.TestAndSet‬‬
‫• להלן אבסטרקציה של מה שפקודת‪-‬המכונה הזאת מבצעת‪:‬‬
‫למשתנה מוכנס "בכל‬
‫מקרה" הערך נעול‬
‫{ )‪bool TestAndSet( bool &var‬‬
‫; ‪bool old_val = var‬‬
‫; ‪var = true‬‬
‫; ‪return old_val‬‬
‫}‬
‫מוחזר הערך הקודם של המנעול‬
‫©צבי מלמד‬
‫‪39‬‬
‫‪Synchronization Hardware‬‬
‫{)‪bool TestAndSet( bool &var‬‬
‫; ‪bool old_val = var‬‬
‫; ‪var = true‬‬
‫; ‪return old_val‬‬
‫}‬
‫המנעול מאותחל לערך‬
‫‪) false‬לא נעול(‬
‫{ )‪while (1‬‬
‫; ))‪while (TestAndSet(lock‬‬
‫קטע קריטי‬
‫; ‪lock = false‬‬
‫כל עוד מוחזר לי‬
‫שהמנעול כבר היה‬
‫נעול ‘אני ממתין‬
‫)‪(busy wait‬‬
‫קטע שיורי‬
‫{‬
‫הפתרון מבטיח בלבדיות‪ ,‬והתקדמות‪.‬‬
‫הוא אינו מבטיח הוגנות או מניעת הרעבה‬
‫©צבי מלמד‬
‫‪40‬‬
Synchronization Hardware
‫ אטומית‬swap() ‫שימוש בפקודת‬
:‫ אטומית‬swap ‫ מציעה פקודת‬,‫ בעל תכונות דומות‬,‫• פתרון דומה‬
void swap(bool &v1, bool &v2) {
bool temp = v1 ;
v1 = v2 ;
v2 = temp ;
}
41
‫©צבי מלמד‬
‫‪Synchronization Hardware‬‬
‫‪using atomic swap‬‬
‫{ )‪void swap(bool &v1, bool &v2‬‬
‫; ‪bool temp = v1‬‬
‫המנעול מאותחל לערך‬
‫‪) false‬לא נעול(‬
‫; ‪v1 = v2‬‬
‫; ‪v2 = temp‬‬
‫}‬
‫אני רוצה לנעול‬
‫– כלומר‪,‬‬
‫להציב ערך‬
‫‪ true‬למנעול‬
‫{ )‪while (1‬‬
‫; ‪key= true‬‬
‫)‪while (key == true‬‬
‫; )‪swap(key, lock‬‬
‫‪ swap‬מכניס ל‪ key -‬את ערכו‬
‫של ‪ ,lock‬לכן עת ערכו של ‪key‬‬
‫הוא ‪ false‬משמע 'תפסנו' את‬
‫המנעול פתוח‪ ,‬וה‪swap -‬‬
‫שהחזיר לנו איתות על כך‪ ,‬גם‬
‫נעל את המנעול עבורנו‪.‬‬
‫קטע קריטי‬
‫; ‪lock = false‬‬
‫קטע שיורי‬
‫{‬
‫©צבי מלמד‬
‫‪42‬‬
‫סינכרון בעזרת חומרה – ללא ‪starvation‬‬
‫•‬
‫הפקודות הנ"ל אינן מבטיחות חופש מהרעבה‪.‬‬
‫•‬
‫נציע פרוטוקול שישתמש ב‪ TestAndSet -‬ויממש את כל שלוש הדרישות‬
‫מפרוטוקול סינכרון‪.‬‬
‫•‬
‫מבני הנתונים המוכרים לכל התהליכים‪:‬‬
‫; } ‪bool waiting[n] = { false‬‬
‫•‬
‫מציין מי רוצה להיכנס לקטע הקריטי בעזרתו נעביר את הזכות להיכנס בין‬
‫המעוניינים בזה אחר זה‪ :‬כל מי שיצא יאתר את הבא אחריו שמעוניין להיכנס ו‪-‬‬
‫'יזמין' אותו‪.‬‬
‫; ‪bool lock = false‬‬
‫•‬
‫משתנה זה יציין האם אין אף תהליך שמעוניין להיכנס לקטע קריטי )ואז‬
‫הראשון שרוצה יכול להיכנס )ורק הקפד לנעול אחריך את הדלת(‬
‫•‬
‫לכל תהליך יוגדר משתנה לוקלי ‪ key‬כמו קודם‪.‬‬
‫©צבי מלמד‬
‫‪43‬‬
‫סינכרון בעזרת חומרה‬
‫ללא ‪ – starvation‬קטע הכניסה‬
‫{ )‪while (1‬‬
‫; ‪waiting[me] = true‬‬
‫; ‪key = true‬‬
‫"גם אני רוצה להכנס"‬
‫)‪while (waiting[me] && key‬‬
‫ישמש כמו במקרה הקודם‬
‫עם ‪TestAndSet‬‬
‫]‪ :waiting[me‬משתנה זה‬
‫עשוי לשנות את ערכו כאשר‬
‫'חבר' 'יזמין' אותי להיכנס‬
‫אחריו‪ ,‬כאשר הוא יצא‬
‫וישנה את ערך המשתנה‬
‫עבורי‬
‫‪ key‬ישנה את ערכו אם רק אני‬
‫רוצה להיכנס לקטע קריטי‬
‫ומתקיים ש‪lock == false -‬‬
‫; )‪key = TestAndSet(lock‬‬
‫; ‪waiting[me] = false‬‬
‫קטע קריטי‬
‫קטע היציאה‬
‫קטע שיורי‬
‫}‬
‫נראה שקיימת אפשרות להכנס‬
‫לקטע הקריטי אפילו אם המנעול‬
‫תפוס )‪(lock==true‬‬
‫האם זה ‪ BUG‬אם שזאת נקודה‬
‫מרכזית באלגוריתם?‬
‫©צבי מלמד‬
‫‪44‬‬
‫סינכרון בעזרת חומרה – ללא ‪starvation‬‬
‫קטע היציאה‬
‫כאשר אני יוצא אני בודק‬
‫אם יש מישהו שביקש‬
‫להיכנס לקטע הקריטי‬
‫‪...........‬‬
‫קטע קריטי‬
‫; ‪other = (me +1) % n‬‬
‫)]‪while (other != me &&!waiting[other‬‬
‫אם מצאתי מישהו כזה‬
‫; ‪other = (other +1) % n‬‬
‫)‪if (other != me‬‬
‫אני מסמן לו שיכנס‪...‬‬
‫;‪waiting[other] = false‬‬
‫‪else‬‬
‫; ‪lock = false‬‬
‫אחרת – אני משחרר את‬
‫המנעול‬
‫הקטע השיורי‬
‫}‬
‫הנקודה המרכזית‪ ...‬אני גואל את ‪ other‬מלולאת ההמתנה שהוא מצוי‬
‫בעיצומה‪.‬‬
‫הוא יוצא ממנה‪ ,‬מבלי שבעצם שוחרר המנעול! שיטת המיטה החמה‪.‬‬
‫©צבי מלמד‬
‫‪45‬‬
compare & swap
:‫ קיימת הפקודה הבאה‬IA32 -‫• ב‬
compare&swap(mem, R1, R2) {
if (mem == R1) {
mem = R2 ;
return true ;
}
return false ;
}
?TestAndSet ‫ כיצד נממש בעזרתה את‬:‫• תרגיל‬
46
‫©צבי מלמד‬
‫©צבי מלמד‬
‫‪47‬‬
‫סמפורים ‪Semaphores‬‬
‫•‬
‫•‬
‫•‬
‫•‬
‫•‬
‫סמפור = שיטת תקשורת המבוססת על שימוש בדגלים לאיתות‬
‫)לכל אות קיימת תנועת דגל מיוחדת(‪.‬‬
‫בהקשר שלנו‪ :‬התהליכים יאותתו זה לזה האם ניתן\אסור להיכנס‬
‫לקטע קריטי‬
‫מטרה‪ :‬הסמפור משמש בכדי להשיג מנעול‬
‫כיצד?‬
‫– סמפור הוא משתנה שלם )‪(int‬‬
‫– הניתן לאיתחול לערך ‪) 1‬או מספר גדול יותר(‬
‫בנוסף לאיתחול‪ ,‬מוגדרות עליו רק שתי הפעולות האטומיות‬
‫‪ (a‬המתנה\נעילה‬
‫‪ (b‬איתות\שחרור‬
‫©צבי מלמד‬
‫‪48‬‬
‫סמפורים ‪Semaphores‬‬
‫‪(a‬‬
‫המתנה\נעילה‬
‫‪(a‬‬
‫איתות\שחרור‬
‫{ )‪wait(s‬‬
‫)‪while (s <= 0‬‬
‫‪; // no-op‬‬
‫; ‪s--‬‬
‫}‬
‫{ )‪signal(s‬‬
‫; ‪s++‬‬
‫}‬
‫דרישות אטומיות‪:‬‬
‫‪s++, s-- .1‬‬
‫‪ .2‬כאשר הבדיקה ב‪ wait()-‬נכשלת אז חובה ש ‪ s--‬תהיה אטומית יחד עם בדיקת‬
‫התנאי שנכשלה‬
‫• אין מניעה שתהיינה פסיקות ב‪ busy-wait -‬של הלולאה ה‪while-‬‬
‫©צבי מלמד‬
‫‪49‬‬
‫סמפורים‬
‫• וכיצד ישמש אותנו הסמפור לצורך הגנה על קטע קריטי‬
‫• נגדיר משתנה שיוכר לכל התהליכים‪mutex = 1; :‬‬
‫• כל תהליך יבצע‪:‬‬
‫התהליך חסום כל עוד ערכו‬
‫של ‪ mutex‬איננו חיובי‪ .‬ברגע‬
‫שנכנסנו )ערכו היה חיובי(‬
‫הקטנו את ערכו ב‪1-‬‬
‫{ )‪while (1‬‬
‫;)‪wait(mutex‬‬
‫הקטע הקריטי‬
‫;)‪signal(mutex‬‬
‫שחרר את הסמפור לאחרים‪...‬‬
‫פקודת ‪ signal‬מגדילה את‬
‫ערכו ב‪1-‬‬
‫הקטע השיורי‬
‫}‬
‫©צבי מלמד‬
‫‪50‬‬
‫סמפורים‬
‫• סמפור יכול לסייע לנו גם במשימות סנכרון אחרות‪ ,‬לא רק בקטע‬
‫קריטי‬
‫• לדוגמה‪ :‬נניח ש‪ P2 -‬יכול לבצע קטע קוד רק אחרי ש‪' P1 -‬הכין' לו‬
‫נתונים‪ ,‬כלומר סיים לבצע קטע קוד‪.‬‬
‫איתחול‬
‫;‪synch = 0‬‬
‫‪ P2‬מבצע‬
‫‪ P1‬מבצע‬
‫קוד ההכנה‬
‫;)‪wait(synch‬‬
‫;)‪signal(synch‬‬
‫קוד השימוש‬
‫©צבי מלמד‬
‫‪51‬‬
‫מימוש סמפורים‬
‫• הפתרונות שראינו עד כה בין בתכנה ובין בחמרה חייבו ‪busy‬‬
‫‪) :waiting‬התהליך מבצע לולאה ריקה בקטע הכניסה שלו(‬
‫• אם הקטע הקריטי ארוך – ההמתנה מבזבזת הרבה ‪cpu-time‬‬
‫• סמפור עם המתנה עסוקה נקרא גם‪: spinlock :‬‬
‫‪.the process spins while waiting for a lock‬‬
‫• יתכן מצב שנעדיף המתנה עסוקה על‪-‬פני שתי החלפות הקשר –‬
‫לדוגמא‪ ,‬במערכת בת כמה מעבדים‪ ,‬עם קטע קריטי קצר יחסית‬
‫• במערכת עם מעבד יחיד‪ ,‬או כאשר הקטע‪-‬הקריטי ארוך – נעדיף‬
‫שהתהליך ילך לישון עד שהוא יוכל להיכנס לקטע הקריטי ואז‬
‫יעירו אותו‪.‬‬
‫©צבי מלמד‬
‫‪52‬‬
‫מימוש סמפורים‬
‫• נראה כיצד ניתן להשיג את התוצאה בעזרת סמפור "ישנוני"‪:‬‬
‫• נגדיר‪:‬‬
‫{ ‪struct Sleepy-Semaphore‬‬
‫; ‪int value‬‬
‫ערך הסמפור "כרגיל" ‪//‬‬
‫רשימת תהליכים ‪struct Process *waiting ; //‬‬
‫שממתינים על הסמפור ‪//‬‬
‫}‬
‫©צבי מלמד‬
‫‪53‬‬
5.2.12 ‫עד כאן‬
‫מימוש סמפורים‬
signal ‫פעולת‬
wait -‫פעולת ה‬
void signal(Sleepy-Semaphore s) {
void wait(Sleepy-Semaphore s) {
s.value++ ;
s.value-- ;
if (s.value <= 0) {
if (s.value < 0) {
remove a process from
add yourself to s.waiting
s.waiting and wake it up
go to sleep
}
}
}
}
‫ וגם‬,‫• מכיוון שכל אחת משתי הפעולות גם משנה ערך של משתנה‬
‫בודקת את ערכו אזי יש לדאוג שהן תבוצענה באופן אטומי‬
54
‫©צבי מלמד‬
‫מימוש סמפורים – אטומיות בסמפור ישנוני‬
‫• אם עומד לרשותנו ‪ spinlock‬נוכל להשתמש בו‪:‬‬
‫– נגדיר שהקטעים של ה‪ wait -‬וה‪ signal -‬הם קטעים קריטיים‪,‬‬
‫נשתמש ב‪) spinlock -‬סמפור "רגיל"(‬
‫– זה יבטיח לנו שפעולות הסמפור הישנוני תבוצענה באופן אטומי‬
‫– כלומר‪ ,‬תבוצענה ברציפות ובלי הפרעה‬
‫• אבל אז –‬
‫– מה הועילו חכמים בתקנתם?‬
‫– אם בכל מקרה אנו משתמשים ב‪ spinlock -‬בשביל מה לנו‬
‫הסמפור הנוכחי )הישנוני(?‬
‫©צבי מלמד‬
‫‪55‬‬
‫אטומיות בסמפור ישנוני‬
‫• נניח שהקטע הקריטי )המקורי( עליו ברצוננו להגן כולל אלף‬
‫פקודות‪ ,‬ולעומת זאת פעולת ה‪ wait/signal -‬על הסמפור הישנוני‬
‫כוללת עשר פקודות‪.‬‬
‫• אם נשתמש רק ב‪ spinlock -‬תהליך שממתין לקטע הקריטי‬
‫המקורים יבזבז זמן מעבד רב‬
‫• אם נשתמש בסמפור הישנוני‪ :‬תהליך שרוצה להיכנס לקטע‬
‫הקריטי‪ ,‬ראשית צריך להיכנס ל"קטעון קריטי" )בן עשר פקודות(‪.‬‬
‫– מתבצע באמצעות ‪ ,busy-wait‬אבל‪ ,‬סביר שההמתנה הזאת‬
‫תהיה קצרה‪.‬‬
‫– ההמתנה לקטע קריטי האמיתי – עללה להיות ארוכה‪ ,‬אבל‬
‫במקרה הזה הוא ילך לישון )יחסום את עצמו(‪.‬‬
‫©צבי מלמד‬
‫‪56‬‬
‫אטומיות בסמפור ישנוני‬
‫• תהליך שיוצא מהקטע הקריטי הארוך‪:‬‬
‫• לו היינו משתמשים רק ב‪ , spinlock -‬היה עליו לבצע מעט מאוד‬
‫)‪ signal‬של ‪ – (spinlock‬כלומר להגדיל את ערך הסמפור‪.‬‬
‫• לעומת זאת‪ ,‬כאשר משתמשים ‪sleepy-semaphore:‬‬
‫– ראשית מבצע קטע כניסה לסמפור ‪spinlock‬‬
‫– לאחר מכן סיגנל של הסמפור הישנוני )קטע קוד קצר(‬
‫– לבסוף‪ signal :‬של ‪.spinlock‬‬
‫• עדיין‪ :‬בהנחה שהקטע הקריטי העיקרי הוא ארוך – הרווחנו‬
‫©צבי מלמד‬
‫‪57‬‬
‫‪Deadlocks and Starvation‬‬
‫• נניח שאנו משתמשים בזוג סמפורים‪:‬‬
‫‪.S, Q‬‬
‫• נניח ששני תהליכים המורצים‬
‫במערכת מבצעים את סדרת הפקודות‬
‫המתוארת ממול‬
‫• התוצאה‪ :‬כל אחד מהתהליכים‬
‫ממתין ל‪ signal -‬שישלח לו עמיתו‬
‫)התקוע( ולכן המערכת מצויה במצב‬
‫של חסימה הדדית‪.‬‬
‫©צבי מלמד‬
‫‪58‬‬
‫‪Deadlocks and Starvation‬‬
‫• ‪) deadlock‬חסימה הדדית(‪:‬‬
‫– קבוצת תהליכים מצויה ב‪ deadlock-‬אם כל תהליך בקבוצה‬
‫ממתין לאירוע אשר יכול להיגרם רק ע"י תהליך אחר בקבוצה‪.‬‬
‫• החסימה הבלתי מוגבלת )‪ (indefinite blocking‬או הרעבה‬
‫)‪:(starvation‬‬
‫– התהליך ממתין עד בלי די מבלי לקבל את המשאב\שרות שהוא‬
‫זקוק לו ‪.‬‬
‫– )זאת בעיה דומה‪ ,‬אך שונה מחסימה הדדית(‬
‫• פתרונות החומרה שתיארנו עלולים ליצור הרעבה‪ ,‬כך גם סמפור‬
‫אשר ההמתנה עליו אינה בתור‪.‬‬
‫©צבי מלמד‬
‫‪59‬‬
‫סמפורים בינאריים ‪Binary Semaphore‬‬
‫• סמפורים מונים )‪(counting semaphore‬‬
‫– סמפור שניתן לאתחל לכל ערך טבעי‪,‬‬
‫– )הסמפורים שתיארנו עד כה(‬
‫• סמפור בינארי‬
‫– עשוי לקבל את הערך אפס או אחד בלבד‪.‬‬
‫• לעתים‪ ,‬החומרה או התכנה שעומדות לרשותנו )למשל‪ ,‬ספריית‬
‫‪ (POSIX‬מספקות לנו רק סמפור בינארי‪.‬‬
‫• המטרה‬
‫– לממש סמפור מונה באמצעות זוג סמפורים בינאריים )ומשתנה‬
‫שלם(‬
‫©צבי מלמד‬
‫‪60‬‬
implementing counting semaphore
using binary semaphore
wait(sem_for_counter);
‫אם יש ממתינים לפתחו של‬
counter++ ;
‫הקטע הקריטי אז הער אחד‬
‫ ואל תעשה‬,‫מהם‬
if (counter <= 0)
‫ כי זה‬signal(sem_for_counter)
signal(sem_for_cs) ;
,‫ יעשה זאת במקומך‬,‫שיתעורר‬
‫מיד לכשיתעורר‬
else
signal(sem_for_counter) ;
61
‫©צבי מלמד‬
– wait() ‫פעולת ההמתנה‬
implementing counting semaphore
using binary semaphore
wait(sem_for_counter) ;
counter-- ;
‫ לא ניתן להיכנס לקטע‬:‫כלומר‬
‫קריטי‬
if (counter < 0) {
signal(sem_for_counter) ;
wait(sem_for_cs) ;
‫שחרר הטיפול במונה והמתן‬
‫עד שיפתח הקטע הקריטי‬
}
signal(sem_for_counter) ;
62
‫©צבי מלמד‬
:‫שימו לב‬
‫אם הקטע הקריטי היה פנוי‬
:‫ יחיד ל‬signal ‫אנו עושים‬
‫ ואחרת‬,sem_for_counter
‫)אם המתנו( אנו עושים‬
...‫ נראה מוזר‬.‫שניים‬
signal() ‫פעולת השחרור‬
implementing counting semaphore
using binary semaphore
wait(sem_for_counter);
‫אם יש ממתינים לפתחו של‬
counter++ ;
‫הקטע הקריטי אז הער אחד‬
‫ ואל תעשה‬,‫מהם‬
if (counter <= 0)
‫ כי זה‬signal(sem_for_counter)
signal(sem_for_cs) ;
,‫ יעשה זאת במקומך‬,‫שיתעורר‬
‫מיד לכשיתעורר‬
else
signal(sem_for_counter) ;
63
‫©צבי מלמד‬
‫בעיות סינכרון קלאסיות‬
‫• ‪Classic Problems of Synchronization‬‬
‫• סעיף זה חורג ממערכות הפעלה ומציג מספר בעיות קלאסיות של‬
‫בקרה מקבילית )‪ ,(concurrency control‬בעיות עליהן נהוג לבחון‬
‫מנגנונים ופרוטוקולים של סנכרון )כדוגמת אלה שהוצגו(‪.‬‬
‫©צבי מלמד‬
‫‪64‬‬
‫בעיות סינכרון קלאסיות‬
‫• בעיות שיוצגו כאן‪:‬‬
‫– בעית החוצץ המוגבל ‪The Bounded-Buffer Problem‬‬
‫– בעית קוראים‪-‬כותבים‬
‫– בעית הפילוסופים הסועדים ‪The Dining Philosophers‬‬
‫©צבי מלמד‬
‫‪65‬‬
‫בעית החוצץ המוגבל‬
‫‪The Bounded-Buffer Problem‬‬
‫• הבעיה‪:‬‬
‫– בהינתן מאגר של ‪ n‬תאים )כל אחד מכיל פריט בודד( יש לדאוג‬
‫לסינכרון מילויו וריקונו ע"י יצרנים וצרכנים )אחד או יותר מכל‬
‫סוג(‪.‬‬
‫• פתרון אפשרי של הבעיה באמצעות סמפורים ישתמש בשלושה‬
‫סמפורים שמאותחלים כמתואר‪:‬‬
‫• ‪ – mutex = 1‬מבקר את הגישה למאגר‪.‬‬
‫• ‪ – empty = n‬מונה כמה מקומות פנויים קיימים במאגר‪.‬‬
‫• ‪full = 0‬‬
‫– מונה כמה פריטים מצויים במאגר‬
‫©צבי מלמד‬
‫‪66‬‬
‫©צבי מלמד‬
‫‪67‬‬
‫ – קוד היצרן‬bounded buffer
while (1)
{
create_new_element();
wait(empty); // ‫חכה כל עוד אין מקום בחוצץ‬
// ‫והקטן את מספר הפנויים בחוצץ‬
wait(mutex); //‫המתן לחוצץ עצמו‬
add new element to buffer
signal(mutex); // the buffer is free
signal(full);
// notify consumers - added element
}
68
‫©צבי מלמד‬
‫ – קוד הצרכן‬bounded buffer
while (1)
{
wait(full); // ‫חכה כל עוד אין החוצץ ריק‬
// ‫והקטן באחד את מספר האיברים‬
wait(mutex); //‫המתן לחוצץ עצמו‬
consume/remove element from buffer
signal(mutex);
// the buffer is free
signal(empty);
// notify producers - added space to
// the buffer
}
69
‫©צבי מלמד‬
‫בעיית הקוראים‪-‬כותבים‬
‫‪The readers-Writers Problem‬‬
‫• נניח אובייקט שאליו פונים שני סוגי תהליכים‪:‬‬
‫– קוראים מעוניינים רק לשלוף נתונים מהאובייקט‪.‬‬
‫– כותבים מעוניינים )גם( לעדכן את ערכו של האובייקט‪.‬‬
‫• קוראים רבים עשויים לפנות לאובייקט במקביל‬
‫• כותב חייב לפנות לאובייקט לבד‪ ,‬בכדי למנוע מצבים שבהם‪:‬‬
‫– הקוראים קיבלו נתוני עדכון חלקי )שגוי( של האובייקט‬
‫– או שני כותבים פגעו זה בנתונים של זה‬
‫©צבי מלמד‬
‫‪70‬‬
‫בעיית הקוראים‪-‬כותבים‬
‫‪The readers-Writers Problem‬‬
‫•‬
‫לבעיה שני וריאנטים הנבדלים בקדימות שתינתן לתהליכים השונים‪:‬‬
‫•‬
‫בעיית הקוראים‪-‬כותבים הראשונה‪:‬‬
‫– הקורא לא ימתין למשאב המשותף‪ ,‬אלא אם כן‪) ,‬ברגע הבקשה שלו( כבר‬
‫קיים כותב על המשאב‬
‫– משתמע‪ :‬אם יש כבר קורא שניגש למשאב‪ ,‬אפילו אם יש כותבים‬
‫שממתינים‪ ,‬הקורא החדש יוכל להצטרף לקוראים הקיימים‬
‫•‬
‫בעיית הקוראים‪-‬כותבים השנייה‪:‬‬
‫– כאשר כותב מעוניין לכתוב הוא יוכל לעשות זאת מהר ככל האפשר‬
‫– כלומר שום קורא לא יוכל לקבל אפשרות קריאה כל עוד קיים כותב ‪ .‬כותב‬
‫חדש יקבל זכות קדימה לפני קוראים שכבר ממתינים‬
‫•‬
‫בשני הוריאנטים תיתכן הרעבה‪ :‬בראשון של כותב‪ ,‬בשני של קורא‪.‬‬
‫©צבי מלמד‬
‫‪71‬‬
solution to First Readers-Writers problem
semaphore rc_mutex = 1
wrt = 1,
// for readers only
//
for
int readers_count =0 ; //
readers count, protected by rc_mutex
writers code ‫פעולת‬
wait(wrt)
‫כתוב‬
signal(wrt)
wait -‫פעולת ה‬
‫המתן עד שאין‬
‫לא קורא ולא‬
‫כותב‬
‫שחרר את המונה‬
72
readers and writers
wait(rc_mutex)
‫המתן בכדי לפנות למונה‬
readers_count++;
‫אם אתה הקורא‬
if (readers_count==1)
‫ המתן אם‬,‫הראשון‬
wait(wrt)
‫יש כותב פעיל עד‬
signal(rc_mutex)
,‫ וכמו כן‬,‫שיסיים‬
‫קריאה‬
‫חסום קוראים‬
wait(rc_mutex)
‫נוספים‬
readers_count--;
if (readcount)==0
‫אם אתה הקורא האחרון‬
signal (wrt)
‫אז הזמן כותבים‬
signal (rc_mutex)
‫©צבי מלמד‬
‫אתנחתא – טיפ תכנותי‬
‫• באחד האתרים‪ ,‬שקראתי לאחרונה על הנושאים הללו‪,‬‬
‫ניתקלתי במשפט הבא‪ ,‬שנכון בהרבה מקרים‪ ,‬ובפרט‬
‫בתכנות "עדין ומורכב" כמו תכנות מקבילי‪.‬‬
‫‪– Make it right then make it fast.‬‬
‫‪• and‬‬
‫‪– It is easier to make a correct program fast than‬‬
‫‪to make a fast program correct.‬‬
‫©צבי מלמד‬
‫‪73‬‬
‫בעיית הפילוסופים הסועדים‬
‫‪The Dining-Philosophers Problem‬‬
‫• בעיית הפילוסופים הסועדים מנוסחת באופן הבא‪:‬‬
‫– סביב שולחן עגול יושבים חמישה פילוסופים‪.‬‬
‫– הפילוסופים עשויים לחשוב או לאכול‪.‬‬
‫– כאשר פילוסוף שקוע בהגיגיו אין לו אינטראקציה עם עמיתיו‪.‬‬
‫– כאשר הפילוסוף נתקף רעב‪ ,‬ברצונו לאכול מתוך קערה גדולה‬
‫של אורז במרכז השולחן‪ .‬לשם כך הוא נזקק לשני מקלות‬
‫אכילה‪.‬‬
‫– על גבי השולחן‪ ,‬בין כל שני פילוסופים‪ ,‬מונח מקל אכילה יחיד‪.‬‬
‫©צבי מלמד‬
‫‪74‬‬
‫בעיית הפילוסופים הסועדים‬
‫‪The Dining-Philosophers Problem‬‬
‫• הפעולה האטומית שמבצע פילוסוף )רעב(‪ :‬הרמת מקל‬
‫אכילה יחיד‪.‬‬
‫• כאשר הפילוסוף זכה בשני מקלות הוא יכול לאכול‬
‫• כאשר הוא מסיים לאכול הוא מניח בנחת את המקלות‪,‬‬
‫וחוזר לסרעפיו‪.‬‬
‫• המטרה‪:‬‬
‫– להביא לסיפוקם הרוחני‪ ,‬וחשוב מכך הגשמי‪ ,‬של‬
‫הפילוסופים‪ ,‬בלי להיקלע לחסימה הדדית או‬
‫להרעבה‪.‬‬
‫©צבי מלמד‬
‫‪75‬‬
‫פתרון אפשרי לבעית הפילוסופיים‬
‫•‬
‫עבור כל מקל אכילה נחזיק סמפור שיאפשר להמתין כל עוד הוא אינו פנוי‪:‬‬
‫; }‪semaphore chopstick[5] = {1, 1, 1, 1, 1‬‬
‫•‬
‫כל פילוסוף )שמספרו ‪ (i‬יבצע‪:‬‬
‫{ )‪while (1‬‬
‫המתן למקל שמימינך ‪‬‬
‫; )]‪wait(chopstick[i‬‬
‫המתן לזה שמשמאלך ‪wait(chopstick[(i+1) % 5]) ; ‬‬
‫אכול‬
‫אם כל הפילוסופים יתקפו‬
‫ברעב במקביל‪ ,‬כל אחד מהם‬
‫ירים את המקל הימני‪ ,‬ויחכה‬
‫עד בלי די למקל השמאלי‪ .‬הם‬
‫ישארו רעבים )תרתי משמע(‬
‫; )]‪signal(chopstick[i‬‬
‫; )]‪signal(chopstick[(i+1) % 5‬‬
‫}‬
‫©צבי מלמד‬
‫‪76‬‬
‫שיפורים אפשריים לפתרון לבעית הפילוסופיים‬
‫•‬
‫פתרון קל‪ :‬נוסיף מקל אכילה שישי‪ :‬כך לפחות אחד הפילוסופים יוכל לאכול‪,‬‬
‫לשחרר את מקלותיו‪ ,‬ואז לפי תור גם כל האחרים יוכלו לשבוע‪.‬‬
‫•‬
‫הרמת זוג מקלות האכילה תבוצע בקטע קריטי; אם רק אחד מהם פנוי יש‬
‫לשחרר את זה שנרכש‪ .‬פתרנו את בעיית החסימה ההדדית‪ ,‬אך נקלענו לבעיה‬
‫חלופית‪ :‬חשש להרעבה‪.‬‬
‫• נמס ְ פ ֵ ר את הפילוסופים‪ .‬פילוסוף אי‪-‬זוגי יתחיל בהרמת המקל משמאלו והזוגי‬
‫יתחיל בהרמת המקל שמימינו‪ .‬בדקו שגם כאן נחלצנו מהחסימה ההדדית‪ ,‬אבל‬
‫לא מחשש להרעבה )פילוסוף איטי יושב לצד פילוסוף מהיר וזללן שאוכל לעתים‬
‫מזומנות(‪.‬‬
‫•‬
‫ניוותר כרגע רעבים לפתרון‪.‬‬
‫•‬
‫קיים פתרון שמבוסס על מוניטורים – כנראה שנדלג עליו‬
‫©צבי מלמד‬
‫‪77‬‬
‫הבעיה האמיתית של הפילוסופים הסועדים‬
‫• ננסה לחדד עוד יותר את הבעיה‪:‬‬
‫– מהי הבעיה האמתית של הפילוסופים הסועדים?‬
‫©צבי מלמד‬
‫‪78‬‬
‫הבעיה האמיתית של הפילוסופים הסועדים‬
‫• מסתבר שמדובר בפילוסופים יהודיים‬
‫• לא יודעים לאכול עם מקלות‪-‬סיניים‬
‫©צבי מלמד‬
‫‪79‬‬
‫פתרון א' לבעית המקלות‬
‫תוכנית הדרכה ואימון מצוירת ומפורטת לשימוש במקלות סיניים‬
‫התוכנית נכשלה כי לפילוסופים "אין ראש לזה"‬
‫©צבי מלמד‬
‫‪80‬‬
‫פתרון ב' לבעית המקלות‬
‫• "הראש היהודי ממציא לנו פטנטים"‬
‫• עוד חברת סטארט‪-‬אפ ישראלית בדרך לאקזיט לאחר‬
‫שפיתחה מוצר ייחודי שפותר את בעיית הפילוסופים הסועדים‬
‫©צבי מלמד‬
‫‪81‬‬
‫פתרון ג' לבעית המקלות‬
‫שישתמשו ב‪FORKS -‬‬
‫©צבי מלמד‬
‫‪82‬‬
‫אמצעים לסנכרון תהליכים ביוניקס‬
‫• שניים מהכלים שראינו בדיוננו התיאורטי ממומשים ביוניקס‪:‬‬
‫– סמפורים‬
‫– משתני תנאי‬
‫©צבי מלמד‬
‫‪83‬‬
‫סמפורים ביוניקס‪/‬לינוקס‬
‫•‬
‫הסמפור מבטיח לנו ששתי הפעולות המבוצעות בו‪:‬‬
‫– בדיקת ערכו‪ ,‬ובמידת הצורך המתנה עליו‪.‬‬
‫– שינוי ערכו‪.‬‬
‫•‬
‫תבוצענה באופן אטומי ‪.‬‬
‫•‬
‫ביוניקס‪ :‬הסמפורים הם "האח השלישי" במשפחת ‪) – XSI-IPC‬אחים לתורי‪-‬‬
‫הודעות ולזכרון‪-‬משותף(‬
‫– הטיפול – דומה למשאבים האחרים שהיכרנו‬
‫– צריך ליצור אותם באופן מפורש‪ ,‬והם ממשיכים להתקיים עד שמשחררים‬
‫אותם מפורשות‬
‫• פקודת ‪ – ipcs‬מציגה את המשאבים במערכת )‪ – ipcs –s‬מציגה את‬
‫מערכי הסמפורים(‬
‫• פקודת ‪ – ipcrm‬מוחקת משאבים‬
‫©צבי מלמד‬
‫‪84‬‬
‫יצירת סמפור בעזרת )(‪semget‬‬
‫• כדי לקבל קבוצת סמפורים נתחיל בכך שנייצר מפתח לקבוצה‪:‬‬
‫; )'‪key_t key = ftok("~yoramb", 'y‬‬
‫… )‪if (key == -1‬‬
‫• תוך שימוש במפתח נבקש ליצור קבוצת סמפורים חדשה‪ ,‬או להיקשר לקבוצה‬
‫קיימת‪:‬‬
‫= ‪int sem_set_id‬‬
‫המפתח החיצוני‬
‫‪semget(key,‬‬
‫‪1,‬‬
‫כמה סמפורים אנו רוצים‬
‫; )‪IPC_CREAT | 0600‬‬
‫… )‪if (sem_set_id == -1‬‬
‫• אם מצטרפים לסמפור קיים‪ ,‬הארגומנט השני הוא ‪ – don’t care‬לשם הניקיון‬
‫והקריאות‪ ,‬נעביר אפס‪.‬‬
‫©צבי מלמד‬
‫‪85‬‬
‫©צבי מלמד‬
‫‪86‬‬
‫סמפור ביוניקס ‪ -‬ב‪ .‬איתחול הסמפור‬
‫{ ‪union semun‬‬
‫; ‪int val‬‬
‫; ‪struct semid_ds *buf‬‬
‫; ‪unsigned short *array‬‬
‫;}‬
‫לשם האיתחול נזדקק ל‪ union -‬הבא‬
‫– עלינו להכריז עליו בתכנית‬
‫אנו מתענינים רק בחבר ‪ .val‬נאתחל‬
‫אותו לערך הרצוי‪:‬‬
‫; ‪union semun sem_val‬‬
‫; ‪sem_val.val = 1‬‬
‫איתחול הסמפור ‪) ,0#‬היחידי שיש לנו בקבוצה(‬
‫תיאור הקבוצה‪...‬‬
‫תיאור הסמפור בתוך הקבוצה‬
‫איזו פעולה לבצע‬
‫‪status = semctl(sem_set_id,‬‬
‫‪0,‬‬
‫‪SETVAL,‬‬
‫; )‪sem_val‬‬
‫… )‪if (status == -1‬‬
‫באיזה ערך להשתמש‬
‫©צבי מלמד‬
‫‪87‬‬
‫הפונקציה )(‪semctl‬‬
‫• )(‪ semctl‬מאפשרת לנו לבצע מגוון פעולות על קבוצת הסמפורים‬
‫)מחיקתה‪ ,‬בדיקת ערך של סמפור רצוי בקבוצה(‪.‬‬
‫• בפעולות השונות נספק קוד פעולה שונה‪ ,‬ובמידת הצורך ערכים‬
‫שונים ב‪.union -‬‬
‫©צבי מלמד‬
‫‪88‬‬
‫קוד הכניסה )פעולת ה‪(wait -‬‬
‫הגדרת משתנה ואיתחולו‪:‬‬
‫פעל על סמפור מספר ‪sem_num‬‬
‫הפעולה‪ :‬הקטן את ערכו באחד‬
‫דגלים נוספים‪ :‬לא מענינים אותנו‬
‫)למשל אם איננו רוצים להמתין(‬
‫; ‪struct sembuf sem_op‬‬
‫; ‪sem_op.sem_num = 0‬‬
‫; ‪sem_op.sem_op = -1‬‬
‫; ‪sem_op.flg = 0‬‬
‫ונבצע את הפעולה עם הארגומנטים‪:‬‬
‫מזהה הקבוצה‬
‫מתאר הפעולה‬
‫מספר הסמפורים בפעולה הזאת‬
‫‪if (semop(sem_set_id,‬‬
‫‪&sem_op,‬‬
‫… )‪1) == -1‬‬
‫•‬
‫כאשר חלפנו על‪-‬פני פעולה זאת בהצלחה אנו בקטע הקריטי‪.‬‬
‫©צבי מלמד‬
‫‪89‬‬
‫קוד היציאה )פעולת ה‪(signal -‬‬
‫• פעולת היציאה סימטרית לגמרי לפעולת הכניסה‪ .‬בהבדל שכאן‬
‫נקבע ערך חיובי )הגדלת ערך הסמפור(‪:‬‬
‫; ‪sem_op.sem_op = 1‬‬
‫©צבי מלמד‬
‫‪90‬‬
‫דוגמא ‪IPC Semaphores -‬‬
‫•‬
‫התכנית מבצעת את המשימה הבאה‪:‬‬
‫– הגדר קבוצת סמפורים בת סמפור יחיד‪.‬‬
‫– אתחלו לערך ‪.1‬‬
‫– הולד חמישה ילדים‪.‬‬
‫– כל ילד בלולאה מנסה להוסיף נתונים לקובץ משותף על כן‪:‬‬
‫• ממתין על הסמפור‪.‬‬
‫• פותח את הקובץ‪.‬‬
‫• כותב עליו‬
‫• סוגר אותו‬
‫• משחרר את הסמפור‬
‫©צבי מלמד‬
‫‪91‬‬
IPC Semaphores - ‫דוגמא‬
// file: semaphore1_protect_file.c
// Adopted from Guy Keren (actcom)
#include
#include
#include
#include
#include
#include
#include
#include
92
<stdio.h>
<stdlib.h>
<unistd.h>
<time.h>
<sys/types.h>
<sys/ipc.h>
<sys/sem.h>
<sys/wait.h>
‫©צבי מלמד‬
IPC Semaphores - ‫דוגמא‬
#define NUM_PROCS 5
#define SEM_ID
3879
#define CHILD_PROC
0
#define FILE_NAME "sem_mutex.txt"
// ======structs etc. ======================
union semun {
int val ;
struct semid_ds *buf ;
unsigned short *array ;
} ;
//=====PROTOTYPES ====================
void do_child_loop(int sem_set_id, char* file_name) ;
void update_file(int sem_set_id, char* file_path, int number) ;
void burn_time();
//=====================================
93
‫©צבי מלמד‬
int main() {
int sem_set_id;
union semun sem_val;
int child_pid;
int i;
int rc;
94
IPC Semaphores ‫דוגמא‬
(ftok ‫יצירת קבוצת הסמפורים )ללא‬
sem_set_id = semget(SEM_ID,
1,
IPC_CREAT | 0600);
if (sem_set_id == -1) {
perror("main: semget failed");
exit(EXIT_FAILURE);
}
sem_val.val = 1;
#0 ‫איתחול הסמפור‬
rc = semctl(sem_set_id,
-1 ‫בקבוצה לערך‬
0,
SETVAL,
sem_val);
if (rc == -1) {
perror("main: semctl failed");
exit(EXIT_FAILURE);
}
‫©צבי מלמד‬
‫דוגמא‬
IPC Semaphores
int main()
.....................
for (i=0; i<NUM_PROCS; i++) {
child_pid = fork();
switch (child_pid) {
case -1:
perror("fork failed");
exit(EXIT_FAILURE);
case CHILD_PROC:
do_child_loop(sem_set_id, FILE_NAME);
exit(EXIT_SUCCESS);
default:
break;
}
}
/* wait for all children to finish running */
for (i=0; i<NUM_PROCS; i++)
wait(NULL);
puts("main: we're done\n");
fflush(stdout);
return EXIT_SUCCESS ;
}
95
‫©צבי מלמד‬
IPC Semaphores ‫דוגמא‬
// repeat:
// wait on the sem sem_set_id (on #0 in it)
// then open file_name, write on it, and close it
void do_child_loop(int sem_set_id, char* file_name)
{
pid_t pid = getpid();
int i;
for (i=0; i<7; i++)
update_file(sem_set_id, file_name, pid);
}
96
‫©צבי מלמד‬
‫דוגמא‬
IPC Semaphores
void update_file(int sem_set_id,
char* file_path,
int number)
{
struct sembuf sem_op;
FILE* file;
// Entry section: wait on semaphore
sem_op.sem_num = 0;
// num of sem in set
sem_op.sem_op = -1;
// wait operation
sem_op.sem_flg = 0;
// flags we leave 0
semop(sem_set_id, &sem_op, 1);
97
// The critical section
file = fopen(file_path, "a");
if (file) {
setbuf(file, NULL) ;
fputs("I ", file) ;
fputs("am ", file) ;
fputs("process ", file) ;
fputs("#", file) ;
fprintf(file, "%d\n", number);
fclose(file);
}
‫©צבי מלמד‬
‫כל פעולת כתיבה נשלחת מידית לקובץ‬
‫ללא חציצה‬
‫מבצעים מספר פעולות כתיבה שאינן‬
‫ כך שיש גם יש מקום‬,‫אטומיות‬
‫להחלפת הקשר תוך כדי הפעולה‬
IPC Semaphores ‫דוגמא‬
void update_file(int sem_set_id,
.............................
// Exit section: signal on semaphore
sem_op.sem_num = 0;
sem_op.sem_op = 1;
// signal
operation
sem_op.sem_flg = 0;
semop(sem_set_id, &sem_op, 1);
}
98
‫©צבי מלמד‬
IPC Semaphores - ‫דוגמא‬
<205|0>yoramb@inferno-05:~/os$ less
sem_mutex.txt
I am process #20088
I am process #20087
I am process #20089
I am process #20088
I am process #20090
I am process #20089
I am process #20091
I am process #20090
I am process #20087
I am process #20091
I am process #20088
I am process #20087
I am process #20089
I am process #20088
I am process #20090
I am process #20089
I am process #20091
I am process #20090
<206|0>yoramb@inferno-05:~/os$
I am process #20091
I am process #20087
I am process #20088
I am process #20089
I am process #20090
.‫הפלט מסודר ויפה‬
I am process #20091
I am process #20087
‫לשם השוואה נראה מה קורה ללא סמפורים‬
I am process #20088
I am process #20089
:(‫)השימוש בסמפורים נסגר בהערת תיעוד‬
I am process #20090
I am process #20091
I am process #20087
99
‫©צבי מלמד‬
IPC Semaphores - ‫דוגמא‬
<206|0>yoramb@inferno-05:~/os$ less sem_mutex.txt.no_sem
I am process #20020
I am process #20021
I am process #I am process 20020
I I #I am I am 20022
am process #20021
process am process #I process #20023
I am #20024
am process I 20020
process #am #I 20022
process I 20021
.‫השימוש בסמפורים נסגר בהערת תיעוד‬
am #I am process 20023
am process #I process #20020
am I #20024
.‫ ממש לא מה שרצינו‬.‫הפלט מבולגן ומעורבב‬
process am I 20022
#process #20023
I am process #20021
am I 20020
process I am I am process #20021
#20024
am process process ##I 20023
20022
100
‫©צבי מלמד‬
‫אותו הפלט גם בלי הסמפור?‬
‫•‬
‫אני לא הצלחתי לשחזר את פלט ההרצה שיורם הציג‪) .‬כלומר‪ ,‬גם‬
‫ללא סמפור‪ ,‬הפלט היה מסודר(‬
‫שאלות‬
‫‪ .1‬מהו השינוי המינמילי שצריך לבצע בתוכנית בכדי לבטל את‬
‫הסמפור?‬
‫‪ .2‬מה יכולה להיות הסיבה לכך שאצלי הפלט היה מסודר?‬
‫‪ .3‬איך אני יכול לגרום לכך‪ ,‬שגם אצלי יהיה הבדל – כלומר יהיה‬
‫באלאגן בלי סמפור?‬
‫©צבי מלמד‬
‫‪101‬‬
‫©צבי מלמד‬
‫‪102‬‬
‫המינימום הדרוש לבטל‬
‫את הסמפורים‪...‬‬
‫©צבי מלמד‬
‫‪103‬‬
‫הפלט עדיין מסודר – למרות שביטלתי את הסמפור‬
‫©צבי מלמד‬
‫‪104‬‬
‫תוכנית מעודכנת ליצירת הבדלים בפלט‬
‫©צבי מלמד‬
‫‪105‬‬
‫הפלט תוך שימוש בסמפורים‬
‫©צבי מלמד‬
‫‪106‬‬
‫הפלט ללא שימוש בסמפורים‬
‫©צבי מלמד‬
‫‪107‬‬