מערכות הפעלה

‫מערכות הפעלה‬
‫סיכום הרצאות‬
‫‪ ‬‬
‫סיכם‪ :‬אלעד‪ ‬ריכרדסון‪ ‬‬
‫ערך‪ ‬והוסיף‪ :‬אביחי‪ ‬כהן‪ ‬‬
‫תודה‪ ‬לרז‪ ‬פקטרמן‪ ‬על‪ ‬תרומתו‪ ‬לסיכום‪ ,‬ולכל‪ ‬מי‪ ‬שערך‪ ‬או‪ ‬תיקן‪ .‬‬
‫‪1 ‬‬
‫‪ ‬‬
‫הרצאה‪) 1 ‬מצגת‪ (1 ‬‬
‫מבוא למערכות הפעלה‬
‫מערכות‪ ‬הפעלה ‪ ‬ניתן‪ ‬לחלק‪ ‬לכמה‪ ‬סוגים‪ ‬ישנן‪ ‬מערכות‪ ‬הפעלה‪ ‬כמו‪ DOS ‬שהן‪ ‬פשוטות‪ ‬יחסית‪ ‬ותומכות‪ ‬במשתמש‪ ‬יחיד‪ ‬וישנן‪ ‬‬
‫מערכות‪ ‬מתוחכמות‪ ‬יותר‪ ‬שתומכות‪ ‬בריבוי‪ ‬משתמשים‪ ,‬הראשונה‪ ‬שעשתה‪ ‬זאת‪ ‬הייתה‪ ,Unix ‬אך‪ ‬כיום‪ ‬רוב‪ ‬מערכות‪ ‬ההפעלה‪ ‬‬
‫הנפוצות‪ ‬תומכות‪ ‬בריבוי‪ ‬משתמשים‪ .‬‬
‫כמו‪ ‬כן‪ ‬מערכת‪ ‬הפעלה ‪ ‬מסוימת‪ ‬מתאימה‪ ‬בד"כ‪ ‬לפלטפורמה‪ ‬מתאימה‪ ,‬נעשה‪ ‬ניסיון‪ ‬בשלב‪ ‬מסוים‪ ‬לעשות ‪ ‬מערכות‪ ‬הפעלה‪ ‬‬
‫שמתאימות‪ ‬לפלטפורמות‪ ‬שונות‪ ,‬למשל‪ Microsoft ‬טענו‪ ‬ש­‪ Windows NT‬יוכל‪ ‬לרוץ‪ ‬על ‪ ‬כל‪ ‬חומרה‪ ,‬מה‪ ‬שלא‪ ‬היה‪ ‬נכון‪ ‬‬
‫בפועל‪ Linux .‬לעומת‪ ‬זאת‪ ‬באמת‪ ‬רצה‪ ‬כיום‪ ‬על‪ ‬המון‪ ‬סוגים‪ ‬של‪ ‬חומרה‪ ,‬החל‪ ‬ממחשבים‪ ‬ביתיים‪ ,‬שרתים‪ ‬ועד‪ ‬לטלפונים‪ ‬ניידים‪ .‬‬
‫‪ ‬‬
‫מדוע‪ ‬בעצם‪ ‬יש‪ ‬צורך‪ ‬במערכות‪ ‬הפעלה?‪ ‬‬
‫כשאנחנו‪ ‬בתור‪ ‬מתכנתים‪ ‬הולכים‪ ‬וכותבים‪ ‬תוכנית‪ ‬חדשה ‪ ‬אנחנו‪ ‬לא‪ ‬רוצים‪ ‬לחשוב‪ ‬על‪ ‬החומרה‪ ‬בה‪ ‬אנו‪ ‬משתמשים‪ /‬היינו‪ ‬רוצים‪ ‬‬
‫לעבוד‪ ‬על‪ ‬מעין‪ ‬מחשב‪ ‬אבסטרקטי‪ ,‬אוניברסאלי‪ ,‬בלי‪ ‬להתחשב‪ ‬בפרטים ‪ ‬המדויקים‪ ‬של‪ ‬החומרה‪ .‬כדי‪ ‬שנוכל‪ ‬לבצע‪ ‬הנחה‪ ‬כזאת ‪ ‬‬
‫צריכה‪ ‬להיות‪ ‬תוכנה‪ ‬שתבצע‪ ‬את‪ ‬הקישור‪ ‬בין‪ ‬המחשב‪ ‬האבסטרקטי ‪ ‬לחומרה‪ ‬עצמה‪ ,‬כאן‪ ‬נכנסת‪ ‬מערכת‪ ‬ההפעלה‪ .‬מערכת‪ ‬‬
‫ההפעלה‪ ‬הינה‪ ‬בעצם‪ ‬שכבה‪ ‬שמקשרת‪ ‬בין‪ ‬אפליקציה‪ ‬לבין‪ ‬המחשב‪ ‬הפיזי‪ .‬‬
‫‪ ‬‬
‫‪ ­ Embedded Software‬תוכנה‪ ‬שהותאמה‪ ‬לחומרה‪ ‬מסוימת‪ ‬‬
‫מערכת‪ ‬הפעלה‪ ‬חייבת‪ ‬להיות ‪ embedded ‬שכן‪ ‬היא‪ ‬מותאמת‪ ‬לחומרה‪ ‬עליה‪ ‬אנו‪ ‬רצים‪ .‬המון‪ ‬קוד ‪ ‬במערכת‪ ‬ההפעלה‪ ‬הוא‪ ‬‬
‫ספציפי‪ ‬לחומרה‪ ,‬יש‪ ‬ממש‪ ‬ספריות‪ ‬שכתובות‪ ‬לארכיטקטורות‪ ‬השונות‪ ‬שעליהן ‪ ‬תצטרך‪ ‬מערכת‪ ‬ההפעלה‪ ‬לרוץ‪ .‬אם‪ ‬ננסה‪ ‬להתקין‪ ‬‬
‫לינוקס‪ ‬על‪ ‬חומרה‪ ‬שאין‪ ‬ספריות‪ ‬בשבילה‪ ‬זה‪ ‬פשוט‪ ‬לא‪ ‬יעבוד‪ ,‬צריך‪ ‬לעשות‪ ‬תהליך‪ ‬של‪ .embedding ‬‬
‫‪ ‬‬
‫חשוב‪ ‬להבחין‪ ‬כי‪ ‬כשאנחנו‪ ‬מתקינים‪ ‬מערכות‪ ‬הפעלה‪ ‬כיום‪ ,‬אנחנו‪ ‬לא‪ ‬מקבלים‪ ‬רק‪ ‬את ‪ ‬ה­‪ ,kernel‬הליבה‪ ‬של‪ ‬מערכת‪ ‬ההפעלה‪ ,‬‬
‫אלא‪ ‬תוכנות‪ ‬נוספות‪ ‬שמגיעות‪ ‬עם ‪ ‬ה­‪ ,distribution‬למשל‪ ‬ספריות‪ ‬מתמטיות‪ ,‬תוכנות‪ ‬המטפלות‪ ‬ב­‪ ,UI‬עורך‪ ‬טקסט‪ ,‬קומפיילר‪ ‬‬
‫ועוד‪ .‬אנחנו‪ ‬נדבר‪ ‬בקורס‪ ‬רק‪ ‬על‪ ‬הגרעין‪ ‬של‪ ‬מערכת‪ ‬ההפעלה‪ ,‬האלגוריתמים‪ ‬שבאמצעותם‪ ‬היא‪ ‬מנהלת‪ ‬את ‪ ‬הזיכרון ‪ ‬ואת‪ ‬‬
‫התהליכים‪ ‬במחשב‪ .‬‬
‫‪ ‬‬
‫מערכות‪ ‬ההפעלה‪ ‬הראשונות‪ ‬היו‪ ‬פשוט‪ ‬תוכנית‪ ‬אחת‪ ‬עצומה‪ ‬של‪ ‬קוד‪ ‬ספגטי‪ ,‬ללא‪ ‬כל‪ ‬חוקיות‪ ,‬מה ‪ ‬שהקשה‪ ‬מאוד‪ ‬על‪ ‬הפיתוח‪ ,‬‬
‫ובמיוחד‪ ‬על‪ ‬חלוקת‪ ‬העבודה‪ ‬בין‪ ‬מתכנתים‪ ‬שונים‪ .‬באותה‪ ‬תקופה‪ ‬נוצר‪ ‬ה­‪ concept‬של‪ .Microkernel ‬לקחו‪ ‬את‪ ‬המרכיבים‪ ‬‬
‫המרכזיים‪ ‬של‪ ‬מערכת‪ ‬ההפעלה‪ ,‬ניהול‪ ‬תהליכים‪ ,‬ניהול‪ ‬זיכרון‪ ‬וניהול‪ ‬דיסקים‪ ,‬ושמו‪ ‬אותם‪ ‬בקובץ ‪ ‬אחד‪ ‬קטן‪ ‬יחסית‪ ‬שלא‪ ‬עובר‪ ‬את‪ ‬‬
‫ה­‪ 10,000‬שורות‪ ‬קוד‪ .‬את‪ ‬השאר‪ ‬ה­‪ utilities‬הוציאו‪ ‬מחוץ‪ ‬לקרנל‪ .‬‬
‫די‪ ‬מהר‪ ‬גילו‪ ‬שאמנם‪ ‬זה‪ ‬הופך‪ ‬את‪ ‬חלוקת‪ ‬הקוד‪ ‬לפשוטה ‪ ‬יותר‪ ‬אך‪ ‬יש‪ ‬בעיות‪ ‬ביצועים‪ ‬בשיטה‪ ‬הזאת‪ .‬למשל‪ ‬כשאותו‪ utility ‬‬
‫שמטפל‪ ‬ברכיב ‪ ‬הרשת‪ ‬היה‪ ‬מקבל‪ ‬בקשות‪ ‬רבות‪ ‬היינו‪ ‬צריכים‪ ‬לבצע‪ ‬מעברים‪ ‬רבים‪ ‬בין‪ ‬התוכנה‪ ‬ברמה‪ ‬של‪ ‬המשתמש‪ ‬לבין‪ ‬הרמה‪ ‬‬
‫של‪ ‬מערכת‪ ‬ההפעלה‪ .‬‬
‫בסופו‪ ‬של‪ ‬דבר‪ ‬הגיעו‪ ‬למסקנה ‪ ‬שהדרך‪ ‬הנכונה‪ ‬היא ‪ ‬כן‪ ‬להשאיר‪ ‬את‪ ‬הכלים‪ ‬האלה‪ ‬במערכת‪ ‬ההפעלה‪ ,‬אך‪ ‬לחלק‪ ‬אותם‪ ‬ל­‬
‫‪ .components‬כל‪ ‬קומפוננטה‪ ‬מטפלת‪ ‬בתחום ‪ ‬אחר‪ ‬וממומשת ‪ ‬כ­‪ ,ADT‬כך‪ ‬קל‪ ‬יחסית‪ ‬לקחת ‪ ‬מנגנון‪ ‬מסוים‪ ‬ולהחליף‪ ‬אותו‪ ,‬‬
‫בסה"כ‪ ‬צריך‪ ‬לדאוג‪ ‬שמממשים‪ ‬את‪ ‬כל‪ ‬ה­‪ interface‬כנדרש‪ .‬זו‪ ‬השיטה‪ ‬שבה‪ ‬נהוג‪ ‬להשתמש‪ ‬כיום‪ .‬‬
‫מערכות‪ ‬הפעלה‪ ‬היום‪ ‬ובעתיד‪ ‬‬
‫כיום‪ ‬אנחנו‪ ‬מתחילים‪ ‬לזנוח‪ ‬את‪ ‬המונח‪ ‬של‪" ‬מחשב ‪ ‬אישי"‪ ,‬במקום ‪ ‬לתת‪ ‬לכל‪ ‬אחד‪ Desktop ‬משלו‪ ,‬מקימים‪ Data Centers ‬‬
‫שבהם‪ ‬יש‪ ‬הרבה‪ ‬מחשבים‪ ‬יחסית‪ ‬חזקים‪ .‬כשאני ‪ ‬רוצה‪ ‬לעבוד‪ ‬אני‪ ‬מתחבר‪ ‬מהמחשב‪ ‬שלי‪ ,‬שלא‪ ‬חייב‪ ‬להיות‪ ‬חזק‪ ‬במיוחד‪ ,‬‬
‫למחשב‪ ‬המרוחק‪ ‬ומשתמש‪ ‬בכוח‪ ‬העיבוד‪ ‬שלו‪ .‬מעבר‪ ‬לכך‪ ,‬על‪ ‬אותו ‪ ‬מחשב‪ ‬פיזי‪ ‬מרוחק‪ ‬ניתן‪ ‬לבצע‪ ‬סימולציה‪ ‬של‪ ‬מספר‪ ‬מחשבים‪ ‬‬
‫חלשים‪ ‬יותר‪ .‬כמובן‪ ‬שאני‪ ‬לא ‪ ‬רוצה‪ ‬שמה‪ ‬שקורה‪ ‬במחשב‪ ‬המסומלץ‪ ‬שלי‪ ‬ישפיע‪ ‬על‪ ‬מה‪ ‬שיקרה‪ ‬במחשב ‪ ‬מסומלץ‪ ‬אחר‪ ,‬הפועל‪ ‬‬
‫‪2 ‬‬
‫באותו‪ ‬מחשב‪ ‬פיזי‪ .‬לכן‪ ‬מה‪ ‬שעושים‪ ‬כיום‪ ‬הוא‪ ‬להתקין‪ ‬על‪ ‬המחשב ‪ ‬מערכת‪ ‬הפעלה‪ ‬אחת‪ ‬הנקראת‪ host ‬או‪ hypervisor ‬והיא‪ ‬‬
‫מסמלצת‪ ‬מספר‪ ‬מחשבים‪ ‬ודואגת‪ ‬לשמור‪ ‬על‪ ‬הפרדה‪ ‬ביניהם‪ .‬אנו‪ ‬לא‪ ‬נדבר‪ ‬על‪ ‬מערכות‪ ‬כאלו‪ ‬בקורס‪ .‬‬
‫‪ ‬‬
‫תפקידה של מערכת ההפעלה‬
‫מה‪ ‬ההבדל‪ ‬המהותי‪ ‬בין‪ ‬מערכת‪ ‬ההפעלה‪ ‬לכל‪ ‬אפליקציה‪) ‬תהליך( ‪ ‬אחרת‪ ‬שאנו‪ ‬מריצים?‪ ‬לאפליקציה‪ ‬תמיד‪ ‬יש‪ ‬פעולה‪ ‬שברצונה‪ ‬‬
‫לבצע‪ ,‬או‪ ‬שהיא‪ ‬נמצאת‪ ‬במצב‪ ‬מנוחה‪ .‬למערכת ‪ ‬ההפעלה‪ ‬אף‪ ‬פעם ‪ ‬אין‪ ‬משהו‪ ‬שהיא ‪" ‬רוצה" ‪ ‬לעשות‪ ,‬ישנן‪ ‬שתי‪ ‬סיבות ‪ ‬מרכזיות‪ ‬‬
‫שיכולות‪ ‬לגרום‪ ‬למערכת‪ ‬ההפעלה‪ ‬לבצע‪ ‬פעולה‪ :‬‬
‫●‬
‫●‬
‫בקשה‪ ‬מתוכנה‪ ‬מסוימת‪ ,‬מתן‪ ‬שירות‪ .‬‬
‫חומרה‪ ‬חיצונית‪ ­ ‬ברגע‪ ‬שהחומרה‪ ‬החיצונית‪ ‬אומרת‪ ‬למערכת‪ ‬ההפעלה‪ ‬שיש‪ ‬לה‪ ‬מידע‪ ‬חדש‪ ,‬היא‪ ‬צריכה‪ ‬‬
‫להגיב‪ ‬לה‪) .‬פסיקות‪ ‬חומרה(‪ ‬‬
‫ישנן‪ ‬גם‪ ‬פסיקות‪ ‬תוכנה‪) ‬חריגות‪ ,‬תקלה‪ ‬בקוד‪ ‬שאנו‪ ‬מריצים‪ ­ ‬כמו‪ ‬חלוקה‪ ‬באפס(‪ .‬זו‪ ‬לא‪ ‬בקשה‪ ‬מתוכנה ‪ ‬מסוימת‪ ‬וזו‪ ‬גם‪ ‬לא‪ ‬הודעה‪ ‬‬
‫של‪ ‬חומרה‪ ‬חיצונית‪ ,‬אך‪ ‬היא‪ ‬גם‪ ‬מטופלת‪ ‬ע"י ‪ ‬מערכת‪ ‬ההפעלה‪ .‬בד"כ ‪ ‬מערכת‪ ‬ההפעלה‪ ‬פשוט‪ ‬תסיים‪ ‬את‪ ‬התהליך‪ ‬שגרם‪ ‬‬
‫לשגיאה‪ ‬הזאת‪ .‬אם‪ ‬הקוד‪ ‬שגרם‪ ‬לשגיאה‪ ‬הוא‪ ‬של‪ ‬מערכת‪ ‬ההפעלה‪ ‬נקבל‪ ‬את‪ ‬ה­‪ .Blue Screen Of Death‬‬
‫‪ ‬‬
‫‪ ­ System Calls‬קריאות‪ ‬מערכת‪ ‬‬
‫איזה‪ ‬שירותים‪ ‬מערכת‪ ‬ההפעלה‪ ‬צריכה‪ ‬לתת‪ ‬למשתמש?‪ ‬‬
‫נזכור‪ ‬שמערכת‪ ‬ההפעלה ‪ ‬נותנת‪ ‬לתוכנת‪ ‬מחשב‪ ‬את‪ ‬ההרגשה‪ ‬שהיא‪ ‬חיה‪ ‬בתוך‪ ‬מחשב‪ ‬אבסטרקטי‪ ‬שבו‪ ‬היא‪ ‬התוכנית‪ ‬היחידה ‪ ‬‬
‫הקיימת‪ ,‬לצורך‪ ‬כך‪ ‬מערכת‪ ‬ההפעלה ‪ ‬לא‪ ‬נותנת‪ ‬לאפליקציה‪ ‬לעולם‪ ‬גישה‪ ‬ישירה‪ ‬לרכיבי‪ ‬החומרה‪ ,‬למעט‪ ‬ביצוע‪ ‬פעולות ‪ ‬‬
‫אריתמטיות‪ ‬פשוטות‪ ‬ועבודה‪ ‬עם‪ ‬הרגיסטרים‪ .‬כיום‪ ,‬חומרה‪ ‬יכולה‪ ‬לדעת‪ ‬כאשר‪ ‬תהליך‪ ‬מנסה‪ ‬לגשת‪ ‬אליה‪ ‬באופן ‪ ‬ישיר‪ ‬ולעצור‪ ‬‬
‫אותו‪ ‬אם‪ ‬אין‪ ‬לו‪ ‬הרשאות‪ .‬‬
‫בין‪ ‬השאר‪ ‬מערכת‪ ‬ההפעלה‪ ‬מספקת‪ ‬את‪ ‬התכונות‪ ‬הבאות‪ :‬‬
‫●‬
‫●‬
‫●‬
‫●‬
‫היכולת‪ ‬לאכסן‪ ‬מידע‪ ‬בקבצים‪ .‬‬
‫היכולת‪ ‬לבצע‪ ‬תקשורת‪ ‬עם‪ ‬מחשבים‪ ‬אחרים‪ .‬‬
‫היכולת‪ ‬לבצע‪ ‬תקשורת‪ ‬עם‪ ‬אפליקציות‪ ‬אחרות‪ ‬באמצעות‪ ‬זיכרון‪ ‬משותף‪ .‬‬
‫היכולת‪ ‬לבצע‪ ‬הדפסה‪ ‬על‪ ‬המסך‪ .‬‬
‫בצורה‪ ‬זו‪ ‬מערכת‪ ‬ההפעלה‪ ‬מגבילה‪ ‬את‪ ‬האפליקציה‪ ‬מהשפעה‪ ‬מסוכנת‪ ‬על‪ ‬המחשב‪ ‬האמיתי‪ ,‬ועל ‪ ‬האפליקציות‪ ‬האחרות‪ ‬הרצות ‪ ‬‬
‫עליו‪ .‬מערכת ‪ ‬ההפעלה‪ ‬היא‪ ‬מעין ‪ ‬הורה‪ ‬אחראי‪ ‬של‪ ‬כל‪ ‬התהליכים ‪ ‬הרצים‪ ‬במחשב‪ .‬צריך‪ ‬להבין‪ ‬את‪ ‬התוצאות‪ ‬ההרסניות‪ ‬של‪ ‬גישה‪ ‬‬
‫ישירה‪ ‬של‪ ‬האפליקציה‪ ‬לחומרה‪ ‬חיצונית‪ ,‬למשל‪ ‬האפליקציה‪ ‬הייתה‪ ‬יכולה‪ ‬להחליט‪ ‬שהיא ‪ ‬מפסיקה‪ ‬את‪ ‬פסיקות‪ ‬השעון‪ ‬ובכך‪ ‬הייתה‪ ‬‬
‫גורמת‪ ‬לכך‪ ‬שהיא‪ ‬תהיה‪ ‬האפליקציה‪ ‬היחידה‪ ‬שתרוץ‪ ‬על‪ ‬המחשב‪ .‬‬
‫‪ ‬‬
‫בין‪ ‬אמצעי‪ ‬החומרה‪ ‬בהם‪ ‬תומכת‪ ‬מערכת‪ ‬ההפעלה‪ ‬נמצא‪ ‬את‪ ‬הבאים‪ :‬‬
‫●‬
‫שעון‪ ­ ‬אין‪ ‬לשעון‪ ‬זה ‪ ‬שום ‪ ‬קשר‪ ‬לתדר‪ ‬של‪ ‬מחשב‪ ,‬זהו‪ ‬פשוט‪ ‬אירוע‪ ‬שקורה‪ ‬בין‪ 100 ‬ל­‪ 1000‬פעמים ‪ ‬לשנייה‪ ,‬‬
‫בהתאם‪ ‬להגדרה‪ ,‬ותפקידו‪ ‬למדוד‪ ‬זמן‪ .‬אם‪ ‬יש‪ ‬לי‪ ‬שני‪ ‬תהליכים‪ ‬ששניהם‪ ‬רוצים‪ ‬לרוץ‪ ,‬מערכת‪ ‬ההפעלה ‪ ‬‬
‫צריכה‪ ‬לתת ‪ ‬לשניהם‪ ‬זמן‪ ‬ריצה‪ ,‬אך‪ ‬במידה‪ ‬ויש‪ ‬מעבד‪ ‬אחד‪ ‬לא‪ ‬באמת‪ ‬ניתן‪ ‬להריץ‪ ‬אותם‪ ‬במקביל‪ ,‬ולכן‪ ‬היא‪ ‬‬
‫מקצה‪ ‬לכל‪ ‬אחד‪ ‬מהם‪ ‬פרק‪ ‬זמן‪ ‬מסוים ‪ ‬לריצה‪ ,‬ואז‪ ‬מחליפה‪ ‬אותו‪ ‬בתהליך‪ ‬השני‪ ‬וחוזר‪ ‬חלילה‪ .‬זו‪ ‬אחת‪ ‬‬
‫הסיבות‪ ‬המרכזיות‪ ‬לצורך‪ ‬בשעון‪ .‬‬
‫●‬
‫סנכרון‪ ­ ‬כדי‪ ‬למנוע‪ ‬התנגשות‪ ‬בין‪ ‬חלקי‪ ‬קוד‪ ‬שונים‪ ‬במספר‪ ‬אזורים‪ ‬עדינים‪ ,‬יש‪ ‬צורך ‪ ‬בסנכרון‪ ‬פעולות‪ ‬שונות‪ .‬‬
‫למשל‪ ‬אם‪ ‬בעת‪ ‬הכנסת‪ ‬תהליך ‪ ‬חדש‪ ‬לרשימת‪ ‬התהליכים‪ ,‬מתבצעת‪ ‬גם‪ ‬הוצאה‪ ‬של‪ ‬תהליך‪ ‬אחר ‪ ‬שהסתיים‪ ,‬‬
‫חשוב‪ ‬לשמור‪ ‬על‪ ‬הסדר‪ ‬בין‪ ‬הפעולות‪ .‬זו‪ ‬אחת‪ ‬הבעיות‪ ‬הקשות ‪ ‬שבהן‪ ‬נתמקד‪ .‬בד"כ‪ ‬סנכרון‪ ‬מתבסס‪ ‬על‪ ‬‬
‫‪3 ‬‬
‫תמיכה‪ ‬מהחומרה‪ ,‬כל‪ ‬חומרה‪ ‬מגיעה‪ ‬עם ‪ ‬פקודות‪ ‬מיוחדות‪ ‬המאפשרות ‪ ‬למערכת‪ ‬ההפעלה‪ ‬לממש‪ ‬כל‪ ‬מיני‪ ‬‬
‫מנגנונים‪ ‬של‪ ‬סנכרון‪ ‬שהמימוש‪ ‬בהם‪ ‬גם‪ ‬יעיל‪ ‬וגם‪ ‬פשוט‪ ,‬ללא‪ ‬צורך‪ ‬בקוד‪ ‬מורכב‪ .‬‬
‫●‬
‫‪ ­ I/O Controllers‬כל‪ ‬חומרה‪ ‬חיצונית‪ ‬מתקשרת‪ ‬עם‪ ‬המעבד‪ ‬שלנו‪ ‬באמצעות‪ ‬פעולות‪ .I/O ‬‬
‫○‬
‫‪ ­ DMA‬בשיטה‪ ‬זו ‪ ‬החומרה‪ ‬ניגשת‪ ‬ישירות‪ ‬למעבד‪ ‬וכותבת‪ ‬שם‪ ‬את‪ ‬המידע‪ ‬שהיא‪ ‬רוצה‪ ‬‬
‫להעביר‪) .‬למשל‪ ‬כרטיס‪ ‬רשת(‪ .‬‬
‫‪ ‬‬
‫הרשאות‪ ‬גישה‪ ‬לחומרה‪ ‬‬
‫נזכור‪ ‬שאנו ‪ ‬רוצים ‪ ‬למנוע‪ ‬מהמשתמש‪ ‬לעשות‪ ‬פעולות‪ ‬אסורות‪ ‬כבר‪ ‬ברמת‪ ‬החומרה‪ ,‬חייבים ‪ ‬למנוע‪ ‬מצב‪ ‬שתהליך‪ ‬מנסה‪ ‬ומצליח‪ ‬‬
‫בעצמו‪ ‬לגשת‪ ‬לחומרה‪ ‬חיצונית‪ ,‬פוגע ‪ ‬בתהליכים‪ ‬אחרים‪ ‬ועוד‪ .‬הדרך‪ ‬לעשות‪ ‬זאת‪ ‬באופן‪ ‬וודאי‪ ‬וללא‪ ‬שגיאות‪ ‬היא‪ ‬באמצעות‪ ‬‬
‫החומרה‪ .‬‬
‫בארכיטקטורה‪ ‬של‪ ‬אינטל‪ ‬למשל‪ ‬כשתהליך‪ ‬רץ‪ ‬מוגדרת‪ ‬עבורו‪ ‬הרמה ‪ ‬שבה‪ ‬הוא‪ ‬רץ‪ Current Privilege Level ‬או‪ Ring ‬‬
‫וישנן‪ 4 ‬רמות‪ .‬‬
‫●‬
‫ל­‪ Ring 3‬כמעט‪ ‬אין ‪ ‬הרשאות‪ ,‬אפליקציה ‪ ‬ברמה‪ ‬זו‪ ‬יכולה‪ ‬לבצע‪ ‬חישובים‪ ‬ולגשת‪ ‬לרגיסטרים‪ ,‬שם‪ ‬ירוצו‪ ‬‬
‫האפליקציות‪ ‬של‪ ‬המשתמש‪ .‬‬
‫●‬
‫●‬
‫ל­‪ Ring 0‬יש‪ ‬את‪ ‬כל‪ ‬ההרשאות‪ ,‬שם‪ ‬בד"כ‪ ‬תרוץ‪ ‬מערכת‪ ‬ההפעלה‪ .‬‬
‫‪ Ring 1‬ו­‪ 2‬הן‪ ‬רמות‪ ‬ביניים‪ ‬שלא‪ ‬ממש‪ ‬משתמשים‪ ‬בהן‪ .‬‬
‫לכל‪ ‬פעולה‪ ‬מוגדרת‪ ‬רמה‪ ­ I/O PL) ‬מהו‪ ‬ה­‪ PL‬שצריך‪ ‬כדי‪ ‬לבצע‪ ‬את‪ ‬הפעולה(‪ .‬כשאנו‪ ‬ניגשים‪ ‬לזיכרון‪ ‬החומרה‪ ‬מסתכלת‪ ‬על‪ ‬‬
‫ה­‪) DPL‬מהו‪ ‬ה­‪ PL‬שצריך(‪ ‬ומשווה‪ ‬אותו‪ ‬עם‪ CPL ‬של‪ ‬הקוד‪ ‬ומוודאת‪ ‬ש­‪ .CPL<=DPL‬‬
‫‪ ‬‬
‫כשמבקשים‪ ‬שירות‪ ‬ממערכת‪ ‬ההפעלה‪ ,‬עוברים‪ ‬מ­‪ CPL 3‬ל­‪ CPL 0‬מבצעים‪ ‬את‪ ‬הקוד‪ ‬של ‪ ‬מערכת‪ ‬ההפעלה‪ ‬המטפל‪ ‬בבקשה‪ ‬‬
‫של‪ ‬המשתמש‪ ‬וחוזרים‪ ‬ל­‪ .CPL 3‬‬
‫‪ ‬‬
‫דוגמא‪ :‬‬
‫‪ ‬‬
‫‪ ‬‬
‫בחומרה‪ ‬יש ‪ ‬טבלה‪ ‬בשם‪ ,INTERRUPT TABLE ‬ובה‪ ‬מופיעות‪ ‬הפונקציות‪ ‬המתאימות‪ ‬לכל‪ ‬פסיקה‪ .‬בתא‪ 128 ‬נמצאת‪ ‬‬
‫הפונקציה‪ .sys_call() ‬כאשר‪ ‬אנו‪ ‬מבצעים‪ ‬פקודת‪ interrupt ‬של‪ ‬שירות‪ 128 ‬תיקרא‪ ‬הפונקציה‪ ‬של‪ ‬שירות ‪ ‬מערכת‪ ,‬אך‪ ‬לפני‪ ‬כן‪ ‬‬
‫החומרה‪ ‬מבצעת‪ ‬את‪ ‬המעבר‪ ‬ל­‪ .Ring 0‬כל‪ ‬קריאה‪ ‬למערכת‪ ‬ההפעלה ‪ ‬נעשית‪ ‬באמצעות‪ ‬הפסיקה‪ ‬המסוימת‪ ‬הזאת‪ ,‬קביעת‪ ‬‬
‫השירות‪ ‬נקבעת‪ ‬לפי‪ ‬הערך‪ ‬ברגיסטר‪ .eax ‬‬
‫כמו‪ ‬כן‪ ‬יש‪ ‬טבלה‪ ‬שאינה‪ ‬מוכרת‪ ‬לחומרה‪ ‬בשם‪ ,SYS_CALL_TABLE ‬ובה‪ ‬מוגדר‪ ‬השירות‪) ‬הפונקציה(‪ ‬שיש‪ ‬לתת‪ ‬עבור‪ ‬כל‪ ‬‬
‫ערך‪ ‬של‪ .eax ‬אחרי‪ ‬ביצוע‪ ‬הפונקציה ‪ ‬חוזרים‪ ‬ל­‪ ,sys_call‬הפקודה‪ ‬האחרונה‪ ‬בה‪ ‬היא ‪ iret ‬שמסיימת‪ ‬את‪ ‬הפסיקה ‪ ‬ומחזירה‪ ‬‬
‫את‪ ‬ה­‪ CPL‬מ­‪ 0‬ל­‪ .3‬‬
‫‪ ‬‬
‫‪ ‬‬
‫‪4 ‬‬
‫תהליכים‬
‫מה‪ ‬מאפיין‪ ‬תהליך?‪ ‬‬
‫מהו‪ ‬בעצם‪ ‬תהליך?‪ ‬ניתן‪ ‬להגיד‪ ‬כי‪ ‬תהליך‪ ‬הינו‪ ‬מופע‪ ‬של‪ ‬תוכנית‪ ‬הקיים‪ ‬בזמן‪ ‬ריצה‪ ‬ומנוהל‪ ‬ע"י‪ ‬מערכת‪ ‬ההפעלה‪ .‬‬
‫מעבר‪ ‬לקוד‪ ‬עצמו‪ ‬האחראי‪ ‬לביצוע‪ ‬התהליך‪ ,‬התהליך‪ ‬מוגדר‪ ‬גם‪ ‬ע"י‪ ‬הנתונים‪ ‬ברגיסטרים‪ ,‬במחסנית‪ ‬ובערימה‪ ,‬למשל‪ ‬השורה‪ ‬‬
‫הבאה‪ ‬לביצוע‪ ‬שמורה‪ ‬ברגיסטר‪ .‬לכל‪ ‬תהליך‪ ‬מוגדר‪ ‬מספר‪ ‬תהליך‪ (process id) ‬המאפיין‪ ‬אותו‪ ,‬וכן‪ ‬רשומה‪ ‬המתאימה‪ ‬למספר ‪ ‬‬
‫זה‪ ‬ומכילה‪ ‬מידע‪ ‬נוסף­‪ ‬מתאר‪ ‬תהליך‪ ,Process Control Block) ‬או‪ ‬ב­‪ Linux‬נקרא‪ ‬גם‪ .(Process Descriptor ‬‬
‫‪ ‬תהליך‪ ‬יודע‪ ‬רק‪ ‬את‪ ‬מספר‪ ‬הת‪.‬ז‪ .‬של‪ ‬עצמו‪ ,‬ושל‪ ‬תהליכים‪ ‬אחרים‪ .‬ה­‪ kernel‬מקבל‪ ‬את‪ ‬הת‪.‬ז‪ .‬ולפיו‪ ‬מוצא‪ ‬את‪ ‬הרשומה‪ ‬של‪ ‬‬
‫התהליך‪ ‬המכילה‪ ‬את‪ ‬כל‪ ‬המידע‪ ‬הדרוש‪ ‬לניהולו‪ .‬בין‪ ‬השאר‪ ,‬מצביע‪ ‬למחסנית‪ ‬של‪ ‬התהליך‪ ‬בזיכרון‪ ,‬ערכי‪ ‬הרגיסטרים‪ ‬שלו‪ ‬בזמן‪ ‬‬
‫הפסקת‪ ‬ריצתו‪ ,‬קבצים‪ ‬שהוא‪ ‬פתח‪ ,‬מי‪ ‬המשתמש‪ ‬שהריץ‪ ‬אותו‪ ‬ועוד‪ .‬‬
‫ב­‪ Linux‬שעליו‪ ‬נעבוד‪ ‬יש‪ 95 ‬שדות‪ ‬ברשומה‪ ‬של‪ ‬תהליך‪ .‬‬
‫‪ ‬‬
‫מרחב‪ ‬הכתובות‪ ‬‬
‫כמו‪ ‬שאמרנו ‪ ‬תפקידה‪ ‬של‪ ‬מערכת ‪ ‬ההפעלה‪ ‬הוא‪ ‬לספק‪ ‬לתהליך‪ ‬מעין‪ ‬מחשב‪ ‬אבסטרקטי‪ ‬ופשוט‪ ‬עליו ‪ ‬הוא‪ ‬עובד‪ ,‬כחלק‪ ‬מכך‪ ‬כל‪ ‬‬
‫תהליך‪ ‬חושב‪ ‬שיש‪ ‬לו‪ ‬מרחב‪ ‬כתובות‪ ‬רציף‪ ‬השייך‪ ‬לו‪ ‬בלבד‪ .‬‬
‫מרחב‪ ‬כתובות‪ ‬זה‪ ‬מחולק‪ ‬לסגמנטים‪ ‬באופן‪ ‬הבא‪ :‬‬
‫‪ ‬‬
‫●‬
‫●‬
‫●‬
‫●‬
‫‪ ­ code‬הקוד‪ ‬עצמו‪ ‬הדרוש‪ ‬לריצת‪ ‬התהליך‪ .‬‬
‫‪ ­ static data‬משתנים‪ ‬סטטיים‪ ,‬גלובליים‪ ‬וקבועים‪ ‬של‪ ‬התוכנית‪ .‬‬
‫‪ ­ Heap‬הקצאות‪ ‬דינמיות‪ ‬שביצעה‪ ‬התוכנית‪ ‬‬
‫‪ ­ Stack‬משתנים ‪ ‬מקומיים‪ ,‬כתובת‪ ‬חזרה ‪ ‬של ‪ ‬פונקציה‪ ,‬מצב ‪ ‬הרגיסטרים‪ ‬לפני‪ ‬הקריאה‪ ‬לפונקציה‪ ‬‬
‫ופרמטרים‪ ‬לפונקציה‪ .‬כמו‪ ‬כן‪ ,‬החזרה‪ ‬של‪ struct ‬נעשית‪ ‬על‪ ‬המחסנית‪ .‬‬
‫נבחין‪ ‬כי‪ ‬הערימה‪ ‬גדלה‪ ‬כלפי‪ ‬הכתובות‪ ‬הגבוהות‪ ‬ואילו‪ ‬המחסנית‪ ‬גדלה‪ ‬כלפי‪ ‬הכתובות‪ ‬הנמוכות‪ ,‬מדוע?‪ ‬רוצים‪ ‬לתת‪ ‬לתהליך‪ ‬לנצל‪ ‬‬
‫את‪ ‬כל‪ ‬המרחב‪ ‬שיש‪ ‬לו‪ ,‬כחלק‪ ‬מכך‪ ‬נותנים‪ ‬לו‪ ‬את‪ ‬הגמישות‪ ‬ליצור‪ ‬ערימה‪ ‬גדולה‪ ‬או‪ ‬מחסנית‪ ‬גדולה‪ ‬בהתאם‪ ‬לצורך ‪ ‬שלו‪ ,‬ולכן‪ ‬הם‪ ‬‬
‫צריכים‪ ‬לגדול‪ ‬בכיוונים‪ ‬הפוכים‪ ,‬על‪ ‬מנת‪ ‬לנצל‪ ‬את‪ ‬אותו‪ ‬מרחב‪ .‬‬
‫‪ ‬‬
‫‪5 ‬‬
‫זמן‪ ‬חיים‪ ‬של‪ ‬תהליך‪ ‬‬
‫את‪ ‬זמן‪ ‬החיים‪ ‬של‪ ‬תהליך‪ ‬ניתן‪ ‬לחלק‪ ‬למספר‪ ‬חלקים‪ ‬כפי‪ ‬שניתן‪ ‬לראות‪ ‬בדיאגראמה‪ ‬להלן‪ .‬‬
‫‪ ‬‬
‫●‬
‫‪ ­ New‬שלב‪ ‬יצירת‪ ‬התהליך‪ ‬והרשומה‪ ‬המתאימה‪ ‬לו‪ .‬בסוף‪ ‬היצירה‪ ‬התהליך‪ ‬מועבר‪ ‬למצב‪ ‬‬
‫‪ ,Ready‬הוא‪ ‬אינו‪ ‬מתחיל‪ ‬ריצה‪ ‬באופן‪ ‬מיידי‪ ‬שכן‪ ‬יש‪ ‬תהליך‪ ‬אחר‪ ‬שנמצא‪ ‬במהלך‪ ‬ריצה‪ .‬‬
‫●‬
‫‪ ­ Ready‬התהליך‪ ‬אינו ‪ ‬רץ‪ ‬שכן‪ ‬יש ‪ ‬תהליך‪ ‬אחר‪ ‬שרץ‪ ‬כרגע‪ .‬מערכת‪ ‬ההפעלה‪ ‬יכולה‪ ‬להחזיק‪ ‬‬
‫תהליך‪ ‬זמן‪ ‬רב‪ ‬במצב‪ ,Ready ‬עד‪ ‬להעברתו‪ ‬למצב‪ .Running ‬‬
‫●‬
‫‪ ­ Running‬מערכת‪ ‬ההפעלה‪ ‬מקצה‪ ‬זמן‪ ‬ריצה‪ ‬לתהליך‪ ,‬ומאפשרת‪ ‬לו‪ ‬לבצע‪ ‬את‪ ‬עבודתו‪ .‬אם‪ ‬‬
‫התהליך‪ ‬הסתיים‪ ‬במהלך‪ ‬זמן‪ ‬הריצה‪ ‬שלו‪ ‬נעביר‪ ‬אותו‪ ‬למצב‪ .Terminated ‬במקרה‪ ‬שבו‪ ‬‬
‫התהליך‪ ‬לא‪ ‬סיים‪ ‬את ‪ ‬הריצה‪ ‬שלו‪ ,‬אך‪ ‬חרג ‪ ‬מה­‪ time slice‬אשר‪ ‬הוקצה‪ ‬עבורו‪ ,‬מערכת ‪ ‬ההפעלה‪ ‬‬
‫תחזיר‪ ‬אותו‪ ‬למצב‪ .Ready ‬אם‪ ‬תהליך‪ ‬מבקש‪ ‬לקבל‪ ‬נתונים‪ ,‬לבצע‪ ‬פעולת‪ I/O ‬וכד'‪ ‬הוא‪ ‬יעבור‪ ‬‬
‫למצב‪ .Waiting ‬נבחין‪ ‬כי‪ ‬תהליך‪ ‬שצריך ‪ ‬לחשב‪ ‬חישוב‪ ‬מורכב‪ ‬על‪ ‬ה­‪ CPU‬יבצע‪ ‬מעברים‪ ‬רבים‪ ‬‬
‫בין‪ ‬מצב‪ Ready ‬ל­‪ ,Running‬בהתאם‪ ‬לזמנים‪ ‬שיוקצו‪ ‬עבורו‪ .‬‬
‫●‬
‫‪ ­ Terminated‬מצב‪ Zombie ‬של‪ ‬התהליך‪ ,‬בעת‪ ‬סיום‪ ‬ריצתו‪ .‬למה ‪ ‬לא‪ ‬פשוט‪ ‬מוחקים‪ ‬את ‪ ‬‬
‫התהליך‪ ‬אלא ‪ ‬מחזיקים‪ ‬אותו‪ ‬במצב‪ ?Terminated ‬בלינוקס‪ ‬תהליכים‪ ‬יכולים‪ ‬לבקש‪ ‬לקבל‪ ‬הודעה‪ ‬‬
‫על‪ ‬סיום ‪ ‬של ‪ ‬תהליך‪ ‬אחר‪ ,‬המערכת‪ ‬מחזיקה‪ ‬את‪ ‬התהליך‪ ‬במצב‪ Zombie ‬עד‪ ‬שהיא‪ ‬מוודאת‪ ‬שכל‪ ‬‬
‫תהליך‪ ‬אחר‪ ‬קיבל‪ ‬הודעה ‪ ‬על‪ ‬סיומו‪ ‬של‪ ‬תהליך‪ ‬זה‪ ‬ורק‪ ‬לאחר‪ ‬מכן‪ ‬היא‪ ‬מחסלת‪ ‬אותו‪ ,‬ומוחקת‪ ‬אותו‪ ‬‬
‫מהזיכרון‪ .‬‬
‫●‬
‫‪ ­ Waiting‬ה­‪ CPU‬לא‪ ‬יכול‪ ‬לספק‪ ‬באופן‪ ‬מיידי‪ ‬את‪ ‬נתוני‪ ‬ה­‪ ,I/O‬יש‪ ‬לחכות‪ ‬עד‪ ‬להגעת‪ ‬הנתונים‪ .‬‬
‫מהרגע‪ ‬שהוצאנו‪ ‬בקשה‪ ‬לקבל‪ ‬את‪ ‬הנתונים‪ ‬יכולים‪ ‬לעבור‪ ‬חמישה‪ ‬מיליון)!(‪ ‬מחזורי ‪ ‬שעון‪ .‬ולכן‪ ‬נעביר‪ ‬‬
‫אותו‪ ‬למצב‪ ‬המתנה ‪ ‬ונטפל‪ ‬בתהליכים ‪ ‬אחרים‪ .‬ברגע‪ ‬שהאירוע‪ ‬התרחש‪ ,‬נתוני‪ ‬ה­‪ I/O‬הגיעו‪ ‬למשל‪ ,‬‬
‫מערכת‪ ‬ההפעלה‪ ‬מעירה‪ ‬את‪ ‬התהליך‪ ‬ומביאה‪ ‬אותו ‪ ‬למצב‪) Ready ‬שהרי‪ ‬כרגע‪ ‬יכול‪ ‬להיות‪ ‬שיש ‪ ‬‬
‫כבר‪ ‬מישהו‪ ‬אחר‪ ‬שרץ(‪ .‬‬
‫‪ ‬‬
‫‪ Context Switch‬‬
‫מה‪ ‬קורה‪ ‬כשרוצים‪ ‬להחליף‪ ‬תהליך‪ ‬אחד‪ ‬בתהליך ‪ ‬אחר?‪ ‬בעצם‪ ‬רוצים‪ ‬שהקוד‪ ‬של‪ ‬תהליך‪ 1 ‬והמידע‪ ‬שנשמר‪ ‬עבורו‪ ‬יוחלפו‪ ‬בקוד‪ ‬‬
‫ובמידע‪ ‬של‪ ‬התהליך‪ ‬האחר‪ .‬בכל‪ ‬ארכיטקטורה‪ ‬קיים‪ ‬רגיסטר‪ ‬המציין‪ ‬את‪ ‬המיקום‪ ‬בזיכרון‪ ‬של‪ ‬הפקודה‪ ‬הבאה‪ ‬לביצוע‪) ‬באת"מ‪ ‬זהו‪ ‬‬
‫ה­‪ R7‬שמציין‪ ‬את‪ ‬ה­‪ (PC‬וכן‪ ‬ישנו‪ ‬רגיסטר‪ ‬המציין‪ ‬את‪ ‬המיקום‪ ‬של‪ ‬המחסנית‪) ‬באת"מ‪ ‬זהו‪ R6 ‬שמציין‪ ‬את‪ ‬ה­‪ .(SP‬בעצם‪ ‬כל‪ ‬‬
‫מה‪ ‬שצריך‪ ‬לעשות‪ ‬הוא‪ ‬להחליף‪ ‬את‪ ‬ערכי‪ ‬הרגיסטרים‪ ,‬והם‪ ‬אלו‪ ‬שיקבעו‪ ‬איזה‪ ‬תהליך‪ ‬ירוץ‪ ‬לאחר‪ ‬העדכון‪ .‬‬
‫‪6 ‬‬
‫כיצד‪ ‬זה‪ ‬יתבצע ‪ ‬בפועל?‪ ‬בשלב‪ ‬הראשון‪ ‬ניקח‪ ‬את‪ ‬כל‪ ‬ערכי‪ ‬הרגיסטרים‪ ‬של‪ ‬התהליך‪ ‬שרץ ‪ ‬כרגע‪ ‬ונשמור‪ ‬אותם‪ ‬ברשומה‪ ‬שלו‪) ‬ה­‬
‫‪ .(Process Control Block‬לאחר‪ ‬מכן‪ ‬נטען‪ ‬את‪ ‬ערכי‪ ‬הרגיסטרים‪ ‬של‪ ‬תהליך‪ 2 ‬מתוך‪ ‬הרשומה‪ ‬שלו‪ ‬וכך‪ ‬בעצם‪ ‬נעביר‪ ‬אותו‪ ‬‬
‫לתהליך‪ ‬הפעיל‪ .‬על‪ ‬אותה‪ ‬הדרך‪ ‬נבצע‪ ‬את‪ ‬ההחלפה‪ ‬של‪ ‬תהליך‪ ,2 ‬חזרה‪ .‬‬
‫‪ ‬‬
‫‪ ‬‬
‫‪ ‬‬
‫תהליך‪ ‬זה‪ ‬של‪ ‬ניהול‪ ‬התהליך‪ ‬המקבל‪ ‬זמן‪ ‬ריצה‪ ‬וגישה‪ ‬למשאבי‪ ‬המערכת‪ ‬נקרא‪ . Scheduling ‬‬
‫‪ ‬‬
‫תורי‪ ‬מצבים‪ ‬‬
‫מערכת‪ ‬ההפעלה‪ ‬מחזיקה‪ ‬את‪ ‬התהליכים‪ ‬הקיימים‪ ‬בתורים‪ ,‬כך‪ ‬שכל‪ ‬תהליך‪ ‬צריך‪ ‬להימצא ‪ ‬בתור‪ ‬מסוים‪ .‬בתור‪ ‬ה­‪ Ready‬‬
‫‪ ,Queue‬תור‪ ‬התהליכים‪ ‬שמחכים‪ ‬ל­‪ .CPU‬או‪ ‬בתור‪ ‬המתנה‪ ‬כלשהו‪ ,‬תור‪ ‬של‪ ‬תהליכים‪ ‬המחכים‪ ‬לאירוע‪ ‬מסוים‪ .‬‬
‫נבחין‪ ‬כי‪ ‬יש‪ ‬תור‪ Ready ‬יחיד‪),‬תחת‪ ‬ההנחה‪ ‬של‪ ‬מעבד‪ ‬יחיד(‪ ,‬אך‪ ‬מספר‪ ‬תורי‪ ‬המתנה‪ ‬עבור‪ ‬אירועים‪ ‬שונים‪ .‬מעבר‪ ‬לכך‪ ,‬יש‪ ‬‬
‫תורי‪ ‬המתנה‪ ‬שבעת‪ ‬הגעה‪ ‬של‪ ‬אירוע‪ ‬יתעורר‪ ‬רק‪ ‬תהליך‪ ‬אחד‪ ‬בתור‪) ‬למשל‪ ‬בהמתנה‪ ‬להקשת‪ ‬מקלדת‪ ‬רק‪ ‬תהליך‪ ‬אחד‪ ‬יכול ‪ ‬‬
‫לטפל‪ ‬בהקשה(‪ ,‬ויש‪ ‬כאלו ‪ ‬שבהם‪ ‬נרצה ‪ ‬להעיר‪ ‬את‪ ‬כל‪ ‬התהליכים‪ ‬ולהעבירם ‪ ‬למצב‪) Ready ‬למשל‪ ‬אם‪ ‬תהליכים‪ ‬מחכים‪ ‬לשעה‪ ‬‬
‫מסוימת‪ ,‬אפשר‪ ‬להעיר‪ ‬את‪ ‬כולם‪ ‬ביחד(‪ .‬‬
‫‪ ‬‬
‫‪ ‬‬
‫‪7 ‬‬
‫קריאות מערכת ו‪Shell-‬‬
‫‪ Fork‬‬
‫פונקצית‪ ‬השירות‪ fork ‬משמשת‪ ‬לפיצול‪ ‬תהליכים‪ ,‬היא‪ ‬יוצרת‪ ‬עוד ‪ ‬מופע ‪ ‬של ‪ ‬התוכנית‪ ‬ומקצה‪ ‬לו ‪ ‬את‪ ‬המשאבים‪ ‬הנדרשים‪ .‬‬
‫התהליך‪ ‬יודע‪ ‬האם‪ ‬הוא‪ ‬התהליך‪ ‬המקורי‪ ‬או‪ ‬הבן‪ ‬לפי‪ ‬ערך‪ ‬ההחזרה ‪ ‬של‪ .fork ‬בעצם‪ ‬הפונקציה‪" fork ‬חוזרת‪ ‬פעמיים"‪ ,‬כך‪ ‬‬
‫שעבור‪ ‬כל‪ ‬תהליך‪ ‬היא‪ ‬מחזירה‪ ‬ערך ‪ ‬שונה‪ ,‬בתהליך‪ ‬של‪ ‬הבן‪ ‬היא‪ ‬מחזירה ‪) 0 ‬זהו‪ pid ‬לא‪ ‬חוקי(‪ ,‬ובתהליך‪ ‬של‪ ‬האב‪ ‬היא ‪ ‬מחזירה‪ ‬‬
‫את‪ ‬ה­‪ pid‬של‪ ‬הבן‪ .‬‬
‫מרגע ‪ ‬החלוקה‪ ‬לכל‪ ‬תהליך‪ ‬יש ‪ ‬מחסנית‪ PC ,‬ומשאבים‪ ‬משלו‪ ‬ומבחינת‪ ‬מערכת‪ ‬ההפעלה‪ ‬מדובר‪ ‬כבר‪ ‬בשני‪ ‬מופעים‪ ‬שונים‪ ,‬‬
‫למרות‪ ‬כל‪ ‬זאת‪ ‬הם‪ ‬חולקים‪ ‬את‪ ‬אותו‪ ‬הקוד‪ ‬וכן‪ ‬יש‪ ‬לאב‪ ‬ולבן‪ ‬משאבים‪ ‬משותפים‪ ,‬למשל‪ ‬כברירת‪ ‬מחדל‪ ‬הקבצים‪ ‬משותפים‪ .‬‬
‫‪ Fork‬הינה‪ ‬הדרך‪ ‬היחידה‪ ‬שלנו‪ ‬ליצור‪ ‬תהליכים‪ ‬חדשים‪) ‬לפחות‪ ‬ב­‪ Scope‬של‪ ‬הקורס(‪ .‬‬
‫‪ execv‬‬
‫כמובן‪ ‬שלעיתים‪ ‬קרובות‪ ‬נרצה ‪ ‬להתחיל‪ ‬הרצה‪ ‬של‪ ‬תוכנית‪ ‬אחרת‪ ,‬שאינה‪ ‬חלק ‪ ‬מהקוד‪ ‬שלנו‪ .‬פונקצית‪ fork ‬לא‪ ‬מאפשרת‪ ‬לנו‪ ‬‬
‫לעשות‪ ‬זאת‪ .‬לצורך‪ ‬כך‪ ‬נשתמש‪ ‬בפונקציה‪ execv ‬אשר‪ ‬עוצרת ‪ ‬את‪ ‬ביצוע‪ ‬התוכנית‪ ‬הנוכחית‪ ‬וטוענת‪ ‬את‪ prog ‬שהועברה‪ ‬לה‪ ‬‬
‫)ניתן‪ ‬להעביר‪ ‬גם‪ ‬ארגומנטים(‪ ‬נבחין‪ ‬כי‪ ‬קוד‪ ‬זה‪ ‬אינו‪ ‬מתחיל‪ ‬תהליך‪ ‬חדש‪ ,‬אלא‪ ‬מחליף‪ ‬את‪ ‬התוכנית‪ ‬שמריצים‪ ‬בתוך‪ ‬אותו‪ ‬תהליך ‪ ‬‬
‫ואותו‪,PCB ‬וכך‪ ‬ניתן‪ ‬להריץ‪ ‬איזשהו‪ exe ‬שרירותי‪ .‬‬
‫‪ ‬‬
‫באמצעות‪ ‬שתי‪ ‬הפונקציות‪ ‬האלו‪ ‬נוכל‪ ‬לממש‪ shell ‬מינימאלי‪ ,‬כפי‪ ‬שניתן‪ ‬לראות‪ ‬בקוד‪ ‬הבא‪ :‬‬
‫‪ ‬‬
‫‪ ‬‬
‫‪ ‬‬
‫מה‪ ‬בעצם‪ ‬עושה ‪ ‬ה­‪ shell‬בדוגמא?‪ ‬הוא‪ ‬רץ‪ ‬בלולאה‪ ‬אינסופית‪ ‬ומקבל‪ ‬בכל‪ ‬פעם‪ ‬פקודה‪ ‬חדשה‪ ,‬עבור‪ ‬כל‪ ‬פקודה‪ ‬הוא‪ ‬מבצע ‪fork ‬‬
‫כך‪ ‬שהפקודה‪ ‬שהתקבלה ‪ ‬תרוץ‪ ‬על‪ ‬תהליך‪ ‬חדש‪ ‬וה­‪ shell‬יוכל‪ ‬להמשיך‪ ‬ולקרוא‪ ‬את‪ ‬הפקודות‪ ‬הבאות‪ ‬וליצור‪ ‬תהליכים ‪ ‬גם‪ ‬עבורן‪ .‬‬
‫נבחין‪ ‬כי‪ ‬הקוד‪ ‬יודע‪ ‬האם‪ ‬הוא‪ ‬הבן‪ ‬או‪ ‬ה­‪ shell‬המקורי‪ ‬לפי‪ ‬ה­‪ pid‬שהוחזר‪ .‬‬
‫כמו‪ ‬כן‪ ,‬נבחין‪ ‬כי‪ execv ‬היא‪ ‬פונקציה‪ ‬שלא‪ ‬אמורה‪ ‬לחזור‪ ,‬ולכן‪ ‬אם ‪ ‬הגענו‪ ‬לשורה‪ fprintf ‬שמופיעה ‪ ‬אחריה‪ ‬מדובר ‪ ‬בשגיאה‪ ,‬ויש‪ ‬‬
‫לטפל‪ ‬בה‪ .‬‬
‫‪ ‬‬
‫נשים ‪ ‬לב‪ ‬לבעיה‪ ‬ב­‪ shell‬הזה‪ ,‬נניח‪ ‬שפקודת‪ execv ‬נכשלה‪ ,‬לאחר‪ ‬הדפסת‪ ‬הודעת‪ ‬השגיאה‪ ‬התהליך‪ ‬יישאר ‪ ‬בלולאה‪ ‬אינסופית‪ ‬‬
‫של‪ ‬ה­‪ shell‬וינסה‪ ‬לתפקד‪ ‬כ­‪ shell‬בעצמו‪ ,‬הפתרון‪ ‬הנכון‪ ‬היה‪ ‬כמובן‪ ‬לבצע‪ break ‬או‪ return ‬במקרה‪ ‬של‪ ‬כישלון‪ .‬‬
‫‪8 ‬‬
‫‪ ‬‬
‫הרצאה‪) 2 ‬מצגת‪ (2 ‬‬
‫‪Scheduling‬‬
‫כפי‪ ‬שהזכרנו ‪ ‬בהרצאה‪ ‬הקודמת‪ Scheduler ,‬הוא‪ ‬הקוד‪ ‬במערכת‪ ‬ההפעלה‪ ‬שתפקידו‪ ‬לקבוע‪ ‬איזה‪ ‬תהליך ‪ ‬ירוץ‪ ‬‬
‫כעת‪ .‬‬
‫כדי‪ ‬להבין‪ ‬את‪ ‬חשיבותן‪ ‬של‪ ‬שיטות‪ Scheduling ‬שונות‪ ‬נרצה‪ ‬לשנות‪ ‬קצת‪ ‬את‪ ‬צורת‪ ‬המחשבה‪ ‬שלנו‪ ‬ולעבור‪ ‬‬
‫מחשיבה‪ ‬על‪ ‬מחשבים‪ ‬אישיים‪ ‬למחשבי‪ ‬על‪ .‬‬
‫● בניגוד‪ ‬למחשבים‪ ‬האישיים‪ ‬שאנו‪ ‬מכירים‪ ,‬מחשבי‪ ‬על‪ ‬מורכבים‪ ‬מכמות‪ ‬גדולה‪ ‬יותר‪ ‬בסדרי‪ ‬גודל‪ ‬של‪ ‬ליבות‪ ‬‬
‫כך‪ ‬שכל‪ ‬אחת‪ ‬מהן‪ ‬בפני‪ ‬עצמה‪ ‬חלשה‪ ‬בסדרי‪ ‬גודל‪ ‬מליבות‪ ‬של‪ ‬המעבדים‪ ‬בשוק‪ ‬המחשבים‪ ‬האישי‪ ,‬אך‪ ‬‬
‫יחדיו‪ ‬הן‪ ‬חזקות‪ ‬משמעותית‪ ‬מהמחשבים‪ ‬האישיים‪ ‬שאנו‪ ‬מכירים‪ ,‬על‪ ‬תקן‪" ‬זו‪ ‬לא‪ ‬האיכות‪ ‬זו‪ ‬הכמות"‪ .‬‬
‫עוצמתן‪ ‬החלשה‪ ‬מאפשרת‪ ‬לאכסן‪ ‬את‪ ‬הליבות‪ ‬בצפיפות‪ ‬מאוד‪ ‬גבוהה‪ ‬ללא‪ ‬בעיות‪ ‬של‪ ‬עומס‪ ‬חום‪ .‬‬
‫● בד"כ‪ ‬באותו‪ ‬מחשב‪ ‬על‪ ‬משתמשים‪ ‬מספר‪ ‬רב‪ ‬של‪ ‬משתמשים‪) ‬עד ‪ 100 ‬משתמשים‪ ‬שונים(‪ ,‬המשתמשים‪ ‬‬
‫בו‪ ‬במקביל‪ .‬‬
‫● לכל‪ ‬עבודה‪ ‬לביצוע‪ ‬מוקצות‪ ‬מספר‪ ‬ליבות‪ ‬לצורך‪ ‬החישוב‪ ,‬זהו ‪ ‬הגודל‪ ‬של‪ ‬העבודה‪ ,‬שמוגדר‪ ‬ע"י‪ ‬המשתמש‪ .‬‬
‫במהלך‪ ‬ביצוע‪ ‬העבודה‪ ‬הליבות‪ ‬יכולות‪ ‬לתקשר‪ ‬זו‪ ‬עם‪ ‬זו‪ .‬‬
‫לכל‪ ‬תהליך‪ ‬שרץ‪ ‬במחשב‪ ‬יש ‪ ‬בעצם‪ ‬שני‪ ‬מימדים‪ :‬זמן‪ ‬הריצה‪ ‬שלו‪ ‬ומספר‪ ‬המעבדים‪ ‬שהוא‪ ‬משתמש‪ ‬בהם‪ ,‬בניגוד‪ ‬‬
‫למחשבים‪ ‬אישיים‪ ‬שבהם‪ ‬בד"כ‪ ‬כל ‪ ‬תהליך‪ ‬רץ‪ ‬על‪ ‬מעבד‪ ‬אחד‪ .‬במחשבי‪ ‬על‪ ‬נרצה‪ ‬לעיתים‪ ‬קרובות‪ ‬להקצות‪ ‬מספר‪ ‬‬
‫ליבות‪ ‬לאותו‪ ‬התהליך‪ .‬‬
‫‪ ‬‬
‫‪ Batch Scheduling‬‬
‫בקבוצת‪ ‬שיטות‪ ‬תזמון‪ ‬זו ‪ ‬תוכנית‪ ‬רצה‪ ‬בלי‪ ‬הפרעות‪ ,‬ללא‪ ‬עצירה‪ ,‬עד‪ ‬לסיומה‪ .‬הרבה‪ ‬פעמים‪ ‬כותבים‪ ‬את‪ ‬התוכניות‪ ‬‬
‫האלה‪ ‬כך‪ ‬שהן‪ ‬ייכנסו‪ ‬בזיכרון‪ ‬ולא‪ ‬יהיה‪ ‬צורך‪ ‬להשתמש‪ ‬בדיסק‪ .‬בכל‪ ‬נקודת‪ ‬זמן‪ ‬יש‪ ‬רק‪ ‬תוכנית‪ ‬אחת‪ ‬שמשתמשת‪ ‬‬
‫בליבות‪ .‬רק‪ ‬בסוף‪ ‬ביצוע‪ ‬התהליך‪ ‬ניתן‪ ‬להקצות‪ ‬את‪ ‬הליבות‪ ‬לתהליכים‪ ‬אחרים‪ .‬תהליכים‪ ‬שרצים‪ ‬באופן‪ ‬זה‪ ‬נקראים‪ ‬‬
‫‪ .non­preemptive‬‬
‫‪ ‬‬
‫מטריקות‪ ‬שונות‪ ‬‬
‫לצורך‪ ‬בדיקת‪ ‬מערכת‪ ‬ישנם‪ ‬מספר‪ ‬פרמטרים‪ ‬שמעניינים‪ ‬אותנו‪ ,‬נגדיר‪ ‬אותם‪ ‬כעת‪ :‬‬
‫● ‪ ­ Average Wait Time‬זמן‪ ‬ההמתנה‪ ‬הממוצע‪ ‬בין‪ ‬הגשת‪ ‬התהליך‪ ‬עד‪ ‬לתחילת‪ ‬הביצוע‪ ‬שלו‪ .‬‬
‫● ‪ ­ Average Response Time‬זמן‪ ‬התגובה‪ ‬הממוצע‪ ‬בין‪ ‬הגשת‪ ‬התהליך‪ ‬עד‪ ‬לסיום‪ ‬הביצוע‪ ‬שלו‪ .‬‬
‫‪9 ‬‬
‫● ההפרש‪ ‬בין‪ ‬זמן‪ ‬התגובה‪ ‬הממוצע‪ ‬לזמן‪ ‬ההמתנה‪ ‬הממוצע‪ ‬הוא‪ ‬קבוע‪ ‬השווה‪ ‬לזמן‪ ‬הריצה‪ ‬הממוצע‪ .‬‬
‫נשים‪ ‬לב‪ ‬ששתי‪ ‬מטריקות‪ ‬אלו‪ ‬הן‪ ‬ממוצעות‪ ,‬ביחס‪ ‬למספר‪ ‬רב‪ ‬של ‪ ‬תהליכים‪ ‬שאנו‪ ‬מריצים‪ ,‬כמו‪ ‬כן‪ ‬את‪ ‬שתיהן‪ ‬נשאוף‪ ‬‬
‫לצמצם‪ ‬ככל‪ ‬הניתן‪ ‬ובכך‪ ‬לשפר‪ ‬את‪ ‬הביצועים‪ .‬‬
‫‪ ‬‬
‫בתור‪ ‬מתכנן‪ ‬מערכת‪ ‬הפעלה‪ ‬אין‪ ‬לנו‪ ‬ממש‪ ‬השפעה‪ ‬על‪ ‬זמן‪ ‬הריצה‪ ,‬הוא‪ ‬תלוי‪ ‬במרכיבי‪ ‬החומרה‪ ‬וביעילות‪ ‬‬
‫האלגוריתמים‪ ‬בתוכנית‪ ,‬אך‪ ‬זמן‪ ‬ההמתנה‪ ‬נתון‪ ‬להשפעתנו‪ ‬דרך‪ ‬ה­‪ .Scheduling‬‬
‫‪ ‬‬
‫● ‪ ­ Average slowdown‬היחס‪ ‬בין ‪ ‬זמן‪ ‬התגובה ‪ ‬לזמן‪ ‬הריצה‪ ,‬זו‪ ‬מטריקה‪ ‬נוספת‪ ‬שנשתמש‪ ‬בה‪ ‬לעיתים‪ ‬‬
‫כדי‪ ‬לאפיין‪ ‬את‪ ‬המערכת‪ .‬בעצם‪ ‬יחס‪ ‬זה‪ ‬אומר‪" ‬כמה‪ ‬חיכיתי‪ ‬ביחס‪ ‬לכמה‪ ‬זמן‪ ‬לקח‪ ‬לבצע‪ ‬אותי"‪ .‬נרצה‪ ‬‬
‫לצמצם‪ ‬את‪ ‬ה­‪ slowdown‬במטרה‪ ‬למנוע‪ ‬מצב‪ ‬בו‪ ‬זמן‪ ‬ההמתנה‪ ‬ארוך‪ ‬משמעותית‪ ‬מזמן‪ ‬הריצה‪ .‬‬
‫● ‪) Utilization‬ניצולת(‪ ­ ‬אחוז‪ ‬הזמן‪ ‬שבו‪ ‬המעבד‪ ‬מבצע‪ ‬עבודה‪ ‬פרודקטיבית‪ ,‬נרצה‪ ‬למקסם‪ .‬‬
‫● ‪) Throughput‬ספיקה(‪ ­ ‬כמה‪ ‬עבודה‪ ‬אנחנו‪ ‬מסיימים‪ ‬ביחידת‪ ‬זמן‪ ,‬נרצה‪ ‬למקסם‪ .‬‬
‫‪ ‬‬
‫המטריקות‪ ‬שמעניינות‪ ‬אותנו‪ ‬הן‪ ‬תלוית‪ ‬הקשר‪ ,‬למשל‪ ‬כמשתמשים ‪ ‬אכפת‪ ‬לנו‪ ‬מה­‪ response, wait‬ו­‪ slowdown‬‬
‫ואילו‪ ‬כבעלי‪ ‬המחשב‪ ‬נתעניין‪ ‬בניצולת‪ ‬ובספיקה‪ ,‬שכן‪ ‬נרצה‪ ‬לנצל‪ ‬את‪ ‬החומרה‪ ‬שלנו‪ ‬באופן‪ ‬מירבי‪ .‬‬
‫נקודה‪ ‬מעניינת‪ ‬היא‪ ‬שתהליכים‪ ‬ארוכים‪ ‬לא‪ ‬משפיעים‪ ‬מאוד‪ ‬על‪ ‬המטריקה‪ ‬של‪ ‬ה­‪ ,Slowdown‬אלא‪ ‬על‪ ‬המטריקה‪ ‬‬
‫של‪ ‬ה­‪ .Response Time‬שכן‪ ‬נדיר‪ ‬מאוד‪ ‬שזמן‪ ‬ההמתנה‪ ‬שלהם‪ ‬גדול‪ ‬ביחס‪ ‬לזמן‪ ‬הריצה‪ .‬בהתאם‪ ‬לכך‪ ‬התהליכים‪ ‬‬
‫הקצרים‪ ‬הם‪ ‬בעלי‪ ‬ערכי‪ ‬ה­‪ slowdown‬הגדולים ‪ ‬יותר‪ ,‬שכן‪ ‬זמן‪ ‬ההמתנה‪ ‬שלהם‪ ‬יכול ‪ ‬להיות ‪ ‬פי‪ ‬כמה‪ ‬וכמה‪ ‬מזמן ‪ ‬‬
‫הריצה‪ .‬אך‪ ‬ה­‪ Response Time‬הכולל‪ ‬שלהם‪ ‬הוא‪ ‬יחסית‪ ‬קצר‪ .‬‬
‫‪ ‬‬
‫‪ FCFS Scheduling‬‬
‫בשיטה‪ ‬זו‪ ‬של‪ First Come First Served ‬העבודות ‪ ‬מתוזמנות‪ ‬לפי‪ ‬זמן ‪ ‬ההגעה‪ ‬שלהם‪ .‬העבודה‪ ‬שנמצאת ‪ ‬בראש‪ ‬‬
‫התור‪ ‬תבוצע‪ ‬ברגע‪ ‬שיהיו‪ ‬מספיק‪ ‬ליבות‪ ‬פנויות‪ .‬שיטה‪ ‬זו ‪ ‬הינה‪ ‬פשוטה‪ ‬מאוד‪ ,‬אך‪ ‬קל‪ ‬לראות‪ ‬שהיא‪ ‬אינה‪ ‬יעילה‪ ‬‬
‫במיוחד‪ .‬במקרים‪ ‬רבים‪ ‬ליבות‪ ‬יהיו‪ ‬במצב‪ ‬של ‪ ‬אבטלה‪ ‬בזמן‪ ‬שנחכה‪ ‬שיתפנו‪ ‬מספר ‪ ‬ליבות‪ ,‬כאשר‪ ‬בשיטה‪ ‬מתקדמת ‪ ‬‬
‫יותר‪ ‬היינו‪ ‬יכולים‪ ‬להשתמש‪ ‬בזמן‪ ‬זה‪ ‬כדי‪ ‬לבצע‪ ‬עבודה‪ ‬קצרה‪ ‬יותר‪ ‬שדורשת‪ ‬מספר‪ ‬קטן‪ ‬יותר‪ ‬של‪ ‬ליבות‪ ‬לעומת ‪ ‬‬
‫העבודה‪ ‬שיושבת‪ ‬בראש‪ ‬התור‪ .‬‬
‫‪ ‬‬
‫‪ EASY Scheduling‬‬
‫מובססת‪ ‬על‪ ‬שיטת‪ ,FCFS ‬אך‪ ‬בזמן‪ ‬שמחכים‪ ‬שיתפנו‪ ‬מספיק‪ ‬ליבות‪ ‬לצורך‪ ‬ביצוע‪ ‬העבודה‪ ‬שבראש‪ ‬התור‪ ‬אנו‪ ‬‬
‫מחפשים‪ ‬עבודה‪ ‬אחרת‪ ‬שנמצאת‪ ‬בהמתנה ‪ ‬בתור‪ ‬כך‪ ‬שאם‪ ‬נריץ‪ ‬אותה‪ ‬הוא‪ ‬תסיים‪ ‬את‪ ‬פעולתה‪ ‬בזמן‪ ‬ההמתנה‪ ‬של‪ ‬‬
‫העבודה‪ ‬הראשונה‪ ‬מבלי‪ ‬לעכב‪ ‬את‪ ‬פעולתה‪ ,‬תהליך‪ ‬זה‪ ‬נקרא‪ .backfilling ‬‬
‫הקושי‪ ‬המרכזי‪ ‬בשיטה‪ ‬זו‪ ‬הוא‪ ‬שהיא‪ ‬דורשת‪ ‬מאיתנו‪ ‬לחזות‪ ‬את‪ ‬זמן‪ ‬הביצוע‪ ‬של‪ ‬עבודה‪ ‬מסוימת‪ ,‬לפני‪ ‬הביצוע‪ ‬שלה‪ ,‬‬
‫הפתרון‪ ‬הנפוץ‪ ‬במחשבים‪ ‬אלו‪ ‬הוא‪ ‬פשוט‪ ‬להכריח‪ ‬את‪ ‬המשתמש‪ ‬לציין‪ ‬את‪ ‬זמן‪ ‬הריצה‪ ‬הצפוי‪ ‬לתוכנית‪ ,‬כאשר‪ ‬אם‪ ‬‬
‫התוכנית‪ ‬רצה‪ ‬יותר‪ ‬מהזמן‪ ‬הצפוי‪ ‬שלה‪ ‬היא‪ ‬תושמד‪ .‬כך‪ ‬מבחינת‪ ‬המשתמש‪ ‬כדאי‪ ‬לו‪ ‬לציין‪ ‬חסם‪ ‬עליון‪ ,‬על‪ ‬מנת‪ ‬למנוע‪ ‬‬
‫את‪ ‬הריגת‪ ‬התוכנית‪ ‬שלו‪ ,‬אך‪ ‬חסם‪ ‬עליון‪ ‬הדוק‪ ‬כדי‪ ‬לאפשר‪ ‬הכנסה‪ ‬של‪ ‬התהליך ‪ ‬בזמן‪ ‬פנוי‪ .‬זו‪ ‬באמת‪ ‬השיטה‪ ‬‬
‫הנפוצה‪ ‬במחשבי‪ ‬על‪ Easy = Extensible Argonne Scheduling System .‬‬
‫‪ ‬‬
‫‪10 ‬‬
‫‪ SJF Scheduling‬‬
‫שיטת‪ Scheduling ‬זו‪ ‬ממיינת‪ ‬את‪ ‬העבודות‪ ‬לפי‪ ‬זמן‪ ‬הריצה‪ ‬שלהם‪ ,‬ולא‪ ‬לפי‪ ‬זמן‪ ‬ההגעה‪ ‬שלהן‪ .‬תוכניות‪ ‬שלהן‪ ‬זמן‪ ‬‬
‫ריצה ‪ ‬קצר‪ ‬יותר‪ ‬יתבצעו‪ ‬קודם‪ .‬היתרון‪ ‬של‪ ‬שיטה‪ ‬זו‪ ‬שהיא‪ ‬מורידה‪ ‬באופן‪ ‬משמעותי‪ ‬את‪ ‬ה­‪ Wait Time‬הממוצע‪ ,‬שכן‪ ‬‬
‫תהליכים‪ ‬קצרים‪ ‬לא‪ ‬נפגעים‪ ‬על‪ ‬ידי‪ ‬תהליכים‪ ‬ארוכים‪ .‬הבעיה‪ ‬המרכזית‪ ‬של‪ ‬שיטה‪ ‬זו‪ ‬היא‪ ‬שיש‪ ‬מצב‪ ‬תיאורטי‪ ‬שבו‪ ‬‬
‫עבודה‪ ‬מסוימת‪ ‬אף‪ ‬פעם‪ ‬לא ‪ ‬תרוץ‪ ,‬כאשר‪ ‬בכל‪ ‬פעם‪ ‬תגיע‪ ‬עבודה‪ ‬אחרת ‪ ‬שהיא‪ ‬קצרה‪ ‬ממנה‪ .‬ובכל‪ ‬מקרה‪ ‬אנו‪ ‬‬
‫מרעיבים‪ ‬את‪ ‬העבודות‪ ‬הכבדות‪ ‬יותר‪ ‬ומעכבים‪ ‬את‪ ‬הריצה‪ ‬שלהן‪ .‬‬
‫הערה‪ ,SJF ­ Shortest Job First :‬נקרא‪ ‬גם‪ SPTF ­ Shortest Process­Time First ‬‬
‫‪ ‬‬
‫אפקט‪ ‬השיירה‪ (Convoy Effect) ‬‬
‫ב­‪ Scheduling‬שהוא‪ ,non­preemptive ‬בו‪ ‬לא ‪ ‬ניתן‪ ‬לעצור‪ ‬את‪ ‬פעולתו‪ ‬של‪ ‬תהליך‪ ,‬תמיד‪ ‬ייתכן‪ ‬מצב‪ ‬שבו‪ ‬נוצר‪ ‬תור‪ ‬‬
‫עצום‪ ‬של‪ ‬תהליכים‪ ‬המחכים‪ ‬לסיומו‪ ‬של‪ ‬תהליך‪ ‬אחד‪ ‬המנצל‪ ‬את‪ ‬כל‪ ‬הזיכרון‪ ,‬מצב‪ ‬זה‪ ‬מכונה‪ ‬אפקט‪ ‬השיירה‪ .‬שיטת‪ ‬‬
‫‪ SJF‬מנסה‪ ‬לצמצם‪ ‬את‪ ‬אפקט‪ ‬השיירה‪ ‬בכך‪ ‬שהיא ‪ ‬נותנת‪ ‬לתהליכים‪ ‬הקצרים‪ ‬יותר‪ ‬להתבצע‪ ‬קודם‪ ‬לכן‪ ,‬אך‪ ‬זוהי‪ ‬‬
‫עדיין‪ ‬סיטואציה‪ ‬שיכולה‪ ‬לקרות‪ ‬במצבים‪ ‬מסוימים‪) ‬למשל‪ ‬כאשר‪ ‬מגיע‪ ‬תהליך‪ ‬המנצל‪ ‬את‪ ‬כל‪ ‬הזיכרון‪ ‬ואין‪ ‬אף‪ ‬תהליך‪ ‬‬
‫אחר‪ ‬המחכה‪ ‬לביצוע‪ ,‬התהליך‪ ‬הכבד‪ ‬יתחיל‪ ‬להתבצע‪ ‬ולא‪ ‬ניתן‪ ‬יהיה‪ ‬לעצור‪ ‬אותו‪ ‬לאחר‪ ‬מכן(‪ .‬‬
‫‪ ‬‬
‫‪ SJF Optimality‬‬
‫בהינתן‪ ‬מערכת‪ ‬עם‪ ‬ליבה‪ ‬אחת‪ ‬המקבלת‪ ‬את‪ ‬כל‪ ‬התהליכים‪ ‬באופן‪ ‬סדרתי‪ ,‬ותחת‪ ‬ההנחה‪ ‬כי‪ ‬כל‪ ‬התהליכים‪ ‬מגיעים‪ ‬‬
‫ביחד‪ ‬וזמן‪ ‬הריצה‪ ‬שלהם‪ ‬ידוע‪ :‬נרצה‪ ‬להוכיח‪ ‬כי‪ ‬זמן‪ ‬ההמתנה‪ ‬הממוצע‪ ‬של‪ ‬של‪ SJF ‬קטן‪ ‬או‪ ‬שווה‪ ‬לזמן‪ ‬ההמתנה‪ ‬‬
‫הממוצע‪ ‬של‪ ‬כל‪ Batch Scheduling ‬אחר‪ .‬‬
‫‪ ‬‬
‫רעיון‪ ‬ההוכחה‪ :‬‬
‫● יהי‪ S ‬סידור‪ ‬כלשהו‪ ‬של‪ ‬התהליכים‪ ‬המחכים‪ ‬לריצה‪ ‬‬
‫● אם‪ ‬הסידור‪ S ‬שונה‪ ‬מהסידור‪ ‬שהיינו‪ ‬מקבלים‪ ‬מ­‪ SJF‬ניתן‪ ‬להסיק‪ ‬כי‪ ‬קיימים‪ ‬לפחות‪ ‬שני‪ ‬תהליכים‪ P i ‬ו­‬
‫‪ P i+1‬כך‪ ‬שזמן‪ ‬הריצה‪ ‬של‪ , R(P i) ­ P i ‬גדול‪ ‬מזמן‪ ‬הריצה‪ ‬של‪ . R(P i+1) ­ P i+1 ‬נחליף‪ ‬ביניהם‪ ‬ונקבל‪ ‬כי‪ ‬‬
‫קיצרנו‪ ‬את‪ ‬זמן‪ ‬ההמתנה‪ ‬של‪ P i+1 ‬ב )‪ , R(P i‬ומצד‪ ‬שני‪ ‬הגדלנו‪ ‬את‪ ‬זמן‪ ‬ההמתנה‪ ‬של‪ P i ‬ב­ )‪ . R(P i+1‬‬
‫מכיוון‪ ‬שמתקיים‪ R(P i) > R(P i+1) ‬קיבלנו‪ ‬כי‪ ‬בסך‪ ‬הכל‪ ‬שיפרנו‪ ‬את‪ ‬זמן‪ ‬הריצה‪ .‬‬
‫● נמשיך‪ ‬באותה‪ ‬הדרך‪ ‬עד‪ ‬שנקבל‪ ‬את‪ ‬הסידור‪ ‬של‪ .SJF ‬‬
‫‪ ‬‬
‫‪ ‬‬
‫‪ ‬‬
‫‪ ‬‬
‫‪11 ‬‬
‫ובאופן‪ ‬יותר‪ ‬פורמלי‪ :‬‬
‫‪ ‬‬
‫‪ ‬‬
‫וריאציות‪ ‬מבוססות‪ SJF ‬‬
‫‪ ‬‬
‫‪ ‬‬
‫‪ ‬‬
‫‪12 ‬‬
‫הרצאה‪) 3 ‬מצגת‪ (2 ‬‬
‫‪Preemptive Scheduling‬‬
‫הערה‪ :‬במהלך‪ ‬ההרצאה‪ ‬הקרובה‪ ‬נניח‪ ‬כי‪ ‬תהליך ‪ ‬תמיד‪ ‬מוכן‪ ‬לריצה‪ ,‬כלומר‪ ‬הוא‪ ‬במצב‪ ready ‬או‪ running ‬ולא‪ ‬‬
‫מגיע‪ ‬ל­‪ .waiting‬‬
‫‪ Preemption‬‬
‫‪) Preemption‬או‪ ‬הפקעה(‪ ‬מתאר‪ ‬מצב‪ ‬בו‪ ‬התהליך‪ ‬לא ‪ ‬סיים ‪ ‬לעבוד‪ ‬ומערכת‪ ‬ההפעלה‪" ‬מפקיעה"‪ ‬ממנו‪ ‬בכוח‪ ‬את‪ ‬‬
‫זכות‪ ‬הריצה‪ ,‬ומעבירה‪ ‬את‪ ‬המעבד‪ ‬לתהליך‪ ‬אחר‪ .‬‬
‫מתי‪ ‬הגיוני‪ ‬לבצע‪ ?Preemption ‬כאשר‪ ‬שני‪ ‬תהליכים‪ ‬הם‪ ‬בערך‪ ‬באותו‪ ‬האורך‪ ‬לא‪ ‬נרוויח‪ ‬מכך‪ .‬‬
‫נניח‪ ‬שיש‪ ‬לנו‪ ‬שני‪ ‬תהליכים‪ ‬הזקוקים‪ ‬ל­‪ 10‬שעות‪ ‬כל‪ ‬אחד‪ ,‬אם‪ ‬נבצע‪ ‬ריצה‪ ‬לסירוגין ‪ ‬ונחליף‪ ‬תהליך‪ ‬כל‪ ‬שניה‪ ,‬אז‪ ‬נקבל‪ ‬‬
‫כי‪ ‬במקום‪ ‬תהליך‪ ‬אחד‪ ‬שיגמור‪ ‬את‪ ‬פעולתו‪ ‬תוך‪ 10 ‬שעות‪ ‬ותהליך‪ ‬שני‪ ‬שיגמור‪ ‬תוך‪ 10 ‬שעות ‪ ‬נוספות‪ ‬נקבל‪ ‬שני‪ ‬‬
‫תהליכים‪ ‬שיגמרו‪ ‬את‪ ‬פעולתם‪ ‬כמעט‪ ‬יחדיו‪ ‬תוך‪ 20 ‬שעות‪ ,‬ולכן‪ ‬ה­‪ Preemption‬לא‪ ‬יסייע‪ ‬לנו‪ ‬כאן‪ ,‬אלא‪ ‬להיפך‪ .‬‬
‫עם‪ ‬זאת‪ ,‬כשתהליך‪ ‬אחד‪ ‬הרבה‪ ‬יותר‪ ‬קצר‪ ‬מהאחר‪ ‬נרצה‪ ‬לבצע‪ Preemption ‬ובכך‪ ‬לתת ‪ ‬הזדמנות‪ ‬לתהליך‪ ‬הקצר‪ ‬‬
‫לסיים‪ ‬את‪ ‬פעולתו‪ ‬במקום‪ ‬לחכות‪ ‬לסיום‪ ‬פעולת‪ ‬התהליך‪ ‬הארוך‪ ,‬מבלי‪ ‬להשפיע‪ ‬באופן ‪ ‬משמעותי‪ ‬על‪ ‬התהליך‪ ‬‬
‫הארוך‪ .‬‬
‫דוגמא‪ ‬קלאסית‪ ‬אחרת‪ ‬לשימוש‪ ‬ב­‪ Preemption‬היא‪ ‬הדמיה‪ ‬של‪ ,multi­tasking ‬הנפוצה‪ ‬במחשבים‪ ‬הביתיים‪ ‬כיום‪ .‬‬
‫נניח‪ ‬לדוגמא‪ ‬שיש‪ ‬לנו‪ ‬תהליך‪ ‬כגון‪ ‬מעבד‪ ‬תמלילים‪ ‬שלא‪ ‬צורך‪ ‬משאבים‪ ‬רבים‪ ‬אך‪ ‬צריך‪ ‬להציג‪ ‬למשתמש‪ ‬תגובתיות‪ ‬‬
‫גבוהה‪ ‬ויש‪ ‬תהליך‪ ‬אחר‪ ‬שרץ‪ ‬ברקע‪ ‬וזקוק‪ ‬למשאבים‪ ‬רבים‪ .‬באמצעות ‪ preemption ‬נוכל‪ ‬להריץ‪ ‬את‪ ‬התהליך‪ ‬הכבד‪ ‬‬
‫ועם‪ ‬זאת‪ ‬באמצעות‪ ‬החלפה‪ ‬ביניהם‪ ‬כל‪ ‬פרק‪ ‬זמן‪ ‬קצר‪ ‬לאפשר‪ ‬למעבד‪ ‬התמלילים‪ ‬להציג‪ ‬למשתמש‪ ‬פעולה‪ ‬חלקה‪ ‬‬
‫של‪ ‬מעבד‪ ‬התמלילים‪ .‬‬
‫‪ ‬‬
‫מטריקות ומושגים ל‪Preemptive Schedulers-‬‬
‫חלק‪ ‬מהמטריקות‪ ‬שהצגנו‪ ‬בעבר‪ ‬קצת‪ ‬משתנות‪ ‬עבור‪ Preemptive Scheduling ‬‬
‫● ‪ ­ Wait Time‬לא‪ ‬משתנה‪ .‬‬
‫● ‪ ­ Response Time‬כאן‪ ‬בא ‪ ‬לידי‪ ‬ביטוי ‪ ‬השינוי‪ ,‬כאשר‪ ‬יש‪ preemption ‬במהלך‪ ‬הביצוע ‪ ‬זמן‪ ‬זה‪ ‬יכנס‪ ‬גם‪ ‬ל­‬
‫‪ response time‬של‪ ‬התהליך‪ ‬שלנו‪ ,‬ולכן‪ ‬נקבל‪ Response Time ‬שהוא‪ ‬לפחות‪ ‬כמו‪ ‬במערכות‪ batch ‬‬
‫‪ Quantum‬‬
‫פיסת‪ ‬הזמן‪ ‬המקסימלית‪ ‬שבה‪ ‬תהליך‪ ‬יכול‪ ‬לרוץ‪ ‬ללא‪ ‬הפרעה‪ .‬‬
‫ישנן‪ ‬מערכות‪ ‬הפעלה‪ ‬כגון‪ Solaris ‬שבהן‪ ‬לתהליכים‪ ‬בעלי‪ ‬עדיפות‪ ‬גבוהה‪ ,‬למשל‪ ‬מעבד‪ ‬תמלילים‪ ,‬ניתן‪ Quantum ‬‬
‫קטן‪ ,‬כך‪ ‬שהם‪ ‬ירוצו‪ ‬לעיתים‪ ‬קרובות‪ ‬אבל‪ ‬לזמן‪ ‬קצר‪ ,‬ואילו‪ ‬התהליכים‪ ‬בעלי‪ ‬הקדימות‪ ‬הנמוכה‪ ‬ניתן‪ Quantum ‬ארוך‪ ‬‬
‫יותר‪ ‬כך‪ ‬שאם‪ ‬הם‪ ‬כבר‪ ‬רצים‪ ‬הם‪ ‬ירוצו‪ ‬לזמן‪ ‬ארוך‪ .‬במערכות‪ Linux ‬רגילות‪ ‬זה‪ ‬הפוך‪ ,‬כך‪ ‬שזה‪ ‬לא‪ ‬באמת‪ ‬מהותי‪ ‬‬
‫ונתון‪ ‬לוויכוח‪ .‬‬
‫תקורה‪ overhead ­ ‬‬
‫כשמחליפים‪ ‬הקשר‪ ‬יש‪ ‬את‪ ‬התקורה‪ ‬הישירה‪ ‬הנובעת‪ ‬מהחלפת‪ ‬הרגיסטרים‪ ,‬כמו‪ ‬כן‪ ‬יש‪ ‬גם‪ ‬תקורה‪ ‬עקיפה‪ ‬הנובעת‪ ‬‬
‫מכל‪ ‬מיני‪ ‬מבנים‪ ‬בחומרה‪ ‬שמבצעים‪ ‬אופטימיזציה‪ ‬של‪ ‬ביצוע‪ ‬התהליך‪ ‬ומאיצים‪ ‬את‪ ‬פעילותו‪ .‬בכל‪ ‬פעם‪ ‬שאנו‪ ‬משנים‪ ‬‬
‫הקשר‪ ‬אנו‪ ‬מלכלכים‪ ‬אותם‪ ‬והם‪ ‬צריכים‪ ‬להתאים‪ ‬את‪ ‬עצמם‪ ‬מחדש‪ ‬לתהליך‪ .‬תקורה‪ ‬עקיפה‪ ‬יכולה‪ ‬לעלות‪ ‬לנו‪ ‬סדר‪ ‬‬
‫גודל‪ ‬יותר‪ ‬גדול‪ ‬מתקורה‪ ‬רגילה‪ ‬וצריך‪ ‬להתחשב‪ ‬גם‪ ‬בה‪ .‬‬
‫‪ ‬‬
‫‪13 ‬‬
‫)‪ Round­Robin Scheduling (RR‬‬
‫השיטה‪ ‬הפשוטה‪ ‬ביותר‪ ,‬כל‪ ‬תהליך‪ ‬רץ‪ ‬עד‪ ‬שנגמר‪ ‬לו‪ ‬ה­‪ ,Quantum‬ואז‪ ‬עוברים‪ ‬לתהליך‪ ‬הבא‪ .‬כשעוברים‪ ‬את‪ ‬כל‪ ‬‬
‫התהליכים‪ ‬נגמר‪) epoch ‬עידן(‪ ‬ומתחילים‪ ‬עידן‪ ‬חדש‪ ‬באותה‪ ‬הדרך‪ .‬כמובן‪ ‬שיש‪ ‬לנו‪ ‬צורך‪ ‬כאן‪ ‬בשעון‪ ‬כדי‪ ‬למדוד ‪ ‬את‪ ‬‬
‫ה­‪ .Quantum‬שעון‪ ‬זה‪ ‬מתבצע‪ ‬באמצעות‪ ‬פסיקת‪ ‬חומרה‪ ,‬ניתן‪ ‬להגדיר ‪ ‬פרמטרים ‪ ‬שונים‪ ‬של‪ ‬משכי‪ ‬זמן‪ ‬לחומרה‪ .‬‬
‫מגדירים‪ interrupt handler ‬לשעון‪ ‬שתפקידו‪ ‬יהיה‪ ‬לבצע‪ Context Switch ‬לתהליך‪ ‬הבא‪ .‬‬
‫‪ ‬‬
‫נבחין‪ ‬כי‪ ‬ככל‪ ‬שה­‪ Quantum‬שואף‪ ‬לאפס‪ ‬נקבל‪ ‬הדמיה‪ ‬של‪ ‬שני‪ ‬תהליכים‪ ‬הרצים‪ ‬במקביל‪ ‬בחצי‪ ‬המהירות‪ ‬של‪ ‬‬
‫תהליך‪ ‬הרץ ‪ ‬לבד‪ ,‬ואילו‪ ‬כאשר‪ ‬ה­‪ Quantum‬שואף‪ ‬לאינסוף‪ ‬נקבל‪ ,First Come First Served ‬כמו ‪ ‬ב­‬
‫‪ .Non­preemptive Scheduling‬‬
‫‪ ‬‬
‫האם‪ ‬עדיף‪ ‬שכל‪ ‬התהליכים‪ ‬יחכו‪ ‬בתור‪ ‬אחד‪ ‬משותף‪ ‬לריצה‪ ,‬או‪ ‬שהתורים‪ ‬יחולקו‪ ‬לפי‪ ‬המעבדים‪ ‬השונים?‪ ‬הבחירה ‪ ‬‬
‫הטבעית‪ ‬היא‪ ‬לעשות‪ ‬תור‪ ‬אחד‪ ‬משותף‪ ‬לריצה‪ ,‬בצורה‪ ‬זו‪ ‬לא ‪ ‬ניתן‪ ‬לרמות‪ ‬את‪ ‬המערכת‪ ,‬כשמעבד‪ ‬כלשהו‪ ‬מתפנה‪ ‬‬
‫התהליך‪ ‬שבראש‪ ‬התור‪ ‬יתחיל‪ ‬לרוץ‪ .‬בפועל‪ ‬יש‪ ‬היום‪ ‬נטייה‪ ‬לעשות‪ ‬תורים‪ ‬שונים‪ ‬למעבדים‪ ‬השונים‪ ,‬מכיוון‪ ‬שבריצה‪ ‬‬
‫עם‪ ‬מספר‪ ‬מעבדים‪ ‬השימוש‪ ‬בתור‪ ‬יחיד‪ ‬דורש‪ ‬סנכרון‪ ‬מאוד‪ ‬עדין‪ ‬בין‪ ‬המעבדים‪ .‬‬
‫‪ ‬‬
‫אופטימליות‪ ‬של‪ Non­Preemptive Scheduling ‬‬
‫נניח‪ ‬שיש‪ ‬לנו‪ ‬קבוצת‪ ‬תהליכים ‪ ‬הרצים‪ ‬באופן‪ ‬לא‪ ‬רציף‪ ,‬כלומר‪ ‬במדיניות‪ ‬עם‪ ‬הפקעה‪ .‬אם‪ ‬כל‪ ‬התהליכים ‪ ‬כבר‪ ‬רציפים‪ ,‬‬
‫אז‪ ‬באופן‪ ‬מיידי‪ ‬ניתן‪ ‬להריץ‪ ‬אותם‪ .non­preemptive ‬‬
‫אחרת‪ ,‬נניח‪ ‬שיש‪ ‬תהליך‪ ‬לא‪ ‬רציף‪ ,‬נתחיל‪ ‬מהתהליך‪ ‬האחרון‪ ‬ונחפש‪ ‬את‪ ‬התהליך‪ ‬הראשון‪ ‬שלא‪ ‬רץ‪ ‬בצורה‪ ‬רציפה‪ ,‬‬
‫נאסוף ‪ ‬את‪ ‬כל‪ ‬חלקי‪ ‬ההרצה‪ ‬של‪ ‬התהליך‪ ‬הנ"ל‪ ‬מלבד‪ ‬האחרון‪ ‬ונסיר‪ ‬אותם‪ .‬לאחר‪ ‬מכן‪ ‬נקדים‪ ‬את‪ ‬שאר‪ ‬הפרוסות‪ ‬‬
‫שרצות‪ ‬ונצופף‪ ‬את‪ ‬הרווחים‪ .‬נקבל‪ ‬מקום‪ ‬המתאים‪ ‬בדיוק‪ ‬לחלקי‪ ‬ההרצה‪ ‬שהסרנו‪ ,‬נכניס‪ ‬אותם‪ ‬שם‪ ‬ונקבל‪ ‬הרצה‪ ‬‬
‫רציפה‪ ‬גם‪ ‬של‪ ‬תהליך‪ ‬זה‪ .‬נמשיך‪ ‬על‪ ‬אותה‪ ‬דרך‪ ‬ונקבל‪ ‬הרצה‪ ‬רציפה ‪ ‬גם‪ ‬של‪ ‬תהליכים‪ ‬אחרים‪ .‬נתבונן‪ ‬בשינוי‪ ‬‬
‫בזמנים?‪ ‬כמובן‪ ‬שזמן‪ ‬הריצה‪ ‬הממוצע‪ ‬לא‪ ‬השתנה‪ ‬לאף‪ ‬תהליך‪ .‬‬
‫מה‪ ‬לגבי‪ ‬זמן‪ ‬המתנה?‪ ‬‬
‫● כל‪ ‬מה‪ ‬שאחרי‪ ‬התהליך‪ ‬הלא‪ ‬רציף‪ ‬הראשון‪ ‬לא‪ ‬השתנה‪ .‬‬
‫● כל‪ ‬מה‪ ‬שלפני‪ ‬הפרוסה‪ ‬הראשונה‪ ‬של‪ ‬התהליך‪ ‬הלא‪ ‬רציף‪ ‬הראשון‪ ‬לא‪ ‬השתנה‪ .‬‬
‫● האם‪ ‬זמן‪ ‬ההמתנה‪ ‬של‪ ‬התהליך‪ ‬הלא‪ ‬רציף‪ ‬השתנה?‪ ‬נזכור‪ ‬כי‪ ‬זמן‪ ‬ההמתנה‪ ‬במקרה‪ ‬של‪ preemption ‬‬
‫הוא‪ ‬זמן‪ ‬הסיום‪ ‬של‪ ‬התהליך‪ ‬פחות‪ ‬זמן‪ ‬הריצה ‪ ‬שלו‪ ,‬וכמובן‪ ‬שזה‪ ‬לא‪ ‬השתנה‪ ,‬הוא‪ ‬קיבל‪ ‬בדיוק‪ ‬אותו‪ ‬זמן‪ ‬‬
‫ריצה‪ ‬והסתיים‪ ‬בדיוק‪ ‬באותו‪ ‬מקום‪ .‬‬
‫● מה‪ ‬לגבי‪ ‬התהליכים‪ ‬שצופפנו?‪ ‬הקדמנו‪ ‬כל ‪ ‬אחד‪ ‬מהם‪ ‬לפחות‪ ‬בפרוסה‪ ‬אחת‪ ‬ולכן‪ ‬הוא‪ ‬בהכרח ‪ ‬מסתיים‪ ‬יותר‪ ‬‬
‫מוקדם‪ ,‬כלומר‪ ‬זמן‪ ‬ההמתנה‪ ‬שלו‪ ‬קטן‪ .‬‬
‫ולכן‪ ‬זמן‪ ‬ההמתנה‪ ‬לא‪ ‬נפגע‪ ,‬במקרה‪ ‬הטוב‪ ‬הוא‪ ‬אפילו‪ ‬השתפר‪ .‬נמשיך‪ ‬באותו‪ ‬אופן‪ ‬על‪ ‬כל‪ ‬התהליכים‪ ‬עד‪ ‬שנקבל‪ ‬כי‪ ‬‬
‫כל‪ ‬התהליכים‪ ‬רציפים‪ ,‬וקיבלנו‪ scheduling ‬שהוא‪ non­preemptive ‬לאחר‪ ‬לכל‪ ‬היותר‪ n ‬איטרציות‪ ,‬כאשר‪ n ‬הוא‪ ‬‬
‫מספר‪ ‬התהליכים‪ ‬שהרצנו‪ .‬‬
‫אז‪ ‬הוכחנו‪ ‬כי‪ ‬לכל‪ ‬אלגוריתם‪ ‬עם‪ ‬הפקעה‪ ‬יש‪ ‬אלגוריתם‪ ‬בלי ‪ ‬הפקעה ‪ ‬שהוא‪ ‬לפחות‪ ‬טוב‪ ‬באותה‪ ‬מידה‪ .‬הראנו‪ ‬‬
‫בהרצאה‪ ‬הקודמת‪ ‬כי‪ ‬לכל‪ ‬אלגוריתם‪ ‬ללא‪ ‬הפקעה‪ SJF ‬הוא‪ ‬האופטימלי‪ ,‬ולכן‪ ‬תחת‪ ‬התנאים‪ ‬שכל‪ ‬התהליכים‪ ‬‬
‫מגיעים‪ ‬ביחד‪ ,‬יש‪ ‬מעבד‪ ‬יחיד‪ ‬וזמני‪ ‬הריצה‪ ‬ידועים‪ ‬אז‪ SJF ‬הוא‪ ‬האלגוריתם‪ ‬האופטימלי‪ .‬‬
‫‪ ‬‬
‫‪ ‬‬
‫‪14 ‬‬
‫ובאופן‪ ‬יותר‪ ‬פורמלי‪ ‬‬
‫‪ ‬‬
‫‪ ‬‬
‫‪15 ‬‬
‫הוכחנו‪ ‬לפני‪ ‬מספר‪ ‬שורות‪ ‬כי‪ ‬לכל‪ ‬אלגוריתם‪ preemptive ‬קיים‪ ‬אלגוריתם‪ non­preemptive ‬עם‪ ‬זמן‪ ‬המתנה‪ ‬שווה‪ ‬‬
‫אם‪ ‬לא‪ ‬טוב‪ ‬יותר‪ .‬אז‪ ‬למה‪ ‬בעצם‪ ‬אנחנו‪ ‬צריכים‪ ?Preemptive Schedulers ‬‬
‫● ראשית‪ ‬יש‪ ‬לכך‪ ‬שימוש‪ ‬חשוב‪ ‬במחשבים‪ ‬אישיים‪ ‬לצורך‪ ‬הפעלת‪ ‬תהליכים‪ ‬במקביל‪ .‬בתור‪ ‬משתמשים‪ ‬נרצה‪ ‬‬
‫לשפר‪ ‬זמן‪ ‬המתנה‪ ,‬אבל‪ ‬לא‪ ‬זמן‪ ‬המתנה‪ ‬ממוצע‪ ,‬אנחנו‪ ‬רוצים‪ ‬לשפר‪ ‬ספציפית‪ ‬זמני‪ ‬המתנה‪ ‬של‪ ‬תהליכים ‪ ‬‬
‫הדורשים‪ ‬למשל‪ ‬קלט‪ ‬מהשתמש‪ .‬מערכת‪ preemptive ‬נותנת‪ ‬לנו‪ ‬את‪ ‬הגמישות ‪ ‬לעשות‪ ‬זאת‪ ,‬לאפשר‪ ‬את‪ ‬‬
‫פעולתו‪ ‬של‪ ‬תהליך‪ ‬המקבל‪ ‬או‪ ‬מציג‪ ‬קלט‪ ‬למשתמש‪ ,‬גם‪ ‬אם‪ ‬תהליך‪ ‬אחר‪ ‬כבר‪ ‬התחיל‪ ‬את‪ ‬ההרצה‪ ‬שלו‪ .‬‬
‫כמו‪ ‬כן‪ ,‬נזכור‪ ‬כי‪ ‬ההנחות‪ ‬שהנחנו‪ ‬בשתי ‪ ‬ההוכחות ‪ ‬אינן‪ ‬עומדות‪ ‬תמיד‪ ‬בתנאים‪ ‬אמיתיים‪ ,‬כדי‪ ‬ליצור‪ ‬סדר‪ ‬הפעלה‪ ‬יעיל‪ ‬‬
‫באלגוריתם‪ non­preemptive ‬נצטרך‪ ‬לדעת‪ ‬מראש‪ ‬את‪ ‬סדרת‪ ‬התהליכים‪ ‬הרוצים‪ ‬לרוץ‪ ‬במחשב‪ ,‬מה‪ ‬שלא‪ ‬מובטח‪ ‬‬
‫לנו‪ ,‬ולכן‪ ‬זמן‪ ‬ההמתנה‪ ‬של‪ non­preemptive ‬לא‪ ‬בהכרח ‪ ‬יהיה‪ ‬טוב‪ ‬יותר‪ .‬וכמו‪ ‬כן‪ ‬ברוב‪ ‬המחשבים‪ ‬כיום‪ ‬אין‪ ‬מעבד‪ ‬‬
‫יחיד‪ .‬‬
‫דיברנו‪ ‬כבר‪ ‬על‪ ‬הבעייתיות‪ ‬שיכולה ‪ ‬להיות ‪ ‬באלגוריתמים‪ preemptive ‬בתחילת‪ ‬ההרצאה‪ .‬בניסיון‪ ‬להריץ‪ ‬שני‪ ‬‬
‫תהליכים‪ ‬ארוכים‪ ‬שנניח‪ ‬שזמן‪ ‬הריצה‪ ‬שלהם‪ ‬הוא‪ ,1000 ‬נקבל‪ ‬כי‪ ‬במקום‪ ‬זמן‪ ‬תגובה‪ ‬ממוצע ‪ ‬של‪ 1000) 1500 ‬‬
‫לראשון‪ ‬ו­‪ 2000‬לשני‪ ‬ב­‪ (SJF‬נקבל‪ ‬זמן‪ ‬תגובה‪ ‬ממוצע‪ ‬של‪ ‬כמעט‪) 2000 ‬התהליכים‪ ‬ירוצו‪" ‬במקביל" ‪ ‬וסיימו‪ ‬את‪ ‬‬
‫פעולתם‪ ‬באתו‪ ‬הזמן(‪ .‬‬
‫‪ ‬‬
‫מתקיים‪ ‬כי‪ ‬זמן‪ ‬ההמתנה‪ ‬המקסימלי‪ ‬ב­‪ RR‬הוא‪ ‬לכל‪ ‬היותר‪ ‬פעמיים‪ ‬זמן‪ ‬ההמתנה‪ ‬של‪ :SJF ‬‬
‫כלומר‪ ‬זמן‪ ‬הריצה‪ ‬של‪ ‬התהליך‪ ‬ה­‪ k‬הינו‪ ‬סכום‪ ‬העיכובים‪ ‬שלו‪ ‬ע"י‪ ‬תהליכים‪ ‬אחרים‪ ,‬כולל‪ ‬הוא‪ ‬עצמו‪) ‬כיוון‪ ‬שהעיכוב‪ ‬‬
‫שהוא‪ ‬מעכב‪ ‬את‪ ‬עצמו‪ ‬מוגדר‪ ‬להיות‪ ‬זמן‪ ‬הריצה‪ ‬שלו‪ ,‬וכל‪ ‬העיכובים‪ ‬שתהלכים‪ ‬אחרים‪ ‬מעכבים‪ ‬אותו‪ ‬הוא‪ ‬זמן‪ ‬‬
‫ההמתנה‪ ‬שלו(‪ .‬‬
‫‪ ‬‬
‫ובאופן‪ ‬יותר‪ ‬פורמלי‪ :‬‬
‫‪ ‬‬
‫‪16 ‬‬
‫‪ ‬‬
‫‪ ‬‬
‫‪ ‬‬
‫‪ ‬‬
‫‪ Gang Scheduling ­ RR in a parallel system‬‬
‫כאשר‪ ‬במערכת‪ ‬מקבילית‪ ‬יש‪ ‬לנו‪ ‬תהליכים‪ ‬שזמן‪ ‬הריצה‪ ‬שלהם‪ ‬קטן‪ ‬משמעותית‪ ‬מזמן‪ ‬הריצה‪ ‬של‪ ‬תהליכים‪ ‬אחרים‪ ‬‬
‫ישנו‪ ‬היגיון‪ ‬בביצוע‪ .Preemptive Scheduling ‬שיטה‪ ‬זו‪ ‬של‪ ‬ביצוע‪ RR ‬במערכות‪ ‬מקבילות‪ ‬נקראת‪ Gang ‬‬
‫‪ .Scheduling‬‬
‫נחלק‪ ‬את ‪ ‬הזמן‪ ‬ל­‪ ,slots‬כל‪ ‬עבודה‪ ‬יכולה ‪ ‬לרוץ‪ ‬במהלך‪ slot ‬מסוים‪ ,‬ולאחר‪ ‬מכן‪ ‬פעולתה‪ ‬מופסקת‪ .‬האלגוריתם‪ ‬‬
‫מנסה‪ ‬למלא‪" ‬חורים"‪ ‬בזמן ‪ ‬הריצה‪ ‬ע"י‪ ‬הכנסת ‪ ‬תהליכים‪ ‬קצרים‪ ‬יותר‪ ‬למקומות‪ ‬פנויים‪ ‬אלו‪ .‬האלגורים‪ ‬מנסה‪ ‬להביא‪ ‬‬
‫למינימום‪ ‬את‪ ‬מספר‪ ‬ה­‪ slots‬ע"י‪ ‬איחוד‪ ‬של ‪" ‬חורים"‪ ‬כאשר‪ ‬זה‪ ‬אפשרי‪ .‬אלגוריתם‪ ‬זה‪ ‬הוא‪ ‬שימושי‪ ‬כי‪ ‬הוא‪ ‬לא‪ ‬דורש‪ ‬‬
‫שנדע‪ ‬מראש‪ ‬את‪ ‬זמני‪ ‬הריצה‪ ‬שלה‪ ‬תהליכים‪ .‬‬
‫תהליכים‪ ‬שרוצים ‪ ‬לרוץ‪ ‬ביחד‪ ‬צריכים‪ ‬לרוץ‪ ‬באותו‪ ‬ה­‪ ,time slot‬בתוך‪ ‬אותו ‪ time slot ‬יכולים‪ ‬להיות‪ ‬כמה‪ ‬‬
‫‪ !quantums‬המערכת‪ ‬מנסה‪ ‬לנייד‪ ‬תהליכים‪ ‬ולבחור‪ ‬באיזה‪ ‬מעבד‪ ‬הוא‪ ‬רוצה‪ ‬לרוץ‪ ,‬מטרתה‪ ‬היא‪ ‬לפנות‪ ‬מעבד‪ ‬‬
‫ולהריץ‪ ‬עליו‪ ‬תהליכים‪ ‬חדשים‪ ‬של‪ ‬משתמש‪ ‬אחר‪ .‬‬
‫‪ ‬‬
‫ככל‪ ‬שיש‪ ‬יותר‪ slots ‬המערכת‪ ‬איטית‪ ‬יותר‪ ,‬שני‪ slots ‬מספקים‪ ‬לנו‪ ‬מערכת‪ ‬איטית‪ ‬פי‪ ‬שתיים‪ .‬‬
‫‪17 ‬‬
‫עבור‪ slot ‬מסוים‪ ‬התהליך‪ ‬שרץ‪ ‬בו ‪ ‬יהיה‪ ‬אותו‪ ‬תהליך‪ ‬עד‪ ‬לסוף‪ ‬הריצה‪ ‬שלו‪ .‬אורך‪ ‬ה­‪ slot‬מוגדר‪ ‬כמשך‪ ‬הזמן‪ ‬‬
‫שהתהליך‪ ‬יכול‪ ‬לרוץ‪ ‬עד‪ ‬ל­‪ .preemptive‬מספר‪ ‬ה­‪ slots‬הוא‪ ‬בעצם‪ ‬מספר‪ ‬התהליכים‪ ‬שרצים‪ ‬במקביל‪ .‬‬
‫שיטה‪ ‬זו‪ ‬נתמכת‪ ‬ברוב ‪ ‬המחשבים‪ ‬אך‪ ‬לא‪ ‬משתמשים‪ ‬בה‪ ‬שכן‪ ‬היא‪ ‬נועדה‪ ‬להרצת‪ RR ‬על‪ ‬מחשבי‪ ‬על‪ ,‬וידוע‪ ‬‬
‫שבמקרים‪ ‬כאלה‪ ‬עדיף‪ ‬להריץ‪ .Batch ‬‬
‫‪ ‬‬
‫נבחר‪ quantum ‬כך‪ ‬שהתקורה‪ ‬קטנה‪ ‬מאוד‪ ‬באופן‪ ‬יחסי‪ ,‬אחרת‪ ‬נשלם‪ ‬המון‪ ‬על‪ ‬תקורה‪ .‬‬
‫נניח‪ ‬שהתקורה‪ ‬עולה‪ ‬אפס‪ ,‬במקרה‪ ‬זה‪ ‬תמיד‪ ‬נוכל‪ ‬לשפר‪ ‬תזמון‪ preemptive ‬קיים‪ ,‬ל‪ schedule‬מסוג‪ batch ‬‬
‫שהזמן‪ ‬שלו‪ ‬יהיה‪ ‬קצר‪ ‬יותר‪ ,‬בהינתן‪ ‬שנוכל‪ ‬לסדרם‪ ‬בכל‪ ‬סדר‪ ‬שנרצה‪ ‬וזמני‪ ‬הריצה‪ ‬ידועים‪ ‬מראש‪ ,‬וכמובן‪ ‬שאם‪ ‬ה­‬
‫‪ Context Switch‬עולה‪ ‬אז‪ ‬נקבל‪ ‬שיפור‪ ‬אפילו‪ ‬טוב‪ ‬יותר‪ .‬‬
‫‪ ‬‬
‫אז‪ ‬מה‪ ‬בכל‪ ‬זאת‪ ‬היתרון‪ ‬ב­‪ ?Preemptive‬מאפשר‪ ‬סיום‪ ‬מהיר‪ ‬של‪ ‬תהליכים‪ ‬קצרים‪ .‬‬
‫‪ ‬‬
‫‪ SRTF‬‬
‫אחת‪ ‬החולשות‪ ‬של‪ SJF ‬היא‪ ‬שהיא‪ ‬מניחה‪ ‬שכל ‪ ‬התהליכים‪ ‬מגיעים‪ ‬באותה‪ ‬העת‪ SRTF ,‬היא‪ ‬וריאציה‪ ‬של‪ SJF ‬‬
‫שלא‪ ‬מניחה‪ ‬הנחה‪ ‬זו‪ ‬ומשתמשת‪ ‬ב­‪ Preemption‬כדי‪ ‬להתמודד‪ ‬איתה‪ ‬באופן‪ ‬יעיל‪ .‬בכל‪ ‬פעם‪ ‬שמגיעה‪ ‬עבודה‪ ‬‬
‫חדשה‪ ,‬או‪ ‬שעבודה‪ ‬כלשהי‪ ‬הסתיימה‪ ‬נתחיל‪ ‬את‪ ‬העבודה‪ ‬עם‪ ‬זמן‪ ‬הריצה‪ ‬הנותר‪ ‬הקצר‪ ‬ביותר‪ .‬הדבר‪ ‬נותן‪ ‬לנו‪ ‬‬
‫פעולה‪ ‬אופטימלית‪ .‬וקל‪ ‬לראות‪ ‬שאם‪ ‬התהליכים‪ ‬מגיעים ‪ ‬באותה‪ ‬העת‪ ‬אנו‪ ‬מקבלים ‪ ‬בדיוק‪ ‬את‪ .SJF ‬זהו‪ ‬אלגוריתם‪ ‬‬
‫אופטימלי‪ ‬גם‪ ‬ללא‪ ‬ההנחה‪ ‬של‪) .SJF ‬אנחנו‪ ‬מניחים‪ ‬שהחלפת‪ ‬הקשר‪ ‬לא‪ ‬עולה‪ ‬לנו‪ ‬זמן(‪ .‬‬
‫‪ ‬‬
‫‪ Selfish RR‬‬
‫עדיפות‪ ‬התהליכים‪ ‬מוגדרת‪ ‬לפי‪ ‬פז"ם‪ ,‬התהליכים ‪ ‬הותיקים‪ ‬יותר‪ ‬רצים‪ ‬ב­‪ ,RR‬ואילו‪ ‬התהליכים‪ ‬החדשים‪ ‬רצים‪ ‬ב­‬
‫‪ FIFO‬כשאף‪ ‬תהליך‪ ‬ותיק‪ ‬לא‪ ‬צריך‪ ‬לרוץ‪ .‬‬
‫ניתן‪ ‬לשחק‪ ‬עם‪ ‬פרמטר‪ ‬ההזדקנות)‪ , (aging‬הזדקנות‪ ‬מהירה‪ ‬נותנת‪ ‬לנו‪ ,RR ‬ואיטית‪ ‬נותנת‪ ‬לנו‪ .FCFS ‬‬
‫‪ ‬‬
‫עדיפויות‬
‫לכל‪ ‬תהליך‪ ‬מקצים‪ ‬חשיבות‪ ,‬ב­‪ SJF‬לדוגמא‪ ,‬העדיפות‪ ‬מוגדרת‪ ‬לפי ‪ ‬אורך‪ ‬זמן‪ ‬הריצה‪ ,‬ככל‪ ‬שאתה‪ ‬יותר‪ ‬קצר‪ ‬תרוץ‪ ‬‬
‫מוקדם‪ ‬יותר‪ ,‬הדבר‪ ‬מאפשר‪ ‬לנו‪ ‬לקבוע‪ ‬שתהליך‪ ‬מסוים‪ ‬הוא‪ ‬חשוב‪ ‬אפילו‪ ‬אם‪ ‬זמן‪ ‬הריצה‪ ‬הנותר‪ ‬שלו‪ ‬הוא‪ ‬עוד‪ ‬ארוך‪ .‬‬
‫‪ ‬‬
‫‪ Negative Feedback Principle‬‬
‫נחשוב ‪ ‬על‪ ‬תהליך‪ ‬שרוצה‪ ‬לרוץ ‪ ‬הרבה‪ ,‬אם‪ ‬תהליך‪ ‬זה‪ ‬יחכה‪ ‬טיפה‪ ‬יותר‪ ‬לא‪ ‬יקרה‪ ‬לו‪ ‬נזק‪ ‬משמעותי‪ .‬לעומת‪ ‬זאת‪ ‬‬
‫תהליך‪ ‬שזקוק‪ ‬רק‪ ‬לזמן‪ ‬קצר‪ ‬מאוד‪ ,‬אפילו‪ ‬פחות‪ ‬מהזמן‪ ‬המוקצה‪ ,‬כדי‪ ‬לטפל‪ ‬באיזשהו‪ ‬קלט‪ .‬לכן‪ ‬נרצה‪ ‬לסווג‪ ‬את‪ ‬‬
‫התהליכים‪ ‬ולהבין‪ ‬איזה‪ ‬תהליך‪ ‬הוא‪ CPU­Bound ‬ואיזה‪ ‬תהליך‪ ‬הוא‪ .I/O­Bound ‬דרך‪ ‬אחת‪ ‬לסווג‪ ‬היא‪ ‬לפי‪ ‬ניצול‪ ‬‬
‫זמן‪ ‬הריצה‪ ,‬מי‪ ‬שניצל‪ ‬פחות‪ ‬מהזמן‪ ‬שהוא‪ ‬קיבל‪ ‬הוא‪ ‬כנראה ‪ I/O Bound ‬ולכן‪ ‬נרצה‪ ‬להקדים‪ ‬אותו‪ ,‬ומי‪ ‬שניצל‪ ‬את ‪ ‬כל‪ ‬‬
‫הזמן‪ ‬שלו‪ ‬הוא‪ ‬כנראה‪ CPU­Bound ‬ולכן‪ ‬נעכב‪ ‬אותו‪ .‬‬
‫נבין‪ ‬כי‪ ‬בעיית ‪ ‬התזמון‪ ‬היא ‪ ‬קשה‪ ‬מאוד‪ ,‬רוב‪ ‬ה­‪ schedulers‬שמערכות‪ ‬הפעלה‪ ‬השונות‪ ‬הם‪ ‬פשוטים‪ ‬למדי ‪ ‬כאשר‪ ‬‬
‫השיטה‪ ‬השולטת‪ ‬במתן‪ ‬עדיפויות‪ ‬היא‪ :‬‬
‫● ריצה‪ ‬מורידה‪ ‬את‪ ‬העדיפות‪ ‬להמשיך‪ ‬לרוץ‪ ‬‬
‫● אי­ריצה‪ ‬מגבירה‪ ‬את‪ ‬העדיפות‪ ‬לרוץ‪ ‬עוד‪ ‬‬
‫כמו‪ ‬כן‪ ‬תהליכים‪ ‬הקשורים‪ ‬ל­‪ I/O‬מקבלים‪ ‬עדיפות‪ ‬גבוהה‪ ‬יותר‪ ‬בד"כ‪ .‬‬
‫‪ ‬‬
‫‪18 ‬‬
‫‪ Multi­level priority queue‬‬
‫כל‪ ‬מערכות‪ ‬ההפעלה‪ ‬שאנו‪ ‬מכירים‪ ‬מבצעות‪ ‬תהליך‪ ‬הדומה‪ ‬לזה‪ ,‬יש‪ ‬מספר‪ ‬תורי‪ RR ‬המתאימים‪ ‬לעדיפויות‪ ‬שונות‪ ,‬‬
‫כאשר‪ ‬ככל‪ ‬שתהליך‪ ‬מקבל‪ ‬יותר‪ ‬זמן‪ ‬ריצה‪ ‬הוא‪ ‬הולך‪ ‬ויורד‪ ‬בתורי‪ ‬העדיפויות‪ ,‬בהתאם‪ ‬ל‪ Negative Feedback‬‬
‫‪ .Principle‬‬
‫ב­‪ Windows‬תורים‪ ‬גבוהים‪ ‬מקבלים‪ Quantum ‬מאוד‪ ‬קצר‪ ,‬אך‪ ‬הם‪ ‬בד"כ‪ ‬בקושי‪ ‬מחכים‪ .‬‬
‫ב­‪ Linux‬הגישה‪ ‬שונה‪ ,‬התהליך‪ ‬החשוב‪ ‬מבחינתי‪ ‬הוא‪ ‬זה‪ ‬שרוצה‪ ‬לרוץ ‪ ‬הכי‪ ‬הרבה‪ ‬זמן‪ .‬הם‪ ‬מקבלים‪ ‬גם‪ Quantum ‬‬
‫ארוך‪ ‬וגם‪ ‬זמן‪ ‬המתנה‪ ‬קצר‪ .‬‬
‫‪ ‬‬
‫ה‪ Scheduler-‬של לינוקס‬
‫הגדרות‪) :‬להשלים‪ ‬הגדרות‪ ‬מהשקפים!!(‪ ‬‬
‫●‬
‫●‬
‫●‬
‫●‬
‫●‬
‫●‬
‫●‬
‫‪ ­ Task‬כל‪ ‬תהליך‪ ‬שרץ‪ ‬במערכת‪ ‬ההפעלה‪ ‬לינוקס‪ ,‬לכל‪ Thread ‬גם‪ ‬כן‪ ‬נקרא‪ .task ‬‬
‫‪ ­ epoch‬כאשר‪ ‬מריצים‪ RR ‬על‪ ‬התהליכים‪ ‬במערכת‪ ‬כל‪ ‬תהליך‪ ‬רץ‪ ‬זמן‪ ‬מסויים‪ (Quantum) ‬שמוקצה‪ ‬לו‪ ,‬‬
‫כאשר‪ ‬כל‪ ‬התהליכים‪c ‬מערכת‪ ‬מסיימים‪ ‬את‪ ‬ריצת‪ ‬ה­‪ Quantum‬שלהם‪ ‬נגמר‪ ‬ה­‪) epoch‬עידן(‪ ‬ומתחיל‪ ‬‬
‫אחד‪ ‬חדש‪ ‬שבו‪ ‬שוב‪ ‬כל‪ ‬התליכים‪ ‬רצים‪ ‬את‪ ‬ה­‪ Quantum‬שהוקצה‪ ‬להם‪ .‬‬
‫‪ ­ Task’s priority‬לכל‪ ‬תהליך‪ ‬מוגדר‪ ‬מספר ‪ ‬שמייצג ‪ ‬את‪ ‬העדיפות‪ ‬שלו‪ ,‬ככל‪ ‬שמספר‪ ‬זה‪ ‬יותר‪ ‬גבוהה‪ ‬ישנה‪ ‬‬
‫עדיפות‪ ‬ריצה‪ ‬יותר‪ ‬גבוהה‪ ‬לתהליך‪ ‬זה‪ .‬לכל‪ ‬תהליך‪ ‬יש‪ 2 ‬סוגי‪ ‬עדיפויות‪ ‬דינמית‪ ‬וסטטית‪ .‬‬
‫‪ ­ Task’s static priority‬עדיפות‪ ‬זאת‪ ‬לא‪ ‬משתנה‪ ‬במהלך‪ ‬הריצה‪ ‬אלא‪ ‬אם‪ ‬המשתמש‪ ‬מזמן‪ ‬את‪ ‬פונקצית‪ ‬‬
‫המערכת‪ ,nice ‬העדיפות‪ ‬הסטטית‪ ‬מגדירה‪ ‬את‪ ‬ה­‪ Quantum‬המקסימאלי‪ ‬עבור‪ ‬התהליך‪ .‬‬
‫‪ ­ Task’s dynamic priority‬עדיפות‪ ‬זאת‪ ‬מייצגת‪ ‬את‪ ‬הזמן‪ ‬שנשאר‪ ‬לתהליך‪ ‬לרוץ‪ ,‬עדיפות‪ ‬זאת‪ ‬קטנה‪ ‬‬
‫עם‪ ‬הזמן‪ ,‬כאשר‪ ‬היא‪ ‬מגיעה‪ ‬לאפס‪ ‬התהליך‪ ‬מפסק‪ ‬לרוץ‪ ­ ‬כלומר‪ ‬הוא‪ ‬מוותר‪ ‬על‪ ‬המעבד‪ .‬בכל‪ ‬תחילת‪ ‬‬
‫עידן‪ ‬חדש‪ ‬העדיפות‪ ‬הדינמית‪ ‬מתחדשת‪ ‬לערך‪ ‬העדיפות‪ ‬הסטטית‪ ‬של‪ ‬התהליך‪ .‬‬
‫‪ ­ processor‬איזה‪ core ‬אתה‪ ,‬אין‪ ‬משימה‪ ‬שמקבלת‪ ‬יותר‪ ‬מליבה‪ ‬אחת‪ .‬‬
‫‪ ­ need_resched‬דגל‪ ‬המסמן‪ ‬שיש‪ ‬צורך‪ ‬לבצע‪ ‬תזמון‪ ‬מחדש‪ ‬של‪ ‬התהליכים‪ .‬‬
‫‪ ‬‬
‫הערות‪ :‬‬
‫*‪ ‬לכל‪ ‬תהליך‪ ‬מוקצה‪ Quantum ‬משלו‪ ,‬המהווה‪ ‬את‪ ‬זמן‪ ‬הריצה‪ ‬שלו‪ ‬בטיקים‪ .‬בכל‪ ‬טיק‪ ‬יורד‪ ‬זמן‪ ‬הריצה‪ ‬שנותר‪ ‬לנו‪ .‬‬
‫במערכת‪ Linux ‬שאנו‪ ‬לומדים‪ ‬שעדיפות‪ ‬שקולה‪ ‬לזמן‪ ‬הריצה‪ ,‬ככל‪ ‬שיש‪ ‬יותר‪ ‬זמן‪ ‬ריצה‪ ‬מקבלים‪ ‬עדיפות‪ ‬‬
‫גבוהה‪ ‬יותר‪ .‬זמן‪ ‬הריצה‪ ‬מוגדר‪ ‬לפי‪ ‬ה­‪ ,Kernel's Niceness‬וכתוצאה‪ ‬מכך‪ ‬גם‪ ‬זמן‪ ‬הריצה‪ .‬‬
‫*בביצוע‪ fork ‬זמן‪ ‬הריצה‪ ‬מתחלק‪ ,‬על‪ ‬מנת‪ ‬למנוע‪ ‬ניסיונות‪ ‬לרמות‪ ‬את‪ ‬המערכת‪ .‬ולהשיג‪ ‬ריצה‪ ‬ארוכה‪ ‬ע"י‪ ‬ביצוע‪ ‬‬
‫‪ .fork‬‬
‫*‪ ‬בשקף‪ ­ 49 ‬מגדירים‪ ‬את‪ ‬העדיפות‪ ‬כמספר‪ ‬הטיקים‪ ‬ב­‪ ,50ms‬וזהו‪ ‬הקשר‪ ‬בין‪ ‬עדיפות‪ ‬לזמן‪ ‬אמיתי‪) .‬טיק‪ ‬אחד‪ ‬רץ‪ ‬‬
‫ב­‪ .(10ms‬‬
‫‪ ‬‬
‫לכל‪ task_Struct ‬יש‪ ‬חמישה‪ ‬שדות‪ ‬שבהן‪ ‬משתמש‪ ‬ה­‪ :Scheduler‬‬
‫● ‪ ­ nice‬קובע‪ ‬עדיפות‪ ‬סטטית‪ ‬של‪ ‬תהליך‪ .‬‬
‫● ‪ ­ counter‬כמה‪ ‬פסיקות‪ ‬שעון‪ (ticks) ‬נשאר‪ ‬לתהליך‪ ‬עד‪ ‬להחלפתו‪ ‬על‪ ‬ידי‪ ‬תהליך‪ ‬אחר‪ .‬‬
‫● ‪ ­ processor‬ה­‪ id‬של‪ ‬המעבד‪ ‬עליו‪ ‬התהליך‪ ‬רץ‪ ‬כרגע‪ .‬‬
‫● ‪ ­ need_resched‬האם‪ ‬התהליך‪ ‬יכול‪ ‬להמשיך‪ ‬לרוץ‪ ‬או‪ ‬שצריך‪ ‬להחליף‪ ‬אותו‪) ‬מעבד‪ ‬אחר‪ ‬יכול‪ ‬לעיתים‪ ‬‬
‫לזהות‪ ‬את‪ ‬הצורך‪ ‬בהחלפת‪ ‬הקשר‪ ‬של‪ ‬תהליך‪ ‬מהמעבד‪ ‬שלנו‪ ,‬הוא‪ ‬אמנם‪ ‬אינו‪ ‬יכול‪ ‬להחליף‪ ‬אותו‪ ‬אך‪ ‬הוא‪ ‬‬
‫יכול‪ ‬לסמן‪ ‬שיש‪ ‬לבצע‪ ‬החלפה(‪ ‬‬
‫● ‪ ­ mm‬מצביע‪ ‬למרחב‪ ‬הזיכרון‪ ‬הוירטואלי‪ ,‬מתאר‪ ‬את‪ ‬הזיכרון‪ ‬של‪ ‬התהליך‪ .‬‬
‫‪19 ‬‬
‫‪ ‬‬
‫‪ ‬‬
‫‪ :Nice‬‬
‫● משתמש‪ ‬יכול‪ ‬לקבוע‪ ‬את‪ ‬הערך‪ ‬בין‪ ‬מינוס‪ 20 ‬ל­‪ .19‬ערך‪ ‬קטן‪ ‬יותר‪ ‬נותן‪ ‬עדיפות‪ ‬גבוהה‪ ‬יותר‪ ,‬והערך‪ ‬ה­‪ def‬‬
‫הוא‪ ‬אפס‪ ‬‬
‫● ב­‪ Kernel‬מתבצעת‪ ‬המרה‪ ‬לערך‪ ‬בין‪ 1 ‬עד‪ ,140 ‬כאשר‪ ‬ככל‪ ‬שהערך‪ ‬גדול‪ ‬יותר‪ ‬יש‪ ‬עדיפות‪ ‬גבוהה‪ ‬יותר‪ .‬‬
‫‪ ‬‬
‫‪ :Task's counter‬‬
‫● כשלכל‪ ‬התהליכים‪ ‬שמוכנים‪ ‬לרוץ‪ ‬נגמר‪ ‬ה­‪ ,epoch‬ה­‪ ,time slice‬עוברים‪ ‬אחד‪ ‬אחד‪ ‬ומחשבים‪ ‬עבורם‪ ‬את‪ ‬‬
‫ה­‪ time_slice‬בשדה‪ counter ‬‬
‫● נבחין‪ ‬כי‪ ‬הנוסחא‪ ‬לחישוב‪ ‬ה‪ counter‬הינה‪ .c = c/2 + alpha ‬‬
‫● לכאורה‪ ‬ה­‪ c/2‬מיותר‪ ‬שהרי‪ ‬עבור‪ ‬תהליכים‪ ‬שנגמר‪ ‬זמן‪ ‬הריצה‪ ‬שלהם‪ ‬ה­‪ .c=0‬אך‪ ‬נזכור‪ ‬כי‪ ‬יש‪ ‬גם‪ ‬‬
‫תהליכים‪ ‬שנמצאים‪ ‬במצב‪ waiting ‬ועבורם‪ c ‬שונה‪ ‬מאפס‪ .‬נבחין‪ ‬כי‪ ‬אם‪ ‬תהליך‪ ‬כל‪ ‬הזמן‪ ‬ממתין‪ ‬ואף‪ ‬פעם‪ ‬‬
‫לא‪ ‬מקבל‪ ‬זמן‪ ‬ריצה‪ ‬ה­‪ counter‬שלו‪ ‬מתכנס‪ ‬ל­‪ ,(nice_to_tick)*2‬וחסום‪ ‬על‪ ‬ידיו!‪ ‬‬
‫בעצם‪ ‬זו‪ ‬הדרך‪ ‬שבה‪ ‬אנו‪ ‬נותנים‪ ‬בונוס‪ ‬לתהליכי‪ I/O ‬שנמצאים‪ ‬זמן‪ ‬רב‪ ‬בהמתנה‪ .‬‬
‫● עבור‪ ‬תהליכים‪ ‬שרצים‪ ‬כל‪ ‬הזמן‪ ‬הערך‪ ‬תמיד‪ .nice_to_tick ‬‬
‫תהליך‪ ‬אופייני‪ ‬עם‪ ‬ערך‪ NICE ‬דיפולטיבי‪ ‬ירוץ‪ 6 ‬פסיקות‪ ‬שעון‪ ,‬תהליך‪ ‬עם‪ ‬ערך‪ ‬בעל‪ ‬העדיפות‪ ‬הנמוכה‪ ‬ביותר‪ ‬יקבל‪ ‬‬
‫פסיקה‪ ‬אחת‪ ,‬ואילו‪ ‬בעל‪ ‬העדיפות‪ ‬הגבוהה‪ ‬ביותר‪ ‬יקבל‪ 11 ‬פסיקות‪ ‬שעון‪ .‬‬
‫‪ ‬‬
‫מימוש‪ ‬ה­‪ Scheduler‬‬
‫●‬
‫●‬
‫●‬
‫●‬
‫‪ ­ ()goodness‬כמה‪ ‬כדאי‪ ‬להריץ‪ ‬תהליך‪ ‬מסוים‪ .‬‬
‫‪ ­ ()schedule‬האם‪ ‬לבצע‪ ‬החלפת‪ ‬הקשר‪ ‬או‪ ‬לא‪ ,‬תוך‪ ‬כדי‪ ‬שימוש‪ ‬ב­‪ .goodness‬‬
‫__‪ ­ ()wake_up_common‬הערת‪ ‬תהליכים‪ ‬המחכים‪ ‬לסיומה‪ ‬של‪ ‬פעולה‪ ‬מסוימת‪ ‬ב­‪ .wait queue‬‬
‫‪ ­ reschedule_idle‬בודקת‪ ‬עבור‪ ‬תהליך‪ ‬האם‪ ‬ניתן‪ ‬להריץ‪ ‬אותו‪ ‬על‪ ‬מעבד‪ ‬מסוים‪ .‬‬
‫‪ ‬‬
‫להלן‪ ‬הקוד‪ ‬עצמו‪ ‬‬
‫‪ ‬‬
‫‪int goodness(task t, cpu this_cpu) { // bigger = more desirable ‬‬
‫‪g = t.counter //A task with more ticks left is more desirable ‬‬
‫‪if( g == 0 ) ‬‬
‫‪// exhausted quantum, wait until next epoch ‬‬
‫‪return 0 ‬‬
‫‪if( t.processor == this_cpu ) ‬‬
‫‪// try to avoid migration between cores ‬‬
‫‪g += PROC_CHANGE_BONUS //Reward the cpu ‬‬
‫‪if( t.mm == this_cpu.current_task.mm ) //Ask ‬‬
‫‪// prioritize threads sharing same address space ‬‬
‫‪// as context­switch would be cheaper ‬‬
‫‪g += SAME_ADDRESS_SPACE_BONUS ‬‬
‫‪return g ‬‬
‫‪ } ‬‬
‫‪ ‬‬
‫‪ ‬‬
‫‪void reschedule_idle(task t) { ‬‬
‫‪20 ‬‬
next_cpu = NIL if( t.processor is idle )
// t’s most recent core is idle next_cpu = t.processor else if( there exists an idle cpu ) // some other core is idle next_cpu = least recently active idle cpu //Nowadays, There is more sense in choosing an active cpu and turning off this one to save power else
// no core is idle; is t more desirable // than a currently running task? threshold = PREEMPTION THRESHOLD foreach cpu c in [all cpus]
// find c where t is most desirable gdiff = goodness(t,c) ­ goodness( c.current_task,c) if( gdiff > threshold ) threshold = gdiff //Choose the cpu that will have the best threshold next_cpu = c if( next_cpu != NIL )
// found a core for t prev_need = next_cpu.current_task.need_resched next_cpu.current_task.need_resched = true //When is this field checked? In user mode need_resched cannot be accessed, The change can only be done Kernel mode of the next_cpu,the flag is checked when getting back from kernel mode to user mode. we want to make sure the cpu will enter kernel mode, so if we are the first to ask it to resched we want to interrupt it and force it into kernel mode. (It will always be interrupted during clock interrupt, but we don’t want to wait without need). if( (prev_need == false) && (next_cpu != this_cpu) ) interrupt next_cpu //Note that we are not telling the next_cpu which task to run, it may decide to run some other task with an higher priority } void __wake_up_common(wait_queue q) { // blocked tasks residing in q wait for an event that has just happened // so try to reschedule all of them… foreach task t in [q] remove t from q add t to ready­to­run list reschedule_idle(t) } //When will we schedule different tasks on different cpus? It may seem that we will always choose the cpu with the least important running process but we’ve got to remember that the goodness gives a bonus for running a process on the original cpu, and therefore different process may choose different cpus If there are enough processes the first tasks may start running before we reschedule the last ones and therefore the cpu with the least important running process may change! 21 void schedule(cpu this_cpu) { // called when need_resched of this_cpu is on, when switching from // kernel mode back to user mode. need_resched can set by, e.g., the // tick handler, or by I/O device drivers that initiate a slow operation // (and hence move the associated tasks to a wait_queue) prev = this_cpu.current_task START: if( prev's state is runnable ) next = prev next_g = goodness(prev, this_cpu) else next_g = ­1 foreach task t in [runnable && not executing] // search for ‘next’ = the next task to run on this_cpu cur_g = goodness(t, this_cpu) if( cur_g > next_g ) next = t
next_g = cur_g if( next_g == ­1 )
// no ready tasks end function
// schedules “idle task” (halts this_cpu) else if( next_g == 0 )// all quanta exhausted => start new epoch foreach task t //for all tasks t.counter = t.counter/2 + NICE_TO_TICKS(t.nice) goto START; else if( next != prev ) next.processor = this_cpu next.need_resched = false
// 'next' will run next context_switch(prev, next); if( prev is still runnable ) reschedule_idle(prev)
// perhaps on another core // ‘next’ (which may be equal to ‘prev’) will run next…. } 22 ‫המשך‪ ‬הרצאה‪ ­ 3 ‬תהליכים‪ ‬וחוטים‪) ‬מצגת‪ (3+4 ‬‬
‫‪ ‬‬
‫נרצה‪ ‬לעיתים‪ ‬קרובות‪ ‬להריץ‪ ‬עבודה‪ ‬מסוימת‪ ‬על‪ ‬פני‪ ‬כמה‪ ‬תהליכים‪ ‬שונים‪ ,‬מוטיבציה‪ ‬אחת‪ ‬לכך‪ ‬היא‪ ‬לדאוג‪ ‬‬
‫שהשירות‪ ‬ימשיך‪ ‬לתפקד‪ ‬בצד‪ ‬של‪ ‬המשתמש‪ ‬גם‪ ‬אם‪ ‬בינתיים‪ ‬מתבצעות‪ ‬גישות‪ ‬לדיסק‪ ‬וגם‪ ‬אם‪ ‬משימה‪ ‬מסוימת‪ ‬‬
‫נופלת‪ .‬‬
‫שרתים‪ ‬ברשת‪ ‬משתמשים‪ ‬בדבר‪ ‬הדומה‪ ‬מאוד‪ ‬ל­‪ ,multi­processing‬בניהול‪ ‬בקשות‪ ‬שמגיעות‪ ‬אליה‪ .‬כל‪ ‬בקשה‪ ‬‬
‫מקבלת‪ ‬תהליך‪ ‬משלה‪ ,‬וכך‪ ‬מעבדים‪ ‬שונים‪ ‬יוכלו‪ ‬לבצע‪ ‬משימות‪ ‬שונות‪ ‬ומערכת‪ ‬ההפעלה‪ ‬תדאג‪ ‬לבצע‪ ‬החלפה‪ ‬‬
‫בהתאם‪ ‬לצורך‪ ‬בין‪ ‬המשימות‪ ‬‬
‫‪ ‬‬
‫דוגמא‪ : ‬הכפלת‪ ‬מטריצות‪ ­ ‬ניתן‪ ‬לחלק‪ ‬עבודה‪ ‬גדולה‪ ‬לתתי‪ ‬עבודות‪ ‬ולבצען‪ ‬במקביל‪ .‬הקושי‪ ‬בביצוע‪ ‬חלוקה‪ ‬הוא‪ ‬‬
‫שלכל‪ ‬תהליך‪ ‬יש‪ ‬מרחב‪ ‬כתובות‪ ‬וזיכרון‪ ‬משלו‪ ,‬אז‪ ‬נצטרך‪ ‬לשכפל‪ ‬את‪ ‬המטריצה‪ ‬עבור‪ ‬כל‪ ‬תהליך‪ ,‬ואין‪ ‬לדעת‪ ‬אם‪ ‬כל‪ ‬‬
‫העותקים‪ ‬ייכנסו‪ ‬בזיכרון‪ ,‬שלא‪ ‬לדבר‪ ‬על‪ ‬הבעיה‪ ‬בביצוע‪ ‬המיזוג‪ ‬בסוף‪ ‬החישוב‪ .‬פתרון‪ ‬בסיסי‪ ‬יהיה‪ ‬לאפשר‪ ‬להם‪ ‬‬
‫לגשת‪ ‬לאיזור‪ ‬משותף‪ ‬בו‪ ‬תישמר‪ ‬המטריצה‪ ,‬אך‪ ‬במקרה‪ ‬זה‪ ‬קצב‪ ‬קריאת‪ ‬הנתונים‪ ‬יכול‪ ‬להיות‪ ‬יותר‪ ‬איטי‪ .‬חלוקת‪ ‬‬
‫החישוב‪ ‬לא‪ ‬בהכרח‪ ‬מועילה!‪ ‬‬
‫‪ ‬‬
‫‪ Multi­threading‬‬
‫תהליכים‪ ‬החולקים‪ ‬ביניהם‪ ‬את‪ ‬אותו‪ ‬המידע‪ ‬נקראים‪ ‬חוטים‪ ­ (Threads) ‬תהליכים‪ ‬אלה‪ ‬משתפים‪ ‬את‪ ‬כל‪ ‬הנתונים‪ ,‬‬
‫הזיכרון‪ ,‬המשתנים‪ ‬הגלובליים‪ ‬והסטטים‪ .‬מה‪ ‬שלא‪ ‬משותף‪ ‬הוא‪ ‬המחסנית‪, ‬הרגיסטרים‪ ‬ומבנה‪ ‬הנתונים‪ ‬שמתארים‪ ‬‬
‫אותם‪ ,‬על‪ ‬מנת‪ ‬לאפשר‪ ‬להם‪ ‬לרוץ‪ ‬בנפרד‪ .‬‬
‫הם‪ ‬יכולים‪ ‬לגשת‪ ‬למחסניות‪ ‬אחד‪ ‬של‪ ‬השני‪ ,‬אבל‪ ‬זו‪ ‬לא‪ ‬התנהגות‪ ‬רצויה‪ ,‬מידע‪ ‬משותף‪ ‬צריך‪ ‬לשבת‪ ‬ב­‪ !Heap‬‬
‫תהליכים‪ ‬שלא‪ ‬ביקשו‪ ‬ליצור‪ ‬חוטים‪ ‬בפועל‪ ‬מריצים‪ ‬חוט‪ ‬אחד‪ .single­threaded process ­ ‬‬
‫‪ ‬‬
‫‪ Standard API for Threads‬‬
‫‪ ­ openMP‬מנסה‪ ‬ליצור‪ ‬מספר‪ ‬אופטימלי‪ ‬של‪ ‬חוטים‪ ,‬אלגוריתם‪ ‬די‪ ‬יפה‪ ‬אבל‪ ‬אף‪ ‬אחד‪ ‬לא‪ ‬משתמש‪ ‬בו‪ ‬כי‪ ‬היה‪ ‬תקן‪ ‬‬
‫אחר‪ ‬שהקדים‪ ‬אותו‪ ,‬סיפור‪ ‬עצוב‪ ‬ונוגע‪ ‬ללב‪ ): ‬‬
‫ב­‪ Java‬וב‪ C++11‬יש‪ ‬תמיכה‪ ‬מובנית‪ ‬בחוטים‪ .‬‬
‫‪ ­ POSIX‬ספריה‪ ‬שרצה‪ ‬כמעט‪ ‬על‪ ‬כל‪ ‬פלטפורמה‪ ,‬מאוד‪ ‬נפוצה‪ Portable Operating System Interface .‬‬
‫‪ ­ pthreads‬ספרית‪ ‬החוטים‪ ‬של‪ .POSIX ‬‬
‫‪ ‬‬
‫מה‪ ‬משותף‪ ‬לחוטים?‪ ‬‬
‫מבחינת‪ ‬ה­‪ user‬ה­‪ threads‬חולקים‪ ‬את‪ ‬כל‪ ‬המידע‪ ,‬למעט‪ ‬הדברים‪ ‬הייחודיים‪ ‬לריצתם‪ .‬‬
‫בתוך‪ ‬מערכת‪ ‬ההפעלה‪ ,‬כל‪ ‬מערכת‪ ‬הפעלה‪ ‬מחליטה‪ ‬איך‪ ‬היא‪ ‬מחלקת‪ ‬את‪ ‬החוטים‪ .‬בלינוקס‪ ‬כל‪ ‬חוט‪ ‬מיוצר‪ ‬על‪ ‬ידי ‪ ‬‬
‫תהליך‪ ‬נפרד‪ .‬הפונקציה‪ clone ‬יוצרת‪ ‬חוט‪ ‬חדש‪ fork) .‬הוא‪ ‬בעצם‪ ‬תהליך‪ ‬חדש‪ ‬שלא‪ ‬משתף‪ ‬שום‪ ‬דבר‪ ‬עם‪ ‬‬
‫התהליך‪ ‬הקודם(‪ .‬‬
‫‪ ‬‬
‫ההבדל‪ ‬המהותי‪ ‬בין‪ ‬חישוב‪ ‬מקבילי‪ ‬על‪ ‬ידי‪ ‬תהליכים‪ ‬לבין‪ ‬חישוב‪ ‬מקבילי‪ ‬על‪ ‬ידי‪ ‬חוטים‪ ,‬הוא‪ ‬שבחישוב‪ ‬על‪ ‬ידי‪ ‬תהליכים‪ ‬‬
‫צריך‪ ‬איזשהו‪ ‬ערוץ‪ ‬תקשורת‪ ‬בין‪ ‬התהליכים‪ ‬ואילו‪ ‬בחישוב‪ ‬על‪ ‬ידי‪ ‬חוטים‪ ‬פשוט‪ ‬משתמשים‪ ‬באיזור‪ ‬הזיכרון‪ ‬המשותף‪ .‬‬
‫ערוץ‪ ‬התקשורת‪ ‬נעשה‪ ‬באמצעות‪ ,pipe ‬אשר‪ ‬ניתן‪ ‬לכתוב‪ ‬אליו‪ ‬מידע‪ ‬מתהליך‪ ‬אחד‪ ‬ולקרוא‪ ‬אותו‪ ‬מתהליך‪ ‬אחר‪ .‬‬
‫‪ ‬‬
‫דוגמא‪ ‬לתקשורות‪ ‬בין‪ ‬תהליכים‪ :Pipe ­ ‬‬
‫‪23 ‬‬
‫ישנם‪ ‬הרבה‪ ‬סוגים‪ ‬של‪ ‬ערוצי‪ ‬תקשורת‪ ‬בין‪ ‬תהלכים‪ ,‬אנו‪ ‬נציד‪ ‬את‪ pipe ‬שהוא‪ ‬דרך‪ ‬פשוטה‪ ‬לתקשר‪ ‬בין‪ ‬תהליכים‪ .‬‬
‫אנחנו‪ ‬נשתמש‪ ‬ב‪ pipe‬לצורך‪ ‬העברת‪ ‬הודעה‪ ‬יחידה‪ ‬מתהליך‪ ‬אחד‪) ‬תהליך‪ ‬האב(‪ ‬לתהליך‪ ‬אחר‪) ‬תהליך‪ ‬הבן(‪ .‬‬
‫‪ pipe‬מוגדר‪ ‬ע"י‪ ‬שני‪ ,file_descriptor ­ fd ‬כלומר‪ ‬ע"י‪ , [int pipe_fd[2 ‬כך‪ ‬שהאיבר‪ ‬הראשון‪ ‬במערך‪ ‬הוא‪ ‬ה­‪ fd‬‬
‫שמשמש‪ ‬לקריאה‪ ‬מה‪ pipe‬והתא‪ ‬השני‪ ‬במערך‪ ‬הוא‪ ‬ה­‪ fd‬שמשמש‪ ‬לכתיבה‪ ‬ל‪ .pipe‬‬
‫כתיבה‪ ‬וקריאה‪ ‬ל‪ pipe‬נעשות‪ ‬ע"י‪ ‬קריאות‪ ‬המערכת‪ read ‬ו­‪ .write‬הקרנל‪ ‬שומר‪ ‬את‪ ‬כל‪ ‬מה‪ ‬שנכתב‪ ‬ל‪ ,Pipe‬כך‪ ‬‬
‫שמאוחר‪ ‬יותר‪ ‬הוא‪ ‬יהיה‪ ‬מסוגל‪ ‬לתת‪ ‬שירות‪ ‬עבור‪ ‬קריאה‪ ‬של‪ ‬נתונים‪ ‬אלו‪ .‬‬
‫)‪ ­ write( pipe_fd[1], src_buf, N‬קריאת‪ ‬מערכת‪ ‬שתפקידה‪ ‬הוא‪ ‬להעתיק‪ ‬את‪ ‬ה‪ src_bul‬לתא‪ ‬השני‪ ‬ב‪ .pipe‬‬
‫)‪ ­ read( pipe_fd[0], dst_buf, N‬קריאת‪ ‬מערכת‪ ‬שתפקידה‪ ‬לרשום‪ ‬ל‪ dst_buf‬את‪ ‬מה‪ ‬שנרשם‪ ‬בעבר‪ ‬ל‪ Pipe‬‬
‫ושמור‪ ‬עכשיו‪ ‬בתא‪ ‬הראשון‪ ‬ב‪) pipe‬כלומר‪ ‬ב­‪ .([pipe_fd[0 ‬‬
‫נשים‪ ‬לב‪ ‬שלא‪ ‬ניתן‪ ‬לקרוא‪ ‬מ‪ pipe‬אם‪ ‬לא‪ ‬נכתב‪ ‬אליו‪ ‬עדיין‪ ‬שום‪ ‬דבר‪ ,‬במקרה‪ ‬זה‪ ‬התהליך‪ ‬יקבל‪ ‬יחסם‪ .‬‬
‫‪ ‬‬
‫המאקרו‪ :DO_SYS ‬‬
‫‪ ‬‬
‫הערה‪ :‬אם‪ ‬נסתכל‪ ‬במאקרו‪ DO_SYS ‬ובמקרואים‪ ‬אחרים‪ ‬נשים‪ ‬לב‪ ‬כי‪ ‬הם‪ ‬בד"כ‪ ‬מוקפים‪ ‬ב­‪ .do while‬המטרה‪ ‬‬
‫נועדה‪ ‬למנוע‪ ‬באגים‪ ‬בהם‪ ‬המתכנת‪ ‬עלול‪ ‬לשכוח‪ ‬שהמאקרו‪ ‬בפועל‪ ‬עלול‪ ‬להיות‪ ‬מתורגם‪ ‬ליותר‪ ‬משורה‪ ‬אחת‪ ‬בזמן‪ ‬‬
‫‪ .precompilation‬‬
‫‪if (...) ‬‬
‫‪DO_SYS ‬‬
‫‪else ‬‬
‫במקרה‪ ‬זה‪ ‬למשל‪ ‬ה­‪ else‬יתייחס‪ ‬ל­‪ if‬הפנימי‪ ‬אם‪ ‬לא‪ ‬היינו‪ ‬משתמשים‪ ‬ב­‪ .do_while‬‬
‫מקרה‪ ‬פשוט‪ ‬יותר‪ ‬הוא‪ ‬שהמאקרו‪ ‬פשוט‪ ‬מבצע‪ ‬מספר‪ ‬שורות‪ ,‬והקפה‪ ‬ב­‪ if‬תכלול‪ ‬רק‪ ‬את‪ ‬הראשון‪ ‬‬
‫‪if ‬‬
‫‪DO_MACRO ‬‬
‫בעצם‪ ‬זו‪ ‬פשוט‪ ‬דרך‪ ‬לעטוף‪ ‬את‪ ‬הקוד‪ ‬בבלוק‪ ‬משלו‪ ,‬מה‪ ‬שאי‪ ‬אפשר‪ ‬לעשות‪ ‬סתם‪ ‬כך‪ ‬ב­‪ .C‬‬
‫נבחין‪ ‬כי‪ DO_SYS ‬לא‪ ‬מחזירה‪ ‬לנו‪ ‬ערך!‪ ‬‬
‫‪ ‬‬
‫‪ ‬‬
‫‪ ‬‬
‫‪ ‬‬
‫‪ ‬‬
‫‪ ‬‬
‫‪ ‬‬
‫‪24 ‬‬
‫ולא‬ ‫שלו‬ ‫המערך‬ ‫לתוך‬ ‫קורא‬ ‫הבן‬ ,‫הבן‬ ‫של‬ ‫המערך‬ ‫את‬ ‫ולא‬ ‫שלו‬ ‫המערך‬ ‫את‬ ‫מילא‬ ‫האבא‬ : ‫הנ"ל‬ ‫לקוד‬ ‫הסבר‬
‫ולבן‬ ‫לאב‬ ‫ולכן‬ !‫חוטים‬ ‫ולא‬ ‫תהליכים‬ ‫אלו‬ ‫כי‬ ‫נזכור‬ !‫תהליכים‬ ‫בין‬ ‫משותף‬ ‫לא‬ ‫הגלובלי‬ ‫המשתנה‬ !‫האבא‬ ‫של‬ ‫המערך‬
.(‫בזבל‬ ‫)מלאים‬ ‫ריקים‬ ‫היו‬ ‫שניהם‬ ‫השכפול‬ ‫בשלב‬ ,‫נפרד‬ ‫גלובלי‬ ‫מערך‬ ‫יש‬
// Assume that filling g_msg requires a lot of computational work. // So we want to use 2 threads, one thread to fill the // first half of g_msg and another to fill the second half void fill_g_msg( void ) { pthread_t t1, t2; // launch the two threads pthread_create(&t1, NULL, thread_fill, “first"); //‫את‬ ‫להריץ‬ ‫הולכים‬ ‫שניהם‬ thread_fill ‫שונים‬ ‫פרמטרים‬ ‫עם‬ pthread_create(&t2, NULL, thread_fill, “second"); //‫המחסנית‬ ‫הוא‬ ‫השני‬ ‫הפרמטר‬ , NULL ‫חדשה‬ ‫מחסנית‬ ‫ליצור‬ ‫רוצים‬ ‫שלא‬ ‫אומר‬ // wait for both threads to finish pthread_join(t1, NULL); pthread_join(t2, NULL); } void* thread_fill(void *arg) //‫יעדכנו‬ ‫אך‬ ,‫הזאת‬ ‫הפונקצה‬ ‫את‬ ‫יבצעו‬ ‫החוטים‬ ‫שני‬ ‫המערך‬ ‫של‬ ‫שונים‬ ‫חלקים‬ { // assume I’m the “first” thread int i; int lo = 0; int hi = N/2; if( strcmp((char*)arg, “second”) == 0 ) { // I’m the “second” thread 25 ‫‪lo = N/2 + 1; ‬‬
‫‪hi = N ­ 1; ‬‬
‫‪} ‬‬
‫‪for(i = lo; i <= hi; i++) ‬‬
‫‪g_msg[i] = /*do some really hard work here*/ ; ‬‬
‫‪return null; ‬‬
‫‪} ‬‬
‫‪ ‬‬
‫‪ ‬‬
‫‪ ‬‬
‫אם‪ ‬יש ‪ ‬לנו‪ ‬מעבד‪ ‬יחיד‪ ‬אז‪ ‬אנחנו‪ ‬לא‪ ‬מרוויחים‪ ‬כאן‪ ‬שום‪ ‬דבר‪ ,‬רק‪ ‬הוספנו‪ overhead ‬של‪ ‬מעבר‪ ‬בין‪ ‬התהליכים‪ ,‬אבל‪ ‬‬
‫כשיש‪ ‬יותר‪ ‬ממעבד‪ ‬אחד‪ ‬נוכל‪ ‬לשפר‪ ‬את‪ ‬הביצועים‪ ‬שכן‪ ‬הקוד‪ ‬יוכל‪ ‬תיאורטית‪ ‬לרוץ‪ ‬בקצב‪ ‬כפול‪ .‬‬
‫‪ ‬‬
‫מסקנה‪ :‬אם‪ ‬יש‪ ‬לנו‪ ‬צורך‪ ‬של‪ ‬ממש‪ ‬לשתף‪ ‬נתונים‪ ‬אז‪ ‬חוטים‪ ‬הם‪ ‬בחירה‪ ‬טבעית‪ ,‬הרי ‪ ‬הכל‪ ‬משותף‪ ,‬בסך‪ ‬הכל‪ ‬צריך‪ ‬‬
‫להקצות‪ ‬את‪ ‬הנתונים‪ ‬בערימה)‪ (heap‬או‪ ‬כמשתנים‪ ‬גלובליים‪ ‬וכל‪ ‬החוטים‪ ‬יראו‪ ‬אותם‪ .‬‬
‫‪ ‬‬
‫בתהליכים‪ ‬ניתן‪ ‬להעביר‪ ‬הודעות‪ ‬באמצעות‪ pipe ‬או‪ ‬ערוץ‪ ‬תקשורת‪ ‬אחר‪ ,‬או‪ ‬ליצור‪ ‬איזור‪ ‬זיכרון ‪ ‬משותף‪ ‬וכך‪ ‬העברת‪ ‬‬
‫נתונים‪ ‬תהיה‪ ‬דומה‪ ‬להעברה‪ ‬בחוטים‪ ,‬למעט‪ ‬העובדה‪ ‬שהרבה‪ ‬יותר‪ ‬מסובך‪ ‬ומסורבל ‪ ‬לבצע‪ ‬סנכרון‪ ‬בכתיבה‪ ‬‬
‫והקריאה‪ ‬כשמודבר‪ ‬בתהליכים‪ .‬לכן‪ ‬באופן‪ ‬כללי‪ ‬נרצה‪ ‬להשתמש‪ ‬בחוטים‪ ‬כדי‪ ‬לשתף‪ ‬מידע‪ ,‬אם‪ ‬למשל‪ ‬אנו‪ ‬רוצים‪ ‬‬
‫להשתמש‪ ‬בשתי ‪ ‬תוכניות‪ ‬שונות‪ ‬אחת‪ ‬שקוראת‪ ‬ואחת‪ ‬שכותבת‪ ,‬אין‪ ‬לנו‪ ‬מנוס‪ ‬מהלשתמש‪ ‬בתהליכים‪ ‬ולשתף ‪ ‬את‪ ‬‬
‫המידע‪) ‬שכן‪ ‬חוטים‪ ‬מריצים‪ ‬אותו‪ ‬קוד‪ ,‬גם‪ ‬אם‪ ‬קטעים‪ ‬שונים‪ ‬ממנו(‪ .‬‬
‫‪ ‬‬
‫ככל‪ ‬שהיחס ‪ ‬בין‪ ‬הזמן ‪ ‬שהתהליך‪ ‬רץ‪ ‬לבין‪ ‬זמן‪ ‬החלפת‪ ‬ההקשר ‪ ‬גדל‪ ,‬כלומר‪ ‬ככל‪ ‬שהחלפת‪ ‬ההקשר‪ ‬זניחה ‪ ‬ביחס‪ ‬ל­‬
‫‪ quantum‬של‪ ‬תהליך‪ ‬אז‪ ‬השפעתו‪ ‬באמת‪ ‬קטנה‪ ‬בהתאם‪ ‬לאינטואיציה‪ .‬‬
‫בהחלפת‪ ‬הקשר‪ ‬צריך‪ ‬להפריד‪ ‬בין‪ ‬שני‪ ‬דברים‪ :‬‬
‫● פגיעה‪ ‬ישירה‪ ­ ‬הזמן‪ ‬שלוקח‪ ‬למערכת‪ ‬ההפעלה‪ ‬לעשות‪ ‬את‪ ‬ההחלפה‪ ,‬באמת‪ ‬ברוב‪ ‬המקרים‪ ‬הוא‪ ‬ממש ‪ ‬‬
‫זניח‪ ,‬עבור‪ ‬אלגוריתם‪ ‬זימון‪ ‬טוב‪ ‬מספיק‪ .‬‬
‫● פגיעה‪ ‬עקיפה‪ ­ ‬פקטור‪ ‬נוסף‪ ‬שצריך‪ ‬להתחשב‪ ‬בו‪ ,‬לעיתים‪ ‬הוא‪ ‬פוגע‪ ‬במערכת‪ ‬בכמה‪ ‬סדרי‪ ‬גודל‪ ‬יותר‪ .‬זמן‪ ‬‬
‫של‪ ‬הגעה‪ ‬של‪ ‬מערכת‪ ‬לקצב‪ ‬מקסימלי‪ ­ ‬מתברר‪ ‬שאחרי‪ ‬החלפת‪ ‬הקשר‪ ‬החומרה‪ ‬מריצה‪ ‬את‪ ‬התהליך‪ ‬‬
‫לאט‪ ‬מאוד‪ ,‬ולוקח‪ ‬לה‪ ‬זמן‪" ‬להתחמם"‪ .‬‬
‫‪ ‬‬
‫רוב‪ ‬הזמן‪ ‬המערכת‪ ‬קוראת‪ ‬נתונים‪ ‬ומבצעת‪ ‬איתם‪ ‬איזשהו‪ ‬חישוב‪ ,‬הבעיה‪ ‬היא‪ ‬שהרבה‪ ‬מאוד‪ ‬פעמים‪ ‬לא‪ ‬קוראים‪ ‬‬
‫נתונים‪ ‬מתוך‪ ‬הרגיסטרים‪ ‬אלא ‪ ‬מתוך ‪ ‬הזיכרון‪ ‬הראשי‪ .‬נניח‪ ‬שיש‪ ‬לנו‪ ‬נתונים‪ ‬שמגיעים‪ ‬מזיכרון‪ ,‬הזיכרון‪ ‬צריך‪ ‬להיות‪ ‬‬
‫מסוגל‪ ‬לספק‪ ‬נתונים‪ ‬בקצב ‪ ‬שהמעבד‪ ‬רוצה‪ ‬לקרוא‪ ‬אותם‪ ,‬לצערנו‪ ‬זה‪ ‬לא‪ ‬המצב‪ ,‬הזיכרון‪ ‬לא‪ ‬מתקרב‪ ‬אפילו‪ ‬לקצב‪ ‬‬
‫של‪ ‬מעבד‪ ,‬יש‪ ‬הבדל‪ ‬של‪ ‬שניים­שלושה‪ ‬סדרי‪ ‬גודל‪ .‬לכן‪ ‬מעבד‪ ‬לא‪ ‬יכול‪ ‬לקרוא‪ ‬נתונים‪ ‬בקצב‪ ‬הרצוי‪ .‬‬
‫מה‪ ‬הפתרון‪ ‬של‪ ‬בעיה‪ ‬זו?‪ ‬נבחין‪ ‬שכאשר‪ ‬מסתכלים‪ ‬על‪ ‬נתונים‪ ‬הם‪ ‬מקיימים‪ ‬שתי‪ ‬תכונות‪ :‬‬
‫● לוקליות‪ ‬בזמן‪ ­ ‬אם‪ ‬השתמשתי‪ ‬בכתובת‪ ‬מסוימת‪ ‬עכשיו‪ ,‬סיכוי‪ ‬גבוה‪ ‬שארצה‪ ‬להשתמש‪ ‬בה‪ ‬שוב‪ ‬ולגשת‪ ‬‬
‫לאותו‪ ‬מקום‪ ‬בזיכרון‪) .‬דוגמא‪ ‬קלאסית‪ :‬עבודה‪ ‬על‪ ‬משתנה‪ ‬מסוים‪ ‬בתוך‪ ‬פונקציה‪ ,‬ביצוע‪ ‬לולאה(‪ .‬‬
‫● לוקליות‪ ‬במקום‪ ­ ‬אם‪ ‬קראתי‪ ‬כתובת‪ ,X ‬סביר‪ ‬להניח‪ ‬שאקרא‪ ‬בקרוב‪ ‬את‪) X+2 ‬דוגמא‪ ‬קלאסית‪ :‬קריאת‪ ‬‬
‫מערך(‪ .‬‬
‫למה‪ ‬אבחנה‪ ‬זו‪ ‬חשובה?‪ ‬למרות‪ ‬שלתהליך‪ ‬יש‪ ‬המון‪ ‬נתונים‪ ,‬בפרק‪ ‬זמן‪ ‬מסוים‪ ‬הוא‪ ‬צריך‪ ‬כמות‪ ‬מזערית‪ ‬של‪ ‬נתונים‪ .‬‬
‫למה‪ ‬שלא‪ ‬נשתמש‪ ‬באיזו‪ ‬חומרה‪ ‬שהיא‪ ‬מהירה‪ ‬בהרבה‪ ‬מזיכרון‪ ,‬עם‪ ‬מעט‪ ‬מקום‪ ‬ונשמור‪ ‬רק‪ ‬את‪ ‬הנתונים‪ ‬ה"חמים" ‪ ‬‬
‫במקום‪ ‬ובזמן‪ .‬הפתרון‪ ‬הוא‪ ‬באמת‪ ‬להשתמש‪ ‬ב­‪ .Cache‬‬
‫‪26 ‬‬
‫‪ ‬‬
‫‪ ­ Cache‬זיכרון‪ ‬מטמון‪ :‬‬
‫●‬
‫●‬
‫●‬
‫●‬
‫●‬
‫יש‪ ‬את‪ ‬רמת‪ ‬הרגיסטרים‪ ‬שהם‪ ‬מהירים‪ ‬ביותר‪ .‬‬
‫‪ ­ L1‬בגודל‪ ,64KB ‬מחולק‪ ‬לשני‪ ‬חצאים‪ 32KB ,‬ל­‪ data‬ו­‪ 32KB‬ל­‪ .instructions‬החומרה‪ ‬הזאת‪ ‬‬
‫מהירה‪ .‬‬
‫‪ ­ L2‬בגודל‪ ,256KB ‬יותר‪ ‬איטי‪ ‬מ­‪ ,L1‬צריך‪ ‬כ­‪ 12‬מחזורי‪ ‬שעון‪ ‬לקרוא‪ ‬ממנו‪ .‬‬
‫‪ ­ L3‬בגודל‪ ,8MB ‬לוקח‪ 36 ‬מחזורי‪ ‬שעון‪ .‬‬
‫‪ ­ DRAM‬הזיכרון‪ ‬עצמו‪ ,‬עד‪ ,32GB ‬לוקח‪ 230 ‬מחזורי‪ ‬שעון‪ .‬‬
‫‪ ‬‬
‫‪ ‬‬
‫נסתכל‪ ‬למשל‪ ‬על‪ ‬המעבד‪ ‬הזה‪ ,‬לכל‪ ‬ליבה‪ ‬יש‪ L1 ‬ו­‪ L2‬משלה‪ ,‬כך‪ ‬הזמן‪ ‬הפיזי‪ ‬שלוקח‪ ‬להגיע‪ ‬למעבד‪ ‬הוא‪ ‬קטן ‪ ‬‬
‫משמעותית‪ L3 .‬לעומת‪ ‬זאת‪ ‬משותף‪ ‬לכולם‪ ‬וחיצוני‪ ,‬ולכן‪ ‬הוא‪ ‬איטי‪ ‬יותר‪ .‬‬
‫‪ ‬‬
‫כעת‪ ,‬ננסה‪ ‬להבין‪ ,‬מהו‪ ‬המחיר‪ ‬העקיף‪ ‬הזה?‪ ‬למה‪ ‬לוקח‪ ‬למעבד‪ ‬זמן‪ ‬להתחמם?‪ ‬אחרי‪ ‬שאנו‪ ‬מחליפים‪ ‬תהליך‪ ‬אחד‪ ‬‬
‫בתהליך‪ ‬האחר‪ ‬ה­‪­Cache‬ים‪ ‬לא‪ ‬מעודכנים‪ ,‬ולכן‪ ‬כל‪ ‬פעם‪ ‬שנרצה‪ ‬לקרוא‪ ‬נתון‪ ‬נצטרך‪ ‬להגיע‪ ‬עד‪ ‬ל­‪ .DRAM‬עם‪ ‬‬
‫הזמן‪ ‬ה­‪­Cache‬ים‪ ‬מתמלאים‪ ‬ואז‪ ‬המערכת‪ ‬מגיעה‪ ‬לזמן‪ ‬האופטימלי‪ ‬הזה‪ .‬זמן‪ ‬המילוי‪ ‬הוא‪ ‬משמעותי‪ ‬לביצועים‪ .‬‬
‫כשמחליפים‪ ‬בין‪ ‬חוט‪ ‬לחוט‪ ‬אמנם‪ ‬יש‪ ‬ערכים‪ ‬שהם‪ ‬אינם‪ ‬רלוונטיים‪ ‬לשני‪ ‬החוטים‪ ,‬אך ‪ ‬רוב‪ ‬המידע‪) ‬הקוד‪ ,‬משתנים‪ ‬‬
‫גלובליים‪ ‬וכד'(‪ ‬רלוונטי‪ ‬לשני‪ ‬החוטים‪ ‬ולכן‪ ‬הזמן‪ ‬שלוקח‪ ‬למערכת‪ ‬להתחמם‪ ‬במעבר‪ ‬מחוט‪ ‬אחד‪ ‬לאחר‪ ‬הוא‪ ‬הרבה‪ ‬‬
‫יותר‪ ‬קצר‪ ‬שכן‪ ‬אנו‪ ‬מתחילים‪ ‬ממצב‪ ‬שיש‪ ‬הרבה‪ ‬נתונים‪ ‬רלוונטיים‪ ‬ב­‪ ,Cache‬ומתחילת‪ ‬הביצוע‪ ‬הוא‪ ‬יותר‪ ‬מהיר‪ ‬שכן‪ ‬‬
‫ה­‪ Cache‬כבר‪ ‬מעודכן‪ ‬באופן‪ ‬חלקי‪ .‬‬
‫‪ ‬‬
‫אם‪ ‬ניזכר‪ ‬בהרצאה‪ ‬על‪ Schedulers ‬אחד‪ ‬הנתונים‪ ‬שבדקנו‪ ‬בכדאיות‪ ‬להחלפת‪ ‬הקשר‪ ‬היא ‪ ‬האם‪ ‬התהליך‪ ‬משתף ‪ ‬‬
‫זיכרון‪ ‬עם‪ ‬התהליך‪ ‬שרץ‪ ‬כרגע‪ ‬במעבד‪) ‬יכול‪ ‬להיות‪ ‬שהתהליך‪ ‬החדש‪ ‬שאנו‪ ‬רוצים‪ ‬להביא‪ ‬הוא‪ ‬חוט‪ ‬אחר‪ ‬של‪ ‬אותו‪ ‬‬
‫‪27 ‬‬
‫התהליך(‪ ,‬ולכן‪ ‬נתנו‪ ‬בונוס‪ ‬למקרה‪ ‬כזה‪ ‬שכן‪ ‬אם‪ ‬נבצע‪ ‬את‪ ‬החלפת‪ ‬ההקשר ‪ ‬עם‪ ‬המעבד‪ ‬הזה‪ ‬אז‪ ‬ה­‪­Cache‬ים‪ ‬יהיו‪ ‬‬
‫מעודכנים‪ ‬יותר‪ ‬ונקבל‪ ‬תקורה‪ ‬עקיפה‪ ‬קטנה‪ ‬יותר‪ .‬‬
‫‪ ‬‬
‫ככל‪ ‬שיש‪ ‬יותר‪ ‬ליבות‪ ‬הביצועים‪ ‬יותר‪ ‬גרועים‪ ,‬כי‪ ‬כל‪ ‬ליבה‪ ‬בולמת‪ ‬את‪ ‬הגישה‪ ‬של‪ ‬ליבות‪ ‬אחרות‪ .‬‬
‫‪ ‬‬
‫אפשר‪ ‬לשתף‪ ‬זיכרון‪ ‬גם ‪ ‬בין‪ ‬תהליכים‪ ‬באופן‪ ‬ישיר‪ ,‬אפשר‪ ‬לבקש‪ ‬ממערכת‪ ‬הפעלה‪ ‬להגדיר‪ ‬בלוק‪ ‬בזיכרון‪ ‬שיהיו‪ ‬כמה‪ ‬‬
‫תהליכים‪ ‬שיוכלו‪ ‬לגשת‪ ‬לשם‪ ,‬חשוב‪ ‬להבין‪ ‬שגם‪ ‬אם‪ ‬מדובר‪ ‬באותה‪ ‬כתובת‪ ‬בזיכרון‪ ‬הפיזי‪ ,‬הכתובת‪ ‬של‪ ‬הבלוק‪ ‬‬
‫בזיכרון‪ ‬הוירטואלי‪ ‬של‪ ‬כל‪ ‬תהליך‪ ‬יכולה‪ ‬להיות‪ ‬שונה‪ .‬‬
‫‪ ‬‬
‫‪ Shared Memory‬‬
‫●‬
‫●‬
‫●‬
‫●‬
‫●‬
‫●‬
‫‪ ­ shmget‬יוצרת‪ ‬את‪ ‬הבלוק‪ ‬ומחזירה‪ ‬איזשהו‪ .identifier ‬בד"כ‪ ‬משתמשים‪ ‬בו‪ ‬על‪ ‬מנת‪ ‬לשתף‪ ‬זיכרון‪ ‬בין‪ ‬‬
‫אב‪ ‬ובן‪ .‬האבא‪ ‬יוצר‪ ‬זיכרון‪ ‬מבצע‪ fork ‬ואז‪ ‬הוא‪ ‬והבן‪ ‬יכולים‪ ‬ל"דבר"‪ ­ ‬להשתמש‪ ‬בזיכרון‪ ‬המשותף‪ .‬‬
‫‪ ­ shmat‬לקשר‪ ‬את‪ ‬הבלוק‪ ‬לזיכרון‪ ‬הוירטואלי‪ ‬של‪ ‬תהליך‪ .‬‬
‫‪ ­ shmdt‬לבטל‪ ‬את‪ ‬הקישור‪ .‬‬
‫‪ ­ shmctrl‬מאפשר‪ ‬לשנות‪ ‬תכונות‪ ‬של‪ ‬ה­‪ ,shared_memory‬למשל‪ ‬בהתחלה‪ ‬שיהיה‪ ‬ספציפי‪ ‬לתהליך‪ ‬‬
‫ואחרי‪ ‬עדכון‪ ‬מסוים‪ ‬להפוך‪ ‬אותו‪ ‬ל­‪ .public‬‬
‫‪ ­ shm_open‬יוצר‪ ‬קובץ‪ ‬במערכת ‪ ‬קבצים‪ ‬מיוחדת‪ ,‬מאפשר‪ ‬לשני‪ ‬תהליכים ‪ ‬בלתי‪ ‬תלויים‪ ‬לשתף‪ ‬זיכרון‪ .‬כל ‪ ‬‬
‫תהליך‪ ‬פותח‪ ‬את‪ ‬הזיכרון‪ ‬בנפרד‪) ‬הוא‪ ‬יווצר‪ ‬רק‪ ‬בפעם‪ ‬הראשונה(‪ ‬וכל‪ ‬אחד‪ ‬כותב‪ ‬לשם‪ ‬בזמנו‪ .‬‬
‫‪ ­ shm_unlink‬כל‪ ‬תהליך‪ ‬יכול‪ ‬לסגור‪ ‬בצורה‪ ‬בלתי‪ ‬תלויה‪ ,‬ברגע‪ ‬שהאחרון‪ ‬סוגר‪ ‬ה­‪ shm‬נעלם‪ ‬לגמרי‪ .‬‬
‫‪ ‬‬
‫‪ copy_on_write optimization‬‬
‫כשיוצרים‪ ‬תהליך‪ ‬חדש‪ ‬כל‪ ‬הזיכרון‪ ‬של‪ ‬תהליך‪ ‬האבא‪ ‬משוכפל‪ ‬לכאורה‪ ‬לתהליך‪ ‬הבן‪ ,‬את‪ ‬כל ‪ ‬הנתונים‪ ‬שיש‪ ‬לאבא‪ ‬‬
‫צריך‪ ‬להעתיק‪ .‬לכן‪ ‬היינו‪ ‬מצפים‪ ‬ש­‪ fork‬יהיה‪ ‬מאוד ‪ ‬יקר‪ ‬ויימשך‪ ‬זמן‪ ‬רב‪ .‬בפועל‪ ‬מערכת‪ ‬ההפעלה‪ ‬מעתיקה‪ ‬רק‪ ‬את‪ ‬‬
‫ה­‪ descriptors‬של‪ ‬הזיכרון‪ ,‬ומכבה‪ ‬את‪ ‬הרשאות‪ ‬הכתיבה ‪ ‬לזיכרון‪ ‬הזה‪ .‬נניח‪ ‬שהאבא ‪ ‬מנסה‪ ‬לכתוב‪ ‬לזיכרון‪ ‬שלו‪ ,‬‬
‫מתרחש‪ .page fault ‬מערכת‪ ‬ההפעלה‪ ‬בוחנת‪ ‬את‪ ‬ה­‪ page fault‬ומסיקה‪ ‬שהאיזור‪ ‬אכן‪ ‬צריך‪ ‬להיות‪ ‬נגיש‪ ‬לכתיבה‪ ‬‬
‫)למשל‪ ‬במחסנית(‪ ,‬אך‪ ‬מסיבה‪ ‬כלשהי‪ ‬ה­‪ page‬לא‪ ‬נגיש‪ ‬לקריאה‪ ,‬היא‪ ‬בודקת‪ ‬כמה‪ ‬תהליכים‪ ‬משתפים‪ ‬את‪ ‬הדף‪ ‬‬
‫הזה‪ ‬ומגלה‪ ‬שיש‪ ‬יותר‪ ‬מתהליך‪ ‬אחד‪ ‬כזה‪ ,‬מה‪ ‬שלא‪ ‬צריך ‪ ‬לקרות‪ ‬לדף‪ ‬מחסנית‪ .‬מכאן‪ ‬יכולה ‪ ‬מערכת‪ ‬ההפעלה‪ ‬‬
‫להסיק‪ ‬שזה‪ ‬מנגון‪ ‬ה­‪ copy_on_write‬ולכן‪ ‬עליה‪ ‬להעתיק‪ ‬את‪ ‬הזיכרון‪ ‬לבן‪ ‬ולהוריד‪ ‬את‪ ‬החסימה‪ .‬‬
‫ברוב‪ ‬המקרים‪ ‬אין‪ ‬בכלל‪ ‬העתקה‪ ,‬שכן‪ ‬לעיתים‪ ‬קרובות‪ ‬אחרי‪ fork ‬יש‪ execv ‬ולכן‪ ‬אין‪ ‬צורך‪ ‬להעתיק‪ ‬את‪ ‬המידע‪ ‬של‪ ‬‬
‫האב‪ ,‬והרשאות‪ ‬הגישה‪ ‬יוחזרו‪ ‬לו‪ ‬מבלי‪ ‬לגרור‪ ‬העתקה‪ ‬של‪ ‬הזיכרון‪ .‬‬
‫‪ ‬‬
‫‪ ­ Multitasking‬שיטה‪ ‬המאפשרת‪ ‬למערכת‪ ‬ההפעלה‪ ‬להריץ‪ ‬מספר ‪ ‬תהליכים‪ ‬על‪ ‬אותו‪ ‬המעבד ‪ ‬באמצעות‪ ‬חלוקת‪ ‬‬
‫הזמן‪ .‬‬
‫‪ ­ Multiprogramming‬תוכנית‪ ‬מאוד‪ ‬גדולה‪ ‬שמכילה‪ ‬הרבה‪ ‬תוכניות‪ ‬ב"ת‪ ‬בתוכה‪ Multiprocessing 0) ‬תהליך‪ ‬‬
‫אחד‪ ‬שאנו‪ ‬מנסים‪ ‬לחלק‪ ‬לבלוקים‪ ‬שצריכים‪ ‬לבצע‪ ‬משימות‪ ‬שונות(‪ .‬‬
‫חוטים‪ ‬ברמת‪ ‬המשתמש‪ ‬‬
‫עד‪ ‬עכשיו‪ ‬דיברנו‪ ‬על‪ ‬חוטים‪ ‬שמערכת‪ ‬ההפעלה‪ ‬מכירה‪ ,‬ב­‪ Linux‬כל‪ ‬חוט‪ ‬הוא‪ ‬בעצם‪ ‬תהליך‪ ‬שמשתף‪ ‬את‪ ‬כל‪ ‬‬
‫המידע‪ ‬עם‪ ‬תהליך‪ ‬אחר‪) ‬אך‪ ‬מבחינת‪ ‬מערכת‪ ‬ההפעלה‪ ‬הוא‪ ‬עדיין‪ ‬תהליך(‪ .‬‬
‫בחלק‪ ‬גדול‪ ‬מהמקרים‪ ‬המשתמש‪ ‬שכותב ‪ ‬את‪ ‬הקוד‪ ‬שלו‪ ‬רואה‪ ‬שהוא‪ ‬יכול‪ ‬להריץ‪ ‬חלק‪ ‬מהקוד‪ ‬כחוט‪ ‬נפרד‪ ,‬אבל‪ ‬‬
‫עלות‪ ‬יצירת‪ ‬החוט‪ ‬לא‪ ‬שווה‪ ‬את‪ ‬המאמץ‪ .‬עם‪ ‬זאת‪ ,‬נניח‪ ‬שעלות‪ ‬יצירת‪ ‬החוטים‪ ‬הייתה ‪ ‬אפסית‪ ‬וכן‪ ‬לא‪ ‬היה‪ ‬צורך‪ ‬‬
‫בסנכרון‪ ‬אז‪ ‬ניתן‪ ‬ליצור‪ ‬חוטים‪ ‬ברמת‪ ‬המשתמש‪ .‬‬
‫‪28 ‬‬
‫מה‪ ‬זה‪ ‬בעצם‪ ‬חוט‪ ‬ברמת‪ ‬המשתמש?‪ ‬המשתמש‪ ‬בעזרת‪ ‬ספריה‪ ‬או‪ ‬קוד‪ ‬שהוא‪ ‬כותב‪ ‬בעצמו‪ ‬מדמה‪ ‬בתוך‪ ‬התוכנית‪ ‬‬
‫שלו‪ ‬חוטים‪ ‬שונים‪ ‬באמצעות‪ ‬מעבר‪ ‬בין‪ ‬קטעים‪ ‬שונים‪ ‬בקוד‪ ‬ושינוי‪ ‬הנתונים‪ ‬המתאימים‪ ‬ברגיסטרים‪ ‬וב­‪ structs‬‬
‫)למשל‪ ‬באמצעות‪ .(setjmp, longjmp ‬מבחינת‪ ‬מערכת‪ ‬ההפעלה‪ ‬יש‪ ‬רק‪ ‬תהליך‪ ‬אחד‪ ,‬שרץ‪ ‬על‪ ‬אותו‪ timeslice ‬‬
‫אבל ‪ ‬ה­‪ user‬משתמש‪ ‬ב­‪ timeslice‬הזה‪ ‬ומחלק‪ ‬אותו‪ ‬בין‪ ‬מספר‪ ‬מטרות‪) ‬למשל‪ ‬המשתמש ‪ ‬רוצה‪ ‬במהלך‪ ‬ביצוע‪ ‬‬
‫התוכנית‪ ‬לקרוא‪ ‬איזה‪ ,database ‬אבל‪ ‬התהליך‪ ‬יכול‪ ‬להיתקע‪ ‬או‪ ‬לקחת‪ ‬זמן‪ ‬רב‪ ‬אז‪ ‬הוא‪ ‬רוצה‪ ‬במהלך‪ ‬זמן‪ ‬זה‪ ‬‬
‫להמשיך‪ ‬בביצוע‪ ‬החישוב(‪ .‬‬
‫בד"כ‪ ‬נבחר‪ ‬להשתמש‪ ‬בחוטים‪ ‬ברמת‪ ‬המשתמש‪ ‬כאשר‪ ‬נרצה‪ ‬שליטה‪ ‬על‪ ‬ה­‪ scheduling‬בין‪ ‬חלקי‪ ‬התוכניות‪ ‬‬
‫השונים‪ ,‬או‪ ‬במקרים‪ ‬בהם‪ ‬לא‪ ‬נרצה‪ ‬לשלם‪ ‬את‪ ‬העלות‪ ‬של‪ ‬שימוש‪ ‬ב­‪ scheduler‬בעת‪ ‬החלפת‪ ‬ההקשר‪ ‬בין‪ ‬החוטים‪ ‬‬
‫שלנו‪ .‬‬
‫‪ ‬‬
‫‪ ‬‬
‫‪ ­ OmpSs‬תומך‪ ‬גם‪ ‬בחוטים‪ ‬ברמת‪ ‬המשתמש‪ ,‬מכריזים‪ ‬על‪ ‬הפונקציה‪ ‬כ­‪ ,task‬מגדירים‪ ‬כתובות‪ ‬שהולכים‪ ‬לקרוא‪ ‬‬
‫ולכתוב‪) ‬ב­‪ ,(inout‬כך‪ ‬ה­‪ omp‬יודעת‪ ‬לקבוע‪ ‬מה‪ ‬יוכל‪ ‬לרוץ‪ ‬במקביל‪ ,‬ומגדירים‪ ‬את‪ ‬הפונקציה‪ ‬עצמה‪ .‬‬
‫נשים‪ ‬לב‪ ‬בדוגמא‪ ‬שבגלל‪ ‬ההגדרה‪ ‬של‪ ‬פונקציית‪ sort ‬כל‪ ‬קריאה‪ ‬ל­‪ sort‬היא‪ task ‬נפרד‪ ,‬כאשר‪ ‬שני‪ ‬ה­‪ sort‬ב"ת ‪ ‬‬
‫לפי‪ ‬ה­‪ inout‬שלהם‪ .‬‬
‫הספריה‪ ‬הזאת‪ ‬חדשה‪ ‬יחסית‪ ‬ויש ‪ ‬לה ‪ ‬כל‪ ‬מיני‪ ‬מגבלות‪ ,‬או‪ ‬שהכתובות‪ ‬צריכות‪ ‬להיות‪ ‬ממש‪ ‬זהות‪) ‬ממש‪ ‬אותם ‪ ‬‬
‫חלקים‪ ‬של‪ ‬מערך‪ ‬ואז‪ ‬לא‪ ‬ניתן‪ ‬להריץ‪ ‬במקביל(‪ ‬או‪ ‬כתובות‪ ‬זרות‪ ‬לגמרי‪ ,‬לא‪ ‬ניתן‪ ‬לתת‪ ‬חיתוך‪ ‬של‪ ‬כתובות‪ ­ ‬במקרה‪ ‬‬
‫זה‪ ‬הוא‪ ‬ינסה‪ ‬להריץ‪ ‬אותם‪ ‬במקביל‪ ‬אפילו‪ ‬שהדבר‪ ‬אסור‪ .‬‬
‫‪ ‬‬
‫‪ ­ setjmp‬פונקציה‪ ‬ששומרת‪ ‬את‪ ‬הערכים‪ ‬של‪ ‬הרגיסטרים‪ ‬במיקום‪ ‬מסוים‪ .‬‬
‫‪ ­ longjmp‬פונקציה‪ ‬שמקבלת‪ ‬כתובת‪ ‬בזיכרון‪ ‬וטוענת‪ ‬משם‪ ‬את‪ ‬הריגסטרים‪ .‬‬
‫‪ ‬‬
‫‪ ‬‬
‫‪ ‬‬
‫‪ ‬‬
‫‪ ‬‬
‫‪ ‬‬
‫‪29 ‬‬
‫הרצאה‪) 4 ‬מצגת‪(4 ‬‬
‫‪ ‬‬
‫סנכרון‬
‫הקדמה‪ ‬‬
‫יש‪ ‬חשיבות‪ ‬גדולה‪ ‬בסנכרון‪ ‬בין‪ ‬גישות‪ ‬שונות‪ ‬לזיכרון‪ .‬‬
‫נניח‪ ‬ששני‪ ‬אנשים‪ ‬ניגשים‪ ‬ל­‪ server‬ורוצים‪ ‬למשוך‪ ‬משם‪ ‬כסף‪ ­ ‬‬
‫אם‪ ‬אין‪ ‬סנכרון‪ ‬אנחנו‪ ‬יכולים‪ ‬לגרום‪ ‬למצב‪ ‬בעייתי‪ ‬בו‪ ‬שתי‪ ‬משיכות‪ ‬‬
‫שמתבצעות‪ ‬ביחד‪ ‬גורמות‪ ‬לעדכון‪ ‬יחיד‪ ‬של‪ ‬אחת‪ ‬מהן‪ ,‬מה‪ ‬שיוצר‪ ‬‬
‫בעיה‪ ‬ברורה‪ .‬‬
‫‪ ‬‬
‫ננסה‪ ‬להמחיש‪ ‬את‪ ‬הבעיה‪ ‬באמצעות‪ ‬בעיית‪ ‬החלב‪ ,‬נניח‪ ‬שבן‪ ‬אדם‪ ‬א'‪ ‬רואה‪ ‬שאין ‪ ‬חלב‪ ‬בבית‪ ‬והולך‪ ‬לקנות‪ ‬חלב‪ ,‬בן‪ ‬‬
‫אדם‪ ‬ב'‪ ‬רואה‪ ‬שאין‪ ‬חלב‪ ‬כמה‪ ‬דקות‪ ‬אחריו‪ ‬והולך‪ ‬לקנות‪ ‬חלב‪ ‬גם‪ ‬הוא‪ ,‬אחרי‪ ‬ששניהם‪ ‬יהיו‪ ‬במכולת‪ ‬נסיים‪ ‬עם‪ ‬שני‪ ‬‬
‫חלב‪ ): ‬‬
‫פתרונות‪ :‬‬
‫‪ .1‬הפתרון‪ ‬הפשוט‪ ‬ביותר‪ ‬בעולם‪ ‬האמיתי‪ ‬הוא‪ ‬שאדם‪ ‬א'‪ ‬ישאיר‪ ‬פתק‪ ‬כאשר‪ ‬הוא‪ ‬רואה‪ ‬שאין‪ ‬חלב‪ ‬וככה‪ ‬אדם‪ ‬ב'‪ ‬‬
‫יידע‪ ‬שכבר‪ ‬הלכו‪ ‬לקנות‪ ‬חלב‪ ‬ולא‪ ‬יילך‪ ‬גם‪ ‬הוא‪ .‬‬
‫○ פיתרון‪ ‬זה‪ ‬לא‪ ‬עובד‪ ‬במקרה‪ ‬של ‪ ‬תהליכים‪ ­ ‬אם‪ ‬הם‪ ‬מגיעים‪ ‬ביחד‪ ‬הם‪ ‬יחליטו‪ ‬לשים ‪ ‬פתק‪ ‬לפני‪ ‬‬
‫שהם‪ ‬יראו‪ ‬את‪ ‬הפתק‪ ‬של‪ ‬האחר‪ ,‬וילכו‪ ‬שניהם‪ .‬‬
‫‪ .2‬הפתרון‪ ‬השני‪ ‬הוא‪ ‬להשאיר‪ ‬פתק‪ ‬לפני‪ ‬שבודקים‪ ‬אם‪ ‬אין‪ ‬חלב‪ .‬‬
‫○ הבעיה‪ : ‬ייתכן‪ ‬שאף‪ ‬אחד‪ ‬לא‪ ‬יקנה‪ ‬חלב‪ ‬אם‪ ‬שניהם‪ ‬ישימו‪ ‬פתקים‪ ‬ויראו‪ ‬שהשני‪ ‬גם‪ ‬שם‪ ‬פתק‪ .‬‬
‫‪ .3‬הפתרון‪ ‬השלישי‪ ‬הוא‪ ‬פתרון‪ ‬שלכאורה‪ ‬עובד‪ ,‬אדם‪ ‬א'‪ ‬ישאיר‪ ‬פתק‪ ‬ואז‪ ‬יבדוק‪ ‬כמו‪ ‬בפתרון‪ 2 ‬האם‪ ‬אדם‪ ‬ב'‪ ‬‬
‫שם‪ ‬פתק‪ .‬ואילו‪ ‬אדם‪ ‬ב'‪ ‬שם‪ ‬פתק‪ ‬הוא‪ ‬יחכה‪ ‬עד‪ ‬שלא‪ ‬יהיה‪ ‬פתק‪ ‬של‪ ‬אדם‪ ‬ב'‪ ,‬בסופו‪ ‬של‪ ‬דבר‪ ‬אם‪ ‬אדם‪ ‬א'‪ ‬‬
‫לא‪ ‬יצא‪ ‬לקנות‪ ‬אז‪ ‬אדם‪ ‬ב'‪ ‬יעשה‪ ‬זאת‪ .‬‬
‫○ גם‪ ‬פתרון‪ ‬זה‪ ‬לא‪ ‬עובד‪ : ‬נבחין‪ ‬כי‪ ‬אין‪ ‬סנכרון‪ ‬בין‪ ‬ה­‪ Cache‬של‪ ‬מעבדים‪ ‬שונים‪ ,‬וכך‪ ‬ניתן‪ ‬להגיע‪ ‬‬
‫למצב‪ ‬שערך‪ ‬מסוים‪ ‬בזיכרון‪ ‬כבר‪ ‬השתנה ‪ ‬אבל‪ ‬מעבד‪ ‬אחר‪ ‬חושב‪ ‬שהוא‪ ‬עוד‪ ‬לא‪ ‬השתנה‪ .‬נניח‪ ‬‬
‫ששני‪ ‬מעבדים‪ ‬קראו‪ ‬ערך‪ ‬מסוים‪ ‬בזיכרון‪ ,‬שניהם‪ ‬הוסיפו‪ ‬את‪ ‬הערך‪ ‬ל­‪ .Cache‬כעת‪ ‬מעבד‪ ‬אחד ‪ ‬‬
‫מעדכן‪ ‬אותו‪ .‬במעבד‪ ‬השני‪ ‬עדיין‪ ‬יש‪ ‬את‪ ‬הערך‪ ‬הישן‪ ‬ב­‪ ,Cache‬ולכן‪ ‬המעבד‪ ‬לא‪ ‬ייגש‪ ‬לערך‪ ‬‬
‫המעודכן‪ ,‬ולא‪ ‬מוגדר‪ ‬מתי‪ ‬הערך‪ ‬החדש‪ ‬יגיע!‪ ‬הדבר‪ ‬הורס‪ ‬את‪ ‬פתרון‪ 3 ‬כי‪ ‬גם‪ ‬כשנעדכן‪ ‬את‪ ‬‬
‫הפתקים‪ ‬שלנו‪ ,‬לא‪ ‬ברור‪ ‬מתי‪ ‬העדכון‪ ‬יגיע‪ ‬לתהליך‪ ‬האחר‪ .‬‬
‫ניתן‪ ‬לפתור‪ ‬בעיה‪ ‬זאת‪ ‬באמצעות‪ ‬כל‪ ‬מיני‪ ‬דרכים‪ ,‬למשל‪ ­ volatile : ‬ערכים‪ ‬שניתן‪ ‬לגשת‪ ‬אליהם‪ ‬ממקום‪ ‬אחר‪ ‬ולכן‪ ‬‬
‫לא‪ ‬שמים‪ ‬אותם‪ ‬ב­‪ Cache‬אלא‪ ‬ניגשים‪ ‬אליהם‪ ‬ישירות‪ .‬‬
‫‪ ‬‬
‫כיצד‪ ‬נפתור‪ ‬את‪ ‬הבעיה?‪ ‬‬
‫)‪ : Mutual exclusion (mutex‬הגרעין‪ ‬של‪ ‬הבעיה‪ ‬הוא‪ ‬שיש‪ ‬כאן‪ ‬חוטים‪ ‬שמשנים‪ ‬ערכים‪ ‬מסוימים ‪ ‬באופן‪ ‬לא‪ ‬‬
‫סנכרוני‪ .‬לכן‪ ‬אנו‪ ‬רוצים‪ ‬להגדיר‪ ‬משימות‪ ‬מסוימות‪ ‬שפועלות‪ ‬באופן‪ ‬סדרתי‪ .‬‬
‫‪ ‬‬
‫קטע‪ ‬קריטי‪ ­ ‬באטומיות‪ ‬של‪ ‬קטע‪ ‬קריטי‪ ‬הכוונה‪ ‬היא‪ ‬שאף‪ ‬חוט ‪ ‬אחר‪ ‬לא‪ ‬יוכל‪ ‬לשנות‪ ‬את‪ ‬הערכים ‪ ‬שמופיעים‪ ‬בקטע‪ ‬‬
‫הקריטי‪ ‬ולהפריע‪ ‬לחישוב‪ ‬זה‪ ,‬מבחינת‪ ‬כל‪ ‬חוט‪ ‬אחר‪ ‬קטע‪ ‬הקוד‪ ‬הקריטי‪ ‬הוא‪ ‬כמו‪ ‬פקודה‪ ‬אטומית‪ ‬אחת‪ ‬ואין‪ ‬ערכי‪ ‬‬
‫ביניים‪ .‬אז‪ ‬בעצם‪ ‬אנחנו‪ ‬רוצים‪ ‬לפתח‪ ‬שיטה‪ ‬לשמור‪ ‬על‪ ‬קטע‪ ‬קריטי‪ .‬‬
‫משל‪ ‬למה‪ ‬הדבר‪ ‬דומה? ‪ ‬יש‪ ‬לנו ‪ ‬מחשב‪ ‬בחדר‪ ‬עם‪ ‬מנעול‪ ,‬בכניסה‪ ‬לחדר‪ ‬יש‪ ‬מפתח‪ ,‬כאשר‪ ‬אדם‪ ‬נכנס‪ ‬לחדר‪ ‬הוא‪ ‬‬
‫נועל‪ ‬את‪ ‬עצמו‪ ‬בפנים‪ ,‬מריץ‪ ‬את‪ ‬הקוד‪ ‬מבלי‪ ‬שאף‪ ‬אדם‪ ‬אחר‪ ‬יפריע‪ ‬לו‪ .‬‬
‫‪30 ‬‬
‫נרצה‪ ‬להגדיר‪ ‬משאבים‪ ,‬שרק‪ ‬חוט‪ ‬אחד‪ ‬יוכל‪ ‬לטפל‪ ‬בהם‪ .‬כל‪ ‬רכיב‪ ‬חומרה‪ ‬או‪ ‬כל‪ ‬חלק‪ ‬קוד‪ ‬יכול‪ ‬להיות‪ ‬משאב‪ .‬‬
‫לדוגמא‪ ,‬מדפסת‪ ­ ‬רק‪ ‬תוכנית‪ ‬אחת‪ ‬יכולה‪ ‬להדפיס‪ ‬בשלב‪ ‬מסוים‪ ,‬פונקציה‪ ­ ‬ניתן‪ ‬להגדיר ‪ ‬פונקציה‪ ‬שצריך‪ ‬שרק‪ ‬חוט‪ ‬‬
‫אחד‪ ‬ישתמש‪ ‬בה‪ ‬בפרק‪ ‬זמן‪ ‬מסוים‪ .‬‬
‫‪ Deadlock‬‬
‫‪ ‬‬
‫‪ ‬‬
‫‪ ­ R1,2‬משאבים‪ ,‬למשל‪ ‬קבצים‪ .‬‬
‫‪ ­ P1,2‬תהליכים‪) ‬חוטים(‪ .‬‬
‫‪ ‬‬
‫‪ ‬‬
‫‪ ‬‬
‫‪ ‬‬
‫‪ ‬‬
‫נניח‪ P2 ‬פתח‪ ‬את‪ R1 ‬ורוצה‪ ‬לפתוח‪ ‬את‪ ,R2 ‬ואילו‪ P1 ‬פתח‪ ‬את ‪ R2 ‬ורוצה‪ ‬לפתוח‪ ‬את‪ .R1 ‬אך‪ ‬הם‪ ‬לא‪ ‬יתקדמו‪ ‬כי‪ ‬‬
‫כל‪ ‬אחד‪ ‬רוצה‪ ‬לפתוח‪ ‬את‪ ‬שני‪ ‬הקבצים‪ ‬ויש‪ ‬לו‪ ‬רק‪ ‬אחד‪ ‬מהם‪ ,‬בעצם‪ ‬יש‪ ‬לנו ‪ ‬כאן‪ ‬תלות‪ ‬מעגלית‪ ­ ‬דבר‪ ‬זה‪ ‬נקרא‪ ‬‬
‫‪ .deadlock‬‬
‫‪ ‬‬
‫‪ ­ Livelock‬תכונה‪ ‬של‪ ‬תכנות‪ ,‬המערכת‪ ‬לא‪ ‬במצב‪ ‬דפוק‪ ‬אבל‪ ‬הקוד‪ ‬שלנו‪ ‬כן‪ .‬‬
‫דוגמא‪ ‬קלאסית‪" :‬אחריך"‪ ­ ‬הדלת‪ ‬פתוחה‪ ,‬אבל‪ ‬כל‪ ‬תהליך‪ ‬נותן‪ ‬קדימות‪ ‬לשני‪ ,‬ולכן‪ ‬בפועל‪ ‬אף‪ ‬אחד‪ ‬לא‪ ‬נכנס‪ .‬‬
‫‪ ‬‬
‫אם‪ ‬במקום‪ ‬פשוט‪ ‬לנסות‪ ‬לנעול‪ ‬את‪ ‬הקובץ‪ ‬ולחכות‪ ‬עד‪ ‬שייפתח‪ ‬כמו‪ ‬בדוגמא‪ ‬הקודמת‪ ‬היינו‪ ‬מנסים‪ ‬לנעול‪ ‬קובץ‪ ‬אחד‪ ‬‬
‫עד‪ ‬להצלחה‪ ,‬מנסים‪ ‬לנעול‪ ‬את‪ ‬השני‪ ‬ואם‪ ‬הוא‪ ‬תפוס‪ ‬אז‪ ‬משחררים‪ ‬אותו‪ ‬ואת‪ ‬הראשון‪ ‬ומתחילים‪ ‬מהתחלה‪ .‬‬
‫בדוגמא‪ ‬הקודמת‪ ‬היינו‪ ‬יכולים ‪ ‬להגיע‪ ‬למצב‪ ‬שכל‪ ‬אחד‪ ‬מהם‪ ‬תופס‪ ‬רק‪ ‬קובץ‪ ‬אחד‪ ‬ומשחרר‪ ,‬נבחין‪ ‬כי‪ ‬זהו‪ ‬מצב ‪ ‬שניתן‪ ‬‬
‫לבלות‪ ‬בו‪ ‬זמן‪ ‬ניכר‪ ‬אך‪ ‬ניתן‪ ‬לצאת‪ ‬ממנו‪ ‬אם‪ ‬תהליך‪ ‬אחד‪ ‬מספיק‪ ‬זריז‪ .‬‬
‫שיטות‪ ‬תכנות‪ ‬‬
‫● ‪ ­ Lock free‬לא‪ ‬משתמשים‪ ‬במנעולים‪ ­ ‬לא‪ ‬מגיעים‪ ‬למצב‪ ‬שמחכים‪ ‬לכך‪ ‬שמשאב‪ ‬מסוים‪ ‬ייפתח‪ ,‬אלא‪ ‬‬
‫כותבים‪ ‬קוד‪ ‬שמנסה‪ ‬לעבוד‪ ‬מבלי‪ ‬לחכות‪ .‬זה‪ ‬אמנם‪ ‬חוסך‪ ‬לנו‪ ‬את‪ ‬הצורך‪ ‬בעבודה‪ ‬עם‪ ‬המנעולים‪ ‬אבל‪ ‬‬
‫בשיטת‪ ‬תכנות‪ ‬זו‪ ‬ייתכן‪ ‬שנבצע‪ ‬את‪ ‬אותו ‪ ‬קטע ‪ ‬קוד‪ ‬מספר‪ ‬פעמים‪ ,‬ובכך‪ ‬נבצע‪ ‬עבודה‪ ‬מיותרת‪) .‬נניח‪ ‬נעדכן‪ ‬‬
‫איזשהו‪ ‬ערך‪ ‬ברשימה‪ ‬ואז‪ ‬נעדכן‪ ‬אותו‪ ‬שוב‪ ‬לאותו‪ ‬הערך(‪ .‬‬
‫● ‪ ­ Wait Free‬קוד‪ ‬שבניגוד‪ ‬ל­‪ Lock Free‬גם‪ ‬לא‪ ‬מבצע‪ ‬עבודה‪ ‬מיותרת‪ ,‬מצליח‪ ‬לבצע‪ ‬את‪ ‬עבודתו‪ ‬תמיד‪ .‬‬
‫)ב­‪ Lock Free‬יכול‪ ‬להיות‪ ‬מצב‪ ‬שלא‪ ‬נחכה‪ ‬ונגלה‪ ‬בדיעבד‪ ‬שעשינו‪ ‬עבודה‪ ‬מיותרת‪ ,‬תחום‪ ‬שנמצא‪ ‬‬
‫בחיתוליו(‪ .‬בעצם‪ Wait Free ‬מכיל‪ ‬את‪ .Lock Free ‬‬
‫הוגנות‪ ‬‬
‫מנעול‪ ‬יוגדר‪ ‬כהוגן‪ ‬אם‪ ‬אין‪ ‬בו‪ ‬הרעבה‪ ‬נצחית‪ – ‬כלומר‪ ‬אין‪ ‬מצב‪ ‬שתהליך‪) ‬חוט(‪ ‬מחכה‪ ‬לנצח‪ ‬למנעול‪ ‬בזמן‪ ‬‬
‫שתהליכים‪) ‬חוטים(‪ ‬אחרים‪ ‬נכנסים‪ ‬לקטע‪ ‬הקריטי‪ ‬ובעצם‪" ‬תופסים"‪ ‬את‪ ‬המנעול‪ ‬לעצמם‪ .‬במנעול‪ ‬לא‪ ‬הוגן‪ ‬‬
‫נוכל‪ ‬לקבל‪ ‬מצב‪ ‬שבו‪ ‬אחד‪ ‬מהתהליכים‪ ‬מורעב‪ ‬ע"י‪ ‬שאר‪ ‬התהליכים‪ ‬שמבקשים‪ ‬לנעול‪ ‬את‪ ‬המנעול‪) ‬תהליכים‪ ‬‬
‫ישנים‪ ‬ותהליכים‪ ‬חדשים(‪ .‬ניתן‪ ‬לחזק‪ ‬את‪ ‬ההוגנות‪ ‬עוד‪ ‬יותר‪ ‬ע"י‪ ‬הדרישה‪ ‬שתהליכים‪) ‬החוטים(‪ ‬ייכנסו‪ ‬לקטע‪ ‬‬
‫הקריטי‪ ‬לפי‪ ‬סדר‪ ‬ההגעה‪ ‬שלהם‪ ,‬כלומר‪ .FIFO ‬‬
‫‪31 ‬‬
‫חיזוק‪ ‬נוסף‪ ‬של‪ ‬ההוגנות‪ ‬הוא‪ ‬קביעת‪ ‬זמן‪ ‬סף‪ ‬מסויים‪ ,‬שמרגע‪ ‬שהתהליך‪ ‬מבקש‪ ‬לגשת‪ ‬למנעול‪ ‬לא‪ ‬יוכל‪ ‬להיות‪ ‬‬
‫מצב‪ ‬שהוא‪ ‬יחכה‪ ‬יותר‪ ‬מזמן‪ ‬הסף‪ ‬שהוגדר‪ ‬כדי‪ ‬לקבל‪ ‬את‪ ‬הגישה‪ ‬למנעול‪) .‬זה‪ ‬סוג‪ ‬של‪ ‬הבטחה‪" ­ ‬לא‪ ‬תחכה‪ ‬‬
‫יותר‪ ‬מככה‪ ‬וככה‪ ‬זמן"‪ ,‬או‪ ‬לפחות‪ ‬נבטיח‪ ‬שלא‪ ‬נרעיב‪ ‬אותו‪ ‬לחלוטין(‪ .‬‬
‫‪ ‬‬
‫מנעולים‪ ‬‬
‫מי‪ ‬שמגיע‪ ‬כשמשאב‪ ‬חופשי‪ ‬משתמש‪ ‬בו‪ ‬ונועל‪ .‬‬
‫אם‪ ‬המשאב‪ ‬נעול‪ ‬צריך‪ ‬להחליט‪ ‬האם‪ ‬מחכים‪ ‬כ­‪ ,busy­wait‬או ‪ ‬שמוותרים‪ ‬על‪ ‬המעבד‪ ‬ונותנים‪ ‬למישהו‪ ‬אחר‪ ‬את‪ ‬‬
‫המעבד‪) ‬שיכול‪ ‬להיות‪ ‬שהוא‪ ‬האחד‪ ‬שנעל‪ ‬וכך‪ ‬הויתור‪ ‬ישחק‪ ‬לטובתנו(‪ .‬‬
‫נבחין‪ ‬כי‪ ‬לא‪ ‬כל‪ ‬כך‪ ‬קל‪ ‬לנעול‪ ‬משאב‪ ­ ‬נניח‪ ‬שהיינו‪ ‬נועלים‪ ‬באמצעות‪ ‬דגל‪ ,‬אנו‪ ‬חוזרים‪ ‬לבעיה ‪ ‬המקורית‪ ‬של‪ ‬קניית‪ ‬‬
‫החלב‪ ­ ‬איך‪ ‬דואגים‪ ‬שלא‪ ‬שני‪ ‬אנשים‪ ‬יסמנו‪ ‬את‪ ‬המשאב‪ ? ‬כלומר‪ 2 ‬משאבים‪ ‬ינעלו‪ ‬את‪ ‬המנעול‪ ‬בו‪ ‬זמנית?‪ ‬‬
‫כיצד‪ ‬מנעול‪ ‬פותר‪ ‬את‪ ‬הבעיה‪ ‬בעצם?‪ ‬‬
‫לפני‪ ‬ביצוע‪ ‬ה­‪ critical_section‬נועלים‪ ‬את‪ ‬המנעול‪ ‬ומבצעים‪ ‬את‪ ‬החישוב‪ ,‬כאשר‪ ‬בסופו‪ ‬משחררים‪ ‬אותו‪ .‬‬
‫אם‪ ‬יגיע‪ ‬חוט‪ ‬חדש‪ ‬והוא‪ ‬ינסה‪ ‬לנעול‪ ‬את‪ ‬המנעול‪ ‬הוא‪ ‬לא‪ ‬יוכל‪ ‬להתקדם‪ ‬ויעבור‪ ‬לתור‪ ‬המתנה‪ ,‬מתישהו‪ ‬החוט‪ ‬המקורי‪ ‬‬
‫יסיים‪ ‬את‪ ‬ה­‪ critical­section‬ויפתח‪ ‬את‪ ‬המנעול‪ ,‬ברגע‪ ‬שהוא‪ ‬פתח‪ ‬את‪ ‬המנעול‪ ‬החוט‪ ‬המקורי‪ ‬יעבור‪ ‬מתור‪ ‬‬
‫המתנה‪ ‬לתור‪ .ready ‬יכול‪ ‬להיות‪ ‬שהוא‪ ‬יתחיל‪ ‬לרוץ‪ ‬מיד‪) ,‬אם‪ ‬לא‪ ‬אז‪ ‬אין‪ ‬בעיה(‪ ‬ינעל‪ ‬את‪ ‬המנעול‪ ‬מצידו‪ ‬ויתחיל ‪ ‬‬
‫בביצוע‪ ‬החישוב‪ .‬‬
‫נחזור‪ ‬לבעיית‪ ‬הבנק‪ ­ ‬נשים‪ ‬לב ‪ ‬שהערך‪ ‬שנחזיר‪ ‬של‪ ‬היתרה‪ ‬בחשבון‪ ‬מוחזר‪ ‬אחרי‪ ‬פתיחת‪ ‬המנעול‪ ‬ולכן‪ ‬תחזיר‪ ‬את‪ ‬‬
‫הערך‪ ‬הלא‪ ‬מעודכן‪ .‬כדי‪ ‬לפתור‪ ‬זאת‪ ‬ה­‪ critical_section‬צריך‪ ‬להכיל‪ ‬גם‪ ‬את‪ ‬ה­‪ ,return‬מה‪ ‬שכמובן‪ ‬לא‪ ‬ניתן‪ ‬‬
‫לעשות‪ ‬מתוך‪ ‬הפונקציה‪ ‬ולכן‪ ‬ה­‪ critical­section‬צריך‪ ‬להכיל‪ ‬את‪ ‬כל‪ ‬הפונקציה‪ .‬‬
‫‪ ‬‬
‫ניתן‪ ‬לראות‪ ‬כי‪ ‬גם‪ ‬לצורך‪ ‬מימוש‪ ‬המנעול ‪ ‬יש‪ ‬צורך‪ ‬בנעילה‪ ‬של‪ ‬המערכת‪ ‬כדי‪ ‬להתמודד‪ ‬עם‪ ‬ה­‬
‫‪ .critical_section‬ישנן‪ ‬שתי‪ ‬גישות‪ ‬לביצוע‪ ‬הנעילה‪ ‬הזו‪ :‬ביצוע‪ ‬באמצעות‪ ‬תוכנה‪ ‬או‪ ‬ביצוע‪ ‬באמצעות‪ ‬‬
‫חומרה‪ .‬‬
‫‪ ‬‬
‫‪ FINE­GRAINED SYNCHRONIZATION‬‬
‫‪ ‬‬
‫ביצוע‪ ‬באמצעות‪ ‬תוכנה‪ ‬‬
‫החלפות‪ ‬הקשר‪ : ‬החלפת‪ ‬הקשר‪ ‬מתבצעת‪ ‬במקרים‪ ‬הבאים‪ : ‬‬
‫­‪ ‬קוד‪ ‬חוסם‪ ‬שקורא‪ ‬ל­‪ ,wait‬קריאה‪ ‬מפורשת‪ ‬להחלפת‪ ‬הקשר‪ .‬‬
‫‪ ­ ‬אירוע‪ ‬חיצוני‪ ­ ‬בד"כ‪ ‬פסיקת‪ ‬שעון‪ ‬או‪ ‬פסיקת‪ ‬חומרה‪ ‬אחרת‪ .‬‬
‫אנחנו‪ ‬בעצם‪ ‬רוצים‪ ‬לעצור ‪ ‬את‪ ‬החלפת‪ ‬ההקשר‪ ,‬הדבר‪ ‬ימנע‪ ‬את‪ ‬הבעיות ‪ ‬ב­‪ ,critical_section‬כך‪ ‬נדע‪ ‬בוודאות ‪ ‬‬
‫שבמערכת‪ ‬הנוכחית‪ ‬לא‪ ‬יהיו‪ ‬החלפות‪ ‬הקשר‪ ‬שלא‪ ‬בשליטתנו‪ .‬‬
‫אנחנו‪ ‬לא‪ ‬נראה‪ ‬דוגמאות‪ ‬לכיצד‪ ‬לבצע‪ ‬נעילות‪ ‬באמצעות‪ ‬תוכנה!‪ ‬‬
‫ביצוע‪ ‬באמצעות‪ ‬חומרה‪ ‬‬
‫כל‪ ‬חומרה‪ ‬מאפשרת‪ ‬תמיכה‪ ‬בפקודת‪ ‬מכונה‪ ‬כזו‪ ‬או‪ ‬אחרת‪ ‬המאפשרת‪ ‬לנו‪ ‬לבצע‪ ‬סט‪ ‬פעולות‪ ‬באופן‪ ‬אטומי‪ ‬אשר‪ ‬‬
‫באמצעותו‪ ‬נוכל‪ ‬לממש‪ ‬מנעול‪ .‬‬
‫‪ ‬‬
‫‪ ­ xchg‬קריאה‪ ‬לפקודת‪ ‬מכונה‪ ‬שכותבת‪ ‬ערך‪ ‬מכתובת‪ ‬מסוימת‪ ‬ומחזירה ‪ ‬את‪ ‬הערך‪ ‬שיישב‪ ‬שם‪ ,‬פקודה‪ ‬זו‪ ‬קוראת‪ ‬‬
‫גם‪ ‬ל­‪ lock‬לפני‪ ‬ביצוע‪ ‬הכתיבה‪ ,‬ובכך‪ ‬מבטיחה‪ ‬לנו‪ ‬את‪ ‬סדר‪ ‬ביצוע‪ ‬הפעולות‪ ,‬מה‪ ‬ש­‪ xchg‬נותנת‪ ‬לנו ‪ ‬זו‪ ‬בעצם‪ ‬‬
‫‪32 ‬‬
‫האפשרות‪ ‬לבצע‪ ‬את‪ ‬פקודת‪ ‬ההחלפה‪ ‬באופן‪ ‬אטומי‪ ,‬כך ‪ ‬שבוודאות‪ ‬לא‪ ‬תהיה‪ ‬החלפת‪ ‬הקשר‪ ‬בין‪ ‬הכתיבה‪ ‬להחזרת‪ ‬‬
‫הערך‪ .‬‬
‫כשאנו‪ ‬מריצים‪ ‬פקודות‪ ‬הן‪ ‬לא‪ ‬בהכרח‪ ‬רצות‪ ‬בסדר‪ ‬שבו‪ ‬הן‪ ‬נכתבו‪ ,‬הן‪ ‬רצות‪ ‬במקביל‪ ‬ומתבצעות‪ ‬בסדר‪ ‬שונה‪ .‬אנו‪ ‬לא‪ ‬‬
‫רוצים‪ ‬שאחרי‪ xchg ‬יקרה‪ ‬מצב‪ ‬שבגלל‪ ‬אופטימזיציה‪ ‬של‪ ‬המעבד‪ ‬פקודה ‪ ‬שאמורה‪ ‬לרוץ‪ ‬עם‪ ‬מנעול‪ ‬נעול‪ ‬תרוץ‪ ‬עם‪ ‬‬
‫מנעול‪ ‬פתוח‪ ,‬לכן‪ ‬יש‪ ‬צורך‪ ‬בקריאה‪ ‬ל­‪ .lock‬‬
‫ב­‪ acquire‬אנו‪ ‬בעצם‪ ‬מנסים‪ ‬לכתוב‪ ‬למנעול‪ ‬ערך‪ 1 ‬ומחכים‪ ‬שנקבל‪ ‬ערך‪ ‬חזרה‪ ‬שונה‪ ‬מאפס‪ .‬‬
‫● נניח‪ ‬הגענו‪ ‬למנעול‪ ‬פתוח‪ ,‬אנו‪ ‬מנסים‪ ‬לכתוב‪ ­ ‬כותבים‪ 1 ‬לשדה‪ ,lock ‬אם‪ ‬המנעול‪ ‬היה‪ ‬פתוח‪ ‬הערך‪ ‬‬
‫שיוחזר‪ ‬לנו‪ ‬הוא‪ 0 ‬ולכן‪ ‬נוכל‪ ‬לסיים‪ ‬את‪ ‬הלולאה‪ ,‬אנחנו‪ ‬יודעים‪ ‬שעכשיו‪ ‬הערך‪ ‬הוא‪ 1 ‬ושבעבר‪ ‬היה‪ ‬שם‪ ‬ערך‪ ‬‬
‫‪ 0‬ומכאן‪ ‬כי‪ ‬נעלנו‪ ‬כנדרש‪ .‬‬
‫● נניח‪ ‬הגענו‪ ‬למנעול‪ ‬נעול‪ ,‬אנו‪ ‬מנסים‪ ‬לכתוב‪ ­ ‬כתובים‪ 1 ‬לשדה‪ ,lock ‬אך‪ ‬יוחזר ‪ 1 ‬שכן‪ ‬המנעול‪ ‬כבר‪ ‬היה‪ ‬‬
‫נעול‪ ,‬ונתקע‪ ‬בלולאה‪ ‬עד‪ ‬שמישהו‪ ‬אחר‪ ‬לא‪ ‬ישחרר‪ ‬את‪ ‬המנעול‪ .‬‬
‫‪ ­ release‬מיד ‪ ‬כותבים‪ ‬ערך‪ ,0 ‬אנחנו‪ ‬יודעים‪ ‬בוודאות‪ ‬שהיה‪ ‬שם‪ ‬כבר‪ ,1 ‬אם‪ ‬אני‪ ‬נעלתי‪ ‬רק‪ ‬אני‪ ‬יכול‪ ‬לפתוח‪ ,‬‬
‫ומתירים‪ ‬את‪ ‬פעולת‪ ‬הפסיקות‪ .‬‬
‫‪ ‬‬
‫למה‪ ‬גם‪ ‬כשכותבים‪ ‬עושים‪ ‬את‪ ‬זה‪ ‬עם‪ ?xchg ‬למה‪ ‬אנחנו‪ ‬לא‪ ‬רוצים‪ ‬שפקודות‪ ‬שרצות‪ ‬אחרי‪ ‬המנעול‪ ‬ירוצו‪ ‬לפניו?‪ ‬‬
‫מה‪ ‬קורה‪ ‬בשאר‪ ‬המעבדים?‪ ‬בטוח‪ ‬שבמעבד‪ ‬שלנו‪ ‬רק‪ ‬אותו‪ ‬תהליך‪ ‬רץ‪ ,‬אבל‪ ‬בשאר ‪ ‬המעבדים‪ ‬יכול‪ ‬להגיע‪ ‬תהליך‪ ‬או‪ ‬‬
‫חוט ‪ ‬שרוצה‪ ‬לנעול‪ ‬מנעול‪ ,‬מנסה‪ ‬לנעול‪ ‬אותו‪ ‬במעבד‪ ‬נוסף‪ ,‬ומחכים‪ ‬בלולאה‪ .‬כלומר‪ ‬עד‪ ‬שלא‪ ‬נשחרר‪ ‬את‪ ‬הנעילה‪ ‬‬
‫מעבדים‪ ‬אחרים‪ ‬לא ‪ ‬מתקדמים‪ .‬אם‪ ‬פתאום‪ ‬נאפשר‪ ‬לפקודות‪ ‬שבאות‪ ‬אחרי‪ ‬פתיחת‪ ‬מנעול‪ ‬לבוא‪ ‬לפני‪ ‬פתיחת‪ ‬מנעול‪ ‬‬
‫אז‪ ‬אמנם‪ ‬נשפר‪ ‬את‪ ‬הפעולה‪ ‬במעבד‪ ‬שלנו‪ ‬אבל‪ ‬נתקע‪ ‬עוד‪ ‬יותר‪ ‬את‪ ‬כל‪ ‬המעבדים‪ ‬האחרים‪ .‬‬
‫הדבר‪ ‬ההרסני‪ ‬הוא‪ ‬ש­‪ enable_Interrupts‬יתבצע‪ ‬לפני‪ ,xchg ‬כלומר‪ ‬כשהמנעול‪ ‬עוד‪ ‬נעול‪ ,‬ותתבצע‪ ‬החלפת‪ ‬‬
‫הקשר‪ .‬זה‪ ‬דבר‪ ‬בעייתי‪ ‬בפני‪ ‬עצמו‪ ,‬אבל‪ ‬כל‪ ‬עוד‪ ‬נחזור‪ ‬רק‪ ‬נעכב‪ ‬את‪ ‬המעבד‪ .‬מה‪ ‬יקרה‪ ‬אם‪ ‬נעבור‪ ‬בהחלפה‪ ‬לקוד ‪ ‬‬
‫שרוצה‪ ‬לנעול‪ ‬את‪ ‬המנעול?‪ ‬הוא‪ ‬ייתקע‪ ‬בלולאה‪ ‬אינסופית‪ ‬שלא‪ ‬ניתן‪ ‬לצאת‪ ‬ממנה‪ ‬כי‪ ‬כבר‪ ‬אין‪ ‬החלפות!‪ ‬‬
‫מה‪ ‬קורה‪ ‬אם‪ ‬תהליך‪ ‬מגלה‪ ‬באמצע‪ ‬עבודה‪ ‬עם‪ ‬משאב‪ ‬שהוא‪ ‬צריך‪ ‬לחכות‪ ‬למשהו?‪ ‬הגיוני‪ ‬לעשות‪ ‬החלפת‪ ‬הקשר‪ ‬‬
‫ולהשאיר‪ ‬את‪ ‬המנעול‪ ‬נעול‪ ,‬אבל‪ ‬אז‪ ‬אנחנו‪ ‬עלולים ‪ ‬להגיע‪ ‬לתהליך‪ ‬אחר‪ ‬שרוצה‪ ‬לנעול‪ ‬את‪ ‬המנעול‪ ‬והוא‪ ‬גם‪ ‬ייתקע‪ .‬‬
‫לכן‪ ‬נרצה‪ ‬לאפשר‪ ‬החלפת‪ ‬הקשר‪ ‬רק‪ ‬אחרי‪ ‬פרק‪ ‬זמן‪ ‬מסוים‪ .‬‬
‫נוסיף‪ ‬תיקון‪ ‬ל­‪ acquire‬כך‪ ‬שהוא‪ ‬פותח‪ ‬ונועל‪ ‬את‪ ‬הפסיקות‪ .‬זה‪ ‬מאפשר‪ ‬לנו‪ ‬בעצם‪ ‬לקבל‪ ‬פסיקות‪ ‬שעון‪ ,‬אם‪ ‬‬
‫תהליך‪ ‬מגיע‪ ‬ומנסה‪ ‬לנעול‪ ‬את‪ ‬המנעול‪ ‬ונגמר‪ ‬לו‪ ‬ה­‪ time slice‬אז‪ ‬נאפשר‪ ‬החלפת‪ ‬הקשר‪ .‬נבחין‪ ‬כי ‪ ‬במערכת‪ ‬‬
‫לינוקס‪ ‬שבה‪ ‬פסיקת‪ ‬שעון‪ ‬רק‪ ‬מוסיפה‪ need_resched ‬הדבר‪ ‬לא‪ ‬יפתור‪ ‬את‪ ‬הבעיה‪ ‬ונצטרך‪ ‬לעשות‪ ‬שינויים‪ ‬נוספים‪ .‬‬
‫‪33 ‬‬
‫הערה‪ :‬אם‪ ‬אנחנו‪ ‬מגיעים‪ ‬לקוד‪ ‬עם‪ ‬פסיקות‪ ‬חסומות‪ ‬לא‪ ‬ניתן‪ ‬לפתוח‪ ‬אותו‪ ‬סתם‪ .‬פסיקה‪ ‬לא‪ ‬יכולה‪ ‬לתפוס‪ ‬מנעול‪ ‬‬
‫שהוא‪ ‬נעול‪ .‬‬
‫האם‪ ‬יש‪ ‬בעיה‪ ‬מקבילה‪ ‬ב­‪ ?user space‬‬
‫סיגנלים‪ ­ ‬נוצרים‪ ‬כתגובה‪ ‬לאיזה‪ ‬מאורע‪ ‬חיצוני‪ ,‬רוב‪ ‬הפסיקות‪ ‬בחומרה‪ ‬שולחות‪ ‬בסוף‪ ‬סיגנל‪ ‬ל­‪ .user space‬בכל‪ ‬‬
‫רגע‪ ‬נתון‪ ‬אנחנו‪ ‬עלולים‪ ‬להריץ‪ ‬תהליך‪ ‬אחר‪ ,‬חוט‪ ‬אחר‪ ,‬או‪ ‬פונקציה‪ ‬שמטפלת‪ ‬בסיגנל‪ .‬‬
‫אין‪ ‬לנו‪ ‬את‪ ‬היכולת‪ ‬להגיד‪ ‬אני‪ ‬רוצה‪ ‬להריץ‪ ‬קטע‪ ‬קריטי‪ ‬בלי‪ ‬שאף‪ ‬אחד‪ ‬יפריע‪ ‬לי‪ .‬מצד‪ ‬שני‪ ‬ניתן‪ ‬להשתמש‪ ‬בשירותים‪ ‬‬
‫של‪ ‬מערכת‪ ‬ההפעלה‪ ‬כדי‪ ‬לכתוב‪ ‬קוד‪ ‬מסונכרן‪ .‬‬
‫‪ ‬‬
‫מימוש‪ ‬מנעול‪ ‬באמצעות‪) atomic ops ‬במקום‪ xchg ‬שכבר‪ ‬ראינו(‪ ‬‬
‫● ‪ , CAS ­ cas(addr,val1,val2) ­ Compare and Swap‬מקבלת‪ ‬כתובת‪ ‬בזיכרון‪ ‬ושני‪ ‬ערכים‪ .‬היא‪ ‬‬
‫בודקת‪ ‬האם‪ ‬הערך‪ ‬בזיכרון‪ ‬שווה‪ ‬ל­‪ ,val1‬אם‪ ‬כן‪ ‬היא‪ ‬כותבת‪ val2 ‬ומחזירה‪ ,val1 ‬אחרת‪ ‬היא‪ ‬מחזירה‪ ‬את‪ ‬‬
‫הערך‪ ‬האמיתי‪ ‬מהכתובת‪ ‬ולא‪ ‬מבצעת‪ ‬עדכון‪ .‬‬
‫‪struct lock { ‬‬
‫‪uint lock; ‬‬
‫‪} ‬‬
‫‪ ‬‬
‫‪lock (lock* l) { ‬‬
‫‪disable_int(); ‬‬
‫‪while (cas(l­>lock,0,1) != 0) { ‬‬
‫‪enable_int(); ‬‬
‫‪disable_int(); ‬‬
‫}‬
‫‪ ‬‬
‫‪} ‬‬
‫‪ ‬‬
‫‪unlock (lock* l) { ‬‬
‫‪cas(l­>lock,1,0) //We know for sure that the value is 1 right now ‬‬
‫‪enable_int(); ‬‬
‫‪} ‬‬
‫‪ ‬‬
‫‪ LL & SC ­ Linked load & store condition‬‬
‫הן‪ ‬תמיד‪ ‬מגיעות‪ ‬ביחד‪ .‬‬
‫○ ‪ ­ LL‬מקבלת‪ ‬כתובת‪ ‬בזיכרון‪ ‬ומחזירה‪ ‬את‪ ‬הערך‪ ‬של‪ ‬הכתובת‪ .‬‬
‫○ ‪ ­ SC‬מקבלת‪ ‬כתובת‪ ‬בזיכרון‪ ‬וערך‪ ‬שרוצים‪ ‬לכתוב‪ ‬לשם‪ ,‬היא‪ ‬בודקת‪ ‬האם‪ ‬הייתה‪ ‬כתיבה‪ ‬לאותה‪ ‬‬
‫כתובת‪ ‬מאז‪ ‬שהפעלתי‪ LL ‬בפעם‪ ‬האחרונה‪ ,‬אם‪ ‬הייתה‪ ‬כתיבה‪ SC ‬לא‪ ‬כותבת‪ ‬שום‪ ‬דבר‪ ‬‬
‫ומחזירה‪ ‬כישלון‪ ,‬אם‪ ‬לא‪ ‬הייתה‪ ‬אז‪ SC ‬כותבת‪ ‬כנדרש‪ .‬‬
‫‪lock (lock* l){ ‬‬
‫‪disable_int(); ‬‬
‫‪while (LL(l­>lock) != 0 || SC(l­>lock,1) ==0) { //short circuit or, if we reached second ‬‬
‫‪condition than lock=0 and we’ll try writing, if we failed we will stay in the loop ‬‬
‫‪enable_int(); ‬‬
‫‪disable_int(); ‬‬
‫‪} ‬‬
‫‪34 ‬‬
‫‪} ‬‬
‫הערה‪ : ‬מעשית‪ ‬משתמשים‪ ‬בד"כ‪ ‬ב­‪ .CAS‬‬
‫‪COARSE-GRAINED VS FINE-GRAINED SYNCHRONIZATION‬‬
‫‪ ‬‬
‫ראשית‪ ‬נרצה‪ ‬להגדיר‪ ‬את‪ ‬המושגים‪ coarse­grained ‬ו­‪ ,fined­grained‬שני‪ ‬מושגים‪ ‬אלו‪ ‬מתייחסים‪ ‬לרמת‪ ‬‬
‫הגרעיניות‪ ‬של‪ ‬המידע‪ ‬אותו‪ ‬אנו‪ ‬נועלים‪ .‬‬
‫‪ Fine Grained Synchronization‬‬
‫בשיטת‪ ‬נעילה‪ ‬זו‪ ‬נרצה‪ ‬לפרק‪ ‬את‪ ‬המשאבים‪ ‬שלנו‪ ‬ליחידות‪ ‬גרעיניות‪ ‬קטנות‪ ‬ככל‪ ‬הניתן‪ ‬ולנעול‪ ‬כל‪ ‬אחת‪ ‬מהן‪ ‬בנפרד‪ ,‬‬
‫בצורה‪ ‬זו‪ ‬נוכל‪ ‬לשפר‪ ‬את‪ ‬המקביליות‪ ‬של‪ ‬הקוד‪ ‬ולאפשר‪ ‬לתהליכים‪ ‬רבים‪ ‬יחסית‪ ‬לגשת‪ ‬לאותו‪ ‬מבנה‪ ‬נתונים‪ ‬כל‪ ‬עוד‪ ‬‬
‫הם‪ ‬מעוניינים‪ ‬בגישה‪ ‬לאיזורים‪ ‬שונים‪ ‬במבנה‪ ,‬עם‪ ‬זאת‪ ‬נקבל‪ ‬תקורה‪ ‬גבוהה‪ ‬יחסית‪ ‬של‪ ‬מנעולים‪ .‬‬
‫דוגמא‪ ‬קלאסית‪ :‬נעילה‪ ‬של‪ ‬חוליות‪ ‬ברשימה‪ ‬או‪ ‬בעץ‪ ‬חיפוש‪ ‬בינרי‪ .‬‬
‫הערה‪ :‬כאשר‪ ‬אנו‪ ‬עובדים‪ ‬עם‪ fine grained ‬נרצה‪ ‬הרבה‪ ‬פעמים‪ ‬להשתמש‪ ‬בנעילה‪ ‬בשיטת‪ ,spinlock ‬זאת‪ ‬מכיוון‪ ‬‬
‫שבד"כ‪ ‬זמן‪ ‬הנעילה‪ ‬ב­‪ fine grained‬הינו‪ ‬קצר‪ ‬יחסית‪ .‬‬
‫דוגמא‪ ‬למנעול‪ ‬שנועל‪ ‬בשיטה‪ ‬זאת‪ ‬הוא‪ .spin lock ‬‬
‫‪ Coarse Grained Synchronization‬‬
‫בשיטת‪ ‬נעילה‪ ‬זו‪ ‬נרצה‪ ‬להסתכל‪ ‬על‪ ‬מבנה‪ ‬הנתונים‪ ‬כיחידת‪ ‬גרעיניות‪ ‬גדולה‪ ,‬כאשר‪ ‬כל‪ ‬תהליך‪ ‬צריך‪ ‬לנעול‪ ‬את‪ ‬כל‪ ‬‬
‫מבנה‪ ‬הנתונים‪ ‬על‪ ‬מנת‪ ‬לקבל‪ ‬אליו‪ ‬גישה‪ ,‬שיטה‪ ‬זו‪ ‬אמנם‪ ‬פוגעת‪ ‬במקביליות‪ ‬אך‪ ‬היא‪ ‬מפשטת‪ ‬את‪ ‬השימוש‪ ‬‬
‫במנעולים‪ .‬‬
‫דוגמא‪ ‬קלאסית‪ :‬שימוש‪ ‬במנעול‪ ‬אחד‪ ‬עבור‪ ‬רשימה‪ ‬מקושרת‪ .‬‬
‫הערה‪ :‬כאשר‪ ‬אנו‪ ‬עובדים‪ ‬עם‪ coarse grained ‬נרצה‪ ‬הרבה‪ ‬פעמים‪ ‬להשתמש‪ ‬בנעילה‪ ‬בשיטת‪ ,block ‬זאת‪ ‬מכיוון‪ ‬‬
‫שבד"כ‪ ‬זמן‪ ‬הנעילה‪ ‬ב­‪ coarse grained‬הינו‪ ‬ארוך‪ ‬יחסית‪ .‬‬
‫דוגמא‪ ‬למנעול‪ ‬שפועל‪ ‬בשיטה‪ ‬זאת‪ ‬הוא‪ .semaphore ‬‬
‫‪ ‬‬
‫*הערה‪ : ‬נציין‪ ‬כמובן‪ ‬כי‪ ‬ישנן‪ ‬דרגות‪ ‬שונות‪ ‬בין‪ ‬שתי‪ ‬השיטות‪ .‬‬
‫‪ ?Spin or block‬‬
‫מה‪ ‬קורה‪ ‬כשמעבד‪ ‬אחד‪ ‬נעל‪ ‬מנעול‪ ‬ומעבד‪ ‬אחר‪ ‬מריץ‪ ‬תהליך‪ ‬שמנסה‪ ‬לנעול‪ ‬את‪ ‬אותו‪ ‬מנעול‪ ? ‬‬
‫התהליך‪ ‬מבצע‪ .busy wait ‬האלטרנטיבה‪ ‬היא‪ ‬לעשות‪ ‬החלפת‪ ‬הקשר‪ ,‬לתת‪ ‬למישהו‪ ‬לרוץ‪ ‬במקומו‪ ‬בתקווה‪ ‬שאותו‪ ‬‬
‫תהליך‪ ‬שרוצה‪ ‬לנעול‪ ‬את‪ ‬המנעול‪ ‬יחזור‪ ‬בסוף‪ ‬לריצה‪ ‬ואז‪ ‬המנעול‪ ‬כבר‪ ‬ייפתח‪) .‬נזניח‪ ‬את‪ ‬המקרה‪ ‬שבו‪ ‬מי‪ ‬שמנסה‪ ‬‬
‫לנעול‪ ‬היא‪ ‬פסיקה‪ ‬ואין‪ ‬החלפת‪ ‬הקשר‪ ‬בפסיקה(‪ .‬‬
‫למה‪ ‬לא‪ ‬עושים‪ ‬החלפת‪ ‬הקשר‪ ‬כזו?‪ ‬השאלה‪ ‬היא‪ ‬מה‪ ‬גורם‪ ‬ליותר‪ ‬נזק‪ .‬‬
‫נניח‪ ‬שיש‪ ‬לנו‪ ‬קוד‪ ‬שמבצע‪ N ‬פעמים‪ ‬חישוב ‪ ‬ו­‪ N‬פעמים‪ ‬סנכרון‪ .‬מה‪ ‬יותר‪ ‬יקר‪ ‬לנו? ‪ ‬אם‪ ‬הסנכרון‪ ‬זו‪ ‬פעולה‪ ‬כל‪ ‬כך‪ ‬‬
‫ארוכה‪ ‬שהיא‪ ‬הרבה‪ ‬יותר‪ ‬ארוכה‪ ‬מהקוד‪ ‬שהוא‪ ‬רוצה‪ ‬להריץ‪ ‬אז‪ ‬לא‪ ‬כדאי‪ ‬לנו‪ ‬לעשות‪ ‬החלפת‪ ‬הקשר‪ .‬‬
‫נניח‪ ‬שיש‪ ‬שני‪ ‬חוטים‪ ­ ‬אחד‪ ‬מנסה‪ ‬להריץ‪ ‬את‪ ‬הקוד‪ ‬הקצר‪ ‬והשני ‪ ‬מחכה‪ ‬שהחוט‪ ‬הראשון‪ ‬יסיים‪ ‬את‪ ‬החישוב‪ ‬כדי‪ ‬שגם‪ ‬‬
‫הוא‪ ‬יוכל‪ ‬להתחיל‪ .‬אם‪ ‬זמן‪ ‬החישוב‪ ‬כל ‪ ‬כך‪ ‬קצר‪ ‬שהחלפת‪ ‬הקשר‪ ‬יותר‪ ‬יקרה‪ ‬אז‪ ‬אין‪ ‬טעם‪ ‬להחליף‪ ,‬אם‪ ‬החוט‪ ‬הראשון‪ ‬‬
‫מבצע‪ ‬איזשהו‪ ‬חישוב‪ ‬מאוד‪ ‬ארוך‪ ‬אז‪ ‬כדאי‪ ‬לבזבז‪ ‬את‪ ‬החלפת‪ ‬ההקשר‪ ‬ולא‪ ‬לחכות‪ ‬ב­‪ .busy_wait‬‬
‫נשים‪ ‬לב‪ ‬שב­‪ kernel‬קוד‪ ‬קריטי‪ ‬הוא‪ ‬בד"כ‪ ‬קצר‪ ‬מאוד‪ ,‬אסור‪ ‬לחסום‪ ‬פסיקות‪ ‬להרבה‪ ‬זמן‪ .‬מכיוון‪ ‬שהקטע‪ ‬תמיד‪ ‬קצר‪ ‬‬
‫אם‪ ‬נתחיל‪ ‬לעשות‪ ‬החלפות‪ ‬הקשר‪ ‬בגלל‪ ‬מנעול‪ ‬נעול‪ ‬אנחנו‪ ‬רק‪ ‬נאבד‪ ‬זמן‪ ,‬ולכן‪ ‬ב­‪ kernel‬כמעט‪ ‬תמיד‪ ‬משתמשים‪ ‬‬
‫ב­‪.busy wait‬ב­‪ user space‬המצב‪ ‬שונה‪ ,‬יכול‪ ‬להיות‪ ‬קטע‪ ‬קריטי‪ ‬מאוד‪ ‬ארוך‪ ,‬ויותר‪ ‬מכך‪ ‬אנחנו‪ ‬בכלל‪ ‬לא‪ ‬יודעים‪ ‬‬
‫מתי‪ ‬יוחלף‪ ‬התהליך‪ .‬‬
‫‪35 ‬‬
‫כלל‪ ‬אצבע‪ :‬ב­‪ user space‬לא‪ ‬משתמשים‪) ‬כמעט(‪ ‬ב­‪ ,busy wait‬אלא‪ ‬גורמים‪ ‬לתהליך‪ ‬לצאת‪ ‬להמתנה‪ ‬וב­‬
‫‪ kernel‬רוב‪ ‬השימוש‪ ‬הוא‪ ‬ב­‪ spin lock‬שכן‪ ‬גורם‪ ‬ל­‪ .busy wait‬‬
‫‪ ‬‬
‫‪) Semaphore‬איתות‪ ‬בדגלים(‪ ‬‬
‫‪ ‬‬
‫‪ ­ wait‬ניגשת‪ ‬למונה‪ ‬של‪ semaphore ‬ומקטינה‪ ‬אותו‪ ‬באחד‪ ,‬אם‪ ‬אחרי‪ ‬‬
‫ההמתנה‪ ‬הוא‪ ‬לא‪ ‬שלילי‪ ‬הכל‪ ‬ממשיך‪ ‬לרוץ‪ ‬הלאה‪ ,‬אם‪ ‬אחרי‪ ‬ההמתנה‪ ‬הוא‪ ‬‬
‫הפך‪ ‬להיות‪ ‬שלילי‪) ‬או‪ ‬שנשאר‪ ‬שלילי(‪ ,‬תהליך‪ ‬נחסם‪ ‬ועובר‪ ‬לתור‪ ‬המתנה‪ .‬‬
‫‪ ­ signal‬אם‪ ‬המונה‪ ‬חיובי‪ ‬אחרי‪ ‬ההמתנה‪ ‬לא‪ ‬עושה‪ ‬כלום‪ ‬ומסתיימת‪ ,‬אם‪ ‬הוא‪ ‬‬
‫לא‪ ‬חיובי‪ ‬אחרי‪ ‬ההגדלה‪) ‬קטן‪ ‬שווה‪ ‬לאפס(‪ ‬היא‪ ‬לוקחת ‪ ‬תהליך‪ ‬שנמצא‪ ‬בתור‪ ‬‬
‫המתנה‪ ‬ומחזירה‪ ‬אותו‪ ‬למצב‪ .ready ‬‬
‫הערה‪ : ‬ניתן‪ ‬להשתמש‪ ‬ב­‪ semaphore‬בתור‪ ‬מנעול‪ ‬ע"י‪ ‬כך‪ ‬שנאתחל‪ ‬אותו‪ ‬ל­‬
‫‪ ,1‬כדי‪ ‬לנעול‪ ‬נעשה‪ ,wait ‬כדי‪ ‬לפתוח‪ ‬נעשה‪ .signal ‬‬
‫‪ ‬‬
‫מה‪ ‬ההבדל‪ ‬המהותי?‪ ‬‬
‫● לא‪ ‬ניתן‪ ‬לפתוח‪ ‬מנעל‪ ‬פעמיים‪ ‬אבל‪ ‬ניתן‪ ‬לפתוח‪ signal ‬פעמיים‪ ,‬שני‪ ‬תהליכים‪ ‬יכולים‪ ‬אחרי‪ ‬זה‪ ‬לעשות‪ wait ‬‬
‫במקביל‪ .‬‬
‫● בעלות‪ :‬במנעול ‪ ‬רגיל‪ ‬ברגע‪ ‬שנעלתי‪ ‬אותו‪ ‬רק‪ ‬אני‪ ‬יכול‪ ‬לפתוח‪ ‬אותו‪ ,‬ואילו‪ ‬ב­‪ semaphore‬כל‪ ‬אחד‪ ‬יכול‪ ‬בכל‪ ‬‬
‫רגע‪ ‬נתון‪ ‬לעשת‪ wait ‬או‪ signal ‬מבלי‪ ‬שעשה‪ wait ‬בעבר‪ .‬זה‪ ‬אמנם‪ ‬הגיוני‪ ‬בחלק‪ ‬מהמקרים‪ ‬אבל‪ ‬צריך‪ ‬‬
‫להבין‪ ‬שאין‪ ‬מישהו‪ ‬שימנע‪ ‬מה­‪ semaphore‬לעבוד‪ ‬לא‪ ‬נכון‪ .‬לכן‪ ‬צריך‪ ‬להיות‪ ‬זהירים!‪ ‬‬
‫מה‪ ‬משמעות‪ ‬המונה?‪ ‬אם‪ ‬הוא‪ ‬חיובי‪ ‬זה‪ ‬בעצם‪ ‬כמה‪ ‬עוד‪ ‬יכולים‪ ‬לעבור‪ ,‬אם‪ ‬הוא‪ ‬שלילי‪ ‬זה‪ ‬כמה‪ ‬מחכים‪ ‬בתור‪ ‬‬
‫ואם‪ ‬הוא‪ ‬אפס‪ ‬אף‪ ‬אחד‪ ‬לא‪ ‬יכול‪ ‬לעבור‪ ‬ואף‪ ‬אחד‪ ‬עדיין‪ ‬לא‪ ‬ממתין‪ .‬‬
‫הערות‪ : ‬‬
‫­‪ ‬קוד‪ ‬שעושה‪ ‬סיגנלים‪ ‬מיותרים‪ ‬הוא‪ ‬מסוכן‪ .‬‬
‫­‪ ‬שימוש‪ ‬אחר‪ ‬ב­‪ semaphore‬הוא‪ ‬מונה‪ ‬סנכרוני‪ ­ ‬מונה‪ ‬שאפשר‪ ‬לשנות‪ ‬אותו‪ ‬מכמה‪ ‬חוטים‪ ‬מבלי‪ ‬לחשוש‪ ‬שייכתב‪ ‬‬
‫ערך‪ ‬זבל‪ .‬‬
‫בעיית‪ producer/consumer ‬‬
‫● יש‪ ‬חוטים‪ ‬שמוסיפים‪ items ‬מפס‪ ‬הייצור‪ ­ ‬מפיקים‪ .‬‬
‫● יש‪ ‬חוטים‪ ‬שמורידים‪ items ‬מפס‪ ‬הייצור‪ ­ ‬צרכנים‪ .‬‬
‫מן‪ ‬הסתם‪ ‬אסור‪ ‬לקחת‪ item ‬מפס‪ ‬ייצור‪ ‬ריק‪ ‬או‪ ‬לשים‪ item ‬על‪ ‬פס‪ ‬ייצור‪ ‬מלא‪ .‬‬
‫‪ ­ ‬אם‪ ‬ידוע‪ ‬מספר‪ ‬החפצים‪ ‬מממשים‪ ‬באמצעות‪ ‬מערך‪ ‬ציקלי‪ .‬‬
‫‪ ‬‬
‫‪ ‬‬
‫● ‪ ­ producer‬מוודא‪ ‬שיש‪ ‬מקום‪ ‬פנוי‪ ‬בפס‪ ‬הייצור‪ ,‬שם‪ ‬את‪ ‬ה­‪ item‬החדש‪ ‬במקום‪ ‬המתאים‪ ,‬מקדם‪ ‬בצורה‪ ‬‬
‫ציקלית‪ ‬ומגדיל‪ ‬את‪ ‬מספר‪ ‬החפצים‪ ‬‬
‫‪36 ‬‬
‫●‬
‫‪ ­ consumer‬הפעולה‪ ‬המקבילה‪ .‬‬
‫‪ ‬‬
‫נבחין‪ ‬כי‪ ‬יש ‪ ‬לנו‪ ‬שני‪ ‬חוטים‪ ‬שמנסים‪ ‬לשנות‪ ‬אותו ‪ ‬מונה‪ ‬ולא‪ ‬יודעים‪ ‬מה‪ ‬יהיה‪ ‬הערך‪ ‬שם‪ ,‬לכן‪ ‬נרצה‪ ‬לתקן‪ ‬את‪ ‬הבעיה‪ ‬‬
‫באמצעות‪ ‬שני‪ : semaphores ‬‬
‫‪ ­ free_space‬שומר‪ ‬כמה‪ ‬מקום‪ ‬פנוי‪ ‬יש‪ ‬לנו‪ .‬‬
‫‪ ­ avai­items‬שומר‪ ‬כמה‪ ‬איברים‪ ‬יש‪ ‬במערך‪ .‬‬
‫ה­‪ producer‬מבצע‪ wait ‬ל­‪ ,free_space‬אם‪ free_space>0 ‬מקטינים‪ ‬אותו‪ ‬וממשיכים‪ ,‬אחרת‪ ‬נחסמים‪ ‬‬
‫לבסוף‪ ‬עושים‪ signal ‬ל­‪ avai_items‬כדי‪ ‬לסמן‪ ‬שיש‪ ‬עוד‪ ‬איבר‪ ‬פנוי‪ .‬‬
‫אם‪ ‬יש ‪ ‬מספר‪ ‬חוטים‪ ‬שמריצים‪ ‬קוד‪ ‬של‪ ­ producers ‬נבחין‪ ‬כי‪ ‬הם‪ ‬משתפים‪ ‬את‪ ‬המצביעים‪ ,‬אם‪ ‬שלושה‪ ‬‬
‫‪ producers‬נקראים‪ ‬בבת‪ ‬אחת‪ ‬זה‪ ‬רעיונית‪ ‬בסדר‪ ‬אבל‪ ‬שלושתם‪ ‬עלולים‪ ‬להשתמש‪ ‬בדיוק‪ ‬באותו‪ ‬ה­‪ item‬וזה‪ ‬לא‪ ‬‬
‫תקין‪ .‬‬
‫‪ ‬‬
‫מנעול‪ ‬קריאות‪ ‬וכתיבות‪ Concurrent readers, exclusive writer (CREW) ­ ‬‬
‫יש‪ ‬מקרים‪ ‬רבים‪ ‬שבהם‪ ‬נרצה‪ ‬רק‪ ‬לקרוא‪ ‬או‪ ‬רק‪ ‬לכתוב‪) ‬מקובץ‪ ‬למשל(‪ ,‬יש‪ ‬מצבים‪ ‬בהם‪ ‬יש‪ ‬הרבה‪ ‬קוראים‪ ‬במערכת‪ .‬‬
‫למה‪ ‬זה‪ ‬משנה ‪ ‬לנו?‪ ‬אם‪ ‬כולם‪ ‬מנסים‪ ‬לכתוב‪ ,‬אין‪ ‬ברירה‪ ‬אלא‪ ‬לנעול‪ ‬את ‪ ‬המנעול‪ ,‬אבל‪ ‬אם‪ ‬יש‪ ‬הרבה ‪ ‬קוראים‪ ‬‬
‫במערכת‪ ‬אפשר‪ ‬לתת‪ ‬לכולם‪ ‬לקרוא‪ ‬ורק‪ ‬לסמן ‪ ‬שלא‪ ‬ניתן‪ ‬לכתוב‪ ‬ל­‪ database‬כרגע‪ ‬וכך‪ ‬נוכל‪ ‬לאפשר‪ ‬קריאות‪ ‬‬
‫במקביל‪ .‬‬
‫‪ ‬‬
‫‪ ‬‬
‫‪ ‬‬
‫‪37 ‬‬
‫חייבים‪ ‬קודם‪ ‬לפתוח‪ ‬את‪ ‬המנעול‪ ‬ואז‪ ‬לחסום‪ ‬תהליך‪ ,‬אבל‪ ‬אז‪ ‬מגיעים‪ ‬לבעיה‪ ‬של‪ /"problem of lost wakeup" ‬‬
‫ראשית‪ ,‬חשוב‪ ‬שנזכור‪ ‬כי ‪ ‬פונקציות‪ wait ‬ו­‪ signal‬אינן‪ ‬אטומיות‪ ,‬ולכן‪ ‬יכולה‪ ‬להתבצע‪ ‬החלפת‪ ‬הקשר‪ ‬בין‪ ‬שחרור‪ ‬של‪ ‬‬
‫מנעול‪ ‬לבין‪ ‬חסימה‪ ‬של‪ ‬תהליך‪ .‬‬
‫‪ ‬נניח‪ ‬כי‪ ‬המונה‪ ,0 = ‬מגיע‪ ‬תהליך‪ ‬מבצע‪ wait ‬ורואה‪ ‬שהוא‪ ‬צריך‪ ‬להיכנס‪ ‬לתור‪ ‬ההמתנה‪ ,‬לשם‪ ‬כך‪ ‬הוא‪ ‬פותח‪ ‬את‪ ‬‬
‫המנעול‪ ‬ולפני‪ ‬שהוא‪ ‬הספיק‪ ‬להיחסם‪ ‬מתבצעת‪ ‬החלפת‪ ‬הקשר‪ ‬ומגיע‪ ‬תהליך‪ ‬אחר‪ ­ ‬עושה‪ ,signal ‬מגדיל‪ ‬את‪ ‬‬
‫המונה‪ ‬באחד‪ ‬ומוציא‪ ‬תהליך‪ ‬מתור‪ ‬המתנה‪) ‬אותו‪ ‬תהליך‪ ‬שעוד‪ ‬לא‪ ‬הספקנו‪ ‬לחסום(‪ ‬ומבצע‪ ‬לו‪ ,wakeup ‬שהוא‪ ‬‬
‫ממילא‪ ‬כבר‪ ‬ער‪ ‬אז‪ ‬זה‪ ‬סתם‪ ‬מציק‪ ‬לו‪ .‬מתישהו‪ ‬מערכת‪ ‬ההפעלה‪ ‬תחזור‪ ‬לתהליך‪ ‬המקורי‪ ‬ואז‪ ‬הוא‪ ‬חוסם‪ ‬את‪ ‬עצמו‪ ,‬‬
‫אבל‪ ‬הוא‪ ‬כבר‪ ‬לא‪ ‬בתור‪ ‬המתנה‪ ‬ואף‪ ‬אחד‪ ‬לא‪ ‬יעיר‪ ‬אותו‪ ‬יותר‪ ): ‬‬
‫‪ ‬‬
‫אם‪ ‬נזכר‪ ‬בקוד‪ ‬שראינו‪ WAKEUP ‬תסמן‪ ‬שיש‪ ‬סיגנל‪ ‬שממתין‪ ‬לו‪ ‬ולכן‪ ‬כשנעשה‪ ‬לו‪ block ‬ה­‪ sched‬מיד‪ ‬יעיר‪ ‬אותו‪ ,‬‬
‫וכך‪ ‬פתרנו‪ ‬את‪ ‬הבעיה‪ ‬ב­‪ scheduler‬שלנו‪ .‬‬
‫אבל‪ ‬אם‪ ‬תהליך‪ ‬נכנס‪ ‬למצב‪ uninterruptable ‬המצב‪ ‬יותר‪ ‬בעייתי!‪ ‬‬
‫‪ ‬‬
‫חוק‪ ‬אמדל‪ ‬‬
‫עבור‪ n ‬חוטים‪ T_n ,‬הזמן‪ ‬שלוקח‪ ‬להריץ‪ ‬את‪ ‬אלגוריתם‪ ‬עם‪ n ‬חוטים ‪ ‬ו­‪ s‬הוא‪ ‬החלק‪ ‬מתוך‪ ‬האלגוריתם‪ ‬שמורץ‪ ‬‬
‫באופן‪ ‬סדרתי‪ ­ ‬רציף‪ ,‬אזי‪ ‬מתקיים‪ :‬‬
‫‪ ‬‬
‫‪,‬‬
‫‪ ­ Condition Variables‬משתנה‪ ‬תנאי‪ ‬‬
‫זהו‪ ‬מנגנון‪ ‬המאפשר‪ ‬הוספת‪ ‬לוגיקה‪ ‬לסנכרון‪ ,‬כשדיברנו‪ ‬על‪ semaphore ‬ראינו‪ ‬שזה‪ ‬מנגנון‪ ‬גנרי‪ ‬שננעל‪ ‬או‪ ‬נפתח‪ ‬‬
‫לפי‪ ‬פעולה‪ ‬שאנו‪ ‬עושים‪ ,‬אין‪ ‬שם‪ ‬שום‪ ‬לוגיקה‪ ‬של‪ ‬מצב‪ ‬המערכת‪ .‬‬
‫ב­‪ Condition Variables‬מנסים‪ ‬לעשות‪ ‬החלטות‪ ‬יותר‪ ‬חכמות‪ ,‬בניגוד‪ ‬למנגנונים‪ ‬שראינו‪ ‬עד‪ ‬עכשיו ‪ ‬שיש‪ ‬להם‪ ‬שתי‪ ‬‬
‫פונקציות‪ ,‬למנגנון‪ ‬זה‪ ‬יש‪ ‬שלוש‪ :‬‬
‫● ‪ ­ wait‬בניגוד‪ ‬ל­‪ semaphore‬שם‪ ‬בדקנו‪ ‬את ‪ ‬המונה‪ ‬ולכל‪ ‬היותר‪ ‬שינינו‪ ‬אותו‪ .‬כאן‪ ‬אם‪ ‬מבצעים‪ wait ‬‬
‫התהליך‪ ‬תמיד‪ ‬נחסם‪ .‬כשעובדים‪ ‬עם‪ condition variables ‬חייבים‪ ‬להעביר‪ ‬מנעול‪ ‬שאותו‪ ‬עלינו‪ ‬לנעול‪ ‬לפני‪ ‬‬
‫ביצוע‪ ‬ה­‪ ,wait‬כשמצבעים‪ wait ‬מוותרים‪ ‬על‪ ‬המנעול‪ ‬ונכנסים‪ ‬לתור‪ ‬המתנה‪ ‬של‪ ‬ה­‪ ,condition‬זו‪ ‬פעולה‪ ‬‬
‫אטומית!‪ ‬לא‪ ‬ניתן‪ ‬לראות‪ ‬תהליך‪ ‬שויתר‪ ‬על‪ ‬המנעול‪ ‬ועוד‪ ‬לא‪ ‬הספיק‪ ‬להיכנס‪ ‬לתור‪ ‬המתנה‪ ‬של‪ .condition ‬‬
‫● ‪ ­ signal‬בוחרת‪ ‬את‪ ‬אחד‪ ‬התהליכים‪ ‬בתור‪ ‬המתנה‪ ‬של‪ ,condition ‬מוציאה‪ ‬אותו‪ ‬משם‪ ‬ומעבירה‪ ‬אותו‪ ‬‬
‫לתור‪ ‬ההמתנה‪ ‬של‪ ‬המנעול‪ ‬אותו‪ ‬הוא‪ ‬איבד‪ ‬ברגע‪ ‬שהוא‪ ‬עשה‪ ,wait ‬לפני‪ ‬שהוא‪ ‬חוזר‪ ‬לריצה ‪ ‬הוא‪ ‬בהכרח‪ ‬‬
‫יחזיק‪ ‬את‪ ‬המנעול‪ .‬נהוג‪ ‬שמי‪ ‬שבמצע‪ signal ‬מחזיק‪ ‬כרגע‪ ‬במנעול‪ ‬אך‪ ‬זה‪ ‬לא‪ ‬הכרחי‪ ‬בניגוד‪ ‬ל­‪ .wait‬‬
‫● ‪ ­ broadcast‬דומה‪ ‬מאוד‪ ‬ל­‪ ,signal‬אך‪ ‬היא‪ ‬מעירה‪ ‬את‪ ‬כל‪ ‬התהליכים‪ ‬הממתינים‪ ‬ושולחת ‪ ‬את‪ ‬כולם‪ ‬‬
‫לנסות‪ ‬לנעול‪ ‬את‪ ‬המנעול‪ ‬שלהם‪) ‬יכול‪ ‬להיות‪ ‬שהם‪ ‬איבדו‪ ‬ב­‪ wait‬אותו‪ ‬מנעול‪ ,‬אך‪ ‬יכול‪ ‬להיות‪ ‬שלכל ‪ ‬אחד‪ ‬‬
‫יש‪ ‬מנעול‪ ‬שונה(‪ ‬‬
‫הבדל‪ ‬חשוב‪ ‬ביחס‪ ‬ל­‪ semaphore‬הוא ‪ ‬שאם‪ ‬אין‪ ‬אף‪ ‬אחד‪ ‬בתור‪ ‬המתנה‪ ‬ניתן‪ ‬להביא‪ ‬את‪ ‬המונה‪ ‬ל­‪ 30‬באמצעות‪ ‬‬
‫‪ ,signal‬ואז‪ 30 ‬תהליכים‪ ‬יוכלו‪ ‬לעבור‪ .‬זאת‪ ‬לעומת‪ ‬הפעולות‪ ‬אצלנו‪ ‬שהן‪ ‬חסרות‪ ‬זיכרון‪ .‬‬
‫‪ ‬‬
‫למה‪ ‬זה‪ ‬טוב?‪ ‬‬
‫מימוש‪ ‬תור‪ ­ ‬אפשר‪ ‬להכניס‪ ‬ולהוציא‪ ‬איברים‪ ,‬אך‪ ‬אסור‪ ‬להוציא‪ ‬איברים‪ ‬כשהתור‪ ‬ריק‪ .‬‬
‫מגדירים‪ ‬מנעול‪ ,‬ב­‪ enqueue‬מיד‪ ‬נועלים‪ ‬אותו‪ ‬ובסוף‪ ‬משחררים‪ ‬אותו‪ ‬כלומר‪ ‬שמים‪ ‬את‪ ‬העצם‪ ‬ונועלים‪ .‬‬
‫בהוצאה‪ ,‬נועלים‪ ‬מנעול‪ ‬בודקים‪ ‬אם‪ ‬התור‪ ‬ריק ‪ ‬ואם‪ ‬לא‪ ‬מאבדים‪ ‬את‪ ‬המנעול‪ ‬ומחכים‪ ,‬מתישהו‪ ‬נקבל‪ ‬את‪ ‬ה­‪ signal‬‬
‫ואת‪ ‬המנעול‪ ‬ואז‪ ‬נוכל‪ ‬להוציא‪ ‬עצם‪ .‬‬
‫‪38 ‬‬
‫‪ ‬‬
‫מה‪ ‬היה‪ ‬קורה‪ ‬אם‪ ‬היינו‪ ‬הופכים‪ ‬את‪ ‬את‪ signal ‬ו­‪ ?put item‬‬
‫כלום‪ ,‬עדיין‪ ‬המנעול‪ ‬בידנו‪ ‬ולכן‪ dequeue ‬לא‪ ‬יוכל‪ ‬להשפיע‪ ‬ולהוציא‪ ‬איבר‪ ,‬ונכונות‪ ‬הקוד‪ ‬לא‪ ‬תיפגע‪ .‬‬
‫למה‪ ‬צריך‪ while ‬ולא‪ ?if ‬‬
‫נניח‪ ‬יש‪ ‬שלושה‪ ‬חוטים‪) ‬למנעולים‪ ‬צריך‪ ‬חוטים(‪ ­ A,B ,‬עושים‪ dequeue ‬ו­‪ C‬עושה‪ .enqueue ‬‬
‫נניח‪ ‬מגיע‪ ,A ‬נועל‪ ‬ועובר‪ ‬ל­‪ ,wait‬מגיע‪ C ‬מכניס‪ ‬איבר‪ ‬לתור‪ ‬ויש‪ Context Switch ‬‬
‫כעת‪ ‬מגיע‪ ,B ‬רואה‪ ‬שהמנעול‪ ‬נעול‪ ‬ונכנס‪ ‬לתור‪ ‬המתנה‪ ‬‬
‫היחיד‪ ‬שיכול‪ ‬לרוץ‪ ‬כעת‪ ‬זה‪ ‬חוט‪ ,C ‬הוא‪ ‬שולח‪ signal ‬וחוט‪ A ‬עובר‪ ‬לתור‪ ‬המתנה‪ ‬של‪ ‬מנעול‪ ,‬פותח‪ ‬את‪ ‬המנעול‪ .‬‬
‫כעת‪ ‬יש‪ ‬שני‪ ‬תהליכים‪ ‬בתור‪ ‬המתנה‪ ,‬תהליך‪ A ‬ותהליך‪ B ‬או‪ ‬הראשון‪ ‬הוא‪ ,FIFO ‬אז‪ B ‬יוצא‪ ‬קודם‪ ,‬אחרת‪ ‬אקראי‪ .‬‬
‫כעת‪ B ‬נועל‪ ‬מנעול‪ ,‬מוציא‪ ‬איבר‪ ‬מהתור‪ ‬ומשחרר‪ ‬את‪ ‬המנעול‪ .‬‬
‫כעת‪ ‬תהליך‪ A ‬יכול‪ ‬לנעול‪ ‬את‪ ‬המנעול‪ ‬ולהוציא‪ ‬איבר‪ ‬מתור‪ ‬ריק!‪ ‬לכן‪ ‬נרצה‪ ‬לשים‪ ‬שם‪ !wait ‬‬
‫למה‪ ‬בעצם‪ ‬זה‪ ‬קרה?‪ A ‬התעורר‪ ‬כי‪ ‬התור‪ ‬כבר‪ ‬לא ‪ ‬היה‪ ‬ריק‪ ,‬אבל‪ ‬עד‪ ‬שהוא‪ ‬חזר‪ ‬לריצה‪ ‬יכול‪ ‬להיות‪ ‬שהתור‪ ‬כבר‪ ‬לא‪ ‬‬
‫ריק‪ ‬ולכן‪ ‬יש‪ ‬צורך‪ ‬בבדיקה‪ ‬חוזרת‪ .‬‬
‫‪ ‬‬
‫‪ producer/consumer‬עם‪ ‬משתני‪ ‬תנאי ‪ ‬‬
‫בדוגמא‪ ‬זו‪ ‬יהיה‪ ‬לנו‪ ‬מנעול‪ ‬בשם‪ bLock ‬ושני‪ ‬משתני‪ ‬תנאי‪ .notFull, notEmpty :‬‬
‫ה­‪ producer‬נועל‪ ‬את‪ ‬המנעול‪ ‬ובודק‪ ‬האם‪ ‬ה­‪ buffer‬מלא‪ ,‬אם‪ ‬כן‪ ‬הוא‪ ‬נכנס‪ ‬להמתנה‪ ,‬אחרת‪ ‬הוא‪ ‬מוסיף‪ ‬את‪ ‬ה­‬
‫‪ item‬ומודיע‪ .‬‬
‫ה­‪ consumer‬בודק‪ ‬אם‪ ‬יש‪ ‬מה‪ ‬לצרוך‪ ‬ואם‪ ‬אין‪ ‬הוא‪ ‬הולך‪ ‬לישון‪ ,‬אם‪ ‬כן‪ ‬הוא‪ ‬מודיע‪ ‬שיש‪ ‬מקום‪ ‬חדש‪ ‬שהתפנה‪ .‬‬
‫‪ ­ ‬נבחין‪ ‬כי‪ consumer, producer ‬לא‪ ‬רצים‪ ‬במקביל‪ ‬שכן‪ ‬יש‪ ‬מנעול‪ ,‬ולכן‪ ‬זה‪ ‬קוד‪ ‬פחות‪ ‬יעיל‪ .‬‬
‫‪ ­ ‬אם‪ ‬היינו‪ ‬מוסיפים‪ ‬לולאה‪ ‬מקיפה‪ ‬בתוך‪ ‬כל‪ ‬נעילה‪ ‬היינו‪ ‬מקבלים‪ ‬שמתמלא‪ ‬לגמרי‪ ‬ומתרוקן‪ ‬לגמרי‪ ‬לחילופין‪ .‬‬
‫‪ ‬‬
‫אז‪ ‬מה‪ ‬נותן‪ ‬לנו‪ ‬ה­‪ ?condition variables‬‬
‫לכאורה‪ ‬אין ‪ ‬בו‪ ‬צורך‪ ‬והוא‪ ‬מוסיף‪ ‬בעיות‪ ‬כפי‪ ‬שראינו‪ ‬עם‪ .A,B,C ‬אך‪ ‬בעולם‪ ‬המודרני‪ ‬משתמשים‪ ‬בו‪ ‬בצורה‪ ‬מאוד‪ ‬‬
‫חזקה‪ :‬בשפת ‪ Java ‬יש‪ ‬תמיכה‪ ‬מובנית‪ ‬בסנכרון‪ .‬ב­‪ Java‬הם‪ ‬הגיעו‪ ‬למסקנה‪ ‬שהכל‪ ‬צריך‪ ‬להיות‪ ‬מסונכרן‪ ,‬המון ‪ ‬‬
‫עבודה‪ ‬עם‪ ‬מנעולים‪ ‬ולכן‪ ‬השפה‪ ‬הייתה‪ ‬איטית‪ ‬מאוד‪ ,‬למרות‪ ‬שברוב‪ ‬המקרים‪ ‬אין‪ ‬צורך‪ ‬בסנכרון‪ ,‬למשל‪ ‬במבנה‪ ‬‬
‫‪ .Vector‬לכן‪ ‬הוסיפו‪ ‬מבני‪ ‬נתונים‪ ‬אחרים‪ ‬דומים‪ ‬מאוד‪ ‬ללא‪ ‬סנכרון‪ .‬בכל‪ ‬זאת‪ ‬מתכנתים‪ ‬משתמשים‪ ‬ב­‪ Vector‬‬
‫במקום‪ ‬ב­‪ ArrayList‬גם‪ ‬על‪ ‬תוכניות‪ ‬של‪ ‬תהליך‪ ‬אחד‪ .‬ניתן‪ ‬להגדיר‪ ‬גם ‪ Class ‬או‪ function ‬בתור‪ ‬מסונכרן‪ .‬כל‪ ‬‬
‫המנגנון‪ ‬ב­‪ Java‬מבוסס‪ ‬על‪ Condition Variables ‬כי‪ ‬ברוב‪ ‬המקרים‪ ‬זה‪ ‬יותר‪ ‬קל‪ ‬ומהיר‪ .‬‬
‫‪39 ‬‬
‫‪ ‬‬
‫הרצאה‪) 5 ‬מצגת‪ (5 ‬‬
‫‪ ‬‬
‫‪ Deadlocks‬‬
‫כשמשתמשים‪ ‬לא‪ ‬נכון‪ ‬בסנכרון‪ ‬עלולים‪ ‬להגיע‪ ‬למצב‪ ‬של‪ .Deadlock ‬‬
‫‪ Deadlock‬זה‪ ‬מצב‪ ‬שיש‪ ‬קבוצה‪ ‬של‪ ‬חוטים‪ ‬או‪ ‬תהליכים‪ ‬שאנחנו‪ ‬יודעים‪ ‬בוודאות‪ ‬שהם‪ ‬לא‪ ‬יצליחו‪ ‬להתקדם‪ .‬‬
‫ייתכן‪ ‬במערכת‪ ‬שיש‪ ‬זמנית‪ ‬מצב‪ ‬שבו‪ ‬קבוצה‪ ‬של‪ ‬תהליכים‪ ‬נתקעים‪ ‬לזמן‪ ‬רב‪ ,‬אבל‪ ‬כל‪ ‬עוד‪ ‬הם‪ ‬יוכלו‪ ‬להשתחרר‪ ‬‬
‫בהסתברות‪ ‬כלשהי‪ ‬זה‪ ‬לא‪ !Deadlock ‬‬
‫‪ Deadlock‬קורה‪ ‬כשכל‪ ‬התהליכים‪ ‬מחכים‪ ‬לאיזשהו‪ ‬אירוע‪ ‬שאחד‪ ‬התהליכים‪ ‬המחכים‪ ‬צריך‪ ‬ליזום‪) ‬למשל‪ ‬קבוצה‪ ‬‬
‫של‪ ‬תהליכים‪ ‬שכל‪ ‬אחד‪ ‬מהם‪ ‬מחכה‪ ‬לאיזשהו‪ ‬משאב‪ ‬שהוא‪ ‬בשימוש‪ ‬של‪ ‬תהליך‪ ‬אחר‪ ‬באותה‪ ‬קבוצה(‪ .‬‬
‫הגדרה‪ ‬מדויקת‪ :‬קבוצה‪ ‬של‪ ‬תהליכים‪ ‬שבה ‪ ‬כל‪ ‬תהליך‪ ‬בקבוצה‪ ‬מחכה‪ ‬לתהליך‪ ‬שמוחזק‪ ‬ע"י‪ ‬תהליך‪ ‬אחר‪ ‬באותה‪ ‬‬
‫קבוצה‪ .‬‬
‫‪ ‬‬
‫‪ The Dining Philosophers‬‬
‫בעיה‪ ‬הממחישה‪ ‬את‪ ‬מושג‪ ‬ה­‪ ,Deadlock‬לכל‪ ‬אחד‪ ‬מהם‪ ‬יש‪ ‬קערת‪ ‬אורז‪ ‬מולו‪ ‬ו­‬
‫‪ Chopstick‬מכל‪ ‬צד‪ .‬‬
‫כל‪ ‬אחד‪ ‬באופן‪ ‬ב"ת‪ ‬חושב‪ ,‬תופס‪ Chopstick ‬מצד‪ ‬ימין‪ ,‬ולאחר‪ ‬מכן‪ Chopstick ‬‬
‫מצד‪ ‬שמאל‪ ,‬אוכל‪ ,‬מניח‪ ‬אותם‪ ‬וחוזר‪ ‬חלילה‪ .‬‬
‫ניתן‪ ‬להגיע‪ ‬למצב‪ ‬של ‪ ,deadlock ‬כל‪ ‬אחד‪ ‬תפס‪ Chopstick ‬אחד‪ ,‬והם‪ ‬מנסים‪ ‬‬
‫לתפוס‪ ‬את‪ ‬השני‪ ‬אבל‪ ‬הוא‪ ‬כבר‪ ‬תפוס‪ .‬הם‪ ‬לעולם‪ ‬לא‪ ‬יצליחו‪ ‬להתקדם!‪ ‬‬
‫‪ ‬‬
‫‪ ‬‬
‫במצגת‪ ‬מוצג‪" ‬מימוש"‪ ‬כקוד‪ ‬עם‪ ‬סמפורים‪ ,‬כל‪ ‬שמוגדרים‪ 5 ‬סמפורים‪ ,‬סמפור‪ ‬לכל‪ . Chopstick ‬‬
‫‪ philosopher(i):‬‬
‫‪ while(1) do‬‬
‫‪ ­think for a while‬‬
‫)]‪ ­wait( chopstick[i‬‬
‫)]‪ ­wait( chopstick[(i+1) % 5‬‬
‫‪ ­eat‬‬
‫)]‪ ­signal( chopstick[(i+1) % 5‬‬
‫)]­‪ signal( chopstick[i‬‬
‫‪ ‬‬
‫‪ ‬‬
‫‪ :resource allocation graph‬‬
‫ננסה‪ ‬להציג‪ ‬דוגמא‪ ‬מורכבת‪ ‬יותר‪ ,‬ראשית‪ ‬נגדיר‪ ‬סימונים‪ :‬‬
‫עיגול‪ ‬כחול‪ ­ ‬תהליך‪ ,‬ריבוע‪ ­ ‬משאב‪ ‬עם‪ ‬מספר‪ ‬עותקים‪ .‬‬
‫קשת‪ ‬מעותק‪ ‬של‪ ‬משאב‪ ‬לתהליך‪ ­ ‬העותק‪ ‬בשימוש‪ ‬של‪ ‬תהליך‪ .‬‬
‫קשת‪ ‬מתהליך‪ ‬למשאב‪ ­ ‬התהליך‪ ‬מחכה‪ ‬למשאב‪ .‬‬
‫כאשר‪ ‬יש ‪ ‬עותק‪ ‬יחיד‪ ‬לכל‪ ‬משאב‪ ‬ניתן‪ ‬להחליף‪ ‬את‪ ‬הקשתות‪ ‬כך‪ ‬שילכו‪ ‬בין‪ ‬‬
‫התהליכים‪ ‬עצמם‪ .‬‬
‫ננסה‪ ‬להבחין‪ ‬בקשר‪ ‬בין‪ ‬מעגלים‪ ‬בגרפים‪ ‬ל­‪ .Deadlocks‬‬
‫*‪ ‬במצגת‪ ‬ישנו‪ ‬הסבר‪ ‬מפורט‪ ‬על‪ ‬הגדרת‪ ‬ה­‪ deadlock‬עפ"י‪ ‬סימון‪ ‬זה‪ .‬‬
‫‪40 ‬‬
‫מה‪ ‬צריך‪ ‬להתקיים‪ ‬בדיוק‪ ‬כדי‪ ‬שיהיה‪ ?Deadlock ‬‬
‫ארבעת‪ ‬התנאים‪ ‬הבאים‪ ‬צריכים‪ ‬להתקיים‪ ‬כולם‪ :‬‬
‫‪ ­ mutual exclusion .1‬גישה‪ ‬למשאב‪ ‬כלשהו‪ ‬היא‪ ‬מוגבלת‪ ‬למספר‪ ‬תהליכים‪ ‬חסום‪ .‬‬
‫‪ ­ hold & wait .2‬אני‪ ‬תופס‪ ‬משאב‪ ‬ונכנס‪ ‬להמתנה‪) ‬למשל‪ ‬כי‪ ‬אני‪ ‬רוצה‪ ‬לנעול‪ ‬משאב‪ ‬אחר(‪ .‬‬
‫‪ ­ Circular wait .3‬ניתן‪ ‬לסדר‪ ‬את‪ ‬התהליכים‪ ‬כך‪ ‬שהתלויות‪ ‬הן‪ ‬מעגליות‪) ‬תנאי‪ ‬הכרחי‪ ‬אך‪ ‬לא‪ ‬מספיק(‪ .‬‬
‫‪ ­ No resource preemption .4‬אין‪ ‬מצב‪ ‬שניתן‪ ‬לקחת‪ ‬בכוח‪ ‬משאב‪ ‬נעול‪ ,‬אחרת‪ ‬מישהו‪ ‬היה‪ ‬יכול‪ ‬לשבור‪ ‬‬
‫את‪ ‬המעגל‪ .‬‬
‫‪ ‬‬
‫‪ Dealing with Deadlocks‬‬
‫ב­‪ 99%‬מי‪ ‬שאחראי‪ ‬לטפל‪ ‬בכך‪ ‬זה‪ ‬אנחנו‪ ‬ולא‪ ‬מערכת‪ ‬ההפעלה!‪ ‬‬
‫התמודדות‪ ‬עם‪ Deadlock ‬מתחלקות‪ ‬לשתי‪ ‬גישות‪ :‬‬
‫‪ .1‬אנחנו‪ ‬נכתוב‪ ‬קוד‪ ‬כך‪ ‬שהמערכת‪ ‬לא‪ ‬תיכנס‪ ‬ל­‪ ,Deadlock‬פשוט‪ ‬כותבים‪ ‬קוד‪ ‬נכון‪ .‬‬
‫‪ .2‬אני‪ ‬כותב‪ ‬קוד‪ ‬שיכול‪ ‬להגיע‪ ‬ל­‪ deadlock‬אבל‪ ‬אני‪ ‬יודע ‪ ‬איך‪ ‬להיחלץ‪ ‬משם‪) ‬למשל‪ ‬מוסיף‪ ‬מנגנון‪ ‬שבודק‪ ‬אם‪ ‬‬
‫יש‪ ‬סיכוי‪ ‬להיכנס‪ ‬ל­‪ deadlock‬ואז‪ ‬תמנע‪ ‬מצב‪ ‬זה(‪ .‬‬
‫‪ ‬‬
‫איך‪ ‬כותבים‪ ‬קוד‪ ‬כמו‪ ‬שצריך?‪ ‬‬
‫מספיק‪ ‬שאחד‪ ‬מארבעת‪ ‬התנאים‪ ‬לא‪ ‬יתקיים‪ ,‬ננסה‪ ‬למנוע‪ ‬מאחד‪ ‬מהם‪ ‬להתקיים‪ :‬‬
‫‪ ­ Hold & Wait .1‬לפני‪ ‬שמתחילים‪ ‬לרוץ ‪ ‬מסתכלים‪ ‬על‪ ‬כל‪ ‬המשאבים‪ ‬ומבקשים‪ ‬את‪ ‬כולם‪ ,‬בסוף‪ ‬הריצה‪ ‬‬
‫משחררים‪ ‬את‪ ‬כולם‪ ,‬וכך‪ ‬אין‪ ‬מצב‪ ‬שנעלתי‪ ‬רק‪ ‬חלק‪ ‬מהמשאבים!‪ ‬‬
‫חסרונות‪ :‬מה‪ ‬אם‪ ‬לא‪ ‬צריך‪ ‬את‪ ‬כל‪ ‬המנעולים‪ ‬ביחד‪ ,‬אולי‪ ‬יש‪ ‬צורך‪ ‬בהם‪ ‬אחד‪ ‬אחרי‪ ‬השני‪ .‬חסרון‪ ‬נוסף‪ ‬הוא‪ ‬‬
‫שצריך‪ ‬לממש‪ ‬מנגנון‪ ‬נעילה‪ ‬של‪ ‬מספר‪ ‬מנעולים‪ ‬בו‪ ‬זמנית‪ .‬‬
‫שיפור‪ :‬אני‪ ‬נועל‪ ‬את‪ ‬המשאבים‪ ‬לפי‪ ‬הצורך‪ ,‬משחררים‪ ‬כל‪ ‬מה‪ ‬שהיו‪ ‬לנו‪ ‬עד‪ ‬עכשיו‪ ,‬ומבקשים‪ ‬את‪ ‬המשאב‪ ‬‬
‫החדש‪ ‬יחד‪ ‬עם‪ ‬כל‪ ‬המשאבים‪ ‬הקודמים‪) ‬וכך‪ ‬אם‪ ‬המשאב‪ ‬החדש‪ ‬לא‪ ‬פנוי‪ ‬שיחרננו‪ ‬את‪ ‬אלו‪ ‬שכבר‪ ‬תפסנו(‪ .‬‬
‫‪ ­ no resource preemption .2‬אם‪ ‬במשך‪ x ‬זמן‪ ‬לא‪ ‬משתמשים ‪ ‬בקובץ‪ ,‬התהליך‪ ‬מאבד‪ ‬בעלות‪ ‬על‪ ‬‬
‫הקובץ‪ ‬ולוקחים‪ ‬ממנו‪ ‬את‪ ‬המשאבים‪ ‬בכוח‪) ‬אם‪ ‬נכנס‪ ‬להמתנה‪ ‬הוא‪ ‬לא‪ ‬ישתמש‪ ‬במשאב(‪ .‬‬
‫‪ ­ mutual exclusion .3‬יש‪ ‬המון‪ ‬מבני‪ ‬נתונים‪ ‬קיימים‪ ,‬כמו‪ ‬רשימות‪ ‬מקושרות‪ ‬שאין‪ ‬הגבלה‪ ‬למספר‪ ‬‬
‫התהליכים‪ ‬שיכולים‪ ‬להשתמש‪ ‬בה‪ ‬ואפשר‪ ‬לממש‪ ‬אותם‪ ‬ללא‪ ‬צורך‪ ‬במנעולים‪ .‬ניתן‪ ‬לעשות‪ ‬זאת‪ ‬ע"י ‪ ‬שימוש‪ ‬‬
‫ב­‪ HW‬שתומך ‪ ‬בפעולות‪ ‬אטומיות‪ ‬בלבד‪ .‬בכל‪ ‬מקרה‪ ‬אם‪ ‬המערכת‪ ‬באמת‪ ‬מסובכת‪ ‬יש‪ ‬צורך‪ ‬במנעול‪ ‬בסופו‪ ‬‬
‫של‪ ‬דבר‪ .‬‬
‫‪ ­ circular wait .4‬לכל‪ ‬משאב‪ ‬נותנים‪ ‬מספר‪ ,‬כשנועלים‪ ‬משאב‪ ‬אפשר‪ ‬לנעול‪ ‬רק‪ ‬משאב‪ ‬העל‪ ‬מספר‪ ‬יותר‪ ‬‬
‫גבוה‪ ‬ממה‪ ‬שיש‪ ‬לי‪ ‬עכשיו‪ ,‬נועלים‪ ‬דברים‪ ‬לפי‪ ‬הסדר‪ .‬‬
‫‪ ‬‬
‫‪41 ‬‬
‫‪ ‬‬
‫מעשית‪ ‬עבור‪ ‬הפילוסופים‪ ‬למשל‪ ‬נוכל‪ ‬להציע‪ ‬את‪ ‬התיקון‪ ‬הבא‪ :‬‬
‫הבעיה‪ ‬היא‪ ‬רק‪ ‬בפילוסוף‪ ‬האחרון‪ ‬שנועל‪ ‬קודם‪ chopstick ‬עם‪ ‬מספר‪ ‬גבוה‪ ‬ולאחר‪ ‬מכן‪ chopstick ‬עם‪ ‬מספר‪ ‬נמוך‪ ,‬‬
‫ולכן‪ ‬ננעל‪ ‬עבורו‪ ‬קודם‪ ‬את‪ 0 ‬ואז‪ ‬את‪ 4 ‬ולא‪ ‬נקבל‪ ‬המתנה‪ ‬מעגלית!‪ ‬‬
‫‪ ‬‬
‫איך‪ ‬מגלים‪ ?Deadlock ‬‬
‫קיים‪ ‬אלגוריתם‪ ‬שעושה‪ ‬אנליזה‪ ‬של‪ ‬הגרף‪ (resource allocation graph) ‬ומוצא‪ ,Deadlock ‬אם‪ ‬האלגוריתם‪ ‬מוצא‪ ‬‬
‫מעגל‪ ‬בגרף‪ ‬אז‪ ‬קיים‪ ,deadlock ‬אלגוריתם‪ ‬זה‪ ‬לא‪ ‬ב­‪ scope‬של‪ ‬הקורס‪ ,‬אבל‪ ‬נניח‪ ‬שאנחנו‪ ‬משתמשים‪ ‬בו‪ .‬‬
‫‪ ‬‬
‫גישה‪ ‬אחת‪ ‬מציעה‪ ‬שנהרוג‪ ‬כמה‪ ‬מהתהליכים‪ ‬עד‪ ‬שה­‪ Deadlock‬ישתחרר‪ ,‬אבל‪ ‬עלולים‪ ‬בטעות‪ ‬להרוג‪ ‬תהליכים‪ ‬‬
‫מאוד‪ ‬חשובים‪ .‬גישה‪ ‬אחרת‪ ‬מציעה ‪ ‬שניקח‪ ‬תהליכים‪ ‬בכוח‪ ,‬אבל‪ ‬זה‪ ‬מסוכן‪ .‬לכן‪ ‬בד"כ‪ ‬מערכת‪ ‬ההפעלה‪ ‬לא‪ ‬עושה‪ ‬‬
‫שום‪ ‬דבר‪ ‬כדי‪ ‬לטפל‪ ‬ב­‪ ,Deadlock‬קשה‪ ‬לזהות‪ ‬מה‪ ‬צריך‪ ‬לעשות‪ .‬‬
‫‪ ‬‬
‫המנעות‪ ‬מ­‪ Deadlock‬‬
‫‪ ‬‬
‫)לכן‪ ‬נרצה‪ ‬שמערכת‪ ‬ההפעלה‪ ‬תזהה‪ ‬מצב‪ ‬של‪ Deadlock ‬לפני‪ ‬שהוא‪ ‬יתרחש‪ ‬ותמנע‪ ‬כניסה‪ ‬אליו‪ .‬‬
‫כשתהליך‪ ‬בא‪ ‬ומבקש‪ ‬משאבים‪ ‬מהמערכת‪ ,‬המערכת‪ ‬צריכה‪ ‬לחשוב‪ ‬האם‪ ‬ההקצאה‪ ‬הזאת‪ ‬יכולה‪ ‬לגרום‪ ‬ל­‬
‫‪ Deadlock‬בעתיד‪ ,‬אם‪ ‬יש‪ ‬חשש‪ ‬כזה‪ ‬היא‪ ‬לא‪ ‬תאפשר‪ ‬לבצע‪ ‬את‪ ‬ההקצאה‪ ,‬היא‪ ‬יכולה ‪ ‬להחזיר ‪ ‬כישלון‪ ‬או‪ ‬לחסום‪ ‬‬
‫אותו‪ ‬עד‪ ‬שההקצאה‪ ‬תהיה‪ ‬חוקית‪ .‬מתי‪ ‬נקצה‪ ‬אותו?‪ ‬כשנדע‪ ‬שההקצאה‪ ‬לא‪ ‬יכולה‪ ‬לגרום‪ ‬ל­‪ (.Deadlock‬‬
‫‪ ‬‬
‫‪ ‬‬
‫‪ ‬‬
‫‪ ‬‬
‫‪ ‬‬
‫‪42 ‬‬
43 ‫‪ ‬‬
‫סיכום‪ ‬ישן‪ : ‬‬
‫אלגוריתם‪ ‬הבנקאי‪ ­ ‬נניח‪ ‬אני‪ ‬צריך‪ x ‬שקלים‪ ‬מהבנק‪ ,‬ואני‪ ‬מבטיח‪ ‬להחזיר‪ 2x ‬שקלים‪ .‬‬
‫נניח‪ ‬שלבנק‪ ‬יש‪ ‬רק‪ x ‬שקלים‪ ‬בסך‪ ‬הכל‪ .‬ואני‪ ‬מבקש‪ ‬את‪ ‬העסקה‪ ‬בחלקים‪ ,‬בכל‪ ‬פעם‪ .1/2x ‬‬
‫וכעת‪ ‬מגיע‪ ‬אדם‪ ‬אחר‪ ‬שגם‪ ‬רוצה‪ ‬לבצע‪ ‬אותה‪ ‬עסקה‪ ‬בדיוק‪ .‬‬
‫כעת‪ ‬לאחר‪ ‬חודש‪ ‬רוצים‪ ‬לבצע‪ ‬את‪ ‬החלק‪ ‬הראשון‪ ,‬כל‪ ‬אדם‪ ‬מקבל‪ 1/2x ‬ולבנק‪ ‬כבר‪ ‬אין‪ ‬כלום‪ .‬כעת‪ ‬הבנק‪ ‬לא‪ ‬יוכל‪ ‬‬
‫לבצע‪ ‬את‪ ‬החלק‪ ‬הבא‪ ‬של‪ ‬העסקה‪ ‬וכן‪ ‬אף‪ ‬לקוח‪ ‬לא‪ ‬יחזיר‪ ‬את‪ ‬הכסף‪ ,‬שכן‪ ‬הוא‪ ‬עדיין‪ ‬לא‪ ‬הקים‪ ‬את‪ ‬העסק‪ ‬שלו‪ .‬‬
‫ונוצרה‪ ‬לנו‪ ‬כאן‪ ‬בעיה‪) ‬זה‪ ‬אגב‪ ‬מה‪ ‬שקרה‪ ‬במשבר‪ ‬של‪ .(2008 ‬‬
‫‪ ‬‬
‫ניתן‪ ‬לדמות‪ ‬את‪ ‬זה‪ ‬כתהליכים‪ ‬באופן‪ ‬הא‪ :‬‬
‫‪ R1‬מוקצה‪ ‬ל­‪ P1‬‬
‫‪ R2‬מוקצה‪ ‬ל­‪ P2‬‬
‫‪ ‬‬
‫נראה‪ ‬שעד‪ ‬עכשיו‪ ‬אנחנו‪ ‬בסדר‪ ‬אבל‪ ‬ייתכן‪ ‬ש­‪ P2‬רוצה‪ ‬לקבל‪ ‬את‪ R1 ‬ו­‪ P1‬רוצה‪ ‬את‪ R2 ‬וקיבלנו‪ .deadlock ‬‬
‫מה‪ ‬נעשה?‪ ‬לא‪ ‬נקצה‪ ‬ל­‪ P2‬את‪ ,R2 ‬זו‪ ‬הקצאה‪ ‬לא‪ ‬חוקית!‪ ‬זה‪ ‬יכניס‪ ‬אותנו‪ ‬למצב‪ ‬בעייתי‪ ‬שניתן‪ ‬להגיע‪ ‬ממנו‪ ‬ל­‬
‫‪ .deadlock‬זו‪ ‬דוגמא‪ ‬שמראה‪ ‬את‪ ‬הבעיה‪ ‬של‪ ‬השיטות‪ ‬האלו‪ ,‬הן‪ ‬מחמירות‪ ‬ומניחות‪ ‬את‪ ‬הדבר‪ ‬הגרוע‪ .‬הוא‪ ‬מניח‪ ‬‬
‫שכל‪ ‬המשאבים‪ ‬שתהליך‪ ‬יכול‪ ‬לצרוך‪ ‬הוא‪ ‬צורך‪ ‬ביחד‪ ,‬והוא‪ ‬אינו‪ ‬מאפשר‪ ‬ריצה‪ ‬אופטימלית‪ .‬‬
‫‪ ‬‬
‫כל‪ ‬תהליך‪ ‬מתאר ‪ ‬את‪ ‬מספר‪ ‬העותקים‪ ‬המקסימלי‪ ‬שנרצה‪ ‬מכל‪ ‬משאב‪ ,‬שומרים‪ ‬את‪ ‬המידע‪ ‬על‪ ‬כל‪ ‬התהליכים‪ ‬‬
‫במטריצה‪ .‬כמו‪ ‬כן‪ ‬שומרים‪ ‬כמה‪ ‬בפועל‪ ‬עותקים‪ ‬של‪ ‬כל‪ ‬משאב‪ ‬הקצתי‪ ‬לכל‪ ‬תהליך‪ ,‬במטריצה‪ ‬נוספת‪ .‬ומתקיים ‪ ‬‬
‫‪ .[cur[i,j] < max[i,j‬‬
‫הווקטור‪ R ‬מתאר‪ ‬את‪ ‬המשאבים‪ ‬שנדרשים‪ ‬להרצה‪ ‬כרגע‪ ‬ע"י‪ ‬תהליך‪ .P ‬‬
‫הווקטור‪ avail ‬מתאר‪ ‬כמה‪ ‬עותקים‪ ‬של‪ ‬כל‪ ‬משאב‪ ‬זמינים‪ ‬במערכת‪ . ‬‬
‫נניח‪ ‬שקיבלנו‪ ‬את‪ ‬המשאבים‪ ‬שרצינו‪ ‬ב­‪ R‬ונתבונן‪ ‬בשינוי‪ ‬בוקטורים‪ ‬והמטריצות‪ :‬‬
‫הבדיקה‪ ‬הראשונה‪ ‬היא‪ ‬שלא‪ ‬עברנו‪ ‬את‪ max ‬וכן‪ ‬ש­‪ avail‬אינו‪ ‬מכיל‪ ‬ערך‪ ‬שלילי‪ .‬‬
‫נבדוק‪ ‬האם‪ ‬נוכל‪ ‬לספק‪ ‬את‪ ‬הדרישות‪ ‬של‪ ‬כל‪ ‬התהליכים‪ ,‬מחפשים‪ ‬אם‪ ‬יש‪ ‬תהליך‪ ‬שניתן‪ ‬לסיים‪ ‬עכשיו‪ ,‬כל‪ ‬עוד‪ ‬לא‪ ‬‬
‫סיימנו‪ ‬את‪ ‬כולם‪ .‬אם‪ ‬תמיד‪ ‬מצאנו‪ ‬תהליך‪ ‬שיכול‪ ‬להסתיים‪ ‬נחזיר‪ ‬הצלחה‪ ,‬אחרת‪ ‬כישלון‪ .‬‬
‫נבדוק‪ ‬אם‪ ‬כמה‪ ‬שאנחנו‪ ‬עוד‪ ‬נוכל‪ ‬לבקש‪ ‬פחות‪ ‬ממה‪ ‬שיש‪ ‬לנו‪ ‬פנוי‪ ‬אז‪ ‬הוא‪ ‬בהכרח‪ ‬יוכל ‪ ‬לסיים‪ ,‬אם‪ ‬כן‪ ‬נניח‪ ‬ששיחררנו‪ ‬‬
‫אותו‪ ‬ונחפש‪ ‬את‪ ‬התהליך‪ ‬הבא‪ ‬שניתן‪ ‬לסיים‪ .‬כמו‪ ‬כן‪ ‬האלגוריתם‪ ‬מחזיר‪ ‬סדר‪ ‬אפשרי‪ ‬שלפיו‪ ‬נרצה‪ ‬להריץ ‪ ‬על‪ ‬מנת‪ ‬‬
‫למנוע‪ .deadlock ‬‬
‫‪ ‬‬
‫‪2‬‬
‫נבחין‪ :‬סיבוכיות‪ ‬האלגוריתם‪ ‬היא‪ , N ‬אבל‪ ‬זהו‪ ‬אלגוריתם‪ ‬חמדן‪ ,‬לא‪ ‬בדקנו‪ ‬את‪ ‬כל‪ ‬האפשרויות‪ ‬ויכול‪ ‬להיות‪ ‬‬
‫שפספסנו‪ ‬את‪ ‬הצירוף‪ ‬הנכון‪ ‬מתוך‪ N ‬עצרת‪ ‬הצירופים‪ .‬‬
‫נכונות ‪ ‬האלגוריתם‪ ‬נובעת‪ ‬מכך‪ ‬שאם‪ ‬שיחררנו‪ ‬בסדר‪ ‬שונה‪ ,‬אז‪ ‬לכל‪ ‬היותר‪ ‬שיפרנו‪ ‬את‪ ‬המצב‪ ‬שלנו‪) ‬כי‪ ‬בשלב‪ ‬מסוים‪ ‬‬
‫באלגוריתם‪ ‬שיחררנו‪ ‬מישהו‪ ‬שבסדר‪ ‬האחר‪ ‬עוד‪ ‬לא‪ ‬היה‪ ‬משוחרר(‪ ,‬ואם‪ ‬לא‪ ‬מצאנו‪ ‬את‪ ‬הפתרון‪ ‬לא‪ ‬קיים‪ ‬סידור‪ ‬אחר!‪ ‬‬
‫‪ ‬‬
‫השאלה‪ ‬היא‪ ‬אם‪ ‬יש‪ ­ false negative ‬הוא‪ ‬אומר‪ ‬שאין‪ ‬סידור‪ ‬ובפועל‪ ‬יש‪ ‬‬
‫‪ false positive‬לא‪ ‬ייתכן‪ ­ ‬אם‪ ‬הוא‪ ‬אומר‪ ‬שיש‪ ‬סידור‪ ‬אז‪ ‬יש‪ .‬‬
‫חשוב‪ ‬לדעת‪ ‬לעשות‪ ‬את‪ ‬זה‪ ‬פורמלי‪ ‬לבחינה!‪ ‬באינדוקציה‪ ‬ולא‪ ‬בנפנופי‪ ‬ידיים!‪ ‬‬
‫‪ ‬‬
‫‪ ‬‬
‫‪ ‬‬
‫‪44 ‬‬
‫הרצאה‪) 6 ‬מצגת‪ (6 ‬‬
‫‪ ‬‬
‫כמו‪ ‬שאמרנו‪ ‬בהרצאה‪ ‬הראשונה‪ ‬אחד‪ ‬המאפיינים‪ ‬של‪ ‬מערכת‪ ‬ההפעלה‪ ‬הוא‪ ‬שהיא‪ ‬לא‪ ‬רצה‪ ‬מעצמה‪ ,‬אלא‪ ‬מגיבה‪ ‬ל­‬
‫‪ ,events‬כלומר‪ ‬לפסיקות‪ ‬חומרה‪ ‬ותוכנה‪ ‬שהיא‪ ‬מקבלת‪ .‬‬
‫סוגי‪ ‬פסיקות‪ ‬‬
‫מי‪ ‬מייצר‪ ‬את‪ ‬הפסיקות?‪ ‬‬
‫פסיקות‪ ‬חומרה‪ ­ ‬נוצרות‪ ‬ע"י‪ ‬חומרה‪ ‬חיצונית‪ ,‬לחיצה‪ ‬על‪ ‬מקש‪ ‬מקלדת‪ ,‬פסיקת‪ ‬שעון‪ Nic ­ Network Interface , ‬‬
‫‪ Card‬וכד'‪ ,‬פסיקות‪ ‬כאלה‪ ‬יכולות‪ ‬להגיע‪ ‬בכל‪ ‬רגע‪ ‬נתון‪ ‬בלי‪ ‬שום‪ ‬קשר‪ ‬וסנכרון‪ ‬לשעון‪ ‬הפנימי‪ ‬של‪ ‬המעבד‪ .‬‬
‫פסיקות‪ ‬סנכרוניות‪ ,‬פנימיות‪ ­ ‬המעבד‪ ‬מריץ‪ ‬קוד‪ ‬וכתוצאה‪ ‬מכך‪ ‬הוא‪ ‬גורם‪ ‬לפסיקת ‪ ‬תוכנה‪ ,‬זו‪ ‬פסיקה‪ ‬סנכרונית‪ ‬שכן‪ ‬‬
‫היא‪ ‬נוצרת‪ ‬כתוצאה‪ ‬מקוד‪ ‬של‪ ‬המעבד‪ .‬‬
‫‪ ‬‬
‫נבחין‪ ‬בין‪ ‬שני‪ ‬סוגים‪ ‬של‪ ‬פסיקות‪ ‬פנימיות‪ :‬‬
‫● פסיקות‪ ‬מפורשות‪ ­ (explicit) ‬המעבד‪ ‬רוצה ‪ ‬ליזום‪ ‬פסיקה‪ ,‬למשל‪ ‬קריאות‪ ‬מערכת‪ ‬הפעלה‪ ‬במטרה‪ ‬לקבל‪ ‬‬
‫שירות‪ ‬שאנו‪ ‬זקוקים ‪ ‬לו‪ .‬סיבה‪ ‬נוספת‪ ‬היא‪ ,Debugging ‬ה­‪ Debugger‬ממומש‪ ‬באמצעות‪ ‬פסיקה‪ ‬‬
‫מפורשת‪ .‬‬
‫● פסיקות‪ ‬מרומזות ‪ ­ (implicit) ‬פסיקות‪ ‬שלא‪ ‬היינו‪ ‬רוצים‪ ‬לקבל‪ ‬אבל‪ ‬בלית‪ ‬ברירה‪ ‬הן‪ ‬מתבצעות‪ ,‬פסיקות‪ ‬אלו ‪ ‬‬
‫קורות‪ ‬כאשר‪ ‬אנו‪ ‬מבצעים‪ ‬פעולה ‪ ‬לא‪ ‬חוקית‪) ‬גישה‪ ‬לכתובת‪ ‬לא‪ ‬חוקית‪ ,‬ניסיון‪ ‬לבצע‪ ‬פקודה‪ ‬המותרת‪ ‬לביצוע‪ ‬‬
‫רק‪ ‬מ­‪ ,Kernel Space‬ניסיון‪ ‬לשינוי‪] CS ‬ה­‪ ring‬שאנו‪ ‬נמצאים‪ ‬בו‪ ‬שמור‪ ‬ב­‪ Code Segment‬בשני‪ ‬הביטים‪ ‬‬
‫הראשונים‪ ‬ולכן‪ ‬אסור‪ ‬לשנות‪ ‬אותו‪ ‬אבל‪ ‬מותר‪ ‬לשנות‪ ‬את‪ ‬ה­‪ .([eip‬ההתנהגות‪ ‬ברירת‪ ‬המחדל‪ ‬היא‪ ‬ללכת‪ ‬‬
‫ולהרוג‪ ‬את‪ ‬תהליך‪ ‬שביצע‪ ‬את‪ ‬הפעולה‪ ‬הלא‪ ‬חוקית‪ .‬‬
‫יש‪ ‬פקודות‪ ‬לא‪ ‬חוקיות‪ ‬שבהן‪ ‬אשמה‪ ‬מערכת‪ ‬ההפעלה‪ ‬ולא‪ ‬התהליך‪ page fault ,‬למשל‪ ­ ‬מנסים‪ ‬לגשת‪ ‬‬
‫לזיכרון‪ ‬חוקי‪ ‬לגמרי‪ ‬אבל‪ ‬הוא‪ ‬לא‪ ‬נמצא‪ ‬בפועל‪ ‬ב­‪ RAM‬אלא‪ ‬בדיסק‪ ‬בגלל‪ ‬שלמערכת‪ ‬ההפעלה‪ ‬נגמר‪ ‬‬
‫המקום‪ ‬ולכן‪ ‬עבור‪ ‬פסיקה‪ ‬זו‪ ‬לא‪ ‬נהרוג‪ ‬את‪ ‬התהליך‪ ‬אלא‪ ‬נדאג‪ ‬לתקן‪ ‬את‪ ‬ה­‪ .fault‬‬
‫‪ ‬‬
‫טיפול‪ ‬בפסיקות‪ ‬‬
‫לכל‪ ‬פסיקה‪ ‬מוצמד‪ ‬מספר‪ ‬המציין‪ ‬את‪ ‬המיקום‪ ‬שלה‪ ‬ב­‪ ,INTERRUPT_TABLE‬כאשר‪ ‬המיקום‪ ‬שלה ‪ ‬בטבלה‪ ‬‬
‫מצביע‪ ‬על‪ ‬הפונקציה‪ ‬לביצוע‪ .‬‬
‫‪ ‬‬
‫מי ‪ ‬טוען‪ ‬את‪ ‬הטבלה‪ ‬הזאת?‪ ‬כשהמחשב‪ ‬נדלק‪ ‬הדבר‪ ‬היחידי‪ ‬שמחשב ‪ ‬יודע ‪ ‬לעשות‪ ‬זה‪ ‬להתחיל‪ ‬לטעון‪ ‬את‪ ‬ה­‬
‫‪ ,BIOS‬כאשר‪ ‬הדבר‪ ‬האחרון‪ ‬ב­‪ BIOS‬הוא‪ ‬קריאה ‪ ‬לטעינה‪ ‬של‪ ‬מערכת‪ ‬ההפעלה‪ .‬עם‪ ‬זאת‪ ‬הקריאה‪ ‬מה­‪ BIOS‬לא‪ ‬‬
‫מאוד‪ ‬אמינה‪ ‬לכן‪ ‬הדבר‪ ‬הראשון‪ ‬שמערכת‪ ‬ההפעלה‪ ‬עושה‪ ‬היא‪ ‬למחוק‪ ‬את‪ ‬הטבלה‪ ‬שה­‪ BIOS‬טען‪ ‬לשם‪ ,‬ואז‪ ‬היא‪ ‬‬
‫מעדכנת‪ ‬את‪ ‬הטבלה‪ ‬לפי‪ ‬ה­‪ drivers‬שלה‪ ‬על‪ ‬מנת‪ ‬להבטיח‪ ‬התנהגות‪ ‬נכונה‪ .‬‬
‫‪ ‬‬
‫כל‪ ‬פסיקה‪ ,‬לא‪ ‬משנה‪ ‬אם‪ ‬הפעלנו‪ ‬אותה‪ ‬בצורה‪ ‬ישירה‪ ‬או‪ ‬עקיפה‪ ‬אנו‪ ‬תמיד‪ ‬שומרים‪ ‬את‪ ‬ה­‪ Context‬של‪ ‬התהליך‪ ‬‬
‫שרץ‪ ‬עכשיו‪) ‬חמישה‪ ‬שדות(‪ ,‬עוברים‪ ‬לפונקציה‪ ‬המוצבעת‪ ,‬מחליפים‪ ‬מחסנית‪ ‬למחסנית‪ ‬של‪ ‬ה­‪ Kernel‬ומבצעים‪ ‬את‪ ‬‬
‫קריאת‪ ‬המערכת‪ .‬‬
‫חשוב‪ ‬להבין‪ ‬שהתהליך‪ ‬שעל‪ ‬חשבונו‪ ‬מתבצעת‪ ‬הפסיקה‪ ‬הוא‪ ‬לא ‪ ‬בהכרח‪ ‬התהליך‪ ‬שקשור‪ ‬לפסיקה‪ ‬הזאת‪ ,‬גם‪ ‬‬
‫תהליך‪ ‬חישובי‪ ‬יכול‪ ‬להיות‪ ‬זה‪ ‬שיקבל‪ ‬את‪ ‬פסיקת‪ ‬המקלדת‪ .‬‬
‫‪ ‬‬
‫‪45 ‬‬
‫נניח‪ ‬שחסמנו‪ ‬פסיקות‪ ,‬מה‪ ‬קורה‪ ‬בכל‪ ‬זאת‪ ‬כשמגיעה‪ ‬פסיקה?‪ ‬אם‪ ‬המעבד‪ ‬מתעלם‪ ‬מהפסיקה‪ ‬הזאת‪ ‬לגמרי‪ ‬יש‪ ‬‬
‫לכך‪ ‬כל‪ ‬מיני‪ ‬חסרונות‪ ­ ‬למשל‪ ‬לא‪ ‬נספור‪ ‬כמו‪ ‬שצריך‪ ‬את‪ ‬זמן‪ ‬הריצה‪ ‬של‪ ‬התהליך‪ ,‬לא‪ ‬יתבצע ‪ ‬סנכרון‪ ‬עם‪ ‬חומרות‪ ‬‬
‫חיצוניות‪ ‬והן‪ ‬יתנתקו‪ ,‬לכן‪ ‬אסור‪ ‬סתם‪ ‬להתעלם‪ ‬מהפסיקות‪ .‬באינטל‪ ,32 ‬מעבד‪ ‬יכול‪ ‬לזכור‪ ‬על‪ ‬קיום‪ ‬של‪ 2 ‬פסיקות‪ ‬לא‪ ‬‬
‫מטופלות‪ ‬מכל‪ ‬סוג‪ ,‬לכן‪ ‬ברגע‪ ‬שנאפשר‪ ‬שוב‪ ‬פסיקות‪ ‬נטפל‪ ‬בשתי ‪ ‬הפסיקות‪ ‬הללו‪ ,‬כמובן‪ ‬שאנו‪ ‬עדיין‪ ‬בבעיה‪ ‬אם‪ ‬‬
‫מגיעות‪ ‬יותר‪ ‬פסיקות‪ ,‬לכן‪ ‬לא‪ ‬נרצה‪ ‬לחסום‪ ‬פסיקות‪ ‬לזמן‪ ‬רב‪ .‬‬
‫‪ ‬‬
‫חלוקת‪ ‬פסיקה‪ ‬‬
‫פסיקה‪ ‬מחולקת‪ ‬לשני‪ ‬חלקים‪ :‬‬
‫● חלק‪ ‬קריטי‪ ­ Top­halves ­ First Level Interrupts Handler ­ ‬החלק‪ ‬שצריך‪ ‬לבצע‪ ‬עכשיו‪ ‬לפני‪ ‬כל‪ ‬‬
‫פסיקה‪ ‬אחרת‪ ,‬למשל‪ ‬עבור‪ ‬מקלדת‪ ‬זה‪ ‬לדעת‪ ‬על‪ ‬מה‪ ‬הקשתי‪ ,‬כי ‪ ‬אם‪ ‬נמתין‪ ‬המשתמש‪ ‬עלול‪ ‬להקליד‪ ‬שוב‪ ‬‬
‫ונאבד‪ ‬את‪ ‬ההקשה‪ ‬הקודמת‪ ,‬לכן‪ ‬קריטי‪ ‬לגשת‪ ‬ל­‪ driver‬של‪ ‬המקלדת‪ ‬ולקרוא‪ ‬את‪ ‬האות‪ ,‬‬
‫● חלק‪ ‬לא‪ ‬קריטי‪ ­ Bottom­halves ­ ‬עבור‪ ‬המקלדת‪ ‬למשל‪ ‬המשך‪ ‬הטיפול‪ ‬בפסיקה‪ ­ ‬בדיקה‪ ‬של‪ ‬איזה‪ ‬‬
‫תהליך‪ ‬מחכה‪ ‬לאות‪ ‬הזאת‪ ,‬להעיר‪ ‬אותו‪ ‬וכו' ‪ ‬זה‪ ‬החלק ‪ ‬הלא‪ ‬קריטי‪ .‬בד"כ‪ ‬יש‪ ‬רשימה‪ ‬של‪ ‬משימות‪ ‬לביצוע‪ ‬ב­‬
‫‪ Bottom­halves‬ואנו‪ ‬מכניסים‪ ‬אותה‪ ‬לשם‪ .‬‬
‫מתי‪ ‬מבצעים‪ ‬את‪ ‬המשימות‪ ‬הללו?‪ ‬תלוי‪ ‬במערכת‪ ‬ההפעלה‪ ,‬יכול‪ ‬להיות‪ ‬במעבר‪ ‬מ­‪ Kernel‬ל­‪ ,User‬‬
‫יכול‪ ‬להיות‪ ‬כשהמערכת‪ ‬לא‪ ‬עמוסה‪ ,‬אבל‪ ‬בכל‪ ‬מקרה‪ ‬אנו‪ ‬לא‪ ‬חוסמים‪ ‬פסיקות‪ ‬להרבה‪ ‬זמן‪ ‬ולא‪ ‬מענישים‪ ‬את‪ ‬‬
‫התהליך‪ ‬הנוכחי‪ .‬‬
‫‪ ‬‬
‫מה‪ ‬נעשה‪ ‬אחרי‪ ‬שנסיים‪ ‬את‪ ‬הפסיקה?‪ ‬‬
‫עבור‪ ‬פסיקת‪ ‬חומרה‪ ‬זה‪ ‬פשוט‪ ­ ‬לא‪ ‬נעבור‪ ‬לפקודה‪ ‬הבאה ‪ ‬לביצוע‪ ‬אלא‪ ‬נטפל‪ ‬בפסיקה‪ ‬באופן‪ ‬מלא‪ ‬ורק‪ ‬אז‪ ‬נחזור‪ ‬‬
‫לקוד‪ ‬ונחזיר‪ ‬את‪ ‬הפקודה‪ ‬הבאה‪ ‬לביצוע‪ .‬גם‪ ‬עבור‪ ‬פסיקות‪ ‬תוכנה‪ ‬מפורשות‪ ‬מתבצע‪ ‬אותו‪ ‬תהליך‪ .‬‬
‫לעומת‪ ‬זאת‪ ‬עבור‪ ‬פסיקות‪ ‬תוכנה‪ ‬מרומזות‪ ‬שנוצרו‪ ‬כתוצאה‪ ‬מתקלה‪ ‬הפקודה‪ ‬האחרונה‪ ‬שהרצתי‪ ‬היא‪ ‬זו‪ ‬שגרמה‪ ‬‬
‫לתקלה‪ ‬ולכן‪ ‬במקרה‪ ‬כזה‪ ‬מה‪ ‬שנריץ‪ ‬אחרי‪ ‬סיום‪ ‬טיפול‪ ‬בפסיקה‪ ‬זו‪ ‬אותה‪ ‬פקודה‪ ‬שוב‪ ‬ושוב‪ .‬זה‪ ‬מאוד‪ ‬הגיוני‪ ‬בנושא‪ ‬‬
‫של‪ ,page fault ‬אם‪ ‬הכתובת‪ ‬לא‪ ‬חוקית‪ ‬נרצה ‪ ‬לבצע‪ ‬את‪ ‬אותה‪ ‬הפקודה‪ ‬שוב‪ ‬בתקווה‪ ‬שכעת‪ ‬מערכת‪ ‬ההפעלה‪ ‬‬
‫טענה‪ ‬את‪ ‬המידע‪ .‬עבור‪ ‬פקודות‪ ‬לא‪ ‬חוקיות ‪ ‬אחרות‪ ‬ה­‪ signal‬ברירת‪ ‬המחדל‪ ‬גורם‪ ‬להריגת‪ ‬התהליך‪ .‬צריך‪ ‬להיות‪ ‬‬
‫מאוד‪ ‬זהירים‪ ‬בשינוי‪ ‬ה­‪ ,signal‬אם‪ ‬למשל‪ ‬נחליט‪ ‬שאחרי‪ ‬חלוקה‪ ‬באפס‪ ‬ממשיכים‪ ‬את‪ ‬ריצת‪ ‬התוכנית‪ ‬נקבל‪ ‬לולאה‪ ‬‬
‫אינסופית‪ ,‬יש‪ ‬חלוקה‪ ,‬מערכת‪ ‬ההפעלה‪ ‬שולחת‪ ,signal ‬הוא‪ ‬מתעלם‪ ‬ומערכת ‪ ‬ההפעלה‪ ‬מנסה‪ ‬להריץ‪ ‬אותה‪ ‬‬
‫פקודה‪ ‬שוב‪ .‬‬
‫‪ ‬‬
‫ניהול‪ ‬פסיקות‪ ‬במערכת‪ ‬מרובת‪ ‬ליבות‪ :‬‬
‫‪ ­ ‬פסיקות‪ ‬מקומיות‪ ­ ‬קורות‪ ‬תמיד‪ ‬בליבה‪ ‬שגרמה‪ ‬להם‪ .‬‬
‫‪ ­ ‬פסיקות‪ ‬חיצוניות‪ ­ ‬יש‪ ‬מספר‪ ‬גישות‪ ‬לעניין‪ ‬זה‪ :‬‬
‫באופן‪ ‬סטטי‪ ­ ‬למשל‪ ‬כל‪ ‬הפסיקות‪ ‬הולכות‪ ‬לאותו‪ ‬מעבד‪ ,‬מחלקים‪ ‬את‪ ‬הפסיקות‪ ‬השונות‪ ‬לפי‪ ‬תחומים‪ ‬בין‪ ‬המעבדים‪ .‬‬
‫באופן‪ ‬דינמי‪ ,RR ­ ‬שליחת‪ ‬פסיקה‪ ‬למעבד‪ ‬הכי‪ ‬פחות‪ ‬עמוס‪ .‬‬
‫‪ ‬‬
‫‪ Polling‬‬
‫ישנן‪ ‬חומרות‪ ‬חיצוניות‪ ‬שמייצרות‪ ‬פסיקות‪ ‬בקצב‪ ‬מאוד‪ ‬גבוה‪ ,‬למשל ‪ ‬כרטיס‪ ‬רשת‪ ‬שיוצר‪ ‬פסיקה‪ ‬עבור‪ ‬כל‪ ‬פקטה‪ ‬וכך‪ ‬‬
‫המעבד‪ ‬יצטרך‪ ‬כל‪ ‬הזמן‪ ‬לטפל‪ ‬בפסיקות‪ ‬ונקבל‪ ‬בזבוז‪ ‬מאוד‪ ‬גדול‪ ‬של‪ ‬זמן‪ ‬חישוב‪ ‬של‪ ‬מעבד‪ .‬‬
‫לכן‪ ,‬יש‪ ‬גישה‪ ‬אחרת‪ ‬לניהול‪ ‬פסיקות‪ ,‬ניתן‪ ‬להגיד‪ ‬לחומרה‪ ‬מסוימת‪ ‬שלא‪ ‬תשלח‪ ‬פסיקות‪ ‬כלל‪) ‬או‪ ‬פשוט‪ ‬להתעלם(‪ ‬‬
‫ופעם‪ ‬בפרק‪ ‬זמן‪ ‬מסוים‪ ‬ניגשים‪ ‬לחומרה‪ ‬ובודקים‪ ‬אם‪ ‬יש‪ ‬לה‪ ‬משהו‪ ‬חדש‪ ‬להביא‪ ‬לנו‪ .‬‬
‫החסרון‪ ‬הוא‪ ‬שהנתונים‪ ‬עלולים‪ ‬לחכות‪ ‬יותר‪ ‬מדי‪ ‬זמן‪ ,‬ולכן‪ ‬יש‪ ‬כל‪ ‬מיני‪ ‬שיפורים‪ ‬לרעיון‪ ‬ה­‪ .Polling‬‬
‫‪ ‬‬
‫‪46 ‬‬
‫‪ ‬‬
‫השיפורים‪ ‬העיקריים‪ ‬מתחלקים‪ ‬לשני‪ ‬סוגים‪ :‬‬
‫מבוסס‪ ‬חומרה‪ : ‬‬
‫● כרטיס‪ ‬רשת‪ ‬לא‪ ‬מודיע‪ ‬מיד‪ ‬על‪ ‬קבלת‪ ‬פקטה‪ ‬אלא‪ ‬הוא‪ ‬מנסה‪ ‬לאגור‪ ‬כמות‪ ‬גבוהה‪ ‬יותר‪ .‬‬
‫● כרטיס‪ ‬הרשת‪ ‬מחכה‪ ‬פרק‪ ‬זמן‪ ‬מסוים‪ ‬לפני‪ ‬שליחת‪ ‬ההודעה‪ .‬‬
‫רוב‪ ‬כרטיסי‪ ‬הרשת‪ ‬לא‪ ‬תומכים‪ ‬בזה‪ ,‬ולכן‪ ‬יש‪ ‬פתרון‪ ‬מבוסס‪ ‬תוכנה‪ :‬‬
‫● בלינוקס‪ ‬למשל‪ ‬מתחילים‪ ‬לעבוד‪ ‬עם‪ ‬כרטיס‪ ‬הרשת‪ ‬כהתקן‪ ‬חיצוני‪ ‬רגיל‪ ,‬ברגע‪ ‬שמגלים‪ ‬שקצב‪ ‬הפסיקות‪ ‬‬
‫עובר‪ ‬איזה‪ ‬גבול‪ ‬מסוים‪ ‬מערכת‪ ‬ההפעלה‪ ‬מתחילה‪ ‬לעבוד‪ ‬על‪ ‬כרטיס‪ ‬הרשת‪ ‬בצורה‪ ‬של‪ ,Polling ‬‬
‫מתעלמים‪ ‬לגמרי ‪ ‬מפסיקות‪ ‬שמגיעות‪ ‬מהכרטיס‪ ‬הזה‪ ‬וניגשים‪ ‬בעצמנו‪ ‬לחומרה‪ ‬פעם‪ ‬ב­‪ X‬זמן‪ .‬אם‪ ‬מערכת‪ ‬‬
‫ההפעלה‪ ‬רואה‪ ‬שהיא‪ ‬ניגשה‪ ‬פעם‪ ‬או‪ ‬פעמיים‪ ‬ב­‪ polling‬ולכרטיס‪ ‬לא‪ ‬היה‪ ‬מידע‪ ‬חדש‪ ‬היא‪ ‬חוזרת‪ ‬לטפל‪ ‬בו‪ ‬‬
‫כחומרה‪ ‬רגילה‪ ‬מתוך‪ ‬הנחה‪ ‬שהוא‪ ‬עבר‪ ‬את‪ ‬השלב‪ ‬בו‪ ‬היה‪ ‬מספר‪ ‬גבוה‪ ‬של‪ ‬פסיקות‪ .‬‬
‫‪ ‬‬
‫‪ DMA ­ Direct Memory Access‬‬
‫לא‪ ‬רוצים‪ ‬שההתקן‪ ‬ישמור‪ ‬את‪ ‬כל‪ ‬הנתונים‪ ‬שהוא‪ ‬צריך‪ ‬להעביר‪ ‬על‪ ‬עצמו ‪ ‬כך‪ ‬שבכל‪ ‬פעם‪ ‬נצטרך‪ ‬להעתיק‪ ‬את‪ ‬‬
‫המידע‪ ,‬ולכן‪ ‬בד"כ‪ ‬אומרים‪ ‬להתקן‪ ‬לאיזה‪ ‬איזור‪ ‬בזיכרון‪ ‬הוא ‪ ‬יכול‪ ‬לכתוב‪ ‬את‪ ‬הנתונים‪ ‬שלו‪ .‬זו‪ ‬גישה‪ ‬מאוד‪ ‬נפוצה‪ .‬‬
‫החיסרון‪ ‬הוא ‪ ‬שההתקן‪ ‬צריך‪ ‬להודיע‪ ‬למערכת‪ ‬ההפעלה‪ ‬גם‪ ‬כאשר‪ ‬הוא‪ ‬מסיים ‪ ‬לכתוב‪ ,‬מערכת‪ ‬ההפעלה‪ ‬צריכה‪ ‬‬
‫לדעת‪ ‬שהוא‪ ‬סיים‪ ‬לכתוב‪ ‬כדי‪ ‬להגיד‪ ‬שה­‪ buffer‬התפנה‪ .‬‬
‫‪ ‬‬
‫דוגמא‪ ­ ‬קריאה‪ ‬מדיסק‪ ‬‬
‫‪process ‬‬
‫‪ ‬מבצעים‪ ‬פסיקה‪ ‬מפורשת‪=> read() ­ ‬‬
‫‪ ‬מוציאים‪ ‬בקשה‪ ‬לדיסק‪ ‬להוציא‪ ‬נתונים‪ ‬לזיכרון‪=> OS ­ ‬‬
‫‪ ‬משהים‪ ‬את‪ ‬התהליך‪=> process is suspended, others will run until the I/O arrives ­ ‬‬
‫‪=> find blocks from which data should be read ‬‬
‫‪=> initiate DMA transaction ‬‬
‫‪ ‬מסתיימת‪ ‬הקריאה‪ ‬והנתונים‪ ‬מגיעים‪ ‬לזיכרון‪=> DMA done ­ ‬‬
‫‪=> device fires interrupt to notify DMA is done ‬‬
‫‪ Bottom half ‬בגדול‪ ‬רק‪ ‬ליצור‪ ‬משימה‪ ‬של‪=> OS top­half adds relevant bottom to relevant list ­ ‬‬
‫‪=> later, bottom half runs & copies data to user ‬‬
‫‪=> makes process runnable ‬‬
‫‪=> process scheduled and can now read the data ‬‬
‫‪ ‬‬
‫‪ ‬‬
‫סיגנלים‪ ‬‬
‫סיגנל ‪ ‬הוא‪ ‬ערוץ‪ ‬תקשורת‪ ‬בין‪ ‬מערכת‪ ‬הפעלה‪ ‬לתהליכים‪ ,‬זהו‪ event ‬שנוצר‪ ‬ע"י‪ ‬מערכת‪ ‬ההפעלה‪ ‬ומטופל‪ ‬בהקשר ‪ ‬‬
‫של‪ ‬תהליכים‪ .‬לכל‪ ‬תהליך‪ ‬יש‪ Vector ‬של‪ ‬טיפול‪ ‬בסיגנלים‪ ‬שאומר‪ ‬מה‪ ‬צריך‪ ‬לבצע‪ ‬עבור‪ ‬כל‪ ‬אחד‪) ‬יש‪ 31 ‬סיגנלים‪ ‬‬
‫בלינוקס(‪ ,‬השמות‪ ‬של‪ ‬הסיגנלים‪ ‬הם‪ ‬סטנדרטיים‪ ‬ולא ‪ ‬ניתנים‪ ‬לשינוי‪ .‬מה‪ ‬שניתן‪ ‬לשינוי ‪ ‬הוא‪ ‬התגובה‪ ‬של‪ ‬התהליך‪ ‬‬
‫לסיגנל‪ .‬‬
‫‪ ‬‬
‫‪ ‬‬
‫‪ ‬‬
‫‪47 ‬‬
P: ‫טיפשית‬ ‫דוגמא‬
#include <signal.h> #include <stdio.h> #include <stdlib.h> void sigfpe_handler(int signum) { fprintf(stderr,"I divided by zero!\n"); exit(EXIT_FAILURE); } int main() { signal(SIGFPE, sigsegv_handler); // ‫ערך‬ ‫שמשנה‬ ‫הפעלה‬ ‫מערכת‬ ‫קריאת‬ ‫בוקטור‬ ‫מסוים‬ int x = 1/0; return 0; } P: ‫טיפשית‬ ‫פחות‬ ‫דוגמא‬
#include <signal.h> #include <stdio.h> #include <stdlib.h> void sigint_handler(int signum) { printf("I'm ignoring your ctrl­c!\n"); } int main() { // when pressing ctrl­c in shell // => SIGINT is delivered to foreground process signal(SIGINT,sigint_handler); for(;;) { /*endless look*/ } return 0; } ‫עם‬ kill ­9 ‫באמצעות‬ ‫אותו‬ ‫ולהרוג‬ CTRL+Z ‫באמצעות‬ ‫אותו‬ ‫לעצור‬ ‫ניתן‬ ?‫ התהליך‬ ‫ את‬ ‫ להרוג‬ ‫ זאת‬ ‫ בכל‬ ‫ ניתן‬ ‫איך‬
.SIGKILL ‫סיגנל‬ ‫לתהליך‬ ‫ששולחת‬ ‫התהליך‬ ‫של‬ pid­‫ה‬
‫מתהליך‬ ‫למנוע‬ ‫מנת‬ ‫על‬ SIGSTOP­‫ו‬ SIGKILL :‫שלהם‬ handler­‫ה‬ ‫את‬ ‫להחליף‬ ‫ניתן‬ ‫שלא‬ ‫סיגנלים‬ ‫משני‬ ‫אחד‬ ‫זהו‬
.‫שליטה‬ ‫ללא‬ ‫לרוץ‬
SIGCONT ‫באמצעות‬ ‫נעשה‬ ‫הריצה‬ ‫חידוש‬ .breakpoint­‫ל‬ ‫הגעה‬ ‫בעת‬ Debugger­‫ב‬ ‫בד"כ‬ ‫משתמשים‬ STOP­‫ב‬
.‫שלה‬ handler­‫ה‬ ‫את‬ ‫לשנות‬ ‫ניתן‬ ‫אבל‬ ‫אותו‬ ‫למנוע‬ ‫ניתן‬ ‫שלא‬ signal ‫בעצם‬ ‫גם‬ ‫זה‬
48 ‫דוגמא‪ ‬שאפילו‪ ‬יש‪ ‬בה‪ ‬היגיון‪ ‬מסוים‪ :‬‬
‫יש‪ ‬שני‪ ‬סיגנלים‪ sigusr1 ‬ו­‪ sigusr2‬שניתן‪ ‬להשתמש‪ ‬בהם‪ ‬לצורכנו‪ .‬‬
‫‪int g_count=0; ‬‬
‫‪ ‬‬
‫‪void do_work() { for(int i=0; i<10000000; i++); } ‬‬
‫‪ ‬‬
‫‪void sigusr_handler(int signum) { ‬‬
‫‪ printf("Work done so far: %d\n", g_count); ‬‬
‫‪} ‬‬
‫‪ ‬‬
‫‪int main() { ‬‬
‫‪ signal(SIGUSR1,sigusr_handler); ‬‬
‫‪ for(;;) { do_work(); g_count++; } ‬‬
‫‪ return 0; ‬‬
‫‪} ‬‬
‫‪ ‬‬
‫כשנרצה‪ ‬לדעת‪ ‬את‪ ‬מצב‪ ‬התוכנית‪ ‬נשלח‪ ‬לה‪ .kill ­USR1 ‬‬
‫מה‪ ‬הבעיה‪ ‬בקוד ‪ ‬הזה?‪ ‬צריך‪ ‬סנכרון‪ ‬ברמת‪ ‬העיקרון‪ ‬של‪ ,g_count ‬אם‪ g_count ‬היה‪ ‬מבנה‪ ‬יותר‪ ‬מורכב‪ ‬היינו‪ ‬‬
‫יכולים‪ ‬לקבל‪ ‬את‪ ‬ה­‪ signal‬במהלך‪ ‬טיפול‪ ‬במבנה‪ ‬הנתונים‪ ‬ולכן‪ ‬יש‪ ‬צורך‪ ‬בסנכרון‪ .‬‬
‫‪ ‬‬
‫סיגנלים‪ ‬נפוצים‪ ‬‬
‫● ‪ ­ SIGSEGV‬נשלח‪ ‬ע"י‪ bottom half ‬של‪ segv ‬בפועל‪ ‬נקרא‪ ‬כאשר‪ ‬עושים‪ ‬גישה‪ ‬לא‪ ‬חוקית‪ ‬לזיכרון‪ .‬‬
‫● ‪ ­ SIGILL‬נשלח‪ ‬כאשר‪ ‬מנסים‪ ‬לבצע‪ ‬פקודה‪ ‬לא‪ ‬חוקית‪ ‬מבחינת‪ ‬הרשאות‪ ‬ב­‪ .user space‬‬
‫● ‪ ­ SIGFPE‬חלוקה‪ ‬באפס‪ .‬‬
‫הערה‪ :‬הפקודה‪ halt ‬עוברת‪ ‬בהצלחה‪ ‬בלי‪ ‬שום‪ signal ‬אבל‪ ‬לא‪ ‬עושה‪ ‬דבר‪ ,‬זו‪ ‬בעיה‪ ‬גדולה‪ ‬בוירטואליזציה‪ ‬מצפים‪ ‬‬
‫לקבל‪ ‬תגובה‪ ‬ממערכת‪ ‬ההפעלה‪ ‬שעשינו‪ ‬משהו‪ ‬לא‪ ‬חוקי‪ ‬אבל‪ ‬בפועל‪ ‬לא‪ ‬מקבלים‪ ‬שום‪ ‬דבר‪ .‬‬
‫● ‪ ­ SIGCHLD‬כאשר‪ ‬אחד‪ ‬הבנים‪ ‬מקבל‪ SIGSTOP ‬או‪ ‬מת‪ .‬‬
‫● ‪ ­ SIGALRM‬סיגנל‪ ‬שנשלח‪ ‬כעבור‪ ‬זמן‪ ‬מסוים‪ ,‬כמו‪ ‬ע"י‪ alarm ‬ו­‪ .settimer‬‬
‫● ‪ ­ SIGTRAP‬כשרוצים‪ ‬לעשות‪ step ‬ב­‪ .debugger‬‬
‫● ‪ ­ SIGUSR1,SIGUSR2‬אין‪ ‬להם‪ ‬משמעות‪ ‬מיוחדת‪ ‬אפשר‪ ‬לתת‪ ‬להם‪ ‬איזו‪ ‬משמעות‪ ‬שבא‪ ‬לנו‪ .‬‬
‫● ‪ ­ SIGXCPU‬בודק‪ ‬האם‪ ‬תהליך‪ ‬מנצל‪ ‬את‪ ‬כל‪ ‬הזיכרון‪ ‬שלו‪ ,‬אם‪ ‬הוא‪ ‬מתקרב‪ ‬לניצול‪ ‬של‪ ‬כל‪ ‬הזיכרון‪ ‬שלו‪ ‬הוא ‪ ‬‬
‫מקבל‪ ‬סיגנל‪ ‬של‪ SIGXCPU ‬שהוא‪ ‬עלול‪ ‬לחרוג‪ ‬בקרוב‪ ‬מדרישת‪ ‬הזיכרון‪ ‬שלו‪ ,‬אם ‪ ‬הוא‪ ‬לא‪ ‬דואג‪ ‬לצמצם‪ ‬‬
‫את‪ ‬ניצול‪ ‬הזיכרון‪ ‬הורגים‪ ‬אותו‪ .‬‬
‫● ‪ ­ SIGPIPE‬נשלח‪ ‬כשמנסים‪ ‬לכתוב‪ ‬ל­‪ pipe‬שאין‪ ‬לו‪ ‬שום‪ ‬קורא‪ ‬פעיל‪ ‬והנתונים‪ ‬הללו‪ ‬סתם‪ ‬ילכו‪ ‬לאיבוד‪ .‬‬
‫● ‪ ­ SIGIO‬נשלח‪ ‬כשבאיזהו‪ ‬התקן‪ ‬שאפשר‪ ‬לעשות‪ ‬ממנו‪ ‬קריאה‪ ‬נוצרו‪ ‬נתונים‪ .‬למה‪ ‬זה‪ ‬שימושי?‪ ‬קריאה‪ ‬‬
‫מקבצים‪ ‬בצורה‪ ‬לא‪ ‬חוסמת‪ .‬הדרך‪ ‬הפשוטה‪ ‬היא‪ ‬כזכור‪ ‬לפתוח‪ ‬קובץ‪ ‬ולנסות‪ ‬לקרוא‪ ,‬ואם‪ ‬הקובץ‪ ‬ריק‪ ‬‬
‫התהליך‪ ‬ייחסם‪ .‬יש‪ ‬מקרים‪ ‬שבהם‪ ‬אם‪ ‬אין‪ ‬שום‪ ‬מידע‪ ‬בקובץ‪ ‬לא ‪ ‬נרצה‪ ‬להיחסם‪ ,‬אלא‪ ‬לקבל‪ ‬התרעה‪ ‬שאין‪ ‬‬
‫מידע‪ .‬ניתן‪ ‬לבקש ‪ ‬זאת‪ ‬ממערכת‪ ‬ההפעלה‪ ‬וכך‪ ‬אם‪ ‬הקובץ‪ ‬ריק‪ ‬נקבל‪,EAGAIN ‬אמנם‪ ‬אנו‪ ‬יכולים‪ ‬לבדוק‪ ‬‬
‫שוב‪ ‬בעצמנו‪ ‬אבל‪ ‬אפשר‪ ‬גם‪ ‬לחכות‪ ‬ל­‪ SIGIO‬שיגיד‪ ‬שיש‪ ‬שם‪ ‬נתונים‪ .‬‬
‫‪ ‬‬
‫‪ ‬‬
‫‪49 ‬‬
‫‪ Signal System Calls‬‬
‫‪int kill(pid_t pid, int sig) ‬‬
‫●‬
‫מאפשר‪ ‬שליחת‪ signal ‬לתהליך‪ ‬מסוים‪ .‬‬
‫‪ ‬‬
‫‪● int sigprocmask(int how, const sigset_t* set, sigset_t* oldset) ‬‬
‫שינוי‪ ‬ה­‪ mask‬של‪ ‬הסיגנלים‪ ‬שבהם‪ ‬מטפלים‪ ,‬מוסיפים‪ ‬עוד‪ ‬סיגנלים‪ ‬שאנו‪ ‬רוצים‪ ‬לחסום‪ ‬או‪ ‬לשחרר‪,‬או‪ ‬קביעה‪ ‬‬
‫מחודשת‪ ‬לפי‪ ‬ה­‪ .how‬‬
‫‪ ‬‬
‫‪● int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact) ‬‬
‫כזכור‪ ‬קריאת‪ ‬המערכת‪ signal ‬מחליפה‪ ‬את‪ ‬ה­‪ ,handler‬לא ‪ ‬להשתמש‪ ‬בקריאת‪ ‬המערכת‪ ‬אף‪ ‬פעם!‪ ‬זו‪ ‬קריאת‪ ‬‬
‫מערכת‪ ‬שלא‪ ‬מוגדרת‪ ‬היטב‪ ,‬בכל‪ ‬הפצה‪ ‬של‪ ‬לינוקס‪ ‬זה‪ ‬יכול‪ ‬לעשות‪ ‬משהו ‪ ‬שונה‪) ‬למשל‪ ‬יכול ‪ ‬להיות ‪ ‬חד‪ ‬פעמי‪ ‬או‪ ‬‬
‫לתמיד(‪ ‬ולכן‪ ‬נשתמש‪ ‬ב­‪ sigactions‬ונעביר‪ ‬לה‪ ‬מצביע‪ ‬לפוקציה‪ ‬החדשה‪ ‬ומוחזר‪ ‬מצביע‪ ‬לפונקציה‪ ‬הקודמת‪ ‬למקרה‪ ‬‬
‫שנרצה‪ ‬לשחזר‪ ‬אותה‪ .‬‬
‫‪ ‬‬
‫רצוי‪ ‬לדעת‪ ‬ש­‪ signals‬משפיעים‪ ‬על‪ ‬קריאת‪ ‬מערכת‪ ‬הפעלה‪ ‬אחרות‪ ‬למשל‪ ‬אם‪ ‬ביקשנו‪ ‬לעשות ‪ read ‬ונחסמנו‪ ‬‬
‫ברגע‪ ‬שנשלח‪ signal ‬כלשהו‪ ‬לאותו‪ ‬תהליך‪ ‬הפקודה‪ ‬החוסמת‪ ‬נכשלת‪) ‬אם‪ ‬התהליך‪ ‬במצב‪ (INTERRUPTIBLE ‬‬
‫ואז‪ read ‬תסתיים‪ ‬עם‪ ‬שגיאה‪ ‬כאשר‪ ‬ההערך‪ ‬המוחזר‪ ‬היא‪ .ERROR_INTERRUPT ‬‬
‫‪ ‬‬
‫סיגנלים‪ ‬לעומת‪ ‬פסיקות‪ ‬‬
‫מתי‪ ‬הם‪ ‬קורים?‪ ‬‬
‫שניהם‪ ‬יכולים‪ ‬להיות‪ ‬לא‪ ‬סנכרוניים‪ ‬ביחס‪ ‬לפעולתו‪ ‬של‪ ‬התהליך‪ .‬‬
‫‪ ‬מי‪ ‬מייצר‪ ‬אותם?‪ ‬‬
‫● פסיקות‪ ­ ‬חומרה‪ ‬או‪ ‬תהליכים‪ .‬‬
‫● סיגנלים‪ ­ ‬מערכת‪ ‬ההפעלה‪ ‬או‪ ‬תהליכים‪ .‬‬
‫מי‪ ‬מטפל‪ ‬בהם?‪ ‬‬
‫● פסיקות‪ ­ ‬מערכת‪ ‬ההפעלה‪ .‬‬
‫● סיגנלים‪ ­ ‬המשתמש‪ .‬‬
‫מי‪ ‬מייצר‪ ‬אותם‪ ‬ומגדיר‪ ‬את‪ ‬אופן‪ ‬פעולתם?‪ ‬‬
‫● פסיקות‪ ­ ‬החומרה‪ ‬מייצרת‪ ‬אותם‪ ‬ומערכת‪ ‬ההפעלה‪ ‬מחליטה‪ ‬על‪ ‬הלוגיקה‪ .‬‬
‫● סיגנלים‪ ­ ‬מערכת‪ ‬ההפעלה‪ ‬מייצרת‪ ‬אותם‪ ‬והתהליך‪ ‬מגדיר‪ ‬את‪ ‬הלוגיקה‪ .‬‬
‫מי‪ ‬חוסם‪ ‬אותם?‪ ‬‬
‫● פסיקות‪ ­ ‬מערכת‪ ‬ההפעלה‪ .‬‬
‫● סיגנלים‪ ­ ‬התהליך‪ .‬‬
‫‪ ‬‬
‫‪ ‬‬
‫‪ ‬‬
‫‪ ‬‬
‫‪50 ‬‬
‫הרצאה‪) 7 ‬מצגת‪(7 ‬‬
‫‪ ‬‬
‫‪ ‬‬
‫זיכרון וירטואלי‬
‫‪ ‬‬
‫מבוא‪ ‬‬
‫הזכרנו‪ ‬כבר‪ ‬בהרצאה‪ ‬את ‪ ‬ה­‪ ,Cache‬רכיבי‪ ‬זיכרון ‪ ‬שהינם‪ ‬חלק‪ ‬מהמעבד‪ ,‬אשר ‪ ‬משמים‪ ‬אותנו‪ ‬לצורך ‪ ‬חיסכון‪ ‬‬
‫בגישות‪ ‬לזיכרון‪ .‬ה­‪ ,DRAM‬הזיכרון‪ ‬החיצוני‪ ,‬הינו‪ ‬חיצוני‪ ‬למעבד‪ ‬אך‪ ‬נמצא‪ ‬כמה‪ ‬שיותר ‪ ‬קרוב‪ ‬לו‪ ‬על‪ ‬מנת‪ ‬לשפר‪ ‬את‪ ‬‬
‫זמן‪ ‬הריצה‪ .‬‬
‫לידע‪ ‬כללי‪ :‬בד"כ‪ ‬משתמשים‪ ‬בכרטיסי‪ ‬זיכרון‪ ‬בזוגות‪ ,‬ישנן‪ ‬מערכות‪ ‬הפעלה‪ ‬שיתלוננו‪ ‬על‪ ‬צירוף‪ ‬אחר‪ ,‬וישנן‪ ‬מערכות‪ ‬‬
‫הפעלה‪ ‬שפשוט‪ ‬יעבדו‪ ‬באופן‪ ‬מזוויע‪ .‬‬
‫‪ ‬‬
‫דוגמא‪ ‬לדיסק‪ :‬תדר‪ ‬המעבד‪ ­ (1600MHz) ‬כמה‪ ‬פעמים‪ ‬המעבד‪ ‬יכול‪ ‬לשדר‪ ‬מידע‪ ‬לזיכרון‪ ‬בשניה‪ .‬הוא‪ ‬יכול‪ ‬‬
‫להעביר‪ 64bit ‬בשניה‪ ,‬וסה"כ‪ ‬נקבל‪ ‬כי‪ ‬מועברים ‪ 12.8GB ‬לשניה‪ .‬ה­‪ Latency‬של‪ ‬רכיבי‪ ‬זיכרון‪ ‬הוא ‪ ‬בד"כ ‪ ‬סדר‪ ‬גודל‪ ‬‬
‫של‪ 100 ‬ננו‪ ‬שניות‪ ‬במקרה‪ ‬האופטימלי‪ ,‬עד‪ ‬לאלפי‪ ‬ננו‪ ‬שניות‪ .‬‬
‫‪ ‬‬
‫מה‪ ‬ההבדל‪ ‬בין‪ DRAM ‬ל­‪ ? HDD‬‬
‫הגישה‪ ‬ל‪ DRAM‬דורשת‪ ‬מתח‪ ‬ואילו‪ ‬הגישה‪ ‬ל‪ HDD‬לא‪ ‬דורשת‪ ‬מתח‪) ‬חשמל(‪ ,‬המידע‪ ‬ב­‪ DRAM‬אינו‪ ‬נשמר‪ ‬לאחר‪ ‬‬
‫אובדן‪ ‬מתח‪ ,‬ובפרט‪ ‬לאחר‪ ‬כיבוי‪ ‬המחשב‪ .‬‬
‫כמו‪ ‬כן‪ ,‬הגישה‪ ‬ל‪ DRAM‬הרבה ‪ ‬יותר‪ ‬מהירה‪ ‬והעיכוב‪ ‬קטן ‪ ‬יותר‪ .‬נבחין‪ ‬בהבדל‪ ‬המשמעותי‪ ‬בין‪ ‬זמני‪ ‬הגישה‪ ‬הרציפה‪ ‬‬
‫ב­‪ HDD‬לגישה‪ ‬אקראית‪ ,‬הבדלים‪ ‬שאין‪ ‬לנו‪ ‬ב­‪) .DRAM‬שכן‪ ‬כפי‪ ‬שניתן‪ ‬להבין‪ ‬משמו‪ ‬זהו‪ ‬זיכרון‪ ‬שהעבודה‪ ‬הינו‪ ‬הינה‪ ‬‬
‫בד"כ‪ ‬בגישות‪ ‬אקראיות‪ .(Random Access Memory ‬‬
‫‪ ‬‬
‫ניהול‪ ‬זיכרון‪ ‬‬
‫ניזכר‪ ‬כי‪ ‬בתחילת‪ ‬הסמסטר‪ ‬הצגנו‪ ‬כיצד‪ ‬התהליך‪ ‬חושב‪ ‬שהזיכרון‪ ‬נראה‪ ‬רציף‪ ‬החל‪ ‬מכתובת‪ .0 ‬‬
‫אך‪ ‬ברור‪ ‬שאם‪ ‬כל‪ ‬תהליך‪ ‬חושב‪ ‬שזוהי‪ ‬מפת‪ ‬הזיכרון‪ ‬יש‪ ‬לנו‪ ‬כאן‪ ‬בעיה‪ ,‬ראשית‪ ‬תהליכים‪ ‬יכולים‪ ‬להתנגש‪ ‬בגישות ‪ ‬‬
‫לזיכרון‪ ‬מבלי‪ ‬להתכוון‪ ‬לכך‪ .‬ברור ‪ ‬לנו‪ ‬שלא‪ ‬נרצה‪ ‬שתהליכים‪ ‬יגשו‪ ‬לאותם‪ ‬איזורי‪ ‬זיכרון‪ ‬סתם‪ ‬כך‪ ,‬ולכן‪ ‬צריך‪ ‬להגביל‪ ‬‬
‫את‪ ‬התהליך‪ ‬מלגשת‪ ‬לאיזור‪ ‬זיכרון‪ ‬ששייך‪ ‬לתהליך‪ ‬אחר‪ ‬או‪ ‬למערכת‪ ‬ההפעלה‪ .‬‬
‫כמו‪ ‬כן‪ ,‬ייתכן‪ ‬שכל‪ ‬תהליך‪ ‬חושב‪ ‬שיש‪ ‬לו‪ ‬יותר‪ ‬זיכרון‪ ‬ממה‪ ‬שיש‪ ‬בפועל‪ ‬במערכת‪ ,‬ולכן‪ ‬גם‪ ‬את‪ ‬בעיה‪ ‬זו‪ ‬נרצה‪ ‬לפתור‪ .‬‬
‫‪ ‬‬
‫זיכרון‪ ‬וירטואלי‪ ‬‬
‫ישנן‪ ‬כמה‪ ‬פתרונות‪ ‬לבעיה‪ ,‬אנו‪ ‬נלמד‪ ‬את‪ ‬השיטה‪ ‬הנפוצה‪ ‬היום‪ ­ ‬זיכרון‪ ‬וירטואלי‪ .‬‬
‫תחילה‪ ‬נגדיר‪ ‬מספר‪ ‬מושגים‪ :‬‬
‫● כתובת‪ ‬וירטואלית‪ ­ VA ­ ‬כל‪ ‬תהליך‪ ‬עובד‪ ‬עם‪ ‬כתובות‪ ‬וירטואליות‪ ,‬כתובות‪ ‬רציפות‪ ‬החל‪ ‬מכתובת‪ .0 ‬‬
‫● כתובת‪ ‬פיזית‪ ­ PA ­ ‬הכתובת‪ ‬האמיתית‪ ‬של‪ ‬המידע‪ ‬בזיכרון‪ ‬הראשי‪ ,‬מוכרות‪ ‬רק‪ ‬למערכת‪ ‬ההפעלה ‪ ‬‬
‫ולחומרה‪ .‬‬
‫נרצה‪ ‬בעצם‪ ‬שיטה‪ ‬שבהינתן‪ ‬כתובת‪ ‬וירטואלית‪ ‬תיתן‪ ‬לנו‪ ‬כתובת‪ ‬פיזית‪ ,‬ובכך‪ ‬תאפשר‪ ‬לנו‪ ‬לקשר‪ ‬בין‪ ‬האיזור‪ ‬‬
‫הוירטואלי‪ ‬של‪ ‬התהליך‪ ‬לבין‪ ‬איזור‪ ‬פיזי‪ ‬בזיכרון‪ ‬הראשי‪ .‬‬
‫הזיכרון‪ ‬הוירטואלי‪ ‬מדמה‪ ‬לתהליך‪ ‬את‪ ‬התנאים‪ ‬שהוא‪ ‬צופה‪ ‬שהתקיימו‪ ,‬התהליך‪ ‬לא ‪ ‬צריך‪ ‬בכלל‪ ‬לדעת‪ ‬לאן‪ ‬בפועל‪ ‬‬
‫הוא‪ ‬ניגש‪ ,‬ומידע‪ ‬זה‪ ‬מוסתר‪ ‬ממנו‪ .‬‬
‫‪51 ‬‬
‫● ‪ ­ Page‬הזיכרון‪ ‬מחולק‪ ‬לבלוקים‪ ‬בגודל‪ ‬קבוע‪ ,‬כאשר‪ Page ‬הוא‪ ‬בלוק‪ ‬של ‪ ‬מידע‪ ‬רציף‪ ,‬במערכות‪ ‬שאנחנו‪ ‬‬
‫נעבוד‪ ‬איתן‪ ‬גודלו‪ ‬יהיה‪ ‬בד"כ‪ .4KB ‬‬
‫● ‪ ­ Frame‬בלוק‪ ‬בזיכרון‪ ‬הפיזי‪ ‬שמתאים‪ ‬בגודלו‪ ‬להחזקת‪ .Page ‬‬
‫כל‪ Page ‬שיושב‪ ‬בזיכרון‪ ‬הראשי‪ ‬ימופה‪ ‬ל­‪ Frame‬מסוים‪ ,‬ונצטרך‪ ‬לשמור‪ ‬את‪ ‬המיפוי‪ ‬הזה‪ ‬במקום‪ ‬מסוים‪ .‬‬
‫יכול‪ ‬להיות‪ ‬ש­‪ Page‬מסוים‪ ‬לא‪ ‬ממופה‪ ‬ל­‪ Frame‬פיזי‪ .‬מתי‪ ‬למשל?‪ ‬יכול‪ ‬להיות‪ ‬שה­‪ Page‬עוד‪ ‬לא‪ ‬הוקצה‪ ,‬‬
‫התהליך‪ ‬לא‪ ‬היה‪ ‬זקוק‪ ‬לו‪ ‬עד‪ ‬עכשיו‪ .‬יכול‪ ‬להיות‪ ‬גם‪ ‬שה­‪ Page‬יושב‪ ‬בדיסק‪ ,‬זאת‪ ‬משום‪ ‬שאם‪ ‬נגמרים‪ ‬ה­‪ Frames‬‬
‫בזיכרון‪ ‬הראשי‪ ‬מעבירים‪ ‬חלק‪ ‬מה­‪ Pages‬לזיכרון‪ ‬המשני‪ ,‬עד‪ ‬שיש‪ ‬בהם‪ ‬צורך‪ .‬‬
‫‪ ‬‬
‫‪ ‬‬
‫איך‪ ‬ניקח‪ ‬כתובת‪ ‬וירטואלית‪ ‬ונמפה‪ ‬אותה‪ ‬לכתובת‪ ‬פיזית?‪ ‬‬
‫לצורך‪ ‬הדיון‪ ­ ‬נניח‪ ‬שיש‪ ‬בידנו‪ 32bit ‬של‪ ‬כתובת‪ ‬וירטואלית‪ ‬ו­‪ 32bit‬של‪ ‬כתובת‪ ‬פיזית‪ .‬‬
‫נזכור‪ ,‬הזיכרון‪ ‬הפיזי‪ ‬הוא‪ ‬רכיב‪ ‬יחיד‪ ‬ומשותף‪ ‬בין‪ ‬כל‪ ‬התהליכים‪ ‬בפועל‪ .‬נשתמש‪ ‬במיפוי‪ ‬של‪ ‬ה­‪ Pages‬על‪ ‬מנת‪ ‬‬
‫לקבוע‪ ‬אם‪ ‬תהליכים‪ ‬ישתפו‪ ‬ביניהם‪ ‬מידע‪ ‬מסוים‪ ‬או‪ ‬לא‪ ,‬אם‪ ‬נרצה‪ ‬שיתוף‪ ‬נמפה‪ ‬לאותה ‪ ‬כתובת‪ ‬פיזית‪ ,‬אחרת‪ ‬נדאג‪ ‬‬
‫למפות‪ ‬לכתובות‪ ‬שונות‪ .‬‬
‫‪ ‬‬
‫מה‪ ‬המיפוי‪ ‬שלנו‪ ‬צריך‪ ‬בעצם‪ ‬לעשות?‪ ‬ראשית‪ ‬הוא‪ ‬צריך‪ ‬לתרגם ‪ Page ‬ל­‪ .Frame‬לאחר‪ ‬מציאת‪ ‬ה­‪ Frame‬נוכל‪ ‬‬
‫להשתמש‪ ‬ב­‪ Offset‬ישירות‪ ‬מבלי‪ ‬לתרגם‪ ‬אותו‪ .‬‬
‫‪ ‬‬
‫מה‪ ‬עושים‪ ‬אם‪ ‬הזיכרון‪ ‬הוירטואלי‪ ‬הוא‪ ‬גדול‪ ‬יותר‪ ‬מהזיכרון‪ ‬הפיזי‪ ,‬למשל‪ 64bit ‬ל­‪ ?32bit‬עדיין‪ ‬ניתן‪ ‬לשניהם‪ ‬אותו‪ ‬‬
‫גודל‪ ‬של‪ ,offset ‬בהתאם‪ ‬לגודל‪ ‬הדף‪ .‬רק‪ ‬נצטרך‪ ‬לעבוד‪ ‬קשה‪ ‬יותר‪ ‬כדי‪ ‬למפות‪ ‬מ­‪ Page‬ל­‪ .Frame‬‬
‫‪ ‬‬
‫איך‪ ‬עושים‪ ‬את‪ ‬המיפוי‪ ‬הזה?‪ ‬‬
‫הפתרון‪ ‬הפשוט‪ ‬הוא‪ ‬טבלה‪ ‬לכל‪ ‬תהליך‪ ‬שממפה‪ ‬באופן‪ ‬ישיר‪ ‬מכתובת‪ ‬דף‪ ‬וירטואלית‪ ‬לכתובת‪ frame ‬פיזי‪ .‬‬
‫כל‪ entry ‬בטבלה‪ ‬הוא‪ ‬בגודל‪ ,32bit ‬עשרים‪ ‬מהם‪ ‬ישמשו‪ ‬ל­‪ Mapping‬והנותרים‪ ‬ישמשו‪ ‬למידע‪ ‬נוסף‪ ,‬האם‪ ‬המידע‪ ‬‬
‫רק‪ ‬לקריאה‪ ,‬האם‪ ‬הוא‪ ‬שייך‪ ‬למערכת‪ ‬ההפעלה‪ ‬וכו'‪ .‬‬
‫‪ ‬‬
‫למה‪ ‬בעצם‪ ‬החומרה‪ ‬צריכה‪ ‬לעשות‪ ‬את‪ ‬כל‪ ‬הבדיקות‪ ‬כאן‪ ‬ולא‪ ‬מערכת‪ ‬ההפעלה? ‪ ‬נזכור‪ ‬שמערכת‪ ‬ההפעלה‪ ‬היא‪ ‬‬
‫לא‪ ‬חלק‪ ‬מהסיפור‪ ‬הזה‪ ,‬היא‪ ‬מתערבת‪ ‬אך‪ ‬ורק‪ ‬כשהחומרה‪ ‬אומרת‪ ‬שיש‪ ‬בעיה‪ .‬אנחנו‪ ‬לא‪ ‬רוצים‪ ‬שבכל‪ ‬פעם‪ ‬‬
‫שתהליך‪ ‬ינסה‪ ‬לגשת‪ ‬לזיכרון‪ ‬שלו‪ ‬מערכת ‪ ‬ההפעלה‪ ‬תתערב‪ ‬ולכן‪ ‬החומרה‪ ‬צריכה‪ ‬להיות‪ ‬זאת‪ ‬שמגלה‪ ‬את‪ ‬הבעיות‪ ‬‬
‫והיא‪ ‬זקוקה‪ ‬לאינדיקטורים‪ ‬ממערכת‪ ‬ההפעלה‪ ‬לגבי‪ ‬חוקיות‪ ‬הגישה‪ .‬‬
‫‪ ‬‬
‫‪ ‬‬
‫‪52 ‬‬
‫● ‪ ­ Valid‬נקרא‪ ‬גם‪ .Present ‬תפקידו‪ ‬של‪ ‬הביט‪ ‬הוא‪ ‬להגיד‪ ‬האם‪ ‬המידע‪ ‬נמצא‪ ‬בזיכרון‪ ‬או‪ ‬שהוא‪ ‬לא‪ ‬נמצא‪ ‬‬
‫שם‪ ,‬כלומר‪ Valid ‬הינו‪ 0 ‬בין‪ ‬אם‪ ‬דף‪ ‬לא‪ ‬הוקצה‪ ‬כלל‪ ‬או‪ ‬שהוא‪ ‬כרגע‪ ‬שמור‪ ‬בדיסק‪ .‬‬
‫כשהחומרה‪ ‬ניגשת‪ ‬לתרגם‪ ‬כתובת‪ ‬היא‪ ‬בודקת‪ ‬את ‪ ,Valid ‬אם‪ ‬הוא‪ ‬שווה ‪ ‬לאפס‪ ‬היא‪ ‬אינה‪ ‬ממשיכה‪ ‬בתרגום ‪ ‬וגורמת‪ ‬‬
‫לפסיקת‪ .Page Fault ‬מערכת‪ ‬ההפעלה‪ ‬צריכה‪ ‬להחליט‪ ‬מה‪ ‬לעשות‪ ‬עם‪ ‬הפסיקה‪ ,‬היא‪ ‬מקבלת‪ ‬את‪ ‬סיבת‪ ‬התקלה‪ ‬‬
‫ברגיסטר‪ ‬ובודקת‪ ‬למה‪ ‬התקבל‪ .Page Fault ‬אם ‪ ‬למשל‪ ‬המידע‪ ‬נמצא‪ ‬בדיסק‪ ,‬על‪ ‬מערכת‪ ‬ההפעלה‪ ‬למצוא‪ ‬‬
‫‪ Frame‬פנוי‪ ‬ולבקש‪ ‬להביא‪ ‬את‪ ‬ה­‪ .Page‬‬
‫כיצד‪ ‬יודעים‪ ‬מאיפה‪ ‬להביא‪ ‬את‪ ‬ה­‪ ?Page‬לוקחים‪ ‬את‪ 31 ‬הביטים‪ ‬הנותרים‪ ‬ומהם‪ ‬צריך‪ ‬להסיק‪ ‬איפה‪ ‬נמצאת‪ ‬‬
‫הכתובת‪ ,‬כל‪ ‬מערכת‪ ‬הפעלה‪ ‬עושה‪ ‬משהו‪ ‬אחר‪ .‬בלינוקס‪ ‬למשל‪ 7 ‬ראשונים‪ ‬אומרים‪ ‬מה‪ ‬ה­‪ ,Swapper‬בלינוקס‪ ‬יש‪ ‬‬
‫תמיכה‪ ‬בכ­ ‪ .Swappers 27‬הביטים‪ ‬הנותרים‪ 24) ‬ביטים(‪ ‬אומרים‪ ‬מהו‪ ‬ה­‪ Offset‬בתוך‪ ‬ה­‪ .Swapper‬‬
‫● ‪ ­ Page in‬יש‪ ‬מידע‪ ‬שהעברנו‪ ‬מתישהו‪ ‬לדיסק‪ ‬ועכשיו‪ ‬קוראים‪ ‬אותו‪ ‬חזרה‪ ‬לזיכרון‪ ‬‬
‫● ‪ ­ Page Out‬יש‪ ‬מידע‪ ‬שאנחנו‪ ‬לא‪ ‬זקוקים‪ ‬לו‪ ‬כרגע‪ ‬ולכן‪ ‬נעביר‪ ‬לדיסק‪ .‬‬
‫בנוסף‪ ‬יש‪ ‬גם‪ Swap In ‬ו­‪ Out‬שמשמעותם‪ ‬הפוכה‪ .‬‬
‫‪ ‬‬
‫‪ Major Page Fault‬‬
‫כאשר‪ ‬הדף‪ ‬אינו‪ ‬בזיכרון‪ ‬ויש‪ ‬להביאו‪ ‬מהדיסק‪ ‬נקבל‪ Major Page Fault ‬‬
‫‪ .1‬המעבד‪ ‬מזהה‪ ‬מידע‪ ‬לא‪ ‬ולידי‪ ,‬נניח‪ ‬כי‪ ‬בגלל‪ ‬שהוא‪ ‬בדיסק‪ .‬‬
‫‪ .2‬המעבד‪ ‬גורם‪ ‬לפסיקה‪ .‬‬
‫‪ .3‬מערכת‪ ‬ההפעלה‪ ‬מבקשת‪ ‬את‪ ‬המידע‪ ‬מהזיכרון‪ .‬‬
‫‪ .4‬מערכת‪ ‬ההפעלה‪ ‬עושה‪ ‬החלפת‪ ‬הקשר‪ ,‬שמה‪ ‬את‪ ‬התהליך‪ ‬כ­‪ .TASK UNINTERRUPTABLE‬‬
‫‪ .5‬ברגע‪ ‬שהמידע‪ ‬יגיע‪ ‬הדיסק‪ ‬תשלח‪ ‬פסיקה‪ ,‬היא‪ ‬תגלה‪ ‬שהגיעו‪ ‬הנתונים‪ ‬שחיכו‪ ‬להם‪ ‬ותחזיר‪ ‬את‪ ‬התהליך‪ ‬‬
‫למצב‪ .ready ‬‬
‫‪ .6‬מתישהו‪ ‬בהמשך‪ ‬התהליך‪ ‬יחזור‪ ‬לרוץ‪ ‬ויבצע‪ ‬את‪ ‬אותה‪ ‬פקודה‪ ‬שגרמה‪ ‬לפסיקה‪ ,(restartable interrupt) ‬‬
‫כיוון‪ ‬שכעת‪ ‬יש‪ ‬לו‪ ‬את‪ ‬הדף‪ ‬שהוא‪ ‬חיפש‪ .‬‬
‫‪ ‬‬
‫‪ Minor Page Fault‬‬
‫לא‪ ‬כל‪ ‬ה­‪ Page Fault‬מתרחשים‪ ‬מסיבה‪ ‬כזאת‪ .‬‬
‫למשל‪ ‬ב­‪ ,COW ­ Copy On Write‬כפי‪ ‬שתיארנו‪ ‬בביצוע‪ fork ‬משתמשים‪ ‬באותם‪ ‬דפים‪ ‬והופכים‪ ‬אותם‪ ‬ל­‪ read‬‬
‫‪ ,only‬ברגע‪ ‬שאחד‪ ‬מנסה‪ ‬לכתוב‪ ‬לדף‪ ,‬מערכת‪ ‬ההפעלה‪ ‬משכפלת‪ ‬את‪ ‬הדף‪ ‬ונותנת‪ ‬לתהליך‪ ‬שביקש‪ ‬לכתוב‪ ‬את‪ ‬‬
‫ההעתק‪ ‬של‪ ‬הדף‪ ,‬וכך‪ ‬חוסכים‪ ‬את‪ ‬ההעתקה‪ ‬של‪ ‬מרחב‪ ‬הזיכרון‪ .‬זהו‪ ‬גם‪ ‬סוג‪ ‬של‪ ,Page Fault ‬אבל‪ ‬הוא‪ ‬לא‪ ‬דורש‪ ‬‬
‫גישה‪ ‬לדיסק‪ ‬וגם‪ ‬גישות‪ ‬מחוץ‪ ‬למערך‪ null pointers ,‬וכאלה‪ .‬‬
‫יש‪ ‬מקרים‪ ‬שבהם‪ ‬אמנם‪ ‬ה­‪ Frame‬לא‪ ‬נמצא‪ ‬בזיכרון‪ ‬אבל‪ ‬תהליך‪ ‬אחר‪ ‬כבר‪ ‬הביא‪ ‬אותו‪) ‬למשל‪ ‬מידע‪ ‬מהדיסק(‪ ‬ולכן‪ ‬‬
‫מערכת‪ ‬ההפעלה‪ ‬יכולה‪ ‬להביא‪ ‬לו‪ ‬את‪ ‬המידע‪ ‬באופן‪ ‬מיידי‪ ‬מה­‪ I/O Cache‬מבלי‪ ‬אפילו‪ ‬להעבירו‪ ‬להמתנה‪ .‬‬
‫‪ ‬‬
‫‪ mmap‬‬
‫כזכור‪ ‬בד"כ‪ ‬קוראים‪ ‬קבצים‪ ‬באופן‪ ‬סדרתי‪ ‬מהדיסק‪ .‬ישנם‪ ‬מקרים‪ ‬שבהם‪ ‬רוצים‪ ‬לגשת‪ ‬לאיזורים‪ ‬שונים‪ ‬בדיסק ‪ ‬תוך ‪ ‬‬
‫כדי‪ ‬קפיצות‪ ,‬דרך‪ ‬אחת‪ ‬לעשות‪ ‬זאת‪ ‬היא‪ ‬פשוט‪ ‬לקרוא‪ ‬את‪ ‬כל‪ ‬הקובץ‪ ‬ולהתעלם‪ ‬ממה‪ ‬שלא‪ ‬מעניין‪ ‬אותנו‪ .‬‬
‫‪ mmap‬היא‪ ‬קריאת ‪ ‬מערכת‪ ‬המאפשרת‪ ‬לנו‪ ‬להתייחס‪ ‬לקובץ ‪ ‬כאל‪ ‬מערך‪ ‬של‪ ‬בתים‪ ,‬וכך‪ ‬מקבלים‪ ‬כתובת‪ ‬וירטואלית‪ ‬‬
‫שניתן‪ ‬לגשת‪ ‬אליה‪ .‬נניח‪ ‬למשל‪ ‬שרוצים‪ ‬לגשת‪ ‬ל­‪ ,6k‬אז‪ 6k/4k ‬ייתן‪ ‬לנו‪ ‬את‪ ‬מספר‪ ‬הדף ‪ ‬מתחילת‪ ‬הקובץ‪ ‬ו­‬
‫‪ 6kmod4k‬נותן‪ ‬לנו‪ ‬את‪ ‬ה­‪ .offset‬‬
‫‪ ‬‬
‫‪53 ‬‬
‫כשאנחנו‪ ‬רוצים‪ ‬להוציא‪ ‬מידע‪ ‬החוצה‪ ‬מהזיכרון‪ ‬הוירטואלי ‪ ‬צריך‪ ‬להחליט‪ ‬לאן‪ ‬הוא‪ ‬הולך‪ ,‬אם‪ ‬הוא‪ ‬הגיע‪ ‬כ­‪ memory‬‬
‫‪ map‬מקובץ‪ ‬בד"כ‪ ‬נחזיר ‪ ‬אותו‪ ‬חזרה‪ ‬לקובץ‪ .‬ניתן‪ ‬לבצע‪ ‬מיפוי‪" ‬פרטי"‪ ‬שאומר‪ ‬שאם‪ ‬משנים‪ ‬את‪ ‬המידע‪ ‬אנחנו‪ ‬לא‪ ‬‬
‫רוצים‪ ‬שיחזור‪ ‬לקובץ‪ ‬ואז‪ ‬עושים‪ ‬סוג‪ ‬של‪ ,COW ‬כל‪ ‬עוד ‪ ‬לא‪ ‬משנים‪ ‬הוא‪ ‬מגיע‪ ‬מהקובץ‪ ,‬וברגע‪ ‬שמשנים ‪ ‬עושים‪ ‬‬
‫העתק‪ ‬ולא‪ ‬מעדכנים‪ ‬את‪ ‬הקובץ‪ ‬המקורי‪ .‬‬
‫נניח‪ ‬שיש‪ ‬לנו‪ Page ‬שמקורו‪ ‬בזיכרון‪ ,‬ורוצים‪ ‬לזרוק‪ ‬אותו‪ .‬אם‪ ‬המידע‪ ‬לא‪ ‬השתנה‪ ‬בפועל‪ ‬אז‪ ‬לא‪ ‬צריך‪ ‬לעשות‪ ‬לו‪ ‬‬
‫‪ ,page out‬אם‪ ‬שיניתי‪ ‬אותו‪ ‬אז‪ ‬יש‪ ‬צורך‪ ‬לעשות‪ ‬לו‪ .map out ‬אם‪ ‬זה‪ ‬עותק‪ ‬פרטי‪ ‬שלנו‪ ‬נצטרך‪ ‬למצוא‪ ‬לו‪ ‬מקום‪ ‬‬
‫בדיסק‪ ,‬אם‪ ‬זה‪ ‬עותק‪ ‬כללי‪ ‬נצטרך‪ ‬לעדכן‪ ‬את‪ ‬המקור‪ .‬‬
‫‪ ‬‬
‫‪ Demand ­ Paging‬‬
‫מה‪ ‬קורה‪ ‬כשקוראים‪ ‬נתון‪ ‬מהדיסק?‪ ‬מתי‪ ‬הוא‪ ‬יגיע‪ ‬לזיכרון?‪ ‬‬
‫השיטה‪ ‬הבסיסית‪ ‬היא‪ ‬שרק‪ ‬כאשר‪ ‬ניגשים‪ ‬לנתון‪ ‬והוא ‪ ‬לא‪ ‬נמצא‪ ‬שם‪ ‬יוצרים‪ Page Fault ‬ומביאים‪ ‬את‪ ‬המידע‪ .‬‬
‫הבעייתיות‪ ‬היא‪ ‬שבכל‪ ‬פעם‪ ‬שניגש‪ ‬לזיכרון‪ ‬בפעם‪ ‬הראשונה‪ ‬נקבל‪ ,Page Fault ‬מה‪ ‬שמשמעותי‪ ‬לזמן‪ ‬הריצה‪ ‬של‪ ‬‬
‫התוכנית‪ .‬‬
‫יש‪ ‬כל‪ ‬מיני‪ ‬שיפורים‪ ‬שמנסים‪ ‬לפתור‪ ‬זאת‪ ,‬למשל‪ ‬מנסים‪ ‬לזהות‪ ,Patterns ‬אם ‪ ‬רואים‪ ‬שאנו‪ ‬עושים‪ ‬גישות‪ ‬סדרתיות‪ ‬‬
‫לדיסק‪ ‬נביא‪ ‬גם‪ ‬את‪ ‬הדפים‪ ‬שלידו‪ ,‬כלומר‪ ‬גם‪ ‬את‪ ‬הדף‪ ‬שלפניו‪ ‬וגם‪ ‬את‪ ‬הדף‪ ‬שאחריו‪ ‬וזה‪ ‬משפר‪ ‬משמעותית‪ ‬את‪ ‬‬
‫הביצועים‪ .‬כלומר‪ ‬בודקים‪ ‬האם‪ ‬יש‪ ‬לוקליות‪ ‬במקום‪ ‬מביאים‪ ‬עוד‪ ‬דפים‪ ‬מהאיזור‪ ‬שממנו‪ ‬קראנו‪ ‬הרבה‪ .‬‬
‫‪ ­ readahead prefetching‬הפונקציות‪ mmap ‬ו­‪ read ‬מבצעות‪ prefetching ‬כאשר‪ ‬הם‪ ‬מזהות‪ ‬גישה‪ ‬סידרתית‪ .‬‬
‫גישה‪ ‬זאת‪ ‬משלימה‪ ‬את‪ demand ­ paging ‬כדי‪ ‬לנסות‪ ‬למזער‪ ‬את‪ ‬מספר‪ ‬חריגות‪ ‬הדף‪ .‬‬
‫‪ ‬‬
‫‪ Working Set‬‬
‫ניסיון‪ ‬של‪ ‬מערכת‪ ‬ההפעלה‪ ‬להבין‪ ‬באיזה‪ ‬דפים‪ ‬תהליך‪ ‬מסוים‪ ‬משתמש‪ .‬הוא‪ ‬מבוסס‪ ‬על‪ ‬טבלה‪ ‬ומסתכלים‪ ‬על ‪ ‬‬
‫ההיסטוריה‪ ‬של‪ ‬שלושת‪ ‬הגישות‪ ‬האחרונות‪ ‬לדיסק‪ .‬‬
‫מה‪ ‬נותנת‪ ‬לנו ‪ ‬הסטטיסטיקה‪ ‬הזאת?‪ ‬מערכת‪ ‬ההפעלה ‪ ‬יכולה‪ ,‬לפני‪ ‬שהיא‪ ‬נותנת‪ ‬לתהליך‪ ‬לרוץ‪ ,‬לבדוק‪ ‬האם‪ ‬כל‪ ‬‬
‫הדפים ‪ ‬ב­‪ Working Set‬שלו‪ ‬בזיכרון‪ ,‬ואם‪ ‬לא‪ ‬היא‪ ‬לא‪ ‬תיתן‪ ‬לו‪ ‬לרוץ ‪ ‬עד‪ ‬שהם ‪ ‬יהיו‪ ‬בזיכרון‪ .‬כך‪ ‬מערכת‪ ‬ההפעלה‪ ‬‬
‫תתדאג‪ ‬שלפחות‪ ‬הדפים‪ ‬שהוא‪ ‬השתמש‪ ‬בהם‪ ‬לאחרונה‪ ‬יהיו‪ ‬בזיכרון‪ ‬וכך‪ ‬נימנע‪ ‬מ­‪ Page Fault‬בתחילת ‪ ‬ריצת‪ ‬‬
‫התהליך‪ Microsoft .‬משתמשים‪ ‬בשיטה‪ ‬הזאת‪ ‬בצורה‪ ‬מאוד‪ ‬חזקה‪ .‬‬
‫‪ ­ Thrashing‬כשהקצנו‪ ‬הרבה‪ ‬יותר‪ ‬זיכרון‪ ‬ממה‪ ‬שיש‪ ‬לנו‪ ‬אנו‪ ‬עלולים ‪ ‬להגיע‪ ‬למצב‪ ‬שכל‪ ‬תהליך‪ ‬מבצע‪ ,Page Fault ‬‬
‫מעבירים‪ ‬לריצה‪ ‬של‪ ‬תהליך‪ ‬אחר‪ ‬שמבצע‪ Page Fault ‬וכן‪ ‬הלאה‪ ,‬ללא‪ ‬התקדמות‪ ‬בביצוע‪ ‬הקוד‪ .‬זו‪ ‬הייתה‪ ‬בעיה‪ ‬‬
‫ממש‪ ‬חמורה‪ ‬בגרסאות‪ ‬ראשונות‪ ‬של‪ ,Windows ‬למשל‪ .Windows ME ‬‬
‫‪ ‬‬
‫האם‪ ‬הצלחנו‪ ­ ‬כלומר‪ ,‬האם‪ ‬הזיכרון‪ ‬הוירטואלי‪ ‬פותר‪ ‬את‪ ‬הבעיה? ‪ ‬‬
‫● ניתן‪ ‬בקלות‪ ‬לתת‪ ‬לתהליך‪ ‬זיכרון‪ ‬רציף‪ ,‬פשוט‪ ‬מגדירים‪ ‬מרחב‪ ‬כתובות‪ ‬וירטואלי‪ ‬רציף‪ .‬‬
‫● הצלחנו‪ ‬לאפשר‪ ‬לכל‪ ‬תהליך ‪ ‬לקבל‪ ‬יותר‪ ‬זיכרון‪ ‬מהמקום‪ ‬שיש‪ ‬בפועל‪ ‬בזיכרון‪ ‬הראשי‪ ‬באמצעות‪ ‬דפדוף‪ ,‬‬
‫אמנם‪ ‬אנו‪ ‬עלולים‪ ‬לקבל‪ ‬ריצה‪ ‬איטית‪ ‬יותר‪ ,‬אבל‪ ‬מעבר‪ ‬לכך‪ ‬זה‪ ‬עובד‪ .‬‬
‫● הצלחנו‪ ‬להפריד‪ ‬ולבודד‪ ‬תהליכים‪ ‬שונים‪ ,‬ע"י‪ ‬כך‪ ‬שפשוט‪ ‬לא‪ ‬ממפים‪ ‬אותם‪ ‬לאותו‪ ‬בלוק‪ ‬פיזי‪ .‬‬
‫● יש‪ ‬לנו‪ Access Control ‬בטבלת‪ ‬התרגום‪ ‬כנדרש‪ ,‬כבר‪ ‬ברמת‪ ‬החומרה‪ .‬‬
‫‪ ‬‬
‫אבל‪ ‬איך‪ ‬הביצועים?‪ ‬‬
‫הביצועים‪ ‬הם‪ ‬נקודה‪ ‬מאוד‪ ‬בעייתית‪ ,‬אם‪ ‬מקודם‪) ‬כשיש‪ ‬רק‪ ‬כתובות‪ ‬פיזיות(‪ ‬היינו‪ ‬ניגשים‪ ‬לזיכרון‪ ‬באופן‪ ‬ישיר‪ ‬‬
‫באמצעות‪ ‬הכתובת‪ ,‬כעת‪ ‬אנחנו‪ ‬צריכים‪ ‬לגשת ‪ ‬ראשית‪ ‬לזיכרון‪ ‬כדי‪ ‬לקרוא‪ ‬את‪ ‬טבלת‪ ‬התרגום ‪ ‬ופעם‪ ‬שניה‪ ‬בשביל‪ ‬‬
‫הנתונים‪.‬כלומר‪ ,‬הדבר‪ ‬הכי‪ ‬קריטי‪ ‬במערכת‪ ,‬גישה‪ ‬לנתונים‪ ,‬ייקח‪ ‬כעת‪ ‬פי‪ 2 ‬יותר‪ ‬זמן‪ .‬‬
‫‪54 ‬‬
‫‪ ‬‬
‫איך‪ ‬מטפלים‪ ‬בזה? ‪ ‬‬
‫כמו‪ ‬שאמרנו‪ ‬קודם‪ 90% ,‬מהזמן‪ ‬אנחנו‪ ‬ניגשים‪ ‬ל­‪ 10%‬מהנתונים‪) ‬לוקליות‪ ‬‬
‫במקום(‪ .‬לכן‪ ,‬בהרבה‪ ‬מקרים‪ ‬ה­‪ Cache‬יעזור‪ ‬לנו‪ ,‬לא‪ ‬נצטרך‪ ‬לבצע‪ ‬את‪ ‬הגישה‪ ‬‬
‫לדיסק‪ ‬עצמו‪ ,‬אלא‪ ‬ל­‪ .Cache‬כדי‪ ‬לשפר‪ ‬את‪ ‬מהירות‪ ‬התרגום‪ ‬לא‪ ‬נרצה‪ ‬‬
‫להסתמך‪ ‬על‪ L1 ‬ו­‪ ,L2‬זה‪ ‬יגרום‪ ‬לנו‪ ‬לבזבז‪ 2 ‬מחזורי‪ ‬שעון‪ ‬נוספים‪ ‬על‪ ‬תחילת‪ ‬‬
‫ביצוע‪ ‬פקודה‪ ,‬במקרה‪ ‬הטוב‪ .‬‬
‫לכן‪ ‬מגדירים‪ ,TLB ‬חומרה‪ ‬חיצונית‪ ‬מאוד‪ ‬קטנה‪ ‬ומהירה‪ ,‬בין‪ 32 ‬ל­‪ 128‬כניסות‪ ,‬‬
‫שמכילה‪ ‬אך‪ ‬ורק‪ ‬תרגומים‪ ­ ‬במיוחד‪ ‬את‪ ‬התרגומים‪ ‬האחרונים‪ ‬שעשינו‪ ,‬זה‪ ‬‬
‫בעצם‪ Cache ‬לכתובות‪ ‬וירטואליות‪ .‬‬
‫כמה‪ ‬הוא‪ ‬עוזר?‪ ‬מאוד!‪ ‬בתוכנית‪ ‬אופיינית‪ ‬ה­‪ hit rate‬הוא‪ ‬מעל‪ .99% ‬נבחין‪ ‬‬
‫שבכל‪ ‬פעם ‪ ‬שעושים‪ ‬החלפת‪ ‬הקשר‪ ‬התוכן‪ ‬של‪ ‬ה­‪ TLB‬כבר‪ ‬לא‪ ‬רלוונטי ‪ ‬ולכן‪ ‬יש‪ ‬‬
‫לעשות‪ ‬לו‪ ,flush ‬זו‪ ‬אחת‪ ‬הסיבות‪ ‬לכך‪ ‬שאחרי‪ ‬החלפת‪ ‬הקשר‪ ‬תהליך‪ ‬מתחיל‪ ‬‬
‫לרוץ‪ ‬לאט‪ ,‬ולוקח‪ ‬לו‪ ‬זמן‪" ‬להתחמם"‪ .‬מנסים‪ ‬לצמצם‪ ‬את‪ ‬ה­‪ Cold Start‬הזה‪ ,‬‬
‫אחד‪ ‬הדברים‪ ‬שעושים‪ ‬הוא‪ ‬שיש‪ ‬כניסות‪ ‬ב­‪ TLB‬שאף‪ ‬פעם‪ ‬לא‪ ‬נפסלות‪ ,‬למשל‪ ‬נתונים‪ ‬שקשורים‪ ‬למערכת‪ ‬ההפעלה‪ ‬‬
‫לא‪ ‬זזים‪ .‬לכן‪ ‬ליד ‪ ‬כל‪ ‬כניסה‪ ‬רשום‪ ‬האם‪ ‬זו ‪ ‬כתובת‪ ‬שצריך‪ ‬לפסול ‪ ‬אותה‪ ‬או‪ ‬שאין‪ ‬בכך‪ ‬צורך‪ ,(global) ‬כנ"ל‪ ‬בהחלפה‪ ‬‬
‫בין‪ ‬חוטים‪ ‬שאיזור‪ ‬הזיכרון‪ ‬הפיזי‪ ‬שלהם‪ ‬משותף‪ .‬‬
‫‪ ‬‬
‫מה‪ ‬עוד‪ ‬משפיע‪ ‬על‪ ‬הביצועים?‪ ‬‬
‫מדיניות‪ ‬החלפת‪ ‬הדפים‪) ‬אלגוריתם‪ ‬דפדוף(‪ ‬יכולה‪ ‬להשפיע‪ ‬כמובן‪ ‬באופן‪ ‬משמעותי‪ ‬על‪ ‬מספר ‪ ‬ה­‪ Page Faults‬‬
‫וכתוצאה‪ ‬מכך‪ ‬על‪ ‬זמן‪ ‬הריצה‪ .‬‬
‫‪ ‬‬
‫אלגוריתמי‪ ‬דפדוף‪ ‬‬
‫●‬
‫●‬
‫●‬
‫●‬
‫●‬
‫●‬
‫חמדן‪ / ‬בלדי‪ / ‬אופטימלי‪ ­ ‬כשיש‪ ‬צורך‪ ‬להוציא‪ ‬דף‪ ‬אנו‪ ‬בודקים‪ ‬איזה‪ ‬דף‪ ‬נצטרך‪ ‬בעוד‪ ‬הכי‪ ‬הרבה‪ ‬זמן‪ ‬ונזרוק‪ ‬‬
‫אותו‪ ,‬אבל‪ ‬אין‪ ‬דרך‪ ‬קלה‪ ‬לבצע‪ ‬את‪ ‬ההחלטה‪ ‬הזו‪ ‬בלי‪ ‬איזו‪ ‬מכונת‪ ‬זמן‪ .‬‬
‫‪ ­ FIFO‬אין‪ ‬יותר‪ ‬פשוט‪ ‬מזה‪ ,‬צריך‪ ‬לשמור‪ ‬עבור‪ ‬כל‪ ‬דף‪ ‬את‪ ‬זמן‪ ‬הכניסה‪ ‬שלו‪ ,‬נניח‪ ‬באמצעות‪ ‬רשימה‪ .‬‬
‫‪ ­ LRU‬נעיף‪ ‬את ‪ ‬זה‪ ‬שניגשנו‪ ‬אליו‪ ‬הכי‪ ‬הרבה‪ ‬זמן‪) ‬כמו‪ ‬בלדי‪ ‬על‪ ‬העבר(‪ ,Least Recently Used ,‬ברוב‪ ‬‬
‫המקרים‪ ‬הוא‪ ‬מתאים‪ ‬לנו‪ ,‬אבל‪ ‬איך‪ ‬נממש‪ ‬אותו?‪ ‬‬
‫○ בכל‪ ‬פעם‪ ‬שניגשים‪ ‬לדף‪ ‬צריך‪ ‬להעביר‪ ‬אותו‪ ‬ל"ראש‪ ‬הרשימה"‪ ,‬או‪ ‬לשמור‪ ‬את‪ ‬זמן‪ ‬הגישה‪ ‬האחרון‪ ‬‬
‫אליו‪ .‬אי‪ ‬אפשר‪ ‬לממש‪ ‬את‪ ‬זה‪ ‬בצורה‪ ‬יעילה‪ ‬ולכן‪ ‬לא‪ ‬משתמשים‪ ‬ב­‪ .LRU‬אלא‪ ‬בד"כ‪ ‬עושים‪ ‬‬
‫קירובים‪ ‬ל­‪ .LRU‬‬
‫‪ ­ Second Chance‬דומה‪ ‬מאוד‪ ‬ל­‪ ,NRU‬לכל‪ ‬דף‪ ‬אנחנו‪ ‬מצמידים‪ ‬דגל‪ ‬של‪ ,Accessed ‬מודלק‪ ‬ע"י‪ ‬‬
‫החומרה‪ ‬בכל‪ ‬פעם‪ ‬שהיא‪ ‬ניגשת‪ ‬לדף‪ .‬כשרוצים‪ ‬להעיף‪ ‬דף‪ ‬מהזיכרון ‪ ‬עושים‪ ‬סריקה‪ ‬של‪ ‬כל‪ ‬הדפים‪ ‬‬
‫באיזשהו‪ ‬סדר‪ ,‬מסתכלים‪ ‬על‪ .Accessed ‬אם ‪ ‬הוא‪ ,1 ‬הוא‪ ‬לא‪ ‬מעיף‪ ‬את‪ ‬הדף‪ ‬אבל‪ ‬מסמן‪ ‬שכבר‪ ‬נתנו‪ ‬לו‪ ‬‬
‫צ'אנס‪ ‬ונכבה‪ ‬את ‪ ,Access ‬ונמשיך‪ ‬לחפש‪ ‬עד‪ ‬שנמצא‪ ‬מישהו‪ ‬מכובה‪ .‬נניח‪ ‬שחזרנו‪ ‬כבר‪ ‬לאחר‪ ‬כמה‪ ‬גישות‪ ‬‬
‫לדף‪ ‬הראשון‪ ‬שלו‪ ,‬אם‪ ‬מערכת‪ ‬ההפעלה‪ ‬כבר‪ ‬ניגשה‪ ‬אליו‪ ‬פעם‪ ‬אחת‪ ,‬אז‪ ‬נמשיך‪ ‬כמו‪ ‬קודם‪ .‬אם‪ ‬הוא‪ ‬עדיין‪ 0 ‬‬
‫אז‪ ‬נזרוק‪ ‬אותו‪) ‬זה‪ ‬קירוב‪ ‬ל­‪ ,(LFU ­ Least Frequently Used‬‬
‫אלגוריתם‪ ‬שעון‪ ­ ‬וריאציה‪ ‬של ‪ ­ Second Chance ‬מסתכלים‪ ‬על‪ ‬הדפים‪ ‬בזיכרון‪ ‬כמעגל‪ ,‬יש‪ ‬לנו‪ ‬מצביע‪ ‬‬
‫לדף‪ ‬הבא‪ ‬שצריך‪ ‬לסרוק‪ .‬לכל‪ ‬דף‪ ‬יש‪ ‬את‪ ‬ביט‪ ‬ה­‪ ,Accessed‬ששווה‪ ‬לאחד‪ ‬או‪ ‬אפס‪ .‬‬
‫אלגוריתם‪ ­ LFU ­ OPT ‬באלגוריתם‪ ‬זה‪ ‬קיימת‪ ‬הנחה‪ ‬שאנו‪ ‬יודעים‪ ‬מראש‪ ‬מה‪ ‬הולך‪ ‬לקרות‪ .‬כלומר‪ ‬נדע‪ ‬‬
‫מראש ‪ ‬מתי‪ ‬נזדקק‪ ‬לכל‪ ‬דף‪) ‬דבר‪ ‬שכמובן‪ ‬לא‪ ‬קורה‪ ‬במציאות(‪ .‬במקרה‪ ‬זה‪ ‬נוציא‪ ‬מהזיכורן‪ ‬תמיד‪ ‬את‪ ‬הדף‪ ‬‬
‫שלא‪ ‬יהיה‪ ‬בשימוש‪ ‬הכי‪ ‬הרבה‪ ‬זמן‪ .‬‬
‫‪55 ‬‬
‫‪ ‬‬
‫הירכיית ‪-2‬רמות‬
‫נבחין‪ ‬כי‪ ‬לכל‪ ‬תהליך‪ ‬נצטרך‪ ‬לפחות‪ 4MB ‬נתונים‪ ‬רק ‪ ‬בשביל‪ ‬הטבלה‪ ‬שלו‪) ‬מספר‪ ‬הדפים‪ ‬הוא‪ .(2^20 ‬יש‪ ‬כאן‪ ‬בזבוז‪ ‬‬
‫אדיר!‪ ‬‬
‫איך‪ ‬מונעים‪ ‬את‪ ‬הבזבוז‪ ‬הזה?‪ ‬צריך‪ ‬להבין‪ ‬שרוב ‪ ‬הכניסות‪ ‬בטבלה‪ ‬הזאת‪ ‬הן‪ ‬ריקות‪ ,‬ה­‪ Pages‬שם‪ ‬לא‪ ‬מוקצים‪ ‬‬
‫בכלל‪ ,‬שכן‪ ‬נדיר‪ ‬שתהליך‪ ‬משתמש‪ ‬בכל‪ ‬הזיכרון‪ ‬שלו‪ .‬נרצה‪ ‬שתהיה‪ ‬לנו‪ ‬יכולת‪ ‬לא‪ ‬להקצות‪ ‬את‪ ‬כל‪ ‬הטבלה‪ ‬הזאת‪ .‬‬
‫סתם‪ ‬לא‪ ‬להקצות‪ ‬נתונים‪ ‬לא‪ ‬יעזור‪ ‬לנו‪ ,‬אנחנו‪ ‬נראה‪ ‬כתובת‪ ‬זבל‪ ‬וננסה‪ ‬לגשת‪ ‬אליה‪ .‬לכן‪ ‬נפתור‪ ‬את‪ ‬זה‪ ‬בצורה‪ ‬דומה‪ ‬‬
‫לרעיון‪ ‬של‪ ‬הקצאת ‪ ‬הדפים‪ ,‬ניצור‪ ‬טבלה‪ ‬שממנה‪ ‬נצביע‪ ‬ל­‪ Page Table‬המקורי‪ .‬זה‪ ‬בעצם‪ ‬מערך‪ ‬דו‪ ‬מימדי!‪ ‬פשוט‪ ‬‬
‫לא‪ ‬צריך ‪ ‬להקצות‪ ‬כל‪ ‬תת‪ ‬טבלה‪ ‬אם‪ ‬לא‪ ‬ניגשים‪ ‬לכתובות‪ ‬שם!‪ ‬במקרה ‪ ‬הטבלה ‪ ‬שלנו‪ ‬נכנסת‪ ‬בדיוק‪ ‬לדף‪ ‬אחד‪ ,‬זה‪ ‬‬
‫לא‪ ‬בהכרח‪ ‬ככה‪ ,‬ניתן‪ ‬היה‪ ‬לבחור‪ ‬גודל‪ ‬אחר‪ ‬ל­‪ .Page Table‬‬
‫רמה‪ ‬שונה‪ ‬נקבעת‪ ‬לפי‪ ‬גודל‪ ‬של‪ PTE ‬ומספר‪ ‬הכניסות‪ ‬שהיא‪ ‬צריכה‪ ‬להכיל‪ .‬‬
‫‪ ‬‬
‫‪ ‬‬
‫איך‪ ‬נמפה‪ ‬את‪ ‬הכתובת‪ ‬הוירטואלית?‪ ‬‬
‫‪ ‬‬
‫הערה‪ :‬הסבר‪ ‬מפורט‪ ‬יותר‪ ‬בשקפים‪ ,‬בדיוק‪ ‬כמו‪ ‬בממ"ס‪ .‬‬
‫‪ ‬‬
‫היחס‪ ‬בין‪ ‬החומרה‪ ‬והתוכנה‪ ‬‬
‫חשוב‪ ‬להבין‪ ‬שהגודל‪ ‬של‪ ‬כל‪ ‬הטבלאות‪ ,‬הפורמט‪ ‬והמבנה‪ ‬של ‪ ‬הטבלאות‪ ,‬נקבע‪ ‬לפי‪ ‬החומרה‪ .‬מי‪ ‬שאחראי‪ ‬לתחזק‪ ‬‬
‫ולמלא‪ ‬את‪ ‬הטבלאות‪ ‬זה‪ ‬מערכת‪ ‬ההפעלה‪ .‬מי‪ ‬שמשתמש‪ ‬בנתוני‪ ‬העזר‪ ‬שנמצאים‪ ‬שם‪ ‬זה‪ ‬בעיקר‪ ‬החומרה‪ ,‬וכן ‪ ‬‬
‫מערכת‪ ‬ההפעלה‪ ‬שצריכה‪ ‬לתקן‪ ‬את‪ ‬הנתונים‪ ‬שיש‪ ‬שם‪ .‬‬
‫בנוסף‪ ‬לניהול‪ ‬הטבלאות‪ ‬שעושה‪ ‬מערכת‪ ‬ההפעלה‪ ‬גם‪ ‬החומרה‪ ‬עושה‪ ‬עדכונים‪ ‬בתוך‪ ‬הטבלאות‪ ‬הללו‪ ,‬כל‪ ‬פעם‪ ‬‬
‫שהיא‪ ‬ניגשת‪ ‬לאיזשהו‪ PTE ‬היא‪ ‬יכולה‪ ‬לשנות‪ ‬אותו‪ ,‬להדליק‪ ‬את‪ ‬הדגל‪ Accessed, Dirty ‬וכד'‪ .‬‬
‫‪ ‬‬
‫נבחין‪ ‬כי‪ ‬במודל‪ ‬הנוכחי‪ ‬יש ‪ ‬לנו‪ ‬שלוש‪ ‬גישות‪ ‬לזיכרון‪ ‬בכל‪ ‬פעם‪ ,‬בגלל‪ ‬שיש ‪ ‬שתי‪ ‬רמות‪ ‬של‪ ‬טבלאות‪ ‬תרגום‪ ,‬לכן‪ ‬תפקוד‪ ‬‬
‫ה­‪ TLB‬הופך‪ ‬להיות‪ ‬אפילו‪ ‬יותר‪ ‬קריטי!‪ ‬עוד‪ ‬נקודה‪ ‬שמאוד‪ ‬חשובה‪ ‬במערכת‪ ‬זה‪ ‬התפקיד‪ ‬שמשחקים‪ ‬ה­‪­cache‬ים‪ ,‬‬
‫בעיקר‪ L2 ‬אך‪ ‬גם‪ .L1 ‬‬
‫‪56 ‬‬
‫למה?‪ ‬ניזכר ‪ ‬שאמרנו‪ ‬שהבלוק ‪ ‬ששמים‪ ‬ב­‪ ,L2‬זה‪ ‬לא‪ ‬מילה‪ (32bit) ‬אלא‪ 32 ‬בתים‪ ,‬כלומר‪ ‬ל­‪ L2‬מגיע‪ ‬בלוק‪ ‬יותר‪ ‬‬
‫גדול‪ ,‬מביאים‪ ‬לא‪ ‬רק‪ ‬את‪ ‬המילה‪ ‬אלא‪ 8 ‬מילים‪ .‬‬
‫למה‪ ‬זה ‪ ‬חשוב?‪ ‬הוא ‪ ‬בעצם‪ ‬משפר‪ ‬לנו‪ ‬את‪ ‬פעולת‪ ‬ה­‪ ,TLB‬נניח‪ ‬כי‪ ‬תירגמנו‪ ‬ב­‪ TLB‬כתובת‪ ‬של‪ ‬דף‪ ‬מסוים‪ ,‬ה­‪ PTE‬‬
‫שהבאנו‪ ‬נמצא‪ ‬ב­‪ ,cache‬ולכן ‪ ‬ב­‪ L2‬יש‪ ‬לנו‪ ‬גם‪ ‬את‪ ‬ה­‪ PTEs‬הבאים‪ ‬בטבלה‪ ,‬וכשנרצה‪ ‬לקרוא‪ ‬דף‪ ‬שנמצא‪ ‬אחריו‪ ‬‬
‫בזיכרון‪ ‬הוירטואלי‪ ‬נקבל‪ ‬כי‪ ‬ה­‪ PTE‬שלו‪ ‬עצמו‪ ‬כבר‪ ‬נמצא‪ ‬בזיכרון‪ ‬ולכן‪ ‬נחסוך‪ .‬‬
‫‪ ‬‬
‫שאלה‪ ‬לדוגמא‪) ‬מבחינה(‪ ‬‬
‫נתון‪ ‬זיכרון‪ ‬שמנוהל‪ ‬ע"י‪ ‬טבלאות‪ ‬הדפים‪ ‬ב­‪ 4‬רמות‪ .‬מרחב‪ ‬הזיכרון‪ ‬הוא‪ ,64bit ‬גודל‪ ‬הדף‪ ‬הינו‪ ,64KB ‬וגודל‪ ‬כניסה‪ ‬‬
‫ברשימת‪ ‬הדפים‪ .128bit ‬מהו‪ ‬הגודל‪ ‬של‪ ‬הרמה‪ ‬הראשונה‪ ‬וכמה‪ ‬דפים‪ ‬היא‪ ‬מכילה?‪ ‬פרטו‪ ‬את‪ ‬החישובים‪ .‬‬
‫פתרון‪ :‬‬
‫טיפ‪ ­ ‬לשים‪ ‬לב‪ ‬ליחידות‪ ­ ‬ביטים‪ ‬הם‪ ‬לא‪ ‬בתים!‪ ‬‬
‫נשים‪ ‬לב‪ ‬כי‪ ‬גודל‪ ‬דף‪ 216 ­ ‬בתים‪ ,‬וגודל‪ PTE ‬הוא‪ 24 ‬בתים‪ .‬‬
‫‪ ‬‬
‫דרך‪ ‬ארוכה‪ :‬‬
‫קל‪ ‬לראות‪ ‬שצריך‪ 16 ‬ביטים‪ ‬ל‪ offset‬כדי‪ ‬להגיע‪ ‬לכל‪ ‬בית‪ ‬בתוך‪ ‬בלוק‪ .‬‬
‫כמה‪ ‬דפים‪ ‬יש‪ ‬לנו?‪ ‬‬
‫‪64‬‬
‫‪48‬‬
‫‪2‬‬
‫גודל‪ ‬זיכרון‪ ‬וירטואלי‪ ‬לחלק‪ ‬לגודל‪ ‬דף‪ .‬כלומר ‪ , 216‬וקיבלנו ‪ . 2‬‬
‫לכן‪ ‬מספר‪ ‬הכניסות‪ ‬הכולל‪ ‬ב­‪ L4‬הוא‪ . 248 ‬‬
‫והגודל‪ ‬הכולל‪ ‬הוא‪ . 248 ‬כפול‪ ‬הגודל‪ ‬של‪ ­ PTE ‬סה"כ‪ . 252 ‬‬
‫‪ ‬‬
‫‪38‬‬
‫כמה‪ ‬דפים‪ ‬צריך‪ ‬ל­‪ ?L4‬הגודל‪ ‬שלו‪ ‬לחלק‪ ‬לגודל‪ ‬דף‪ ‬סה"כ‪ . 2 ‬‬
‫‪16‬‬
‫ולכן‪ ‬על‪ ‬אותה‪ ‬הדרך‪ ‬מקבלים‪ ‬את‪ ‬הגודל‪ ‬של‪ L3 ‬וכן‪ ‬הלאה‪ ‬עד‪ ‬שנגיע‪ ‬שגודל‪ L1 ‬הוא‪ . 2 ‬וסה"כ‪ ‬היא‪ ‬מכילה‪ ‬דף‪ ‬‬
‫אחד‪ .‬‬
‫הערה‪ :‬מקובל‪ ‬שגודל ‪ ‬הבלוקים‪ ‬בטבלאות‪ ‬הינו‪ ‬כגודל‪ ‬דף‪ ,‬פשוט‪ ‬כי‪ ‬זה‪ ‬נוח‪ ‬יותר‪) ‬וכך‪ ‬נניח(‪ ,‬אבל‪ ‬ברמת‪ ‬העיקרון‪ ‬זה‪ ‬‬
‫לא‪ ‬נכון‪ .‬‬
‫‪ ‬‬
‫דרך‪ ‬קצרה‪ ‬ופשוטה‪ :‬‬
‫נבחין‪ ,‬כל‪ ‬שכבת‪ ‬ביניים‪ ‬אליה ‪ ‬מגיעים‪ ‬היא‪ ‬בגודל‪ ‬דף‪ ,‬וגודל‪ PTE ‬בה‪ ‬הוא‪ ‬קבוע‪ ,‬מכאן‪ ‬ניתן‪ ‬להסיק‪ ‬באופן‪ ‬מיידי‪ ‬את‪ ‬‬
‫מספר‪ ‬הביטים‪ ‬שצריך‪ ‬בכל‪ ‬שלב‪ ‬כדי‪ ‬למפות‪ ‬בתוך‪ ‬הטבלה‪ ,‬במקרה‪ ‬הזה‪ .12 ‬‬
‫זה‪ ‬נכון‪ ‬רק‪ ‬לשלבי‪ ‬ביניים‪ ‬ולא‪ ‬ל­‪ !L1‬‬
‫‪12‬‬
‫מספר‪ ‬הביטים‪ ‬ב­‪ L1‬הוא‪ ‬פשוט‪ ‬מה‪ ‬שנותר‪ ‬ומכאן‪ ‬נקבל‪ ‬את‪ ‬גודלו‪ ‬של‪ L1 ‬שבמקרה‪ ‬זה‪ ‬יוצא‪ 2 ‬כפול‪ ‬גודל‪ ‬של‪ ‬‬
‫‪ .PTE‬‬
‫היו‪ ‬יכולים‪ ‬לקבוע‪ ‬גודל‪ ‬של‪ ‬בלוקים‪ ‬לכל‪ ‬רמה‪ .‬‬
‫‪ ‬‬
‫שאלה‪ ‬נוספת‪) ‬מבחינה(‪ ‬‬
‫אנומלית‪ ‬בלדי‪ ­ ‬ייתכן‪ ‬שאני‪ ‬מגדיל‪ ‬זיכרון‪ ‬וכתוצאה‪ ‬מכך‪ ‬מקבלים‪ ‬יותר‪ .Page Faults ‬‬
‫‪ .‬‬
‫קיימים‪ d,r ‬כך‪ ‬ש­‬
‫כאשר‪ P F A ‬הוא‪ ‬מספר‪ ‬ה­‪ page faults‬עבור‪ ‬אלגוריתם‪ .A ‬‬
‫הגדלת‪ ‬הזיכרון‪ ‬עושה‪ ‬לנו‪ ‬נזק!‪ ‬האלגוריתם‪ ‬המפורסם‪ ‬שסובל‪ ‬מהבעיה‪ ‬הזאת‪ ‬הוא‪ FIFO ‬ו­‪ LFU‬‬
‫‪ ‬‬
‫‪57 ‬‬
‫תכונת‪ ‬המחסנית‪ ‬של‪ ‬אלגוריתמים ‪ ­ ‬כשמריצים‪ ‬אלגורתם‪ A ‬אומרים‪ ‬שהוא‪ ‬מקיים‪ ‬תכונת‪ ‬מחסנית ‪ ‬אם‪ ‬בעת‪ ‬ריצת‪ ‬‬
‫האלגוריתם‪ ‬עם‪ ‬זיכרון‪ ‬גדול‪ ‬יותר‪ ‬איזור‪ ‬הזיכרון‪ ‬מכיל‪ ‬את‪ ‬כל‪ ‬הדפים‪ ‬שהיו‪ ‬בריצה‪ ‬הקודמת‪ ‬‬
‫לכל‪ d,r ‬‬
‫‪ ‬‬
‫אלגוריתמים‪ ‬שמקיימים‪ ‬זאת‪ ‬הם‪ ‬בלדי‪ ‬ו­‪ LRU‬‬
‫אלו‪ ‬תכונות‪ ‬סותרות‪ ‬מי‪ ‬שיש‪ ‬לו‪ ‬את‪ ‬האחת‪ ‬אין‪ ‬לו‪ ‬את‪ ‬השני‪ .‬‬
‫‪ ‬‬
‫האם‪ ‬ל­‪ LFU‬יש‪ ‬אנומליית‪ ‬בלדי?‪ ‬‬
‫כן‪ ,‬צריך‪ ‬להביא‪ ‬דוגמא‪ :‬‬
‫למשל‪ ,d=3 ‬נרצה‪ ‬לקבל‪ ‬בזיכרון‪ ‬בגודל‪ 3 ‬תוצאה‪ ‬יותר‪ ‬טובה‪ ‬‬
‫גישה‪ ‬א'‪ ­ ‬לנסות‪ ‬למצוא‪ ‬את‪ ‬הסדרה‪ ‬הזאת‪ ,‬זה‪ ‬משחק‪ ‬עם‪ ‬המודולו‪ ‬‬
‫גישה‪ ‬ב'‪ ­ ‬רוצים‪ ‬להראות‪ ‬ש­‪ LFU‬יכול‪ ‬להתנהג‪ ‬כמו‪ ,FIFO ‬ידוע‪ ‬שיש‪ ‬סדרה‪ ‬שדופקת‪ ‬את‪ .FIFO ‬‬
‫נניח‪ ‬שמסתכלים‪ ‬על‪ ‬הסדרה‪ ‬ורואים‪ ‬ש­‪ FIFO‬זורק‪ ‬את‪ ‬הדף‪ E ‬ומשאיר‪ ‬את‪ A,B ‬נשנה‪ ‬את‪ ‬הסדרה‪ ‬באופן‪ ‬הבא‪ :‬‬
‫נוסיף‪ ‬רצף‪ ‬ארוך ‪ ‬של‪ A ‬ו­‪ ,B‬כשיגיע‪ ‬זמן‪ ‬להכניס‪ ‬את‪ D ‬מבחינת‪ FIFO ‬לא‪ ‬עשינו‪ ‬שום‪ ‬דבר‪ ,‬מבחינת‪ LFU ‬אנחנו‪ ‬‬
‫מוודאים‪ ‬שהם‪ ‬לא‪ ‬יעופו‪ ‬‬
‫עושים‪ ‬זאת‪ ‬עבור‪ ‬כל‪ page fault ‬וקיבלנו‪ ‬סדרה‪ ‬מתאימה‪ ‬ל­‪ !LRU‬‬
‫‪ ‬‬
‫זיכרון וירטואלי ‪ -‬כתובות פיזיות של ‪64bit‬‬
‫כעת‪ ‬נחזור‪ ‬על‪ ‬הדוגמאות‪ ‬שעשינו‪ ‬קודם‪ ‬לכן‪ ,‬אך‪ ‬הפעם‪ ‬עם‪ ‬מחשבים‪ ‬בעלי‪ 64bit ‬של‪ ‬כתובת‪ ‬פיזית‪ .‬‬
‫‪ 64‬ביטים‪ ‬הם‪ ‬הרבה‪ ‬יותר‪ ‬ממה‪ ‬שאנחנו‪ ‬בפועל‪ ‬צריכים‪ Exabyte) ‬של‪ ‬כתובות‪ ,(RAM ‬לאחר‪ ‬הערכות‪ ‬שונות‪ ‬הגיעו‪ ‬‬
‫למסקנה‪ ‬ש­‪ 48‬ביטים‪ ‬צריכים‪ ‬להספיק‪ ‬לכל‪ ‬המחשבים‪ ‬היום‪ Petabyte) ‬של‪ ‬כתובות‪ RAM ‬ניתנות‪ ‬לייצוג(‪ ,‬מה ‪ ‬‬
‫שנקרא‪ ‬‬
‫‪48‬‬
‫‪2 bit ought to be enough for anybody ‬‬
‫‪ ‬‬
‫לכן‪ ‬נגדיר‪ ‬את‪ ‬הכתובות‪ ‬הוירטואליות‪ ‬שלנו‪ ‬כ­‪ 64‬ביטים‪ ,‬ומהם‪ ‬נגיע‪ ‬ל­‪ 48‬ביטים‪ ‬של‪ ‬כתובת‪ ‬פיזית‪ .‬‬
‫משיקולים‪ ‬כאלה‪ ‬ואחרים‪ ‬החליטו‪ ‬שגודל‪ ‬של‪ ‬דף‪ ‬לא‪ ‬צריך‪ ‬להשתנות‪ ,‬אם‪ ‬נחלק‪ ‬את‪ ‬כמות‪ ‬המידע‪ ‬הפיזי‪ ‬בגודל‪ ‬הדף‪ ‬‬
‫נקבל‪ ‬כי‪ ‬יש‪ ‬לנו‪ 236 ‬מסגרות‪ ,‬נבחין‪ ‬כי‪ ‬כל‪ PTE ‬צריך‪ ‬להכיל‪ 36 ‬ביטים‪ ‬שמייצגים‪ ‬את‪ ‬המסגרת‪ ,‬לכן‪ ‬במערכת‪ ‬‬
‫הזאת‪ PTE ‬הוא‪ 8 ‬בתים‪ ,‬מה‪ ‬שיתן‪ ‬לנו‪ .64bit ‬‬
‫‪ ‬‬
‫חישוב‪ ‬כתובות‪ ‬בהיררכיית‪ 4 ‬רמות‪ ‬‬
‫● ‪ ­ offset‬גודלו‪ ‬לא‪ ‬ישתנה‪ ‬ויישאר‪ 12 ‬ביטים‪ ,‬כי‪ ‬גודל‪ ‬דף‪ ‬הוא‪ .4kb ‬‬
‫● את‪ ‬שאר‪ ‬החישובים‪ ‬נעשה‪ ‬בדומה‪ ‬לתרגיל‪ ‬שפתרנו‪ ,‬הוחלט‪ ‬כי‪ ‬גם‪ ‬בתרגיל‪ ‬זה‪ ‬גודל‪ ‬הטבלה‪ ‬הינו‪ ‬גודל‪ ‬דף‪ ,‬‬
‫גודל‪ PTE ‬הוא‪ 8bits ‬ולכן‪ ‬יש‪ 2 ‬בחזקת‪ 9 ‬כניסות‪ ‬בכל‪ ‬רמה‪ .‬‬
‫● מתעלמים‪ ‬מהביטים‪ ‬העליונים‪ ‬שכן‪ ‬אין‪ ‬בהם‪ ‬צורך‪ .‬‬
‫נבחין‪ ‬כי‪ ‬החישובים‪ ‬עצמם‪ ‬עובדים‪ ‬באותו‪ ‬האופן‪ ,‬רק‪ ‬היינו‪ ‬צריכים‪ ‬להתייחס‪ ‬להבדלים‪ ‬בגודלי‪ ‬הטבלאות‪ .‬‬
‫‪ ‬‬
‫‪64 bit PowerPc‬‬
‫מריץ‪ ‬אפליקציות‪ ‬עם‪ ‬כתובות‪ ‬וירטואליות‪ ‬של‪ ,64bit ‬אך‪ ‬הוא‪ ‬נועד‪ ‬כדי‪ ‬להריץ‪ ‬הרבה‪ ‬אפליקציות‪ ‬במקביל‪ ,‬ולכן‪ ‬נרצה‪ ‬‬
‫למנוע‪ ‬מצב‪ ‬בו‪ ‬תהליך‪ ‬אחד‪ ‬שרץ‪ ‬לא‪ ‬יעיף‪ ‬בטעות‪ ‬דפים‪ ‬של‪ ‬תהליך‪ ‬אחר‪ ,‬לכן‪ ‬הרכיבו‪ ‬ארכיטקטורה‪ ‬שונה‪ ‬לגמרי‪ ‬‬
‫ממה‪ ‬שאנחנו‪ ‬מכירים‪ ‬מהמערכות‪ ‬של‪ ‬אינטל‪ .‬‬
‫‪58 ‬‬
‫‪ ‬‬
‫במערכת‪ ‬של‪ PPC ‬יש‪ ‬שלוש‪ ‬רמות‪ ‬שונות‪ ‬‬
‫● רמה‪ ‬עליונה‪ ­ effective space ­ ‬כל‪ ‬כתובת‪ ‬שם‪ ‬היא‪ 64bit ‬והיא‪ ‬בדיוק‪ ‬הכתובת‪ ‬הוירטואלית‪ ‬שאנו‪ ‬‬
‫מכירים‪ ‬ב­‪ ,x86‬לכל‪ ‬תהליך‪ ‬יש‪ ‬מרחב‪ ‬כתובות‪ ‬ייחודי‪ ‬לו‪ ,‬ההבדל‪ ‬הוא‪ ‬במיפוי‪ .‬‬
‫● הכתובות‪ ‬הללו‪ ‬ממופות‪ ‬למרחב‪ ‬וירטואלי‪ ‬שונה‪ ‬לחלוטין‪ ‬מזה‪ ‬של‪ ,x86 ‬הכתובת‪ ‬של‪ ‬המרחב‪ ‬הוירטואלי‪ ‬היא‪ ‬‬
‫של‪ 80bits ‬והיא‪ ‬משותפת‪ ‬לכל‪ ‬התהליכים‪ ,‬כלומר‪ ‬כל‪ ‬כתובת‪ ‬וירטואלית‪ ‬פרטית‪ ‬של‪ ‬תהליך‪ ‬ממופה‪ ‬‬
‫לכתובת‪ ‬במרחב‪ ‬הוירטואלי‪ ‬הגלובלי‪ .‬‬
‫● הכתובת‪ ‬במרחב‪ ‬הוירטואלי‪ ‬ממופות‪ ‬לכתובת‪ ‬פיזית‪ ‬של‪ ,62bit ‬כפי‪ ‬שמוגדר‪ ‬בארכיטקטורה‪ .‬‬
‫מהו‪ ‬גודל‪ ‬הדף?‪ ‬בארכיטקטורה‪ ‬של‪ ‬אינטל‪ ‬בחרנו‪ ‬בגודל‪ ‬דף‪ ‬קטן‪ ‬על‪ ‬מנת‪ ‬למנוע‪ ‬שברור‪ ,‬זאת‪ ‬למרות‪ ‬שבלוקים‪ ‬‬
‫גדולים‪ ‬היו‪ ‬משפרים‪ ‬את‪ ‬פעולת‪ ‬ה­‪ .TLB‬‬
‫נבחין‪ ‬כי‪ ‬גודל‪ ‬דף‪ ‬ברמה‪ ‬העליונה‪ ‬יכול‪ ‬להיות‪ ‬גדול‪ ‬יותר‪ ‬שכן‪ ‬יש‪ ‬עוד‪ ‬רמת‪ ‬ביניים‪ ‬שמפרידה‪ ‬בין‪ ‬הזיכרון‪ ‬האפקטיבי‪ ‬של‪ ‬‬
‫תהליך‪ ‬לבין‪ ‬הרמה‪ ‬הפיזית‪ ‬של‪ ‬החומרה‪ ,‬ולכן‪ ‬כדי‪ ‬לשפר‪ ‬את‪ ‬הביצועים‪ ‬של‪ TLB ‬נתעסק‪ ‬ברמת‪ ‬ה­‪ effective‬‬
‫בדפים‪ ‬ענקיים‪ ‬של‪ ,256MB ‬מתוך‪ 64bit ‬של‪ ‬כתובת‪ ‬וירטואלית‪ 28 ‬הם‪ ,offset ‬ושאר‪ ‬הביטים‪ ‬מייצגים‪ ‬את‪ ‬מספר‪ ‬‬
‫הדף‪ ‬במרחב‪ ‬הוירטואלי‪ .‬‬
‫כלומר‪ ‬כשרוצים‪ ‬לתרגם‪ ‬כתובת‪ ‬וירטואלית‪ ‬לכתובת‪ ‬של‪ ‬המרחב‪ ‬הוירטואלי‪ 28 ,‬תחתונים‪ ‬לא‪ ‬מתורגמים‪ .‬‬
‫‪ 36‬הביטים‪ ‬הנותרים‪ ‬צריך‪ ‬לתרגם‪ ‬ל­‪ 52‬ביטים‪) ‬שיהיו‪ ‬משותפים‪ ‬לכל‪ ‬התהליכים(‪ ,‬ע"י‪ ‬ה­‪ .SLB‬‬
‫שאלת‪ ‬הבנה‪ :‬מדוע‪ ‬אנחנו‪ ‬צריכים‪ 52 ‬ביטים‪ ‬של‪ ‬דפים‪ ‬וירטואלים‪ ‬אם‪ ‬יש‪ ‬לנו‪ ‬רק‪ 36 ‬ביטים‪ ‬כאלה‪ ‬בכתובת‪ ‬‬
‫הוירטואלית‪ ‬המקורית‪ ‬של‪ ‬תהליך?‪ ‬נזכור‪ ‬כי‪ ‬המרחב‪ ‬הוירטואלי‪ ‬משותף‪ ‬לכל‪ ‬התהליכים‪ ‬ונרצה‪ ‬שיהיה‪ ‬מקום‪ ‬ליותר‪ ‬‬
‫מהכתובות‪ ‬של‪ ‬תהליך‪ ‬אחד‪ .‬‬
‫‪ ‬‬
‫השלב ‪ ‬השני‪ ‬הוא‪ ‬תרגום‪ ‬של‪ ‬כתובת‪ ‬וירטואלית‪ ‬לכתובת‪ ‬פיזית‪ ,‬כאן‪ ‬באמת‪ ‬נרצה‪ ‬לבחור‪ ‬בגודל‪ ‬דף‪ ‬קטן‪ ‬יותר‪ ‬ולא‪ ‬‬
‫לעבוד‪ ‬בבלוקים‪ ‬ענקיים‪ ,‬לכן‪ ‬גודל‪ ‬כל‪ frame ‬פיזי ‪ ‬הוא‪ 4KB ‬כמו‪ ‬ב­‪ ,x86‬ונתסכל‪ ‬על‪ 12 ‬ביטים‪ ‬תחתונים‪ ‬כעל‪ ‬ה­‬
‫‪ offset‬ו­‪ 68‬ביטים‪ ‬שהם‪ ‬מספר‪ ‬ה­‪ ,frame‬אותם‪ ‬נרצה‪ ‬לתרגם‪ ‬ל­‪ 50‬ביטים‪ ‬של‪ ‬מספר‪ ‬ה­‪ ,frame‬ע"י‪ ‬ה­‪ .TLB‬‬
‫‪68‬‬
‫שאלת‪ ‬הבנה‪ :‬איך‪ ‬זה‪ ‬שיש‪ ‬לנו‪ ‬כתובות‪ ‬וירטואליות‪ ‬הרבה‪ ‬יותר‪ ‬גדולות‪ ,‬כלומר‪ ‬כיצד‪ ‬ייתכן‪ ‬שאנו‪ ‬רוצים‪ ‬לתרגם‪ 2 ‬‬
‫מסגרות‪ ‬ל ‪ 250‬מסגרות?‪ ‬נזכור‪ ‬כי‪ ‬לא‪ ‬כל‪ ‬הכתובות‪ ‬הללו‪ ‬צריכות‪ ‬לשבת‪ ‬בפועל‪ ‬בזיכרון‪ ‬הפיזי‪ ,‬יכול‪ ‬להיות‪ ‬שחלקן‪ ‬‬
‫כלל‪ ‬לא‪ ‬הוקצו‪ ‬או‪ ‬שהן‪ ‬יושבות‪ ‬בזיכרון‪ ‬המשני‪ ,‬ומרחב‪ ‬הכתובות‪ ‬הוירטואלי‪ ‬יכול‪ ‬להיות‪ ‬גדול‪ ‬יותר‪ ‬ממרחב‪ ‬הכתובת‪ ‬‬
‫הפיזי‪ .‬‬
‫‪ ‬‬
‫נבחין‪ ‬כי‪ ‬לכתובת‪ ‬במרחב‪ ‬הוירטואלי‪ ‬מסתכלים‪ ‬בתרגום‪ ‬הראשון‪ ‬כ­‪ 28‬ביטים‪ ‬של‪ ,offset ‬ו­‪ 58‬של‪ ‬מספר‪ ‬דף‪ ‬‬
‫ובתרגום‪ ‬השני‪ ‬כ­‪ 12‬ביטים‪ ‬של‪ offset ‬ו­‪ 68‬ביטים‪ ‬של‪ ‬מספר‪ ‬דף‪ .‬‬
‫‪ ‬‬
‫‪ ­ SLB‬מנגנון‪ ‬חומרה‪ ,‬יש‪ ‬אחד‪ ‬ויחיד‪ ‬כזה‪ ,(singleton) ‬שומרת‪ ‬מיפויים‪ ‬אחרונים‪ ‬מכתובת‪ ‬במרחב‪ ‬אפקטיבי‪ ‬למרחב‪ ‬‬
‫וירטואלי‪ ,‬מכיל‪ ‬משהו‪ ‬כמו‪ 32 ‬כניסות‪ ‬והגישה‪ ‬אליו‪ ‬לוקחת‪ ‬בערך‪ ‬מחזור‪ ‬שעון‪ ‬אחד‪ .‬‬
‫‪ ‬‬
‫‪ ­ TLB‬לא‪ ‬להתבלבל‪ ‬עם‪ ‬האחד‪ ‬של‪ ‬אינטל‪ ,‬ה­‪ TLB‬הזה‪ ‬אף‪ ‬פעם‪ ‬לא‪ ‬נפסל‪ ‬בהחלפת‪ ‬הקשר‪ ,‬שכן‪ ‬המרחב‪ ‬‬
‫הוירטואלי‪ ‬אף‪ ‬פעם‪ ‬לא‪ ‬משתנה‪ ‬כשמחליפים‪ ‬בין‪ ‬תהליכים!‪ ‬יש‪ ‬בו‪ 1024 ‬כניסות‪ .‬‬
‫ה­‪ TLB‬נותן‪ ‬לנו‪ ‬מיפוי‪ ‬מכתובת‪ ‬וירטואלית‪ ‬לכתובת‪ ‬פיזית‪ + ‬וירטואלית‪ ‬ולכן‪ ‬כל‪ ‬שורה‪ ‬מכילה‪ 68+50 ‬ביטים‪ .‬‬
‫‪ ‬‬
‫אחד‪ ‬היתרונות‪ ‬של‪ ‬חלוקת‪ ‬המיפוי‪ ‬למיפוי‪ ‬ממרחב‪ ‬אפקטיבי‪ ‬לוירטואלי‪ ‬וממנו‪ ‬לפיזי‪ ‬היא‪ ‬שיפור‪ ‬משמעותי‪ ‬של‪ ‬פעולת ‪ ‬‬
‫ה­‪ Cache‬‬
‫עבור‪ ‬ה­‪ ,SLB‬הדפים‪ ‬הם‪ ‬הרבה‪ ‬יותר‪ ‬גדולים‪ ‬ולכן‪ ‬נקבל‪ ‬מספר‪ ‬קטן‪ ‬בהרבה‪ ‬של‪ .misses ‬‬
‫עבור‪ ‬ה­‪ ,TLB‬מכיוון‪ ‬שהמרחב‪ ‬הוירטואלי‪ ‬משותף‪ ‬לכל‪ ‬התהליכים‪ ‬נקבל‪ ‬כי‪ ‬אין‪ ‬צורך‪ ‬בביצוע‪ flush ‬לאחר‪ ‬החלפת‪ ‬‬
‫הקשר‪ ,‬ולכן‪ ‬גם‪ ‬מיד‪ ‬לאחר‪ ‬החלפת‪ ‬ההקשר‪ ‬נוכל‪ ‬לקבל‪ hits ‬בסבירות‪ ‬גבוהה‪ ‬יותר‪ .‬‬
‫‪59 ‬‬
‫מה‪ ‬שיהיה‪ ‬מאוד‪ ‬משמעותי‪ ‬במערכת‪ ‬שאמורה‪ ‬להריץ‪ ‬מספר‪ ‬רב‪ ‬של‪ ‬תהליכים‪ .‬‬
‫‪ ‬‬
‫נתאר‪ ‬תהליך‪ ‬תרגום‪ ‬‬
‫● חומרה‪ ‬ניגשת‪ ‬ל­‪ ,SLB‬אם‪ ‬לא‪ ‬הצליחה ‪ ‬להוציא ‪ ‬משם‪ ‬מידע‪ ‬נקבל‪ ,page fault ‬זאת ‪ ‬בניגוד‪ ‬לארכיטקטורה‪ ‬‬
‫של‪ ‬אינטל‪ ‬שם‪ ‬היינו‪ ‬עושים‪ ,page walk ‬זאת‪ ‬משום‪ ‬שלחומרה‪ ‬אין‪ ‬שום‪ ‬מידע ‪ ‬לאיך‪ ‬מערכת‪ ‬ההפעלה‪ ‬‬
‫מנהלת‪ ‬את‪ ‬המיפוי‪ ‬הזה‪ ,‬היא‪ ‬הולכת‪ ‬לטבלאות‪ ‬התרגום‪ ‬שלה‪ ‬ופשוט‪ ‬מעדכנת‪ ‬ב­‪ SLB‬את ‪ ‬הכתובת‪ ‬‬
‫המתאימה‪) ‬נזכור‪ ‬כי‪ ‬בשלב‪ ‬זה‪ ‬אין‪ ‬בכלל‪ ‬עניין‪ ‬של‪ ‬דפדוף‪ ‬והבאת‪ ‬כתובות‪ ‬מהזיכרון‪ ,‬מדובר‪ ‬בו‪ ‬מתרגום‪ ‬‬
‫מוירטואלי‪ ‬לוירטאלי‪ ‬ולכן‪ ‬החומרה‪ ‬לא‪ ‬חייבת‪ ‬לשחק‪ ‬תפקיד‪ ,‬כמו‪ ‬כן‪ ‬אנחנו‪ ‬עובדים‪ ‬עם‪ ‬דפים‪ ‬הרבה‪ ‬יותר‪ ‬‬
‫גדולים‪ ‬ויש‪ ‬היגיון‪ ‬בלנהל‪ ‬אותם‪ ‬מצד‪ ‬מערכת‪ ‬ההפעלה(‪ ‬‬
‫● חומרה‪ ‬ניגשת‪ ‬ל­‪ ,TLB‬אם‪ ‬היא‪ ‬לא‪ ‬הצליחה‪ ‬להוציא‪ ‬משם‪ ‬מידע ‪ ‬החומרה‪ ‬מנסה‪ ‬לבנות‪ ‬את‪ ‬התא‪ ‬ב­‪ TLB‬‬
‫בעצמה‪ .‬‬
‫הכיצד?‪ ‬בנוסף‪ ‬ל­‪ TLB‬יש‪ ­ HTAB ‬טבלת‪ hash ‬שמכילה‪ ‬כל ‪ ‬מיני‪ ‬תרגומים‪ ,‬החומרה‪ ‬מפעילה‪ ‬פונקציית‪ ‬‬
‫‪ hash‬ידועה ‪ ‬מראש‪ ,‬הטבלה‪ ‬עצמה‪ ‬גם‪ ‬קבועה‪ ‬ומוקצת‪ ‬בעליית‪ ‬המערכת‪ .‬החומרה‪ ‬ניגשת‪ ‬לתא‪ ‬המתאים‪ ‬‬
‫ובו ‪ 8 ‬תרגומים‪­8 ,‬כתובות‪ ‬וירטואליות‪ ‬ועבור‪ ‬כל‪ ‬אחת‪ ‬מהן ‪ ‬כתובת ‪ ‬פיזית ‪ ‬מתאימה‪) ‬זו‪ ‬הסיבה‪ ‬שאנחנו‪ ‬‬
‫שומרים‪ ‬את ‪ ‬התרגום ‪ ‬כזוג‪ ‬של‪ ‬כתובת‪ ‬וירטואלית ‪ ‬ופיזית(‪ ,‬מחפשים‪ ‬צמד‪ ‬עם‪ ‬כתובת‪ ‬וירטואלית‪ ‬מתאימה‪ ,‬‬
‫ואם‪ ‬מוצאים‪ ‬מעבירים‪ ‬אותו‪ ‬ל­‪ TLB‬ומחדשים‪ ‬את‪ ‬הריצה‪ ,‬אם‪ ‬לא‪ ‬מצאנו‪ ‬את‪ ‬התרגום‪ ‬שחיפשנו‪ ‬אז‪ ‬נהיים‪ ‬‬
‫עצובים‪ ): ‬ואז‪ ‬מפעילים‪ ‬פונקציית‪ hash ‬שניה‪ ‬ומחפשים‪ ‬בתא‪ ‬אחר‪ ,‬אם‪ ‬מצאנו‪ ,(: ‬אחרת‪ .),: ‬מה‪ ‬נעשה?‪ ‬‬
‫נזרוק‪ .page fault ‬‬
‫מערכת‪ ‬ההפעלה‪ ‬הולכת ‪ ‬לדפי‪ ‬התרגום‪ ‬שלה‪ ,‬היא‪ ‬שומרת‪ ‬דפי‪ ‬תרגום‪ ‬מלאים‪ ‬ממרחב‪ ‬וירטואלי‪ ‬למרחב‪ ‬‬
‫פיזי‪ ,‬טבלה‪ ‬ענקית‪ ,‬היא‪ ‬מוצאת‪ ‬את‪ ‬התרגום‪ ‬הנכון‪ ‬מכתובת‪ ‬וירטואלית‪ ‬לכתובת‪ ‬פיזית ‪ ‬והיא‪ ‬מעדכנת‪ ‬את‪ ‬‬
‫ה­‪HTAB‬ע"י‪ ‬אחת‪ ‬פונקציות‪ ‬ה­‪) hash‬כי‪ ‬אין‪ ‬לה‪ ‬גישה‪ ‬ל­‪ ,(TLB‬ודורסת‪ ‬את‪ ‬אחד‪ ‬התרגומים‪ ‬שיושבים‪ ‬שם‪ ,‬‬
‫ומחדשת‪ ‬את‪ ‬הריצה‪ .‬כעת‪ ‬החומרה‪ ‬תוכל‪ ‬למצוא‪ ‬את‪ ‬הנתון‪ ,‬לעדכן‪ ‬את‪ ‬ה­‪ TLB‬ולהמשיך‪ ‬את‪ ‬הריצה‪ ‬‬
‫כרגיל‪ (: ‬‬
‫התרגום‪ ‬הזה‪ ‬הוא‪ ‬כבד‪ ‬וארוך‪ ‬יחסית‪ ,‬גם‪ ‬ללא‪ ,misses ‬מדוע?‪ SLB ‬הוא‪ ‬די‪ ‬יעיל‪ ,‬אבל‪ ‬ה­‪ TLB‬הוא‪ ‬גדול‪ ‬ולכן‪ ‬הוא‪ ‬‬
‫איטי‪ ‬יותר‪ ‬מהאחד‪ ‬של‪ ‬אינטל‪ .‬‬
‫לכן‪ ‬יש‪ ‬לנו‪ buffer ‬נוסף‪ ‬שנקרא‪ ,ERAT ‬שמבצע‪ ‬תרגום‪ ‬ישיר‪ ‬באופן‪ ‬מאוד‪ ‬יעיל‪ ‬מכתובת‪ ‬אפקטיבית‪ ‬לכתובת‪ ‬פיזית‪ ‬‬
‫)מדלג‪ ‬על‪ ‬שלב‪ ‬הביניים(‪ ,‬נבחין‪ ‬כי‪ ‬זה‪ ‬אומר‪ ‬שהוא‪ ‬צריך‪ ‬לתרגם ‪ ‬דפים‪ ‬של‪ 256MB ‬לדפים‪ ‬של‪ ,4KB ‬לכן‪ ‬אם‪ ‬למשל ‪ ‬‬
‫נרצה‪ ‬למחוק‪ ‬דף‪ ‬של‪ ,256MB ‬נצטרך‪ ‬לעבור‪ ‬על‪ 216 ‬דפים ‪ ‬שונים‪ ‬ולמחוק‪ ‬את‪ ‬כולם‪ ‬מה­‪ ,ERAT‬כמובן‪ ‬שהוא‪ ‬לא‪ ‬‬
‫מחזיק‪ ‬את‪ ‬כולם‪ ‬אבל‪ ‬חייבים‪ ‬לוודא‪ ‬שמוחקים‪ ‬אותם‪ ,‬ולכן‪ ‬הבנייה‪ ‬שלו‪ ‬אינה‪ ‬וירטואלית‪ .‬‬
‫‪ ERAT‬רץ‪ ‬מאוד‪ ‬מהר‪ ,‬באותו‪ cycle ‬אפשר‪ ‬לפנות‪ ‬אליו‪ ,‬לקבל‪ ‬כתובת‪ ‬פיזית‪ ‬ולגשת‪ ‬באותו‪ ‬מחזור‪ ‬לכתובת‪ ‬‬
‫הפיזית‪ ,‬כמעט‪ ‬כל‪ ‬התרגומים‪ ‬מצליחים‪ ‬באמצעותו‪ .‬הוא‪ ‬משחק‪ ‬בדיוק‪ ‬את‪ ‬אותו‪ ‬תפקיד‪ ‬כמו‪ TLB ‬אצל‪ ‬אינטל‪ ,‬שגם ‪ ‬‬
‫אצלו‪ ‬היה‪ hit rate ‬מאוד‪ ‬גדול‪ ‬אפילו‪ ‬עבור‪ TLB ‬קטן‪ .‬‬
‫‪ ‬‬
‫‪ ‬‬
‫הירכיית‪ TLB ‬‬
‫גם‪ ‬באינטל‪ ‬הגיעו‪ ‬למסקנה‪ ‬שה­‪ TLB‬מתחילים‪ ‬להיות‪ ‬פחות‪ ‬ופחות‪ ‬יעילים‪ ,‬מדוע?‪ ‬גודלו‪ ‬לא ‪ ‬השתנה‪ ,‬שכן‪ ‬זה‪ ‬ישפיע‪ ‬‬
‫על‪ ‬הביצועים‪ ,‬אבל‪ ‬הצריכה‪ ‬של‪ ‬זיכרון‪ ‬ע"י‪ ‬תהליכים‪ ‬השתנה‪ ‬באופן‪ ‬משמעותי‪ .‬תהליכים‪ ‬רגילים‪ ‬לדרוש‪ ‬כמויות‪ ‬‬
‫גדולות‪ ‬מאוד‪ ‬של‪ ‬זיכרון‪ .‬עבור‪ ‬תהליכים‪ ‬שרוצים‪ 0.5GB ‬של‪ ‬זיכרון‪ ‬ה­‪ TLB‬פשוט‪ ‬לא‪ ‬יצליח‪ ‬לשמור‪ ‬על‪ ‬אפקטיביות‪ ,‬‬
‫לכן‪ ‬הגיעו‪ ‬לשתי‪ ‬מסקנות‪ ‬‬
‫‪ .1‬הגיע‪ ‬הזמן‪ ‬לעשות‪ TLB ‬היררכי‪ ,‬כמו‪ ‬שעשינו‪ ‬ברמות‪ ‬ה­‪ cache‬של‪ ,(data (L1,L2,L3 ‬ולכן‪ ‬יש‪ ‬לנו‪ ‬כעת‪ ‬‬
‫‪ .TLB1, TLB2‬‬
‫‪60 ‬‬
‫‪ ­ superpages .2‬מי‪ ‬אמר‪ ‬בכלל‪ ‬שכל‪ ‬הדפים‪ ‬צריכים‪ ‬להיות‪ ‬באותו‪ ‬גודל?‪ ‬נניח ‪ ‬שאנחנו‪ ‬מסתכלים‪ ‬על‪ ‬איזור ‪ ‬‬
‫ששייך‪ ‬ל­‪ heap‬ואנחנו‪ ‬מצפים‪ ‬שהתהליך‪ ‬ישתמש‪ ‬בהמון‪ ‬זיכרון‪ ‬דינמי‪ ,‬אז‪ ‬מה‪ ‬ההיגיון‪ ‬לחלק‪ 0.5GB ‬של‪ ‬‬
‫זיכרון‪ ‬וירטואלי‪ ‬לדפים‪ ‬של‪ ,4kb ‬שעבור‪ ‬רובם‪ ‬נקבל‪ ?miss ‬‬
‫לכן‪ ‬יש‪ ‬לנו‪ ,superpages ‬שבמקום‪ ‬הרמה‪ ‬הרביעית‪ ‬מצביעים‪ ‬לבלוק‪ ‬גדול‪ ,‬למשל‪ ‬של‪ 1GB ‬של‪ .data ‬‬
‫זה‪ ‬לא‪ ‬טריויאלי‪ ‬למימוש‪ ,‬כי‪ ‬כמעט‪ ‬כל‪ ‬דבר‪ ‬בתרגום‪ ‬צריך‪ ‬להשתנות‪ ,‬וצריך‪ ‬תמיכה‪ ‬מצד‪ ‬החומרה‪ .‬‬
‫כמו‪ ‬כן‪ ‬לא‪ ‬טריויאלי‪ ‬להחליט‪ ‬מתי‪ ‬צריך‪ ‬להשתמש‪ ‬בהם‪ ‬ומתי‪ ‬לא‪ .‬‬
‫ישנם‪ ‬פתרונות‪ ‬שונים‪ ‬שמנסים‪ ‬לעשות‪ ,‬למשל‪ ‬לשנות‪ ‬את‪ ‬הגודל‪ ‬של‪ ‬הטבלאות‪ ‬בתהליך‪ ‬התרגום‪ ,‬למשל‪ PGD ‬של‪ ‬‬
‫‪ ,4MB‬ו­‪ PT‬של‪ ,512kb ‬או‪ ‬אולי‪ ‬אפילו‪ ‬להיפך‪ ‬מבחינת‪ ‬גדלים‪ ,‬יש‪ ‬וריאציות‪ ‬שונות‪ ‬שאפשר‪ ‬לעשות‪ ‬במטרה‪ ‬לקבל‪ ‬‬
‫פתרונות‪ ‬אופטימליים‪ ‬יותר‪ ‬ולקבל‪ ‬ביצועים‪ ‬משופרים‪ .‬‬
‫‪ ‬‬
‫‪ ‬‬
‫‪61 ‬‬
‫הרצאה‪ ­ 8 ‬מערכות‪ ‬קבצים‬
‫מבוא‬
‫אז‪ ‬למה‪ ‬צריך‪ ‬דיסקים‪ ‬חיצוניים?‪ ‬‬
‫נזכור‪ ‬כי‪ ‬הרמות‪ ‬שאיתן‪ ‬עבדנו‪ ‬עד‪ ‬כה‪ ‬הן‪ ‬הרגיסטרים‪ ,‬ה­‪ ,caches‬וה­‪ ,RAM‬הרמות‪ ‬הללו‪ ‬קטנות‪ ‬יחסית‪ ,‬וחשוב‪ ‬‬
‫מכך‪ ‬המידע‪ ‬שלהן‪ ‬לא‪ ‬נשמר‪ ‬מהדלקה‪ ‬להדלקה‪ .‬‬
‫יש‪ ‬לנו‪ ‬צורך‪ ‬באיזושהי‪ ‬חומרה‪ ‬שתכיל‪ ‬כמות‪ ‬גדולה‪ ‬הרבה‪ ‬יותר‪ ‬של‪ ‬מידע‪ ‬ותוכל‪ ‬לשמור‪ ‬אותו‪ ‬לאורך‪ ‬זמן‪ .‬‬
‫זה‪ ‬מוסיף‪ ‬לנו‪ ‬את‪ ‬הרמות‪ ‬של‪ SSD, HDD ‬ו­‪) tape drives‬גדולים‪ ‬למידע‪ ‬שהוא‪ .(Write Once ­ Read Never ‬‬
‫‪ ‬‬
‫מה‪ ‬הדרישות‪ ‬שלנו‪ ‬ממערכת‪ ‬הקבצים?‪ ‬‬
‫●‬
‫●‬
‫●‬
‫●‬
‫●‬
‫●‬
‫●‬
‫נרצה‪ ‬שהמידע‪ ‬יישאר‪ ‬שם‪ ‬לאורך‪ ‬זמן‪ ‬‬
‫נרצה‪ ‬שהמידע‪ ‬יהיה‪ ‬מאורגן‪ ‬בצורה‪ ‬המאפשרת‪ ‬מציאה‪ ‬קלה‪ ‬של‪ ‬המידע‪ .‬‬
‫נרצה‪ ‬תמיכה‪ ‬בבעלויות‪ ‬על‪ ‬קובץ‪ ‬‬
‫נרצה‪ ‬מערכות‪ ‬קבצים‪ ‬שיוכלו‪ ‬להתמודד‪ ‬עם‪ ‬שגיאות‪ ‬ונפילות‪ ‬של‪ ‬חלק‪ ‬מהדיסק‪ .‬‬
‫נרצה‪ ‬ביצועים‪ ‬טובים‪ .‬‬
‫נרצה‪ ‬תמיכה‪ ‬לגישה‪ ‬מקבילית‪ ,‬עם‪ ‬צפי‪ ‬מוגדר‪ ‬הן‪ ‬לקריאה‪ ‬והן‪ ‬לכתיבה‪ .‬‬
‫נרצה‪ ‬עבודה‪ ‬עם‪ ‬דיסק‪ ‬באופן‪ ‬אבסטרקטי‪ ‬שלא‪ ‬תלויה‪ ‬בסוג ‪ ‬הדיסק‪ SSD vs. HDD vs. CD) ‬‬
‫‪ .(vs. tape‬‬
‫‪ ‬‬
‫איך‪ ‬מערכת‪ ‬ההפעלה‪ ‬תאפשר‪ ‬לנו‪ ‬זאת?‪ ‬‬
‫נעבוד‪ ‬בצורה‪ ‬אבסטרקטית‪ ,‬מבחינתנו‪ ‬יש‪ ‬קובץ‪ ‬ואיתו‪ ‬אנו‪ ‬עובדים‪ ,‬מה‪ ‬קורה‪ ‬שם‪ ‬בפועל‪ ‬מבחינת‪ ‬הייצוג‪ ‬של‪ ‬המידע‪ ‬‬
‫לא‪ ‬מעניין‪ ‬אותנו‪ .‬‬
‫‪ ‬‬
‫איזה‪ ‬סוג‪ ‬של‪ ‬קבצים‪ ‬יש‪ ‬לנו?‪ ‬‬
‫● קובץ‪ ‬‬
‫● ספריה‪ ‬‬
‫● קישור‪) ‬לקובץ‪ ‬או‪ ‬ספריה(‪ ,‬בהמשך‪ ‬נרחיב‪ ‬על‪ ‬ההבדל‪ ‬בין‪ soft link ‬ו­‪ hard link‬‬
‫ישנן‪ ‬מערכות‪ ‬קבצים‪ ‬שבהן‪ ‬קובץ‪ ‬וספריה‪ ‬הם‪ ‬כמעט‪ ‬אותו ‪ ‬דבר‪ ‬ואילו‪ ‬יש ‪ ‬מערכת‪ ‬קבצים‪ ‬שבהן‪ ‬אלו‪ ‬ישויות‪ ‬שונות‪ ‬‬
‫לגמרי‪ .‬‬
‫קובץ‬
‫יחידה‪ ‬לוגית‪ ‬של‪ ‬מידע‪ ,‬מבחינתנו‪ ‬זה‪ ‬איזשהו‪ ADT ‬המאפשר‪ ‬פתיחה‪ ,‬סגירה‪ ,‬כתיבה‪ ‬וקריאה‪ ‬של‪ ‬מידע‪ .‬‬
‫מה‪ ‬מגדיר‪ ‬קובץ?‪ ‬‬
‫●‬
‫●‬
‫●‬
‫שם‪ .‬‬
‫תוכן‪ ­ ‬בד"כ‪ ‬רצף‪ ‬של‪ ‬בתים‪ ,‬או‪ ‬טקסט‪ ‬שמקודד‪ ‬ע"י‪ ‬טבלת‪ .ASCII ‬‬
‫‪ ­ meta data‬מידע‪ ‬המתאר‪ ‬את‪ ‬הקובץ‪ ,‬הרשאות‪ ‬גישה‪ ,‬סוג‪ ‬הקובץ‪ ,‬גודלו‪ ,‬המיקום‪ ‬שלו‪ ‬וכו'‪ .‬‬
‫‪ ‬‬
‫מתקיים‪ ‬שהקובץ‪ ‬הוא‪ ‬קבוע‪ : ‬הוא‪ ‬שורד‪ ‬נפילות‪ ‬מתח‪ ,‬ונשאר‪ ‬גם‪ ‬אחרי‪ ‬שתהליך‪ ‬מסוים‪ ‬מת‪/‬הפסיק‪ ‬להשתמש‪ ‬בו‪ .‬‬
‫‪62 ‬‬
‫מקביליות‪ (concurrency) ‬לפי‪ POSIX ‬‬
‫● קריאה‪ ‬מקובץ‪ ­ ‬אין‪ ‬בעיה‪ ,‬כל‪ ‬עוד‪ ‬קבצים‪ ‬רוצים‪ ‬רק‪ ‬לקרוא‪ ‬לא‪ ‬ניתקל‪ ‬בבעיה‪ ‬של‪ ‬סנכרון‪ .‬‬
‫● כתיבה‪ ‬מקובץ‪ ­ ‬נניח‪ ‬שיש‪ ‬לנו‪ ‬שני ‪ ‬תהליכים‪ ‬שרוצים‪ ‬לכתוב‪ ‬לקובץ‪ ,‬לפי‪ ‬דרישות‪ ,POSIX ‬אמנם‪ ‬‬
‫סדר‪ ‬הכתיבה‪ ‬אינו‪ ‬מובטח‪ ‬אך‪ ‬מבחינת‪ ‬שאר‪ ‬התהליכים‪ ‬כל‪ ‬פעולת‪ ‬כתיבה‪ ‬של‪ ‬תהליך‪ ‬הינה‪ ‬‬
‫פעולה‪ ‬אטומית‪ ,‬או‪ ‬שקוראים‪ ‬את‪ ‬הקובץ‪ ‬לפני‪ ‬כתיבה‪ ,‬או ‪ ‬שקוראים‪ ‬אותה‪ ‬אחרי‪ ‬כתיבה‪ ,‬אין‪ ‬תוצאת‪ ‬‬
‫ביניים‪ .‬במערכת‪ ‬קבצים‪ ‬לוקלית‪ ‬בד"כ‪ ‬יש‪ ‬תמיכה‪ ‬מלאה‪ ‬בסטנדרט‪ ‬בעזרת‪ ‬ה­‪ ,IOCache‬‬
‫במערכות‪ ‬אינן‪ ‬לוקליות‪ ‬כמעט‪ ‬אף‪ ‬פעם‪ ‬אין‪ ‬תמיכה‪ ‬בזה‪ .‬‬
‫‪ ‬‬
‫‪ File Metadata‬‬
‫● גודל‪ .‬‬
‫● בעלים‪ . ‬‬
‫● הרשאות‪ ­ ‬בד"כ‪ ‬מיוצג‪ ‬ע"י‪ ,9bits ‬שלושה‪ ‬של‪ ,self ‬שלושה‪ ‬של‪ group ‬ושלושה‪ ‬של‪ ,universe ‬‬
‫כאשר‪ ‬כל‪ ‬שלשה‪ ‬היא‪ ‬ביט‪ ‬לקריאה‪ ,‬ביט‪ ‬לכתיבה‪ ‬וביט‪ ‬להרצה‪ .‬‬
‫● ‪ ­ timestamp‬זמן‪ ‬יצירת‪ ‬קובץ‪) ‬הדברים‪ ‬האלה‪ ‬אינם‪ ‬מחייבים‪ ,‬למשל‪ ‬זמן‪ ‬זה‪ ‬לא‪ ‬קיים‪ ‬במערכת‪ ‬‬
‫של‪ ,(native Unix ‬זמן‪ ‬שינוי‪ ‬וכד'‪ .‬‬
‫● מיקום‪ ­ ‬איפה‪ ‬הבלוקים‪ ‬של‪ ‬התהליך‪ ‬נמצאים‪ ‬בפועל‪ ‬בדיסק‪ .‬‬
‫● סוג‪ ‬הקובץ‪ ­ ‬תיקייה‪ ,‬קובץ‪ ‬בינארי‪ ,‬טקסט‪ ‬וכו'‪ .‬‬
‫‪ ‬נבחין‪ ‬כי‪ ‬שם‪ ‬אינו‪ metadata ‬של‪ ‬הקובץ!‪ ‬זהו‪ ‬חלק‪ ‬מה­‪ data‬של‪ .directory ‬‬
‫תוכן‪ ‬של‪ ‬ספריה‪ ‬הוא‪ ‬בעצם‪ ‬מערך‪ ‬של‪ ‬שם‪ ‬קובץ‪ ‬ומיקום‪ ‬ה­‪ metadata‬שלו‪ ,‬כשרוצים ‪ ‬לקרוא‪ ‬קובץ‪ ‬הולכים‪ ‬לספריה‪ ,‬‬
‫קוראים‪ ‬את‪ ‬התוכן‪ ‬שלה‪ ,‬מתוכה‪ ‬מסיקים‪ ‬את‪ ‬מיקום‪ ‬ה­‪ metadata‬ומתחילים‪ ‬לקרוא‪ ‬את‪ ‬הקובץ‪ .‬‬
‫‪ ‬‬
‫‪ File Descriptors‬‬
‫כשפותחים ‪ ‬קובץ ‪ ‬בהצלחה‪ ‬מקבלים‪ ‬איזשהו‪ ,FD ‬מספר‪ ‬אי‪ ‬שלילי‪) ‬ב­‪ Linux‬עד‪ (255 ‬שמאפשר‪ ‬למערכת‪ ‬ההפעלה‪ ‬‬
‫לגשת‪ ‬לאובייקט‪ ‬ניהול‪ ‬של‪ ‬הקובץ‪ .‬‬
‫ישנן‪ ‬פקודות‪ ‬שעובדות‪ ‬עם‪ ‬השם‪ ‬של‪ ‬הקובץ‪ ‬ולא‪ ‬עם‪ ‬ה­‪ ,FD‬ויש‪ ‬אפילו‪ ‬כאלה‪ ‬שעובדות ‪ ‬עם‪ ‬שניהם‪ ,‬אז‪ ‬עם‪ ‬מה‪ ‬כדאי‪ ‬‬
‫לעבוד?‪ ‬עדיף‪ ‬בד"כ‪ ‬אם‪ ‬יש‪ ‬לכך‪ ‬אופציה‪ ‬לעבוד‪ ‬עם‪ ,FD ‬מדוע?‪ ‬יש‪ ‬לכך‪ ‬כמה‪ ‬סיבות‪ ,‬ביניהן‪ ‬ישנם‪ ‬כמה‪ ‬דברים‪ ‬מאוד‪ ‬‬
‫מרוכבים‪ ‬בהתנהגות ‪ ‬של‪ ‬ה­‪ ,IOCache‬שהופכים‪ ‬את‪ ‬העבודה‪ ‬של‪ ‬השמות‪ ‬של‪ ‬הקבצים ‪ ‬למורכבת‪ ,‬לכן‪ ‬באופן‪ ‬כללי‪ ‬‬
‫נעדיף‪ ‬לעבוד‪ ‬עם‪ .FD ‬‬
‫‪ ‬‬
‫פקודות‪ ‬קנוניות‪ ‬לעבודה‪ ‬עם‪ ‬קבצים‪ ‬‬
‫●‬
‫●‬
‫●‬
‫●‬
‫●‬
‫●‬
‫●‬
‫‪ ­ create‬יצירת‪ ‬קובץ‪ ,‬לא‪ ‬מחזירה‪ .descriptor ‬‬
‫‪ ­ open‬פתיחה‪ ‬של‪ ‬קובץ‪ ‬קיים‪ ,‬בד"כ‪ open ‬יכולה‪ ‬לבצע‪ .create ‬‬
‫‪ ­ deletion‬ביצוע‪ ‬מחיקה‪ ‬של‪ ‬קובץ‪ ‬או‪ ‬ספריה‪ ‬‬
‫‪ ­ rename‬משנה‪ ‬את‪ ‬השם‪ ‬של‪ ‬הקובץ‪ ‬לפי‪ ‬השם‪ ‬הנוכחי‪ ‬‬
‫‪ ­ stat‬קבלת‪ metadata ‬של‪ ‬קובץ‪ .‬‬
‫‪ ­ chmod‬מאפשרת‪ ‬שינוי‪ ‬הרשאות‪ ‬גישה‪ .‬‬
‫‪ ­ chown‬ביצוע‪ ‬שינוי‪ ‬בעלות‪ ,‬נבחין‪ ‬כי‪ ‬לא‪ ‬ניתן‪ ‬להחזיר‪ ‬בעצמנו‪ ‬את‪ ‬הבעלות‪ ‬חזרה‪ ‬אלינו‪ .‬‬
‫‪63 ‬‬
‫●‬
‫●‬
‫●‬
‫●‬
‫●‬
‫‪ ­ write, read‬כשפותחים‪ ‬קובץ‪ ‬מקבלים‪ ‬אינדקס‪ ‬שבאמצעותו‪ ‬אפשר‪ ‬לגשת‪ ‬לאובייקט‪ ‬ניהול‪ ,‬‬
‫שמכיל‪ ‬בין ‪ ‬השאר‪ ‬את‪ ‬ה­‪ ,file pointer‬המיקום‪ ‬שממנו‪ ‬צריך‪ ‬לקרוא‪ ,‬פעולות‪ read ‬ו­‪ write‬עובדות‪ ‬‬
‫בהתאמה‪ ‬מהמיקום‪ ‬הזה‪ ‬‬
‫‪ ­ seek‬שינוי‪ ‬המיקום‪ ‬של‪ ‬ה­‪ file pointer‬‬
‫‪ ­ sync‬מכריחה‪ ‬סנכרון‪ ‬בין‪ ‬ה­‪ IObuffer‬לדיסק‪ ,‬למשל‪ ‬פונקצית‪ flush ‬מרוקנת‪ ‬את‪ ‬ה­‪ .buffers‬‬
‫‪ ­ lock‬ישנה‪ ‬אפשרות‪ ‬לבצע‪ ‬נעילה‪ ‬של‪ ‬קובץ‪ ‬באמצעות‪ ,flock ‬אם‪ ‬מישהו‪ ‬אחר‪ ‬ינסה‪ ‬לעשות‪ flock ‬‬
‫אחרי‪ ‬שאני‪ ‬נעלתי‪ ‬אותו‪ ‬הוא‪ ‬ייחסם‪ ,‬אך‪ ‬הוא‪ ‬בכלל‪ ‬לא‪ ‬חייב‪ ‬לנסות‪ ‬ולנעול‪ ,‬הוא‪ ‬יכול‪ ‬לנסות‪ ‬ולגשת‪ ‬‬
‫למידע‪ ‬בלי‪ ‬לנעול‪ ‬ואז‪ ‬הוא‪ ‬לא‪ ‬ייחסם‪ .‬זה‪ ‬נקרא‪ .advisory lock ‬‬
‫יש‪ ‬גם‪ mandatory lock ‬במערכות‪ ‬הפעלה‪ ‬שונות‪ ­ ‬כשפותחים‪ ‬קובץ‪ ‬לא‪ ‬צריך ‪ ‬לבקש‪ ‬מנעול‪ ,‬‬
‫מערכת‪ ‬ההפעלה‪ ‬תדאג‪ ‬לנעול‪ ‬אותו‪ ‬בשבילנו‪ .‬‬
‫‪ ‬‬
‫סוגי‪ ‬קבצים‪ ‬‬
‫בד"כ‪ ‬מערכות‪ ‬קבצים‪ ‬מפרידות‪ ‬בין‪ ‬טקסט‪ ‬וקוד‪ ‬בינארי‪ ,‬ישנן‪ ‬מערכות‪ ‬שבהן‪ ‬סוג ‪ ‬הקובץ ‪ ‬נקבע‪ ‬לפי‪ ‬הסיומת‪ ‬שלו‪ ,‬‬
‫החיסרון‪ ‬בכך‪ ‬הוא‪ ‬שלא‪ ‬ניתן‪ ‬לדעת‪ ‬אם‪ ‬תוכן‪ ‬הקובץ‪ ‬אכן‪ ‬מתאים‪ ‬למידע‪ ‬שבו‪ .‬‬
‫ב­‪ Unix‬יש‪ ‬לנו‪ ‬את‪ ‬הטיפוסים‪ ‬הבאים‪ :‬‬
‫● קובץ‪ ‬‬
‫● תיקייה‪ ‬‬
‫● ‪ soft link‬‬
‫● ‪ ­ pipe ­ FIFO‬כפי‪ ‬שראינו‪ ‬בתרגולים‪ ‬זה‪ ‬סוג‪ ‬של‪ ‬קובץ‪ ‬אבל‪ ‬הוא‪ ‬מתנהל‪ ‬בצורה‪ ‬שונה‪ ‬לגמרי‪ .‬‬
‫● ‪ ­ socket‬גם‪ ‬הוא‪ ‬מתנהג‪ ‬בצורה‪ ‬שונה‪ ,‬עובדים‪ ‬איתו‪ ‬בצורה‪ ‬אחרת‪ .‬‬
‫● ‪ ­ device file‬גם‪ ‬ראינו‪ ‬בתרגול‪ ‬‬
‫‪ ‬‬
‫‪ Magic Numbers in Unix‬‬
‫בזמנו‪ ‬החליטו‪ ‬לנסות‪ ‬לקבוע‪ ‬איזשהו‪ ‬מספר‪ ‬קסם‪ ‬בתוך‪ ‬הקובץ‪ ‬שיציינו‪ ‬מהו‪ ‬סוג‪ ‬הקובץ‪ ‬בפועל‪ .‬‬
‫במקור‪ ‬היו‪ ‬רק‪ ‬שני‪ ‬בתים‪ ‬לשם‪ ‬כך‪ ,‬כיום‪ ‬יש‪ ‬אפילו‪ ‬יותר‪ ‬בתים‪ ‬לעיתים‪ ,‬הקובץ‪ ‬מתחיל‪ ‬בזה‪ ‬שמציינים‪ ‬איך‪ ‬אפשר‪ ‬‬
‫להריץ‪ ‬את‪ ‬הקובץ‪ .‬זהו‪ ‬אינו‪ ‬סטנדרט‪ ,‬הבעיה‪ ‬המרכזית‪ ‬היא‪ ‬שקשה‪ ‬להמציא‪ ‬מספר‪ ‬חדש‪ ‬כי‪ ‬צריך‪ ‬לתאם‪ ‬את‪ ‬זה‪ ‬עם‪ ‬‬
‫כולם‪ .‬‬
‫‪ ‬‬
‫‪ memory mapped files‬‬
‫מנגנון‪ ‬המאפשר‪ ‬לנו‪ ‬למפות‪ ‬מידע‪ ‬רציף‪ ‬מקובץ‪ ‬לבלוקים‪ ‬רציפים‪ ‬בזיכרון‪ ‬הווירטואלי‪) ‬או‪ ‬להיפך(‪ .‬‬
‫הוא‪ ‬נוח‪ ‬מאוד‪ ‬לעבודה‪ ‬עם‪ ,code ‬הוא‪ ‬חוסך‪ ‬תקורה‪ ‬של‪ ‬שימוש‪ ‬בקריאה‪ ‬וכתיבה‪ ‬רבים‪ ‬מקובץ‪ ‬מסוים‪ .‬‬
‫צריך‪ ‬להיזהר‪ ‬איתו‪ ‬שכן‪ ‬ישנן‪ ‬פעולות‪ ‬שאינן ‪ ‬מוגדרות‪ : ‬נניח‪ ‬שפתחנו‪ ‬איזשהו‪ ‬קובץ‪ ‬כ­‪ memory mapped file‬וכן‪ ‬‬
‫באמצעות‪ ‬פתיחה‪ ‬רגילה‪ ,‬והם‪ ‬לא‪ ‬מודעים‪ ‬זה‪ ‬לזה‪ .‬כעת‪ ‬נניח‪ ‬שכתבנו‪ ‬משהו‪ ‬לתוך‪ ‬הקובץ‪ ‬בפתיחה‪ ‬הרגילה‪ ,‬לא‪ ‬‬
‫מובטח‪ ‬לנו‪ ‬מה‪ ‬יהיה‪ ‬הערך‪ ‬בקריאה ‪ ‬מה­‪ .memory mapped file‬לכן‪ ‬בד"כ‪ ‬נשתמש‪ ‬ב­‪ memory mapping‬רק‪ ‬‬
‫לקבצים‪ ‬שלא‪ ‬ישתנו‪ ,‬כמו‪ ‬הקוד‪ ‬של‪ ‬התוכנית‪ .‬‬
‫‪ ‬‬
‫‪64 ‬‬
‫‪ ‬‬
‫ביטים‪ ‬של‪ :ls ­l ‬‬
‫●‬
‫●‬
‫●‬
‫●‬
‫●‬
‫●‬
‫●‬
‫●‬
‫●‬
‫●‬
‫ביט‪ ‬ראשון‪ ‬סוג‪ ‬הקובץ‪ :‬‬
‫○ ‪ b ­ block device‬‬
‫○ ‪ c ­ character device‬‬
‫○ ‪ p ­ pipe‬‬
‫○ ‪ d ­ directory‬‬
‫לאחר‪ ‬מכן‪ ‬יש‪ ‬את‪ 9 ‬הביטים‪ ‬של‪ ‬ההרשאות‪ .‬‬
‫המספר‪ ‬שאחרי‪ ‬ההרשאות‪ ‬מייצג‪ ‬את‪ ‬המספר‪ ‬ה­‪ hard links‬של‪ ‬קובץ‪) ‬מינימום ‪ 2 ‬לתיקייה‪ ,‬‬
‫מינימום‪ 1 ‬לקובץ(‪ .‬‬
‫שם‪ ‬הבעלים‪ .‬‬
‫שם‪ ‬הקבוצה‪ .‬‬
‫גודל‪ ‬הקובץ‪ .‬‬
‫עבור‪ block device, or character device ‬יש‪ ‬לנו‪ ‬במקום‪ ‬זאת‪ major ‬ו­‪ .minor‬‬
‫ב­‪ FIFO‬הגודל‪ ‬במערכת‪ ‬הקבצים‪ ‬לא‪ ‬בהכרח‪ ‬מייצג‪ ‬את‪ ‬הגודל‪ ‬שלו‪ ‬בפועל‪ ,‬אלו‪ ‬הם‪ ‬רק‪ ‬הנתונים‪ ‬‬
‫שנמצאים‪ ‬בדיסק‪ .‬‬
‫זמן‪ ‬גישה‪ ‬אחרונה‪ ‬לקובץ‪ .‬‬
‫שם‪ ‬הקובץ‪ ‬עבור‪ ‬קבצים‪ ‬וספריות‪ ,‬עבור‪ ­ soft links ‬על‪ ‬מה‪ ‬הוא‪ ‬מצביע‪ .‬‬
‫‪ ‬‬
‫‪ ‬‬
‫‪ Access Control Lists‬‬
‫כל‪ ‬מערכת‪ ‬הפעלה‪ ‬יכולה‪ ‬לתמוך‪ ‬בהגנה‪ ‬הרבה‪ ‬יותר‪ ‬עדינה‪ ‬על‪ ‬קבצים‪ ,‬לתת‪ ‬הרשאות‪ ‬שונות‪ ‬לקבוצות‪ ‬שונות‪ ‬וכד'‪ ,‬‬
‫זה‪ ‬לא‪ ‬סטנדרט‪ ‬אלא‪ ‬תכונה‪ ‬של‪ ‬מערכת‪ ‬ההפעלה‪ .‬‬
‫ב­‪ Mac‬יכלו‪ ‬לתת‪ ‬לכל‪ user ‬הרשאות‪ ‬משלו‪ ,‬זה‪ ‬לא‪ ‬כל‪ ‬כך‪ ‬עבד…‪ ‬היום‪ ‬מערכות ‪ ‬לא‪ ‬ממש‪ ‬תומכות‪ ‬בזה‪ ‬מכיוון‪ ‬שיש‪ ‬‬
‫הרבה‪ ‬מעבר‪ ‬של‪ ‬מידע‪ ‬בין‪ ‬מערכות‪ ‬שונות‪ ‬ואין‪ ‬שום‪ ‬הבטחה‪ ‬לתמיכה‪ ‬בכך‪ .‬‬
‫תיקיות‬
‫היסטורית‪ ‬מערכות‪ ‬קבצים‪ ‬היו‪ ‬מאורגנות‪ ‬בצורה‪ ‬של‪ ‬תיקיות‪ ,‬כאשר‪ ‬כל‪ ‬תיקיה‪ ‬יכולה‪ ‬להכיל‪ ‬קבצים‪ ‬או‪ ‬תתי‪ ‬תיקיות‪ ‬‬
‫נוספות‪ ,‬הבחירה‪ ‬בעץ‪ ‬נעשתה‪ ‬מכיוון‪ ‬שיש‪ ‬הרבה‪ ‬אלגוריתמים‪ ‬שנוח‪ ‬מאוד‪ ‬להריץ‪ ‬על‪ ‬עצים‪ ,‬וזה‪ ‬נעשה‪ ‬יותר‪ ‬מורכב‪ ‬‬
‫כאשר‪ ‬מדובר ‪ ‬בגרף‪ ‬עם‪ ‬מעגלים‪ ,‬דוגמא‪ ‬קלאסית‪ ‬היא‪ ‬פעולת‪ ‬חיפוש‪ ‬שפשוטה‪ ‬למדי‪ ‬בעץ‪ ‬אבל‪ ‬נעשית‪ ‬יותר‪ ‬מורכבת‪ ‬‬
‫במקרה‪ ‬של‪ ‬גרפים‪ ‬עם‪ ‬מעגלים‪ .‬‬
‫היום‪ ‬יש ‪ ‬כל‪ ‬מיני‪ ‬תכונות‪ ‬של‪ ‬מערכות‪ ‬קבצים‪ ‬שגורמות‪ ‬להן‪ ‬להיראות‪ ‬יותר‪ ‬כמו‪ ‬גרף‪ ,‬עדיין ‪ ‬חסר‪ ‬מעגלים‪ ,‬ופחות‪ ‬כמו‪ ‬‬
‫עץ‪ .‬‬
‫‪ ‬‬
‫‪65 ‬‬
‫‪ File Paths‬‬
‫כשאנו‪ ‬עושים‪ ‬פעולה‪ ‬כלשהי‪ ‬על‪ ‬קובץ‪ ‬אנחנו‪ ‬צריכים‪ ‬להעביר‪" ‬שם‪ ‬של‪ ‬קובץ"‪ ‬או‪" ‬מסלול‪ ‬לקובץ"‪ ,‬יש‪ ‬כמה‪ ‬דרכים‪ ‬לציין‪ ‬‬
‫מסלול‪ ‬זה‪ :‬‬
‫● מסלול‪ ‬אבסולוטי‪ ­ ‬דרך‪ ‬אחת‪ ‬לציין‪ ‬מסלול‪ ,‬מהשורש‪ ‬של‪ ‬מערכת‪ ‬הקבצים‪ ‬עד‪ ‬לקובץ‪ ‬הדרוש‪ .‬‬
‫● מסלול‪ ‬יחסי‪ ­ ‬מסלול‪ ‬ביחס‪ ‬לספריית‪ ‬העבודה‪ ,WD ­ Working Directory ,‬הנוכחית‪ .‬‬
‫‪ ‬‬
‫פעולות‪ ‬על‪ ‬תיקיות‪ :‬‬
‫● ‪ ­ mkdir‬יוצרת‪ ‬תיקייה‪ ‬חדשה‪ .‬‬
‫● ‪ ­ rmdir‬מוחקת‪ ‬ספריה‪ ‬קיימת‪ .‬‬
‫‪ ‬‬
‫שמות‪ ‬ייחודיים‪ ‬של‪ ‬ספריות‪ :‬‬
‫● ‪ ­ .‬קישור‪ ‬לספריה‪ ‬הנוכחית‪ ,‬זהו‪ ‬קישור‪ ‬אמיתי‪ ‬שקיים‪ ‬במערכת‪ ‬הקבצים‪ .‬‬
‫● ‪ ­ ..‬קישור‪ ‬לספריית‪ ‬האב‪ ,‬גם‪ ‬זהו‪ ‬קישור‪ ‬אמיתי‪ ‬שקיים‪ ‬במערכת‪ ‬הקבצים‪ .‬‬
‫‪ ‬‬
‫מעבר‪ ‬על‪ ‬ספריה‪ ‬‬
‫קריאת‪ ‬המערכת‪ readdir ‬קוראת‪ ‬לנו‪ ‬בכל‪ ‬פעם‪ direntry ‬נוסף‪ ‬של‪ ‬הספרייה‪ ,‬עד‪ ‬שנעבור‪ ‬על‪ ‬כולם‪ ,‬כך‪ ‬ניתן‪ ‬לעבור‪ ‬‬
‫על‪ ‬כל‪ ‬תתי‪ ‬הספריות‪ ‬והקבצים‪ ‬שנמצאים‪ ‬בתיקיה‪ ‬מסוימת‪ ,‬ניתן‪ ‬להמשיך‪ ‬ולעבור‪ ‬על ‪ ‬מערכת‪ ‬הקבצים‪ ‬באופן‪ ‬‬
‫רקורסיבי‪ .‬‬
‫‪ ‬‬
‫כדי‪ ‬לבצע‪ ‬את ‪ ‬ההדפסה‪ ‬של‪ ‬כל‪ ‬התיקיות‪ ‬והקבצים‪ ‬באופן‪ ‬רקורסיבי‪ ‬צריך‪ ‬לבדוק‪ ‬האם‪ de ‬הוא ‪ ‬תיקייה ‪ ‬ובמידה‪ ‬וכן‪ ‬‬
‫להפעיל‪ ‬עליו‪ ‬את‪ ‬הפונקציה‪ ‬כולה‪ ‬באופן‪ ‬רקורסיבי‪ .‬‬
‫‪ ‬‬
‫‪ Hard Links‬‬
‫לכל‪ ‬קובץ‪ ‬יש‪ ‬שם‪ ‬אחד‪ ‬לפחות‪ ,‬אבל‪ ‬ייתכן‪ ‬שיש‪ ‬יותר‪ .‬איך‪ ‬לקובץ‪ ‬יש‪ ‬יותר‪ ‬משם‪ ‬אחד?‪ ‬יש‪ ‬מה‪ ‬שנקרא‪ .Hard Link ‬‬
‫נזכור‪ ‬כי‪ ‬שם‪ ‬של‪ ‬קובץ‪ ‬לא‪ ‬מופיע‪ ‬ב­‪ metadata‬שלו‪ ,‬הוא‪ ‬מופיע‪ ‬בסך‪ ‬הכל‪ ‬בספרייה‪ ‬שבה‪ ‬נמצא‪ ‬הקובץ‪ ,‬אין‪ ‬לנו‪ ‬שום‪ ‬‬
‫בעיה‪ ‬לשים‪ ‬אותו‪ ‬קובץ‪ ‬בכמה‪ ‬תיקיות‪ ,‬כלומר‪ ‬כניסה‪ ‬עם‪ ‬איזשהו‪ ‬שם‪ ‬שמצביע‪ ‬לאותו‪ .metadata ‬בפועל‪ ‬אין ‪ ‬שום‪ ‬‬
‫דבר‪ ‬שמפריד‪ ‬בין ‪ ‬השם‪ ‬המקורי‪ ‬של‪ ‬קובץ‪ ‬לשמות‪ ‬שלו‪ ‬בתיקיות‪ ‬אחרות‪ ,‬כל‪ ‬שם‪ ‬נוסף‪ ‬נקרא‪ ,Hard Link ‬מרגע‪ ‬‬
‫היצירה‪ ‬הקבצים‪ ‬הם‪ ‬שווי‪ ‬ערך‪ ‬לחלוטין‪ .‬‬
‫מספר‪ ‬ה­‪ Hard Links‬נשמר‪ ‬ב­‪ metadata‬של‪ ‬הקובץ‪ .‬צריך‪ ‬לזכור‪ ‬ש­‪ Hard Link‬עובד ‪ ‬אך‪ ‬ורק‪ ‬באותה‪ ‬מערכת‪ ‬‬
‫קבצים‪ ,‬לא‪ ‬ניתן‪ ‬לבצע‪ ‬קישור‪ ‬בין‪ ‬מערכת‪ ‬הקבצים‪ ‬של‪ ‬המחשב‪ ‬למשל‪ ‬וזו‪ ‬של‪ ‬התקן‪ ‬חיצוני‪ .‬‬
‫‪ ‬‬
‫איך‪ ‬יוצרים‪ ?Hard Link ‬‬
‫פונקצית‪ link ‬מקבלת‪ ‬שם‪ ‬של‪ ‬קובץ‪ ‬מקורי‪ ‬ושם‪ ‬חדש‪ ‬שנרצה‪ ‬לתת‪ ‬לקובץ‪ ‬ויוצרת‪ ‬אותו‪ ‬בתיקייה‪ ‬המתאימה‪) .‬ב­‪ shell‬‬
‫באמצעות‪ ‬פקודת‪ .(ln ‬פונקצית‪ unlink ‬מוחקת‪ ‬את‪ ‬הקישור‪) ‬ב­‪ shell‬באמצעות‪ ‬פקודת‪ .(rm ‬‬
‫‪ ‬‬
‫‪ ‬‬
‫‪66 ‬‬
‫‪ ‬‬
‫‪ Reference Counting‬‬
‫לכל‪ ‬קובץ‪ ‬שומרים‪ ‬את‪ ‬מספר‪ ‬הקישורים‪ ‬אליו‪ .‬מדוע‪ ‬יש ‪ ‬צורך‪ ‬בשמירה‪ ‬של‪ ‬מספר‪ ‬הקישורים‪ ,‬כשאין‪ ‬יותר‪ ‬אף‪ ‬מצביע‪ ‬‬
‫ל­‪ metadata‬של‪ ‬קובץ‪ ‬מסוים‪ ‬אז‪ ‬נוכל‪ ‬לשחרר‪ ‬אותו‪ ‬שכן‪ ‬לאף‪ ‬אחד‪ ‬אין‪ ‬גישה‪ ‬אליו‪ .‬‬
‫‪ ‬‬
‫מה‪ ‬קורה‪ ‬כאשר‪ ‬מנסים‪ ‬לסגור‪ ‬קובץ‪ ‬שהוא‪ ‬כבר‪ ‬פתוח?‪ ‬‬
‫אנחנו‪ ‬עדיין‪ ‬יכולים‪ ‬לגשת‪ ‬לקובץ‪ ‬בעזרת‪ ‬ה­‪ FD‬שכבר‪ ‬פתוח‪ .‬‬
‫אם‪ ‬הקובץ‪ ‬פתוח‪ ‬אצל ‪ ‬יותר‪ ‬מתהליך‪ ‬אחד‪ ‬כאשר‪ ‬הקישור‪ ‬האחרון‪ ‬אליו‪ ‬נמחק‪ ‬אז‪ ‬הקישור‪ ‬ימחק‪ ‬לפני‪ ‬שפונקציה‪ ‬‬
‫‪ unlink‬תחזור‪ ,‬אבל‪ ‬ההסרה‪ ‬של‪ ‬תוכן‪ ‬הקובץ‪ ‬תחכה‪ ‬עד‪ ‬שכל‪ ‬התהליכים‪ ‬שפתחו‪ ‬אותו‪ ‬יסגרו‪ ‬אותו‪ .‬‬
‫‪ ‬‬
‫מה‪ Hard Links ‬עושים‪ ‬להיררכיה?‪ ‬‬
‫הם‪ ‬הורסים‪ ‬את‪ ‬העץ‪ ‬שלנו!‪ ‬אם‪ ‬למשל‪ ‬יש‪ Hard Link ‬לספריית‪ ‬השורש‪ ‬אז‪ ‬נקבל‪ ‬מעגל‪ .‬‬
‫כמעט‪ ‬כל‪ ‬מערכות‪ ‬הקבצים‪ ‬תומכות‪ ‬ברמת‪ ‬העיקרון‪ ‬ב­‪ Hard Links‬לספריות‪ ,‬אבל‪ ‬הן‪ ‬חוסמות‪ ‬את‪ ‬האפשרות‪ ‬‬
‫לבצע‪ ‬אותם‪ ,‬וכך‪ ‬מונעת‪ ‬יצירה‪ ‬של‪ ‬מעגלים‪ ,‬שכן‪ Hard Links ‬לקבצים‪ ‬לא‪ ‬יוכלו‪ ‬ליצור‪ ‬מעגל‪ .‬‬
‫‪ ‬‬
‫למה‪ ‬בכל‪ ‬זאת‪ ‬יש‪ ‬לנו‪ ‬צורך‪ ‬בתמיכה‪ ‬ב­‪ ? Hard Links‬‬
‫‪ .‬ו­‪ ..‬הינם‪ ,Hard Links ‬ומערכת‪ ‬הקבצים‪ ‬משתמשת‪ ‬ב­‪ Hard Links‬לצרכיה‪ .‬‬
‫‪ ‬‬
‫כמה‪ Hard Links ‬לכל‪ ‬היותר‪ ‬ולכל‪ ‬הפחות‪ ‬יש‪ ‬לספריה‪ ‬במערכת?‪ ‬‬
‫יש‪ ‬לכל‪ ‬הפחות‪ ‬שניים‪ ,‬המצביע‪ ‬עליה‪ ‬מתוך‪ ‬התיקייה‪ ‬המכילה‪ ‬אותה‪ ,‬והמצביע‪ . ‬‬
‫המספר‪ ‬המקסימאלי‪ ‬תלוי‪ ‬במספר‪ ‬תתי‪ ‬התיקיות‪ ,‬שכן‪ ‬כל‪ ‬תת‪ ‬תיקיה‪ ‬מכילה‪ Hard Link ‬לתיקיה‪ ‬שלנו‪ ‬תחת‪ .. ‬‬
‫לכן‪ ,‬עבור‪ n ‬תתי‪ ‬תיקיות‪ ‬נקבל‪ n+2 ‬מצביעים‪ .‬‬
‫‪ ‬‬
‫‪ Symbolic Link‬‬
‫בדומה‪ ‬ל­‪ Shortcut‬שאנחנו‪ ‬מכירים‪ ‬ב­‪ ,Windows‬זהו‪ ‬אינו‪ ‬שם‪ ‬אלטרנטיבי‪ ‬לקובץ‪ ,‬זהו‪ ‬בסה"כ‪ ‬קובץ‪ ‬המכיל‪ ‬את‪ ‬ה­‬
‫‪ Path‬אל‪ ‬הקובץ‪ ,‬אין‪ ‬לנו‪ ‬שום‪ ‬בעיה‪ ‬לבצע‪ ‬קישור‪ ‬כזה‪ ‬אל‪ ‬תיקיות‪ ‬ואל‪ ‬קבצים‪ .‬מה‪ ‬יקרה‪ ‬בקוד ‪ C ‬למשל‪ ‬אם‪ ‬ננסה‪ ‬‬
‫לקרוא‪ ‬קובץ‪ ?lnk ‬נקרא‪ ‬את‪ ‬התוכן‪ ‬הבינארי‪ ‬המקודד‪ ‬את‪ ‬ה­‪ path‬ולא‪ ‬את‪ ‬הקובץ‪ ‬המוצבע!‪ ‬שכן‪ ‬זהו‪ ‬אינו‪ ‬שם‪ ‬אחר‪ ,‬‬
‫זה‪ ‬בסך‪ ‬הכל‪ ‬קובץ‪ ‬שמכיל‪ ‬מסלול‪ ‬לקובץ‪ ‬אחר‪ .‬‬
‫‪ ‬‬
‫ההבדל‪ ‬המהותי‪ ‬בין‪ ‬קובץ‪ lnk ‬לבין‪ ,soft link ‬הוא‪ ‬שכאשר‪ ‬מנסים‪ ‬לקרוא‪ ‬קובץ‪ soft link ‬התוכן‪ ‬שנקבל‪ ‬כן‪ ‬יהיה‪ ‬‬
‫התוכן‪ ‬של‪ ‬הקובץ‪ ‬שהוא‪ ‬מצביע‪ ‬אליו‪ ,‬ומי‪ ‬שאחראי‪ ‬על‪ ‬כך‪ ,‬כלומר‪ ‬על‪ ‬לקרוא‪ ‬את‪ ‬ה­‪ ,soft link‬לראות‪ ‬את‪ ‬ה­‪ path‬‬
‫ולקרוא‪ ‬את‪ ‬הקובץ‪ ‬שיש‪ ‬שם‪ ,‬זה‪ ‬מערכת‪ ‬ההפעלה‪ .‬אם ‪ ‬בכל ‪ ‬זאת‪ ‬רוצים ‪ ‬לקרוא ‪ ‬את‪ ‬התוכן‪ ‬של‪ ‬ה­‪ soft link‬ניתן‪ ‬‬
‫לעשות‪ ‬זאת‪ ‬עם‪ .readlink ‬‬
‫קריאת‪ ‬המערכת‪ ­ symlink ‬יוצרת‪) symbolic link ‬ב­‪ .(shell ln ­s‬‬
‫‪ ‬‬
‫נבחין‪ ‬כתוצאה‪ ‬מכך‪ ‬שה­‪ sym link‬בסך‪ ‬הכל‪ ‬מכיל‪ ‬את‪ ‬המסלול‪ ‬לקובץ‪ ‬המקורי‪ ,‬נקבל‪ ‬כי ‪ ‬אם‪ ‬נמחק‪ ‬את‪ ‬הקובץ‪ ‬‬
‫המקורי‪ ‬מערכת‪ ‬ההפעלה‪ ‬תנסה‪ ‬לגשת‪ ‬אל‪ ‬המסלול‪ ‬ותגלה‪ ‬שהוא‪ ‬לא‪ ‬קיים‪ ,‬כלומר‪ ‬הוא‪ ‬יכול ‪ ‬להיות ‪ ‬תלוי ‪ ‬באוויר‪ .‬זאת‪ ‬‬
‫לעומת‪ hard link ‬שלא‪ ‬יכול‪ ‬להגיע‪ ‬למצב‪ ‬כזה‪ .‬‬
‫‪ ‬‬
‫‪ inode‬‬
‫‪ inode‬הוא‪ ‬מבנה‪ ‬הנתונים‪ ‬שעל‪ ‬ידו‪ ‬מערכת‪ ‬ההפעלה‪ ‬מייצגת‪ ‬את‪ ‬הקובץ‪ .‬לכל‪ ‬קובץ‪ ‬יש‪ inode ‬ייחודי‪ ‬לו‪ .‬באופן‪ ‬‬
‫כללי‪ ,‬שם‪ ‬הקובץ‪ ‬מצביע‪ ‬על‪ ‬ה­‪ inode‬שלו‪ .‬‬
‫‪67 ‬‬
‫ה­‪ inode‬מכיל‪ ‬את‪ ‬ה­‪ metadata‬של‪ ‬הקובץ‪ ­ ‬בעלים‪ ,‬זמנים‪ ‬מיוחדים‪ ,‬מיקום‪ ‬הבלוקים‪ ‬בדיסק‪ ‬וכו'‪ .‬‬
‫כל‪ ‬מערכת‪ ‬הפעלה‪ ‬בוחרת‪ ‬איזה‪ ‬מידע‪ ‬היא‪ ‬שומרת‪ ‬ב­‪ inode‬אבל‪ ‬לפי‪ ‬הסטנדרט‪ ‬יש‪ ‬כמה‪ ‬דברים‪ ‬שחובה‪ ‬לשמור‪ .‬‬
‫‪ ‬‬
‫נבחין‪ ‬כי‪ ‬אנחנו‪ ‬שומרים‪ ‬גודל‪ ‬קובץ‪ ,‬גודל‪ ‬של‪ ‬בלוק‪ ‬ומספר‪ ‬בלוקים‪ .‬‬
‫למה‪ ‬אנחנו‪ ‬צריכים‪ ‬את‪ ‬זה?‪ ‬הרי‪ ‬מספיק‪ ‬לנו‪ ‬שניים‪ ‬מתוך‪ ‬שלושת‪ ‬הפרמטרים‪ ‬הללו‪ ‬שכן‪ ‬מתקיים‪ : ‬‬
‫)‪ (block * sizeof(block)) = sizeof(file#‬‬
‫נזכור‪ ‬כי‪ ‬אנחנו‪ ‬לא‪ ‬בהכרח‪ ‬משתמשים‪ ‬בכל‪ ‬הגודל‪ ‬של‪ ‬הבלוק!‪ ‬גודל‪ ‬של‪ ‬קובץ‪ ‬לא‪ ‬חייב‪ ‬להיות‪ ‬כפולה‪ ‬שלמה‪ ‬של‪ ‬‬
‫בלוקים!‪ ‬אנחנו‪ ‬חייבים‪ ‬לשמור‪ ‬את‪ ‬גודל‪ ‬הקובץ‪ ‬כדי‪ ‬למנוע‪ ‬קריאה‪ ‬מסוף‪ ‬הבלוק‪ ‬האחרון‪ ‬שאינו‪ ‬תפוס‪ .‬‬
‫מימוש‪ ‬של‪ ‬מערכת‪ ‬הקבצים‪ ‬‬
‫נזכור‪ ‬כי‪ ‬הרעיון‪ ‬הכללי‪ ‬שציינו‪ ‬הוא‪ ‬שיש‪ ‬ספריה‪ ‬שתוכנה‪ ‬הוא‪ dir entries ‬שהם‪ ‬לפחות‪ ‬השם‪ ‬של‪ ‬הקובץ ‪ ‬ואיפה‪ ‬הוא‪ ‬‬
‫"נמצא"‪ ,‬כלומר‪ ‬הוא‪ ‬מצביע‪ ‬לאיזשהו‪ ‬בלוק‪ ‬ב­‪ Hard Disk‬שמצביע‪ ‬ל­‪ metadata‬של‪ ‬הקובץ‪ .‬‬
‫אם‪ ‬נרצה‪ ‬לממש‪ Hard Link ‬זה‪ ‬פשוט‪ ‬למדי‪ ,‬ניקח‪ ‬את‪ ‬הספרייה‪ ‬שנרצה‪ ‬להוסיף‪ ‬לה‪ ‬את‪ ‬ה­‪ Hard Link‬ונשים‪ ‬שם‪ ‬‬
‫‪ direntry‬חדש‪ ‬שמצביע‪ ‬ל­‪ metadata‬הרצוי‪ .‬‬
‫אם‪ ‬נרצה‪ ‬לממש‪ Soft Link ‬ניצור‪ ‬קובץ‪ ‬חדש‪ ,‬וב­‪ data‬נכתוב ‪ ‬בו‪ ‬את‪ ‬המסלול‪ ‬שאליו‪ ‬נרצה‪ ‬להגיע‪ .‬במערכות‪ ‬קבצים‪ ‬‬
‫מבוססות‪ ,Unix ‬החליטו‪ ‬שזה‪ ‬מיותר‪ ‬ליצור‪ ‬קובץ‪ ‬שהתוכן‪ ‬שלו‪ ‬יהיה‪ ‬המסלול‪ ‬ולכן‪ ‬יש‪ ‬שתי‪ ‬גישות‪ ‬שונות‪ :‬‬
‫● ב­‪ Dir Entry‬עצמו‪ ‬אנחנו‪ ‬נשמור‪ ‬את‪ ‬המצביע‪ ‬לקובץ‪ ‬המטרה‪ ‬עצמו‪ ,‬הבעיה‪ ‬בכך‪ ‬היא‪ ‬שאין‪ ‬לנו‪ ‬‬
‫אפשרות‪ ‬לשמור‪ ‬מידע‪ ‬נוסף‪ .‬חלק‪ ‬ממטרת‪ ‬השימוש‪ ‬ב­‪ Soft Link‬היא‪ ‬שינוי‪ ‬של ‪ ‬הרשאות‪ ‬גישה ‪ ‬‬
‫במעבר‪ ‬במסלול‪ ‬מסוים‪ .‬‬
‫● יותר‪ ‬נפוץ‪ ‬הוא‪ ‬ליצור‪ ‬קובץ‪ ‬חדש‪ ,‬בעל‪ metadata ‬ובו‪ ‬נשמור‪ ‬באמת‪ ‬את‪ ‬ההרשאות‪ ,‬את‪ ‬המסלול‪ ‬‬
‫לקובץ‪ ‬לא‪ ‬נשמור‪ ‬ב­‪ Data‬של‪ ‬הקובץ‪ ‬אלא‪ ‬ב­‪ metadata‬שלו‪ .‬נזכור‪ ‬גישה‪ ‬לזיכרון‪ ‬מורכבת‪ ‬‬
‫מקריאת‪ ‬ה­‪ ,metadata‬ביצוע‪ parsing ‬ורק‪ ‬לאחר‪ ‬מכן‪ ‬קריאת‪ ‬המידע‪ ‬עצמו‪ metadata ,‬הוא‪ ‬‬
‫בד"כ‪ ‬ממש‪ ‬קטן‪ ‬ואילו‪ ‬הקובץ‪ ‬עצמו‪ ‬הוא‪ ‬גדול‪ .‬עבור‪ soft link ‬ה­‪ data‬בד"כ‪ ‬כל‪ ‬כך‪ ‬קטן‪ ‬שניתן‪ ‬יהיה‪ ‬‬
‫להכניס‪ ‬אותו‪ ‬ל­‪ .metadata‬‬
‫‪ ‬‬
‫מימוש‪ ‬של‪ ‬ספריות‪ ‬‬
‫עבור‪ ‬כל‪ ‬ספריה‪ ‬אנחנו‪ ‬שומרים‪ ‬את‪ ‬ה‪ struct‬שנקרא‪ direntry ‬שמכיל‪ ‬את‪ ‬השדות‪ :‬‬
‫● ‪ ­ d_ino‬המספר‪ ‬הייחודי‪ ‬של‪ ‬ה­‪ inode‬של‪ ‬הספריה‪ .‬‬
‫● ‪ ­ d_off‬ה­‪ offset‬ל‪ direntry‬הבא‪ ,‬הספרייה‪ ‬מיוצגת‪ ‬ע"י‪ ‬מערך‪ ‬של‪ ,dir entries ‬הם‪ ‬לא‪ ‬בהכרח‪ ‬‬
‫נמצאים‪ ‬ברצף‪ ‬ולכן‪ ‬יש‪ ‬צורך‪ ‬בפרמטר‪ ‬זה‪ .‬‬
‫● ‪ ­ d_reclen‬הגודל‪ ‬של‪ ‬ה­‪ entry‬הנוכחי‪ ,‬גם‪ ‬בו‪ ‬יש‪ ‬צורך‪ ‬על‪ ‬מנת‪ ‬לבצע‪ ‬את‪ ‬המעבר‪ ‬על‪ ‬מערך‪ ‬ה­‬
‫‪ .dir entries‬‬
‫● ‪ ­ d_type‬סוג‪ ‬הקובץ‪ .‬‬
‫● ‪ ­ d_name‬שם‪ ‬הקובץ‪ ,‬בניגוד‪ ‬לרשום‪ ‬הקובץ‪ ‬מערכות‪ ‬קבצים‪ ‬כנראה‪ ‬משנות‪ ‬את‪ ‬גודל‪ ‬השם‪ .‬‬
‫‪ ‬‬
‫‪ ­ Path Resolution Process‬אלגוריתם‪ : Namei ‬‬
‫בהינתן‪ ‬מסלול‪ ,‬כיצד‪ ‬נגיע‪ ‬לקובץ‪ ‬המיוצג‪ ‬על‪ ‬ידו?‪ ‬‬
‫נפרק‪ ‬את‪ ‬המסלול‪ ‬לאטומים‪ ,‬לפי‪ ‬המסלול‪ ,‬עוברים‪ ‬אטום‪ ‬אחרי‪ ‬אטום ‪u ‬מוודאים‪ ‬שיש‪ ‬הרשאות‪ ‬גישה‪ ‬והרצה‪ ‬לכל‪ ‬‬
‫הספריות ‪ ‬לאורך‪ ‬הדרך‪ .‬בכל‪ ‬פעם‪ ‬ניגשים‪ ‬לאטום‪ ,‬בודקים‪ ‬שיש‪ ‬לנו‪ ‬את‪ ‬ההרשאות‪ ,‬מבקשים‪ ‬את‪ ‬האטום‪ ‬הבא‪ ‬וכן‪ ‬‬
‫האלה‪ ‬עד‪ ‬להגעה‪ ‬לקובץ‪ .‬‬
‫‪68 ‬‬
‫מה‪ ‬יכול‪ ‬לקרות‪ ‬בדרך?‪ ‬ייתכן‪ ‬שנפגוש‪ ,Hard Links ‬אבל‪ ‬מבחינתנו‪ ‬זה‪ ‬לא‪ ‬מקרה ‪ ‬מיוחד‪ ,‬מתייחסים‪ ‬אליהם‪ ‬כאל‪ ‬‬
‫אטום‪ valid ‬לחלוטין‪ .‬כשפוגשים‪ soft link ‬אנחנו‪ ‬צריכים‪ ‬לקבל‪ ‬ממנו‪ ‬את‪ ‬המסלול‪ ‬אותו‪ ‬הוא‪ ‬מכיל‪ ,‬נפרק‪ ‬אותו‪ ‬‬
‫לאטומים‪ ‬ונטפל‪ ‬בהם‪ ‬לפני‪ ‬המשך‪ ‬הטיפול‪ .‬‬
‫הזמן‪ ‬שייקח ‪ ‬לנו‪ ‬לטפל‪ ‬במסלול‪ ‬הזה ‪ ‬הוא‪ ‬לפחות‪ ‬ליניארי‪ ‬במספר‪ ‬האטומים‪ ,‬אבל‪ ‬נזכור‪ ‬שכל‪ ‬אחד‪ ‬מהאטומים‪ ‬יכול‪ ‬‬
‫להכיל ‪ ‬מספר‪ ‬רב‪ ‬של‪ ‬אטומים‪ ‬בתוכו‪ .‬למה‪ ‬זמן‪ ‬הריצה‪ ‬של‪ ‬האלגוריתם‪ ‬יכול‪ ‬להיות‪ ‬ארוך‪ ‬אפילו‪ ‬אם‪ ‬אין‪ ‬לנו‪ ?soft link ‬‬
‫נזכור‪ ‬שכדי‪ ‬למצוא‪ ‬את‪ ‬האטום‪ ‬הבא‪ ‬בהינתן‪ ‬ספריה‪ ‬צריך‪ ‬לעבור‪ ‬על‪ ‬כל‪ ‬ה­‪ dir entries‬עד‪ ‬שנגיע‪ ‬אליו‪ .‬‬
‫בתרגול‪ ‬למדנו‪ ‬איך‪ ‬אפשר‪ ‬להאיץ‪ ‬את‪ ‬החיפוש‪ ‬ע"י‪ ‬שימוש‪ ‬במטמון‪ ‬של‪ ‬ה­‪ .dentries‬‬
‫‪ ‬‬
‫דיסקים‬
‫לכל‪ ‬דיסק‪ ‬יש‪ ‬לפחות‪ ‬ראש‪ ‬קורא‪ ‬אחד‪ .‬‬
‫‪ ­ Track‬מסילה‪ ­ ‬מסלול‪ ‬מעגלי‪ ‬שמעבר‪ ‬עליו‪ ‬לא‪ ‬דורש‪ ‬הזזה‪ ‬של‪ ‬הראש‪ ‬הקורא‪ .‬‬
‫‪ ­ Sector‬בלוק‪ ‬יחיד‪ ‬של‪ data ‬על‪ ‬מסלול‪ ‬מעגלי‪ ,‬עובדים‪ ‬בד"כ‪ ‬בגרעיניות‪ ‬של‪ Cluster ‬המוגדר‪ ‬כארבעה‪ .sectors ‬‬
‫יש‪ ‬הבדל‪ ‬משמעותי‪ ‬בין‪ ‬זמן‪ ‬של‪ ‬גישה‪ ‬אקראית‪ ‬לבין‪ ‬זמן‪ ‬של‪ ‬גישה‪ ‬רציפה‪ .‬‬
‫זמן‪ ‬הגישה‪ ‬מורכב‪ ‬מהזזת‪ ‬הראש‪ ‬הקורא‪ ,‬והזמן‪ ‬שלוקח‪ ‬עד‪ ‬שהסקטור‪ ‬המתאים‪ ‬מגיע‪ ‬לראש‪ ‬הקורא‪ .‬‬
‫מידע‪ ‬נוסף‪ ‬במצגות‪ ‬של‪ ‬מערכות‪ ‬קבצים‪ :P ‬‬
‫‪ ‬‬
‫למה‪ ‬הדיסק‪ ‬נקרא‪ ?''3.5 ‬הוא‪ ‬נכנס‪ ‬בדיוק‪ ‬ל‪ ''3.5‬של‪ ‬הקורא‪ ‬דיסקטים!!‪ ‬‬
‫‪ ‬‬
‫גישה‪ ‬לדיסק‪ ‬‬
‫בדרך‪ ‬כלל‪ ‬אנחנו ‪ ‬אומרים‪ ‬איזה‪ ‬סקטור‪ ,‬משטח‪ ‬וגליל‪ ‬אנחנו‪ ‬רוצים‪ ‬לקרוא‪ ,‬ייצוג‪ ‬זה‪ ‬לא‪ ‬נוח‪ ‬מבחינת‪ ‬התוכנה‪ .‬מאוד‪ ‬‬
‫לא‪ ‬נוח‪ ‬לכתוב‪ ‬קוד‪ ‬שמשתמש‪ ‬בייצוג‪ ‬הזה‪ ,‬הרבה‪ ‬יותר‪ ‬נוח‪ ‬לחשוב‪ ‬על‪ ‬דיסק‪ ‬כמערך‪ ‬של‪ ‬בלוקים‪ ,‬כאשר‪ ‬ה­‪ Disk‬‬
‫‪ Drive‬מתרגם‪ ‬את‪ ‬מספר‪ ‬הבלוק‪ ‬לסקטור‪ ,‬משטח‪ ,‬גליל‪ .‬הייצוג‪ ‬הזה‪ ‬ע"י‪ ‬מערך‪ ‬של‪ ‬בלוקים ‪ ‬נקרא‪ Logical Block ‬‬
‫‪ ,Address‬הייצוג‪ ‬הפנימי‪ ‬לא‪ ‬מעניין‪ ‬אותנו‪ .‬‬
‫‪ ‬‬
‫הגודל‪ ‬הנפוץ‪ ‬של‪ ‬בלוק‪ ‬ב­‪ LBA‬הוא‪ 512 ‬בתים‪ ,‬זה‪ ‬כמובן‪ ‬לא‪ ‬מחייב‪ ‬את‪ ‬מערכת‪ ‬הקבצים‪ ‬להשתמש‪ ‬בבלוקים‪ ‬באותו‪ ‬‬
‫הגודל‪ ,‬שבד"כ‪ ‬הינו‪ ‬בין‪ 2 ‬ל­‪ ,KB 4‬כל‪ ‬בלוק‪ ‬של‪ ‬מערכת‪ ‬הקבצים‪ ‬יתפוס‪ ‬מספר‪ ‬בלוקים‪ ‬רציפים‪ ‬של‪ ‬הדיסק‪ .‬‬
‫‪ ‬למה‪ ‬בכלל‪ ‬להסתבך?‪ ‬למה‪ ‬לא‪ ‬להגיד‪ ‬שכל‪ ‬קובץ‪ ‬יהיה‪ ‬רציף‪ ‬בזיכרון‪ ‬הפיזי?‪ ‬‬
‫‪69 ‬‬
‫בעיקרון‪ ‬זה‪ ‬אפשרי‪ .‬בגישה‪ ‬זאת‪ ‬נקבל‪ ‬שייצוג‪ ‬הקובץ‪ ‬הוא‪ ‬מאוד‪ ‬פשוט‪ ‬וזמן‪ ‬החיפוש‪ ‬הוא‪ ‬מינימאלי‪ .‬אבל‪ ‬נקבל‪ ‬בעיה‪ ‬‬
‫של‪ ‬שברור‪ ‬חיצוני‪ .‬‬
‫שברור ‪ ‬חיצוני‪ ­ ‬יש‪ ‬לנו‪ ‬בלוקים‪ ‬פנויים‪ ‬אבל‪ ‬הם‪ ‬מפוזרים‪ ,‬ולא‪ ‬ניתן‪ ‬להקצות‪ ‬אותם‪ ‬באופן‪ ‬רציף‪ .‬כמו‪ ‬כן ‪ ‬זה‪ ‬מונע‪ ‬מאיתנו‪ ‬‬
‫להגדיל‪ ‬קבצים‪ ‬קיימים‪ ‬במידה‪ ‬ואין‪ ‬עוד‪ ‬זיכרון‪ ‬רציף‪ ‬אחריהם‪ .‬‬
‫‪ ‬‬
‫‪VSFS - Very Simple File System‬‬
‫זוהי‪ ‬שיטה‪ ‬לניהול‪ ‬הזיכרון‪ ‬על‪ ‬הדיסק‪ ,‬להלן‪ ‬דוגמא‪ ‬עבור‪ ‬דיסק‪ ‬קטן‪ ,‬נניח‪ ‬של‪ 64 ‬בלוקים‪ ,‬נצטרך‪ ‬לשמור‪ ‬את‪ ‬המידע‪ ‬‬
‫הבא‪ :‬‬
‫‪ ‬‬
‫‪ ‬‬
‫‪ ­ superblock‬מכיל‪ ‬מידע‪ ‬חשוב‪ ‬על‪ ‬מערכת‪ ‬הקבצים‪ ,‬כגון‪ :‬מספר‪ ‬ה­‪ ,inodes‬מספר‪ ‬ה­‪ ,data blocks‬מיקום‪ ‬‬
‫טבלת‪ ‬ה­‪ .inodes‬מספר‪ ‬קסם‪ ‬בדומה‪ ‬למה‪ ‬שלמדנו‪ ‬על‪ ‬התקנים‪ ‬בתרגול‪ ‬ומספר‪ ‬ה­‪ inode‬של‪ ‬ה­‪ .root directory‬‬
‫מיקום‪ ‬של‪ ‬ה­‪ superblock‬חייב‪ ‬להיות‪ ‬ידוע‪ ,‬בד"כ‪ ‬זהו‪ ‬בלוק‪ 0 ‬בדיסק‪ .‬‬
‫ניתן‪ ‬להגדיר‪ ‬שאנו‪ ‬מחלקים‪ ‬את‪ ‬הדיסק‪ ‬לכמה‪ ‬חתיכות‪ ‬כך‪ ‬שכל‪ ‬חתיכה‪ ‬מהווה‪" ‬כביכול"‪ ‬דיסק‪ ‬קטן‪ ‬יותר‪ .‬הסבר‪ :‬‬
‫‪ ‬‬
‫‪ ‬‬
‫‪70 ‬‬
‫הערה‪ :‬נבחין‪ ‬כי‪ ‬מערכות‪ ‬הקבצים‪ ‬אינן‪ ‬מתחילות‪ ‬מכתובת‪ 0 ‬שלהן‪ ,‬לפי‪ ,Start ‬זה‪ ‬בגלל‪ ‬המידע‪ ‬ששמור‪ ‬לפני‪ ‬כן‪ .‬‬
‫מדוע‪ ‬לא‪ ‬ניתן‪ ‬ליצור‪ hard link ‬ממערכת‪ ‬קבצים‪ ‬אחת‪ ‬לאחרת?‪ ‬‬
‫ב­‪ metadata‬רשום‪ ‬מספר‪ ‬ה­‪ ,inode‬הוא‪ ‬תמיד‪ ‬ילך‪ ‬למספר‪ ‬ה­‪ inode‬של‪ ‬מערכת‪ ‬הקבצים‪ ‬שבה‪ ‬הוא‪ ‬נמצא‪ ,‬ולכן‪ ‬‬
‫לא‪ ‬ניתן‪ ‬לקשר‪ ‬אותו‪ ‬למערכת ‪ ‬קבצים‪ ‬שונה‪ ‬לגמרי‪ ‬עם‪ ‬מספר‪ ‬שונה‪ ‬של‪ .inodes ‬לעומת‪ ‬זאת‪ ‬עבור‪ soft link ‬יש‪ ‬לנו‪ ‬‬
‫מסלול‪ ,‬ואיתו‪ ‬נוכל‪ ‬להסתדר‪ ,‬הוא‪ ‬לא‪ ‬מסתמך ‪ ‬על‪ ‬המבנה‪ ‬הפנימי‪ ‬של‪ ‬מערכת‪ ‬הקבצים‪ ,‬לכן‪ ‬במסלול‪ ‬החיפוש‪ ‬יש‪ ‬‬
‫לבדוק‪ ‬האם‪ ‬עברנו‪ ‬מערכת‪ ‬קבצים‪ .‬‬
‫‪ ‬‬
‫הרכבה‪ ‬של‪ ‬מערכות‪ ‬קבצים‪ ‬‬
‫כשרוצים‪ ‬לחבר‪ ‬מערכת‪ ‬קבצים‪ ‬חדשה‪ ‬למערכת‪ ‬קבצים‪ ‬קיימת‪ ‬מבצעים‪ ‬פעולת‪ .mount ‬‬
‫לדוגמא‪ :‬נניח‪ ‬שמבצעים‪ ‬פעולת‪ .mount ­VFAT /usr2 ‬‬
‫כל‪ ‬הקבצים‪ ‬שנמצאים‪ ‬ב­‪ directory user2‬יוסתרו‪ ,‬מערכת‪ ‬ההפעלה‪ ‬מבינה‪ ‬שזו‪ mounting point ‬והיא‪ ‬עוברת‪ ‬‬
‫לעבוד‪ ‬לפי‪ ‬ה­‪ superblock‬של‪ ‬מערכת‪ ‬הקבצים ‪ ‬החדשה‪ .‬ניסיון‪ ‬לגשת‪ ‬לקבצים‪ ‬הישנים‪ ‬פשוט‪ ‬לא‪ ‬יצליח‪ ‬עד‪ ‬לביצוע‪ ‬‬
‫‪ .umount‬‬
‫‪ ‬‬
‫מציאה‪ ‬של‪ inode ‬‬
‫נניח‪ ‬שברצוננו‪ ‬למצוא‪ ‬בלוק‪ ‬של‪ inode ‬מסוים‪ ,‬לשם‪ ‬כך‪ ‬נצטרך‪ ‬לחשב‪ ‬את‪ ‬הסקטור‪ ‬שלו‪ ,‬את‪ ‬מיקומו‪ ‬בתוך‪ ‬הדיסק‪ .‬‬
‫ראשית‪ ‬נצטרך‪ ‬לדעת‪ ‬איפה‪ ‬מתחיל‪ ‬המערך‪ ‬של‪ ,inode ‬ולהתקדם‪ ‬לפי‪ ‬גודל‪ ‬של‪ .inode ‬‬
‫‪ ‬‬
‫לפי‪ ‬ה­‪ inode‬נרצה‪ ‬לדעת‪ ‬איפה‪ ‬ה­‪ ,data‬הפתרון ‪ ‬הפשוט‪ ‬הוא‪ ‬לשמור‪ ‬ב­‪ inode‬את‪ ‬רשימת‪ ‬הבלוקים‪ ‬של‪ ‬ה­‪ data‬‬
‫אך‪ ‬נצטרך‪ ‬לשמור‪ ‬יותר‪ ‬מדי‪ ‬ביטים‪ ‬על‪ ‬מנת‪ ‬לאפיין‪ ‬את‪ ‬הבלוקים‪ ‬ולא‪ ‬יהיה‪ ‬לנו‪ ‬מספיק‪ ‬מקום‪ ‬לכך‪ .‬‬
‫לכן‪ ‬נרצה‪ ‬להציע‪ ‬מנגנון‪ ‬חכם‪ ‬יותר‪ ,‬שמבצע‪ ‬מיפוי‪ .‬‬
‫‪ ‬‬
‫‪Multi-level Index‬‬
‫איך‪ ‬אנחנו‪ ‬שומרים‪ ‬את‪ ‬הבלוקים‪ ‬של‪ ‬קובץ‪ ‬מסוים‪ ‬בדיסק?‪ ‬ואיך‪ ‬‬
‫אנחנו‪ ‬ניגשים‪ ‬אליהם‪ ‬אח"כ?‪ ‬‬
‫יש‪ ‬לנו‪ 12 ‬מצביעים‪ ‬ישירים‪ ‬שאומרים‪ ‬איפה‪ ‬נמצאים‪ ‬בלוקים‪ ‬‬
‫של‪ ‬זיכרון‪ .‬כלומר‪ ‬אם ‪ ‬הקובץ‪ ‬שלנו‪ ‬מכיל‪ ‬לכל‪ ‬היותר‪ 12 ‬‬
‫בלוקים‪ ‬אז‪ ‬מה‪ ‬שנוכל‪ ‬לעשות‪ ‬זה‪ ‬לשמור‪ ‬את‪ ‬המספרים‪ ‬הללו‪ ‬‬
‫במערך‪ ‬האינדקסים‪ ‬של‪ ‬הבלוקים‪ ,‬זה‪ ‬יספיק‪ ‬לכל‪ ‬קובץ‪ ‬של‪ ‬עד‪ ‬‬
‫‪ ,48KB‬פשוט‪ ‬על‪ ‬ידי‪ ‬ציון‪ ‬של‪ ‬מספרי‪ ‬הבלוקים‪ .‬‬
‫מה‪ ‬קורה‪ ‬אם‪ ‬אנחנו‪ ‬צריכים‪ ‬יותר‪ ‬מזה?‪ ‬לשם‪ ‬כך‪ ‬יש‪ ‬לנו‪ ‬את‪ ‬‬
‫אינדקס‪ ,13 ‬כניסה‪ ‬זו‪ ‬אינה‪ ‬מצביעה‪ ‬לבלוק‪ ‬של ‪ ‬הקובץ‪ ,‬אלא‪ ‬‬
‫היא‪ ‬מצביעה‪ ‬לבלוק‪ ‬המכיל‪ ‬מצביעים‪ ‬לבלוקים‪ ‬נוספים‪ ‬של‪ ‬‬
‫‪ ,data‬רעיון‪ ‬זה‪ ‬נקרא‪ .single­indirect pointer ‬‬
‫איזה‪ ‬קבצים‪ ‬נוכל‪ ‬לייצג‪ ‬בצורה‪ ‬כזאת?‪ ‬הבלוק‪ ‬החדש‪ ‬הוא ‪ ‬ב­‬
‫‪ ,4KB‬כל‪ ‬מצביע‪ ‬הוא ‪ 4 ‬בתים‪ ‬וסה"כ‪ 210 ‬מצביעים‪ ,‬לכן‪ ‬נוכל‪ ‬‬
‫לייצג‪ . 4M ≈ 4KB * (210 + 12) ‬‬
‫‪71 ‬‬
‫דבר‪ ‬נוסף‪ ‬שיש‪ ‬לנו‪ ‬זה‪ ,Double indirect pointer ‬עם‪ ‬שתי‪ ‬רמות‪ ‬של‪ ‬קינון‪ ,‬לאחר‪ ‬רמה‪ ‬אחת‪ ‬נגיע‪ ‬לבלוק‪ ‬של‪ ‬‬
‫מצביעים‪ ‬למצביעים‪ ‬לבלוקים‪ ‬של‪ .data ‬יש‪ ‬לנו‪ 210 ‬מצביעים‪ ‬שכל ‪ ‬אחד‪ ‬מהם‪ ‬מכיל‪ 210 ‬מצביעים‪ ‬לבלוקים‪ ‬של‪ ,4K ‬‬
‫סה"כ‪ .4GB ‬על‪ ‬אותה‪ ‬הדרך‪ ‬נגדיר‪ ,triple indirection ‬שייתן‪ ‬לנו‪ ,4TB ‬וכך‪ ‬נוכל‪ ‬לייצג‪ ‬כל‪ ‬קובץ‪ ‬ריאלי‪ .‬‬
‫‪ ‬‬
‫הערה‪ : ‬הבלוקים ‪ ‬של‪ ‬ה­‪ indirect‬הם‪ ‬בדיוק‪ ‬כמו‪ ‬בלוקים‪ ‬אחרים‪ ‬שאנו‪ ‬מקצים‪ ‬במערכת‪ .‬בלוקים‪ ‬אלו‪ ‬לא‪ ‬‬
‫נמצאים‪ ‬ב­‪ ,inode‬מערכת‪ ‬הקבצים‪ ‬מקצה‪ ‬אותם‪ ‬בדיוק‪ ‬כמו‪ ‬ההקצאה‪ ‬של‪ ‬בלוקים‪ ‬רגילים‪ ‬במערכת ‪ ‬‬
‫הקבצים‪ ,‬ואנו‪ ‬מפרשים‪ ‬אותם‪ ‬כבלוק‪ ‬של‪ ‬מבציעים‪ .‬‬
‫בשיטה‪ ‬זאת‪ ‬הגישה‪ ‬לבלוקים‪ ‬שהם‪ ‬עד‪ 48K ‬היא‪ ‬מאוד‪ ‬מהירה‪ .‬‬
‫‪ Mass­Count Disparity‬‬
‫מדוע‪ ‬ההיררכיה‪ ‬כל‪ ‬כך‪ ‬לא‪ ‬מאוזנת?‪ ‬‬
‫הנתונים‪ ‬עבור‪ ‬הגרף‪ ‬נאספו‪ ‬מ­‪ 12‬מיליון‪ ‬קבצים‪ ‬ביותר‪ ‬מ­‪ 1000‬מערכות‪ ‬קבצים‪ ‬של‪ .UNIX ‬‬
‫‪ ‬‬
‫● ציר‪ ‬ה­‪ X‬מייצג‪ ‬גודל‪ ‬של‪ ‬קובץ‪ ‬בבתים‪ .‬‬
‫● ציר‪ ‬ה­‪ Y‬מייצג‪ ‬פונקצית‪ ‬התפלגות‪ ‬מצטברת‪ .‬‬
‫● העקומה‪ ‬האדומה‪ ‬מייצגת‪ ‬את‪ ‬אחוז‪ ‬הקבצים‪ ‬במצטבר‪ ‬שגודלם‪ ‬קטן‪ ‬או‪ ‬שווה‪ ‬לערך‪ ‬המתאים‪ ‬בציר‪ ‬ה­‪ .x ‬‬
‫● העקומה‪ ‬הכחולה‪ ‬מייצגת‪ ‬את‪ ‬אחוז‪ ‬הבתים‪ ‬המצטבר‪ ‬ששייכים‪ ‬לקבצים‪ ‬שגודלם‪ ‬קטן‪ ‬או‪ ‬שווה‪ ‬לערך‪ ‬‬
‫המתאים‪ ‬בציר‪ ‬ה­‪ .x ‬‬
‫● הפונקציות‪ ‬מתלכדות‪ ‬כאשר‪ ‬אין‪ ‬אף‪ ‬קבצים‪ ‬בגודל‪ ‬שקטן‪ ‬או‪ ‬שווה‪ ‬ל­‪ x‬או‪ ‬כאשר‪ ‬כל‪ ‬הקבצים‪ ‬הם‪ ‬בגודל‪ ‬שקטן‪ ‬‬
‫או‪ ‬שווה‪ ‬ל­‪ .x‬‬
‫● החץ‪ ‬הירוק‪ ‬האמצעי‪ ‬מסמן‪ ‬את‪ ‬המקום‪ ‬בו‪ ‬סכום‪ ‬ערכי‪ ‬ה­‪ Y‬של‪ ‬שתי‪ ‬העקומות‪ ‬שווה‪ ‬ל­‪ .1‬החץ‪ ‬מלמד‪ ‬על‪ ‬‬
‫חוסר‪ ‬איזון‪ ,‬שכן‪ ‬הוא‪ ‬מראה‪ ‬כי‪ 90%~ ‬מהמידע‪ ‬מוכל‪ ‬ב­‪ 10%~ ‬מהקבצים‪ ‬בלבד‪ .‬‬
‫בפירוט‪ ,‬החץ‪ ‬האמצעי‪ ‬מלמד‪ ‬אותנו‪ ‬ש­‪ ~90% ‬מהקבצים‪ ‬אלו ‪ ‬שגודלם‪ ‬קטן‪ ‬או‪ ‬שווה‪ ‬ל­‪ 16KB ‬מכילים‪ ‬‬
‫‪ ~10%‬בלבד‪ ‬מכלל‪ ‬הבתים‪ ‬ששמורים‪ ‬על‪ ‬הדיסק‪ ,‬בעוד‪ ‬ש­‪ ~10% ‬הקבצים‪ ‬הנותרים‪ ‬שגדולים‪ ‬מ­‪ 16KB ‬‬
‫מכילים‪ ‬את‪ ‬יתר‪ ~90% ‬הבתים‪ ‬השמורים‪ ‬על‪ ‬הדיסק‪. ‬המטריקה‪ ‬נקראת‪" ‬יחס‪ ‬משותף"‪ .(”joint ratio“) ‬‬
‫*‪ ‬ניתן‪ ‬לראות‪ ‬שכמעט‪ 99 ‬אחוז‪ ‬מהקבצים‪ ‬מכוסים‪ ‬ע"י‪ direct ‬ו­‪ .single‬‬
‫● מה‪ ‬מלמד‪ ‬החץ‪ ‬השמאלי?‪ ‬חוסר‪ ‬איזון‪ ‬המתבטא‪ ‬בכך‪ ‬שמחצית‪ ‬הקבצים‪) ‬אילו‪ ‬הקטנים ‪ ‬יותר(‪ ‬מכילים‪ ‬רק‪ ‬‬
‫‪ 1­2%‬מהמידע‪ ‬השמור‪ ‬בבתים‪ .‬‬
‫● מה‪ ‬מלמד‪ ‬החץ‪ ‬הימני?‪ ‬חוסר‪ ‬איזון‪ ‬המתבטא‪ ‬בכך‪ ‬שמחצית‪ ‬המידע‪ ‬השמור‪ ‬מוכל‪ ‬בפחות‪ ‬מ­‪ 1% ‬‬
‫מהקבצים‪ ‬בלבד‪) ‬הגדולים‪ ‬יותר(‪ .‬‬
‫‪ ‬‬
‫‪72 ‬‬
‫תופעה‪ ‬זאת‪ ‬של‪ mass­count disparity ‬נפוצה‪ ‬במערכות‪ ‬מחשב‪ ‬‬
‫● אם‪ ‬נבחר‪ ‬קובץ‪ ‬כלשהו‪ ‬מסך‪ ‬הקבצים‪ ‬רוב‪ ‬הסיכויים‪ ‬שהוא‪ ‬יהיה‪ ‬קטן‪ ,‬אך‪ ‬אם‪ ‬נבחר‪ ‬בית‪ ‬כלשהו‪ ‬‬
‫מסך‪ ‬הבתים‪ ‬רוב‪ ‬הסיכויים‪ ‬שהוא‪ ‬יהיה‪ ‬שייך‪ ‬לקובץ‪ ‬גדול‪ .‬‬
‫● אם‪ ‬נבחר‪ ‬תהליך‪ ‬כלשהו‪ ‬מסך‪ ‬כל ‪ ‬התהליכים‪ ‬רוב‪ ‬הסיכויים‪ ‬שהוא‪ ‬יהי‪ ‬קצר‪ ,‬אך‪ ‬אם‪ ‬נבחר‪ ‬יחידת‪ ‬‬
‫זמן‪ ‬מסוימת‪ ‬מכל‪ ‬הזמן‪ ‬אז‪ ‬רוב‪ ‬הסיכויים‪ ‬שהיא‪ ‬תהיה‪ ‬שייכת‪ ‬לתהליך‪ ‬ארוך‪ .‬‬
‫● באופן‪ ‬כללי‪ : ‬מספר‪ ‬קטן‪ ‬של‪ ‬פריטים ‪ ‬מהווה‪ ‬את‪ ‬רוב ‪ ‬המסה‪ ‬הכוללת‪ ,‬בזמן‪ ‬שכל‪ ‬הפריטים ‪ ‬הקטנים‪ ‬‬
‫ביחד‪ ‬נחשבים‪ ‬לחלק‪ ‬קטן‪ ‬מכלל‪ ‬המסה‪ .‬‬
‫‪ ‬‬
‫‪ ‬‬
‫איך‪ ‬ניתן‪ ‬לנצל‪ ‬את ‪ ‬התופעה‪ ‬הזאת‪ ‬בתכנון‪ ‬של‪ ‬מערכות‪ ‬קבצים?‪ ‬למשל‪ ‬ע"י‪ ‬כך‪ ‬שנתכנן‪ ‬את‪ ‬עץ‪ ‬היררכיית‪ ‬הבלוקים‪ ‬‬
‫של‪ ‬מערכת ‪ ‬הקבצים‪ ‬כך‪ ‬שתוכנו‪ ‬של‪ ‬קובץ‪ ‬קטן‪ ‬נמצא‪ ‬קרוב‪ ‬לשורש‪ ‬העץ‪ ,‬ובכך‪ ‬נשפר‪ ‬ביצועים‪ ‬ע"י‪ ‬הקטנת‪ ‬מספר‪ ‬‬
‫פעולות‪ ‬הקלט‪/‬פלט‪ ‬שנדרשות‪ ‬לאחזור‪ ‬המידע‪ ,‬תוך‪ ‬שאיננו‪ ‬מבזבזים‪ ‬הרבה‪ ‬מקום‪ ‬‬
‫על‪ ‬מצביעים‪ .‬או‪ ‬ע"י‪ ‬כך‪ ‬שנבחר‪ ‬גודל‪ ‬בלוק‪ ‬שחלק‪ ‬ניכר‪ ‬מהקבצים‪ ‬נכנסים‪ ‬בתוכו‪ ‬‬
‫ובכך‪ ‬נקטין‪ ‬את‪ ‬מספר‪ ‬פעולות‪ ‬הקלט‪/‬פלט‪ ‬וכן‪ ‬נבטיח‪ ‬את‪ ‬רציפות‪ ‬הקבצים‪ ‬הללו‪ ‬על‪ ‬‬
‫הדיסק‪) .‬זה‪ ‬מראה‪ ‬גם‪ ‬את‪ ‬היעילות‪ ‬של‪ ‬המבנה‪ ‬של‪ .(Multi­level Index ‬‬
‫‪ ‬‬
‫‪ Extents‬‬
‫מבנה‪ ‬היררכי‪ ‬של‪ ‬הבלוקים‪ ‬יכול‪ ‬להקשות‪ ‬את‪ ‬הגישה‪ ‬הסדרתית‪ ,‬כי‪ ‬כשצריך‪ ‬לעבור‪ ‬‬
‫מבלוק‪ ‬לבלוק‪ ‬אחר‪ ‬צריך‪ ‬לקרוא‪ ‬את‪ ‬כל‪ ‬ההיררכיה‪ ‬וזה‪ ‬פוגע‪ ‬בביצועים ‪ ‬בצורה‪ ‬ניכרת‪ ‬‬
‫)במיוחד‪ ‬בקבצים ‪ ‬גדולים(‪ .‬נרצה‪ ‬שהבלוקים‪ ‬יהיו‪ ‬בצורה‪ ‬רציפה‪ ‬על‪ ‬הדיסק ‪ ‬ובכך‪ ‬‬
‫הקריאה‪ ‬הסדרתית‪ ‬תהיה‪ ‬מהירה‪ ,‬לכן ‪ ‬הוצע‪ ‬הרעיון‪ ‬של‪ ­ extent ‬איזור‪ ‬רציף ‪ ‬‬
‫שמורכב‪ ‬ממספר‪ ‬משתנה‪ ‬של‪ ‬בלוקים‪ .‬אנו‪ ‬שומרים‪ ‬ב­‪ inode‬רשימה‪ ‬של‪ ‬מצביעים‪ ‬‬
‫וגדלים‪ ‬שמייצגים‪ ‬את‪ ‬הקובץ‪ ‬עצמו‪ .‬כאשר‪ ‬ה­‪ extent‬מאוד‪ ‬גדולים‪ ‬אפשר‪ ‬לקרוא‪ ‬את‪ ‬‬
‫כל‪ ‬הקובץ‪ ‬באופן‪ ‬סדרתי‪ .‬אם‪ ‬רוצים‪ ‬קבצים‪ ‬גדולים‪ ‬שלא‪ ‬ניתן‪ ‬להכיל‪ ‬ב­‪ extent‬אז‪ ‬‬
‫נשמור‪ ‬את‪ ‬המידע‪ ‬הנוסף‪) ‬שלא‪ ‬נכנס‪ ‬ב­‪ (extent‬במבנה‪ ‬ההיררכי‪ .‬‬
‫יתרונות‪ : ‬מפשט‪ ‬את‪ ‬הגישה‪ ‬הסדרתית‪ ,‬החיפוש‪ ‬של‪ ‬בלוק‪ ‬הוא‪ ‬יותר‪ ‬יעיל‪ .‬‬
‫חסרונות‪ : ‬הניהול‪ ‬בזיכרון‪ ‬יותר‪ ‬מסובך‪ ‬במקרה‪ ‬של‪ ‬מחיקה‪ ,‬צריך‪ ‬לחזות‪ ‬את‪ ‬הגודל‪ ‬של‪ ‬הקובץ‪ ­ ‬יכול‪ ‬לגרום‪ ‬‬
‫לשברור‪ ‬פנימי‪ ‬במקרה‪ ‬שהקצנו‪ ‬יותר‪ ‬מידי‪ ‬ויכול‪ ‬לגרום‪ ‬להקצה‪ ‬נוספת‪ ‬במקרה‪ ‬שהקצנו‪ ‬קצת‪ ‬מידי‪ .‬‬
‫‪ ‬‬
‫במערכת‪ ‬הקבצים ‪ Ext4 ‬של‪ LINUX ‬מתקיים‪ ‬שגודל‪ ‬כל‪ ‬בלוק‪ ‬הוא‪ 4KB ‬ולכן‪ ‬הגודל‪ ‬של‪ extent ‬יכול‪ ‬להיות‪ ‬בין‪ 4KB ‬‬
‫ל­‪ 128MB‬ויכולים‪ ‬לשמור‪ ‬לכל‪ ‬היותר‪ extents 4 ‬בכל‪ .inode ‬את‪ ‬האינדקסים‪ ‬במערכת‪ ‬זאת‪ ‬שומרים‪ ‬ב­‪ ­ Htree‬‬
‫זהו‪ ‬מבנה‪ ‬שדומה‪ ‬לעץ‪ B ‬רק‪ ‬שהגובה‪ ‬שלו‪ ‬הוא‪ ‬לכל‪ ‬היותר‪ .2 ‬השימוש‪ ‬במבנה‪ ‬זה‪ ‬מספק‪ ‬לנו‪ ‬חיפוש‪ ‬יעיל‪ ‬של‪ ‬בלוק‪ .‬‬
‫‪ linked list block allocation‬‬
‫ניתן‪ ‬לשמור‪ ‬את‪ ‬הקובץ‪ ‬ע"י‪ ‬רשימה‪ ‬מקושרת‪ ‬של‪ ‬בלוקים‪ ,‬כאשר‪ ‬כל‪ ‬בלוק‪ ‬‬
‫מצביע‪ ‬לבלוק‪ ‬הבא ‪ ‬ברשימה‪ .‬בייצוג‪ ‬כזה‪ ‬אנחנו‪ ‬צריכים ‪ ‬לשמור‪ ‬לכל‪ ‬קובץ‪ ‬‬
‫את‪ ‬המצביע‪ ‬להתחלה‪ ‬ולסוף‪ ‬הרשימה‪ .‬‬
‫בשיטה‪ ‬זאת‪ ‬אין‪ ‬גישה‪ ‬סדרתית‪ ­ ‬כי‪ ‬כל‪ ‬בלוק‪ ‬נמצא‪ ‬במקום‪ ‬אחר‪ ‬בדיסק‪ .‬‬
‫בייצוג‪ ‬זה‪ ‬חיפוש‪) ‬גישה‪ ‬אקראית(‪ ‬של‪ ‬בלוק‪ ‬הוא‪ ‬יותר‪ ‬יקר‪ ‬כי‪ ‬במקרה‪ ‬‬
‫הגרוע‪ ‬צריך‪ ‬לעבור‪ ‬על‪ ‬כל ‪ ‬הרשימה‪) .‬ניתן‪ ‬לבצע‪ ‬אופטימיזציה‪ ‬ע"י‪ ‬הוצאה‪ ‬‬
‫של‪ ‬המצביעים‪ ‬מחוץ‪ ‬לבלוק‪ ,‬למשל‪ ‬ע"י‪ ‬שמירה‪ ‬שלהם‪ ‬בטבלה‪ ‬חיצונית(‪ .‬‬
‫‪73 ‬‬
‫בעיה‪ ‬נוספת‪ ‬היא‪ ‬שבמידה‪ ‬ובלוק‪ ‬מסוים‪ ‬נמחק‪) ‬איבוד‪ ‬מידע(‪ ‬כל‪ ‬הבלוקים‪ ‬שאחריו‪ ‬נעלמים‪ ‬ואי‪ ‬אפשר‪ ‬לאתר‪ ‬אותם‪ ,‬‬
‫לכן‪ ‬הגיעו‪ ‬למסקנה‪ ‬שצריך‪ ‬את‪ .FAT ‬‬
‫‪ ‬‬
‫‪ ‬‬
‫‪ FAT filesystem‬‬
‫עבור‪ ‬הייצוג‪ ‬של‪ ‬הקובץ‪ ‬כרשימה‪ ‬של‪ ‬בלוקים‪ ‬שהצגנו‪ ‬לעיל‪ ‬ניתן‪ ‬להגדיר‪ ‬את‪ ‬‬
‫המושג‪ ‬הבא‪ .FAT ­ file allocation table : ‬זוהי‪ ‬טבלת‪ ‬ההקצאות‪ ‬של‪ ‬‬
‫הבלוקים‪ ‬עבור‪ ‬הקבצים‪ ‬במערכת‪ ‬הקבצים‪ .‬עבור ‪ ‬כל‪ ‬בלוק‪ ‬אנו‪ ‬שומרים‪ ‬לו‪ ‬‬
‫בטבלה‪ ‬את‪ ‬הבלוק‪ ‬הבא‪ ‬ברשימה‪ ‬שמייצגת‪ ‬את‪ ‬הקובץ‪) ‬כאשר‪ ­1 ‬מייצג‪ ‬את‪ ‬‬
‫סוף‪ ‬הרשימה(‪ .‬‬
‫זוהי‪ ‬טבלה‪ ‬אחת‪ ‬שמשותפת‪ ‬לכל‪ ‬הקבצים‪ .‬לכל‪ ‬בלוק‪ ‬יש‪ ‬כניסה‪ ‬אחת‪ ‬‬
‫בטבלה‪ .‬טבלה‪ ‬זאת‪ ‬נשמרת‪ ‬גם‪ ‬בזיכרון‪ ‬וגם‪ ‬בדיסק‪ .‬‬
‫יתרונות‪ : ‬מימוש‪ ‬פשוט‪ ,‬אין‪ ‬שברור‪ ‬חיצוני‪ ,‬הגישה‪ ‬הרנדומאלית‪ ‬גם‪ ‬היא‪ ‬‬
‫מהירה‪ ‬ע"י‪ ‬הגישה ‪ ‬מהטבלה ‪ ‬שנשמרת‪ ‬בזיכרון‪ ‬וככה‪ ‬לא‪ ‬צריך‪ ‬לגשת ‪ ‬לקובץ‪ .‬‬
‫במקרה‪ ‬זה‪ ‬אם‪ ‬בלוק‪ ‬של‪ ‬מידע‪ ‬נמחק‪ ‬אז‪ ‬רק ‪ ‬הוא‪ ‬נמחק‪ ‬ולא‪ ‬כל‪ ‬מה‪ ‬שאחריו‪ ‬‬
‫ברשימה‪ .‬‬
‫חסרונות‪ : ‬הטבלה‪ ‬יכולה‪ ‬להיות‪ ‬גדולה‪ ‬מאוד‪ ‬ואנחנו‪ ‬שומרים‪ ‬אותה‪ ‬בזיכרון‪ .‬‬
‫גודל‪ ‬הטבלה‪ ‬הוא‪ ‬כגודל‪ ‬הבלוקים‪ ‬בדיסק‪ .‬לדוגמא‪ ‬אם‪ ‬גודל‪ ‬הדיסק‪ ‬הוא‪ ‬‬
‫אז‪ ‬יהיו‪ ‬בטבלה‪ ‬‬
‫‪ 32GB‬וגודל‪ ‬כל‪ ‬בלוק‪ ‬הוא‪ 4KB ‬‬
‫‪ ‬כניסות‪ .‬אם‪ ‬מניחים‪ ‬שגודל‪ ‬כל‪ ‬מצביע‪ ‬הוא‪ 4 ‬בתים‪ ‬אז‪ ‬מקבלים‪ ‬שהגודל‪ ‬של‪ ‬ה­‬
‫‪ FAT‬הוא ‪ .32MB ‬מכיוון‪ ‬שהטבלה‪ ‬מאוד‪ ‬חשובה‪ ‬ולא‪ ‬רוצים‪ ‬שנאבד‪ ‬שם‪ ‬אף ‪ ‬מידע‪ ‬שומרים‪ ‬את‪ ‬הטבלה‪ ‬פעמיים‪ ‬‬
‫למקרה‪ ‬שיהיה‪ ‬איבוד‪ ‬מידע‪ ‬באחת‪ ‬הטבלאות‪ .‬‬
‫‪ ‬‬
‫‪RAID‬‬
‫יש‪ 2 ‬בעיות‪ ‬בעבודה‪ ‬עם‪ ‬דיסקים‪ :‬דיסקים‪ ‬יכולים‪ ‬ליפול‪ ‬ובכך‪ ‬אנו‪ ‬מאבדים‪ ‬מידע‪) ‬דיסקים‪ ‬מתקלקלים‪ ‬לעיתים‪ ‬‬
‫קרובות(‪ .‬בנוסף‪ ‬הגישה‪ ‬לדיסק‪ ‬איטית‪ ‬וכתוצאה‪ ‬מכך‪ ‬אנחנו‪ ‬מקבלים‪ ‬ביצועים‪ ‬לא‪ ‬טובים‪ .‬‬
‫החלק‪ ‬הראשון‪ ‬של‪ ‬הפיתרון‪ ‬הוא‪ ‬לחלק‪ ‬את‪ ‬המידע‪ ‬שלנו‪ ‬על‪ ‬כמה‪ ‬דיסקים‪ ,‬מה‪ ‬שנקרא‪ ,striping ‬ואז‪ ‬אפשר‪ ‬לקרוא‪ ‬‬
‫את‪ ‬המידע‪ ‬במקביל‪ ‬ולשפר‪ ‬את ‪ ‬הביצועים‪ ­ ‬אך‪ ‬הסיכוי‪ ‬שדיסק‪ ‬אחד‪ ‬מתוכם ‪ ‬ייפול‪ ‬הוא‪ ‬יותר‪ ‬גדול‪ .‬בנוסף‪ ‬כדי‪ ‬‬
‫להתמודד‪ ‬עם‪ ‬הנפילות‪ ‬נצטרך‪ ‬להוסיף‪ ‬גם‪ ‬מידע‪ ‬מיותר‪ ‬ממנו‪ ‬לשחזר‪ ‬את‪ ‬המידע‪ ‬שנפל‪ ‬במקרה‪ ‬של‪ ‬נפילה‪ .‬‬
‫‪ ‬‬
‫‪ RAID­0‬‬
‫בשיטה‪ ‬זאת‪ ‬אין‪ ‬בכלל‪ ‬יתירות‪ .‬מחלקים‪ ‬את‪ ‬כל‪ ‬המידע‪ ‬בין ‪ ‬כמה‪ ‬דיסקים‪ .‬במידה‪ ‬ודיסק‪ ‬מסוים‪ ‬נופל‪ ‬לא‪ ‬ניתן‪ ‬לשחזר‪ ‬‬
‫את‪ ‬המידע‪ .‬בשיטה‪ ‬זאת ‪ ‬רק‪ ‬השגנו‪ ‬ביצועים‪ ‬יותר‪ ‬טובים‪ ‬כי‪ ‬ניתן‪ ‬‬
‫לקרוא‪ ‬במקביל‪ ‬משני‪ ‬הדיסקים‪ .‬‬
‫‪ ‬‬
‫‪ RAID­1‬‬
‫בשיטה‪ ‬זאת‪ ‬אנו‪ ‬שומרים‪ ‬את‪ ‬כל‪ ‬המידע‪ ‬פעמיים‪) ‬או‪ ‬יותר(‪ .‬במידה‪ ‬‬
‫שיש ‪ ‬נפילה‪ ‬של‪ ‬דיסק‪ ‬ניתן‪ ‬לשחזר‪ ‬את‪ ‬הכל‪ ‬מהדיסק‪ ‬הנוסף‪.‬בנוסף‪ ‬‬
‫‪74 ‬‬
‫ניתן‪ ‬לקרוא‪ ‬בקצב‪ ‬כפול‪ .‬קצב‪ ‬הכתיבה‪ ‬הוא‪ ‬זהה‪ ‬לכתיבה‪ ‬לדיסק‪ ‬אחד‪ .‬קצב‪ ‬הקריאה‪ ‬הוא‪ ‬יותר‪ ‬מהיר‪ ‬מקריאה ‪ ‬‬
‫בדיסק‪ ‬אחד‪ .‬החיסרון‪ ‬של‪ ‬השיטה‪ ‬הזאת‪ ‬הוא‪ ‬שחצי‪ ‬מהקיבולת‪ ‬שלנו‪ ‬הוא‪ ‬מיותר‪ .‬‬
‫כיום‪ ‬משתמשים‪ ‬בשיטה‪ ‬זאת‪ ‬בענן‪ ‬של‪ ‬שיתוף‪ ‬מידע‪ .‬משכפלים‪ ‬את‪ ‬המידע‪ ‬במערכות‪ ‬אכסון‪ ‬נפוצות‪ 3 ‬פעמים‪ ‬כדי‪ ‬‬
‫שנוכל‪ ‬להתמודד‪ ‬עם‪ ‬עד‪ 2 ‬נפילות‪ ‬בו‪ ‬זמנית‪ .‬לאחר ‪ ‬זמן‪ ‬מסוים‪ ‬דיסק‪ ‬יכול‪ ‬לזייף‪ ‬להתחיל‪ ‬להחזיר‪ ‬ערכים‪ ‬לא‪ ‬טובים‪ ‬‬
‫)זבל(‪ ‬ולכן‪ ‬צריך‪ ‬את‪ ‬הדיסק‪ ‬השלישי‪ ‬שנדע‪ ‬מהו‪ ‬באמת‪ ‬הערך‪ ‬ששמרנו‪ ‬בדיסק‪ .‬‬
‫נבחין‪ ‬כי‪ ‬במערכות‪ ‬ענן‪ ‬בד"כ‪ ‬אין‪ ‬ממש‪ ‬זוג‪ ‬של‪ ‬דיסקים‪ ‬שמהווים‪ ‬העתק‪ ‬אחד‪ ‬של‪ ‬השני‪ ,‬אלא‪ ‬הגיבוי‪ ‬של‪ ‬דיסק‪ ‬מסוים‪ ‬‬
‫מפוזר‪ ‬בין‪ ‬מסבר‪ ‬דיסקים‪ .‬‬
‫‪ ‬‬
‫‪ RAID­4‬‬
‫משתמשים‪ ‬בדיסק‪ ‬שמכיל‪ ‬את‪ ‬הביטים‪ ‬של‪ ‬ה­‪ .parity‬אם‪ ‬יש‪ ‬לנו‪ N ‬‬
‫דיסקים‪ ‬אז‪ ‬ב­‪ N­1‬הדיסקים‪ ‬הראשונים‪ ‬אנו‪ ‬שומרים‪ ‬את‪ ‬המידע‪ ‬‬
‫ובדיסק‪ ‬ה­‪ N‬אנחנו‪ ‬שומרים‪ xor ‬של‪ ‬הבלוקים‪ ‬בכל‪ ‬הדיסקים‪ ‬של‪ ‬‬
‫המידע‪) ‬ה­‪ xor‬הוא‪ ‬ברמת‪ ‬הביטים(‪ .‬במקרה‪ ‬של‪ ‬נפילה‪ ‬של‪ ‬דיסק‪ ‬‬
‫כלשהו‪ ‬ניתן‪ ‬לשחזר‪ ‬ע"י‪ ‬דיסק‪ ‬ה­‪ parity‬את‪ ‬המידע‪ ‬שנפל‪ .‬‬
‫כדי‪ ‬לקרוא‪ ‬ניגשים‪ ‬רק‪ ‬לדיסק‪ ‬של‪ ‬המידע‪ ‬שממנו‪ ‬רוצים‪ ‬לקרוא‪) ‬כמו‪ ‬‬
‫ב­‪ , (RAID0‬כלומר‪ ‬מקבלים‪ ‬קצב‪ ‬קריאה‪ ‬פי‪ .N ‬כדי‪ ‬לכתוב‪ ‬ניגשים‪ ‬‬
‫לדיסק‪ ‬המידע‪ ‬וגם‪ ‬לדיסק‪ ‬של‪ ‬ה­‪ ,parity‬ואפשר‪ ‬לכתוב‪ ‬רק‪ ‬לדיסק‪ ‬‬
‫אחד‪ ‬בכל‪ ‬זמן‪ ‬כי‪ ‬אי‪ ‬אפשר‪ ‬לכתוב‪ ‬לדיסק‪ ‬ה­‪ parity‬במקביל‪ ‬בשני‪ ‬‬
‫מקומות‪ .‬‬
‫במקרה‪ ‬שדיסק‪ ‬אחד‪ ‬נפל‪ ,‬מה‪ ‬עושים?‪ ‬קודם‪ ‬צריך‪ ‬לגלות‪ ‬מי‪ ‬זה‪ ‬‬
‫הדיסק‪ ‬שנפל‪ ‬ואז‪ ‬נדע‪ ‬לשחזר‪ ‬את‪ ‬המידע‪ ‬ע"י‪ .xor ‬במידה‪ ‬ולא‪ ‬יודעים‪ ‬איזה‪ ‬דיסק‪ ‬נפל‪ ‬אז‪ ‬אמנם ‪ ‬נוכל‪ ‬לגלות‪ ‬שנפל‪ ‬‬
‫דיסק‪ ‬כלשהו‪ ‬מחוסר‪ ‬העקביות‪ ‬של‪ ‬ה­‪ ,parity‬אבל‪ ‬לא‪ ‬נוכל‪ ‬לדעת‪ ‬איזהו‪ ‬מהדיסקים‪ ‬נפל‪ .‬‬
‫מבחינת‪ ‬ניצול‪ ‬זיכרון‪ ‬הוא‪ ‬יותר‪ ‬טוב‪ ‬מ­‪ RAID­1‬כי‪ ‬שומרים‪ ‬פה‪ ‬רק‪ ‬דיסק‪ ‬אחד‪ ‬של‪ ‬יתירות‪ ‬עבור‪ ‬המון‪ ‬דיסקים‪ ‬של ‪ ‬‬
‫מידע‪ .‬‬
‫‪ ‬‬
‫‪ ‬‬
‫‪ ‬‬
‫‪ RAID­5‬‬
‫שיטה‪ ‬זאת‪ ‬דומה‪ ‬לשיטה‪ ‬הקודמת‪ ‬רק‪ ‬שכאן‪ ‬מפזרים‪ ‬את‪ ‬‬
‫המידע‪ ‬המיותר‪ (parity) ‬בין‪ ‬כל‪ ‬הדיסקים‪ ‬ולא‪ ‬שומרים ‪ ‬אותו‪ ‬על‪ ‬‬
‫דיסק‪ ‬אחד‪ .‬‬
‫שיטה‪ ‬זאת‪ ‬יותר‪ ‬טובה‪ ‬מהשיטה‪ ‬הקודמת‪ ‬כי‪ ‬עכשיו‪ ‬אין‪ ‬לנו ‪ ‬דיסק‪ ‬‬
‫שניגשים‪ ‬אליו‪ ‬הכי‪ ‬הרבה‪ ‬וככה‪ ‬לא‪ ‬שוחקים‪ ‬את‪ ‬הדיסק‪) ‬בשיטה‪ ‬‬
‫הקודמת‪ ‬היינו‪ ‬ניגשים‪ ‬המון‪ ‬לדיסק‪ ‬של‪ ‬ה­‪ .(parity‬בנוסף‪ ‬‬
‫כשביצענו‪ 2 ‬כתיבות‪ ‬בשיטה‪ ‬הקודמת‪ ‬היינו‪ ‬חייבים‪ ‬שאחת‪ ‬‬
‫תתבצע ‪ ‬לפני‪ ‬השנייה‪ ‬כי‪ ‬שתיהן‪ ‬כתבו‪ ‬לדיסק‪ ‬עם‪ ‬המידע‪ ‬‬
‫המיותר‪ ,‬כעת‪ ‬ייתכן‪ ‬שאת ‪ ‬המידע‪ ‬הנוסף‪ ‬נצטרך‪ ‬לכתוב‪ ‬לשני‪ ‬‬
‫דיסקים‪ ‬שונים‪ ‬ולכן‪ ‬נוכל‪ ‬לבצע‪ ‬את‪ ‬הכתיבה‪ ‬במקביל‪ ‬במקרים‪ ‬‬
‫מסוימים‪ ‬ולכן‪ ‬מקבלים‪ ‬ביצועים‪ ‬יותר‪ ‬טובים‪) ‬הכתיבה‪ ‬המקבילה‪ ‬‬
‫היא‪ ‬רק‪ ‬במקרים‪ ‬שהכתיבות‪ ‬בלי‪ ‬תלויות(‪ .‬‬
‫‪75 ‬‬
‫במידה‪ ‬ודיסק‪ ‬אחד‪ ‬נפל‪ ‬אפשר‪ ‬לשחזר‪ ,‬אם‪ ‬יותר ‪ ‬מדיסק‪ ‬אחד‪ ‬נפל‪ ‬אי‪ ‬אפשר‪ ‬לשחזר‪ ­ ‬ולכן‪ ‬אם‪ ‬דיסק‪ ‬אחד‪ ‬נופל‪ ‬צריך‪ ‬‬
‫לשחזר‪ ‬אותו‪ ‬מהר‪ ‬לפני‪ ‬שייפול‪ ‬עוד‪ ‬דיסק‪ ‬אחר‪ .‬‬
‫‪ ‬‬
‫‪ RAID­6‬‬
‫כמו‪ ‬בשיטה‪ ‬הקודמת‪ ‬רק‪ ‬שכעת‪ ‬מוסיפים‪ ‬עוד‪ ‬רמה‪ ‬‬
‫של‪ ‬יתירות‪ .‬במקרה‪ ‬זה‪ ‬ישנו‪ ‬יתרון‪ ‬שניתן‪ ‬לתמוך ‪ ‬ב­‪ 2‬‬
‫נפילות‪ ‬בו‪ ‬זמנית‪ ‬אך‪ ‬יש‪ ‬יותר ‪ ‬דיסקים‪ ‬שהם‪ ‬עבור‪ ‬‬
‫מידע‪ ‬מיותר‪ .‬כאן‪ ‬יש‪ ‬לנו‪ 2 ‬פונקציות‪ ‬שונות‪ ‬לחישוב‪ ‬‬
‫‪ ­ parity‬אחת‪ xor ‬ואחת‪ ‬פונקציה‪ ‬אחרת‪ ‬שאפשר‪ ‬‬
‫לשחזר‪ ‬איתה‪ .‬‬
‫אם‪ ‬יש ‪ ‬כמה‪ ‬דיסקים‪ ‬שמזייפים‪ ‬אז‪ ‬אפשר‪ ‬רק‪ ‬לגלות‪ ‬‬
‫שיש‪ ‬בעיה‪ ‬ואי‪ ‬אפשר‪ ‬לשחזר‪ .‬‬
‫‪76 ‬‬
‫‪VFS - Virtual File System‬‬
‫‪ – VFS‬מערכת‪ ‬קבצים‪ ‬מדומה‪ ‬היא‪ ‬שכבת‪ ‬תוכנה‪ ‬בגרעין‪ ,‬המספקת‪ ‬ממשק‪ ‬אחיד‪ ‬לכל‪ ‬מערכות‪ ‬הקבצים‪ ‬‬
‫הספציפיות‪ .‬‬
‫מאפשרת‪ ‬להרכיב‪ ‬מערכות‪ ‬קבצים‪ ‬מסוגים‪ ‬שונים‪ ‬ומספקת‪ ‬ממשק‪ ‬אחיד‪ ‬לגישה‪ ‬לקבצים‪ ‬הנמצאים‪ ‬במערכות‪ ‬‬
‫הקבצים‪ ‬השונות‪) ‬גן‪ ‬מערכות‪ ‬קבצים‪ ‬שלא‪ ‬נמצאות‪ ‬אצלנו‪ ‬במחשב(‪ .‬‬
‫כל‪ ‬מערכת‪ ‬קבצים‪ ‬יושבת‪ ‬על‪ ‬חומרה‪ ‬מסוימת‪ ‬וממומשת‪ ‬בצורה‪ ‬אחרת‪ VFS ,‬יוצרת‪ ‬גישה‪ ‬אחידה‪ ‬לכל ‪ ‬הפעולות‪ ‬‬
‫השונות‪ ‬עבור‪ ‬כל‪ ‬מערכות‪ ‬הקבצים‪ ‬השונות‪ .‬‬
‫מתרגמת‪ ‬את‪ ‬קריאות‪ ‬הכלליות‪ ‬לקריאות‪ ‬ספציפיות‪ ‬המתאימות‪ ‬למערכת‪ ‬קבצים‪ ‬בה‪ ‬נמצא‪ ‬הקובץ‪ ,‬ובכך‪ ‬בעצם‪ ‬‬
‫מאפשרת‪ ‬שימוש‪ ‬פשוט‪ ‬יותר‪ ‬בקריאות‪ ‬מערכת‪ ‬כי‪ ‬לא‪ ‬צריך‪ ‬להתייחס‪ ‬למערכת‪ ‬קבצים‪ ‬ספציפית‪ .‬‬
‫מערכת‪ ‬קבצים‪ ‬ספציפית‪ ‬קוראת‪ ‬את‪ ‬הנתונים‪ ‬מה­‪ cache buffer ‬ומשם‪ ‬דרך‪ API ‬של‪ ‬ההתקן‪ ‬חומרה‪ ‬המסוים‪ .‬‬
‫המשתמשים‪ ‬יכולים‪ ‬להתייחס‪ ‬לעץ‪ ‬התיקיות‪ ‬והקבצים‪ ‬כישות ‪ ‬אחת‪ ­ ‬גם‪ ‬במקרה‪ ‬שבעצם‪ ‬הוא‪ ‬מחבר‪ ‬מספר‪ ‬מערכות ‪ ‬‬
‫קבצים‪ ‬שונות‪ .‬‬
‫כל‪ ‬השירותים‪ ‬של‪ ‬מערכת‪ ‬ההפעלה‪ ‬שצריכים‪ ‬לגשת‪ ‬לקבצים‪ ‬משתמשים‪ ‬ב­‪) VFS‬בערך‪ 100 ‬שירותים(‪ .‬‬
‫‪ ‬‬
‫‪ Linux VFS objects‬‬
‫●‬
‫●‬
‫●‬
‫●‬
‫●‬
‫●‬
‫‪ ­ superblock object‬מכיל‪ ‬מידע‪ ‬כללי‪ ‬על‪ ‬מערכת‪ ‬קבצים‪ ‬מסוימת‪ .‬יש‪ ‬לו‪ ‬גם‪ ‬עותק‪ ‬בדיסק‪ .‬‬
‫○ לכל‪ ‬מערכת‪ ‬קבצים‪ ‬קיים‪ ‬אובייקט‪ ‬מיוחד‪ ‬המכיל‪ ‬מידע‪ ‬על‪ ‬המערכת ‪ ‬הנ"ל‪) ‬מידע‪ ‬על‪ ‬‬
‫ההתקן‪ ,‬סוג‪ ‬מערכת‪ ‬הקבצים‪ ‬וכו'(‪ .superblock – ‬‬
‫○ ה­‪ superblock‬כולל‪ ‬מבנה ‪ ‬המכיל‪ ‬פונקציות‪ ‬טיפול‪ ‬המוגדרות‪ ‬עבור‪ ‬מערכת‪ ‬‬
‫הקבצים‪ ‬הנתונה‪ .‬‬
‫‪ – inode object‬מכיל‪ ‬מידע‪ ‬על‪ ‬קובץ‪ ‬מסוים‪ ­ ‬מתמלא‪ ‬ע"י‪ ‬נתונים‪ ‬במערכת‪ ‬קבצים‪ .‬יש‪ ‬לו‪ ‬גם‪ ‬עותק‪ ‬‬
‫בדיסק‪ .‬‬
‫○ לכל‪ ‬קובץ‪ ‬במערכת‪ ‬קבצים‪ ‬רגילה‪ ‬יש‪ inode ‬על‪ ‬הדיסק‪ ‬המכיל‪ ‬מידע‪ ‬על‪ ‬הקובץ‪ .‬‬
‫○ מכיל‪ ‬הרשאות‪ ,‬בעלות‪ ,‬גודל‪ ,‬חותמות‪ ‬זמן‪ ,‬וכו'‪ .‬בנוסף‪ ‬הוא‪ ‬כולל‪ ‬מצביעים‪ ‬לפונקציות‪ ‬‬
‫טיפול‪ ‬בקובץ‪ .‬‬
‫○ מספר‪ ‬ה­‪ inode‬מזהה‪ ‬בצורה‪ ‬חד­משמעית‪ ‬את‪ ‬הקובץ‪ ‬במערכת‪ ‬הקבצים‪ .‬‬
‫‪ – dentry object‬מכיל‪ ‬מידע‪ ‬לגבי‪ ‬כניסה‪ ‬במדריך‪ .‬‬
‫○ מכיל‪ ‬מצביע‪ ‬ל­‪ inode‬של‪ ‬הקובץ‪ ‬או‪ ‬המדריך‪ ‬אליו‪ ‬הכניסה‪ ‬מתייחסת‪ .‬‬
‫○ אם‪ ‬ה­‪ dentry‬הוא‪ ‬של‪ ‬כניסה‪ ‬של‪ ‬תת­מדריך‪ ,‬אז‪ ‬הוא‪ ‬מצביע‪ ‬לרשימה‪ ‬של‪ ‬הבנים‪ ‬‬
‫של‪ ‬אותו‪ ‬תת­מדריך‪ .‬‬
‫○ מטמון‪ ‬של‪ dentries ‬מאפשר‪ ‬לצמצם‪ ‬את‪ ‬הזמן‪ ‬הדרוש‪ ‬ע"י‪ ‬שמירה‪ ‬של‪ dentries ‬‬
‫בזיכרון‪ .‬‬
‫‪ – file object‬מכיל‪ ‬מידע‪ ‬על‪ ‬קובץ‪ ‬שנפתח‪ ‬ע"י‪ ‬תהליך‪ ‬כלשהו‪ .‬‬
‫○ מכיל‪ ‬מצביע ‪ ‬ל­‪ dentry‬המקושר‪ ‬לקובץ‪ ,‬דגלים ‪ ‬שהוגדרו‪ ‬בפתיחת‪ ‬הקובץ‪) ‬קריאה‪ ,‬‬
‫כתיבה‪ ,(...,‬מחוון‪ ‬הקובץ‪ ,‬וכו'‪ .‬כמו ‪ ‬כן‪ ‬מכיל‪ ‬גם‪ ‬מבנה‪ ‬המצביע‪ ‬לפונקציות‪ ‬הטיפול‪ ‬‬
‫בקובץ‪ .‬‬
‫‪ ­ file system type‬נותן‪ ‬מידע‪ ‬איך‪ ‬לקרוא‪ ‬את‪ ‬ה­‪ .superblock‬‬
‫‪ ­ vfsmount‬זהו‪ ‬מבנה‪ ‬שקשור‪ ‬ל‪ .superblock‬הוא‪ ‬נותן‪ ‬מידע‪ ‬על ‪ ‬נקודות‪ ‬חיבור‪ ‬של‪ ‬מערכות‪ ‬קבצים‪ .‬‬
‫לכל‪ ‬נקודת‪ ‬הרכבה‪ ,‬חיפוש‪ ‬קובץ‪ ‬דורש‪ ‬גם‪ ‬את‪ ‬ה­‪ dentry‬וגם‪ ‬את‪ ‬ה­‪ .vfsmount‬‬
‫*‪ ‬כל‪ ‬האובייקטים‪ ‬האלה‪ ‬זהים‪ ‬לכל‪ ‬מערכת‪ ‬קבצים‪ .‬מכל‪ ‬מערכת‪ ‬קבצים‪ ‬שמוסיפים‪ ‬למחשב‪ ‬צריך‪ ‬לקחת‪ ‬את ‪ ‬המידע‪ ‬‬
‫הנדרש‪ ‬כדי‪ ‬לאתחל‪ ‬את‪ ‬האובייקטים‪ ‬הנ"ל‪ ‬של‪ .VFS ‬‬
‫‪77 ‬‬
‫‪ ‬‬
‫הקשר‪ ‬בין‪ ‬כל‪ ‬האובייקטים‪ ‬ב­‪) VFS‬בזיכרון(‪ :‬‬
‫‪ ‬‬
‫‪ ‬‬
‫‪ ‬‬
‫‪ ‬‬
‫‪ ‬‬
‫‪ ‬‬
‫‪ ‬‬
‫וזהו‪ ,‬שיהיה‪ ‬לנו‪ ‬בהצלחה‪ ‬במבחן‪ (: ‬‬
‫‪78 ‬‬