OS PyramidTechnion 234123 · Operating Systemsbasics → exam
Stage 6: Synchronization & Threads
2017B_Spring_BQuestion 3core25 pts

דנית ואוהד, לאחר שלמדו על מנגנוני הסנכרון בקורס מערכת הפעלה, החליטו לכתוב קוד פשוט המדפיס שתי שורות אחת לאחר השנייה. הפתרון הטריוויאלי של לקרוא ל-printf פעמיים בצורה סדרתית היה נראה פשוט מדי, ולכן הם החליטו לקרוא לכל הדפסה בחוט (thread) נפרד, ולדאוג למנגנון סנכרון שיבטיח את סדר הביצוע הרצוי. בשאלה הבאה תבחנו מספר אפשרויות למימוש ההתנהגות המתוארת (ההדפסה בפונקציה second צריכה להופיע לאחר ההדפסה בפונקציה first), כאשר הקוד המשותף לכל המימושים הוא: #include <pthread.h> #include <stdio.h> int main() { pthread_t thread1, thread2; initialize(); pthread_create(&thread1, NULL, first, NULL); pthread_create(&thread2, NULL, second, NULL); pthread_join(thread1, NULL); pthread_join(thread2, NULL); finalize(); return 0; }

Full original question text (raw OCR)

שאלה 3 (25 נק') - סנכרון דנית ואוהד, לאחר שלמדו על מנגנוני הסנכרון בקורס מערכת הפעלה, החליטו לכתוב קוד פשוט המדפיס שתי שורות אחת לאחר השנייה. הפתרון הטריוויאלי של לקרוא ל-printf פעמיים בצורה סדרתית היה נראה פשוט מדי, ולכן הם החליטו לקרוא לכל הדפסה בחוט (thread) נפרד, ולדאוג למנגנון סנכרון שיבטיח את סדר הביצוע הרצוי. בשאלה הבאה תבחנו מספר אפשרויות למימוש ההתנהגות המתוארת (ההדפסה בפונקציה second צריכה להופיע לאחר ההדפסה בפונקציה first), כאשר הקוד המשותף לכל המימושים הוא: #include <pthread.h> #include <stdio.h> int main() { pthread_t thread1, thread2; initialize(); pthread_create(&thread1, NULL, first, NULL); pthread_create(&thread2, NULL, second, NULL); pthread_join(thread1, NULL); pthread_join(thread2, NULL); finalize(); return 0; } א. (4 נק') בניסיון הראשון כתב אוהד את הקוד הבא: int done; void initialize() { done = 0; } void* first(void* p) { printf("I should run first.\n"); done = 1; return NULL; } void* second(void* p) { while (!done); printf("I should run second.\n"); return NULL; } void finalize() { } דנית טענה ללא היסוס כי הקוד של אוהד אמנם נכון, אבל בזבזני. הסבירו את טענתה של דנית. ב. (5 נק') דנית הציעה להשתמש במשתנה תנאי ומנעול mutex כדי לפתור את הבעיה שהייתה בקוד של אוהד: pthread_cond_t c; pthread_mutex_t m; void initialize() { pthread_mutex_init(&m, NULL); pthread_cond_init(&c, NULL); } void* first(void* p) { printf("I should run first.\n"); pthread_cond_signal(&c); return NULL; } void* second(void* p) { pthread_mutex_lock(&m); pthread_cond_wait(&c, &m); pthread_mutex_unlock(&m); printf("I should run second.\n"); return NULL; } void finalize() { pthread_mutex_destroy(&m); pthread_cond_destroy(&c); } למרבה הצער, אוהד הבחין מיד כי הפתרון של דנית שגוי. תארו בפירוט מדוע הקוד שגוי, כלומר תארו תרחיש מסוים שבו הקוד של דנית לא יפעל כנדרש. ג. (5 נקודות) אוהד ודנית לא התייאשו והציעו פתרון שלישי לבעיה: pthread_cond_t c; pthread_mutex_t m; int done; void initialize() { done = 0; pthread_mutex_init(&m, NULL); pthread_cond_init(&c, NULL); } void* first(void* p) { printf("I should run first.\n"); done = 1; pthread_cond_signal(&c); return NULL; } void* second(void* p) { pthread_mutex_lock(&m); while (!done) { pthread_cond_wait(&c, &m); } pthread_mutex_unlock(&m); printf("I should run second.\n"); return NULL; } void finalize() { pthread_mutex_destroy(&m); pthread_cond_destroy(&c); } אך שוב אוהד ודנית התאכזבו כאשר המתרגל רמז להם כי הפתרון הנ"ל שגוי. תארו בפירוט מדוע הקוד שגוי, כלומר תארו תרחיש מסוים שבו הקוד הנ"ל לא יפעל כנדרש. ד. (6 נקודות) השלימו את המימוש המוצע מטה, תוך שימוש במשתנה תנאי יחיד ומנעול mutex יחיד. pthread_cond_t c; pthread_mutex_t m; int done; void initialize() { done = 0; pthread_mutex_init(&m, NULL); pthread_cond_init(&c, NULL); } void* first(void* p) { printf("I should run first.\n"); return NULL; } void* second(void* p) { printf("I should run second.\n"); return NULL; } void finalize() { pthread_mutex_destroy(&m); pthread_cond_destroy(&c); } ה. (5 נקודות) הציעו מימוש תקין העושה שימוש בסמפור יחיד. sem_t s; void initialize() { sem_init(&s, 0, ); /* the 3rd argument is the initial value */ } void* first(void* p) { printf("I should run first.\n"); } return NULL; void* second(void* p) { } printf("I should run second.\n"); return NULL; void finalize() { sem_destroy(&s); } הסבר:

  1. (4 נק') בניסיון הראשון כתב אוהד את הקוד הבא: int done; void initialize() { done = 0; } void* first(void* p) { printf("I should run first.\n"); done = 1; return NULL; } void* second(void* p) { while (!done); printf("I should run second.\n"); return NULL; } void finalize() { } דנית טענה ללא היסוס כי הקוד של אוהד אמנם נכון, אבל בזבזני. הסבירו את טענתה של דנית.

    libc syscall wrapper caching pitfallsSignal delivery and handler timingSemaphore blocking and busy-wait
  2. (5 נק') דנית הציעה להשתמש במשתנה תנאי ומנעול mutex כדי לפתור את הבעיה שהייתה בקוד של אוהד: pthread_cond_t c; pthread_mutex_t m; void initialize() { pthread_mutex_init(&m, NULL); pthread_cond_init(&c, NULL); } void* first(void* p) { printf("I should run first.\n"); pthread_cond_signal(&c); return NULL; } void* second(void* p) { pthread_mutex_lock(&m); pthread_cond_wait(&c, &m); pthread_mutex_unlock(&m); printf("I should run second.\n"); return NULL; } void finalize() { pthread_mutex_destroy(&m); pthread_cond_destroy(&c); } למרבה הצער, אוהד הבחין מיד כי הפתרון של דנית שגוי. תארו בפירוט מדוע הקוד שגוי, כלומר תארו תרחיש מסוים שבו הקוד של דנית לא יפעל כנדרש.

    libc syscall wrapper caching pitfallsSignal delivery and handler timingPipe IPC semanticsMutex correctness and deadlock avoidance
  3. (5 נקודות) אוהד ודנית לא התייאשו והציעו פתרון שלישי לבעיה: pthread_cond_t c; pthread_mutex_t m; int done; void initialize() { done = 0; pthread_mutex_init(&m, NULL); pthread_cond_init(&c, NULL); } void* first(void* p) { printf("I should run first.\n"); done = 1; pthread_cond_signal(&c); return NULL; } void* second(void* p) { pthread_mutex_lock(&m); while (!done) { pthread_cond_wait(&c, &m); } pthread_mutex_unlock(&m); printf("I should run second.\n"); return NULL; } void finalize() { pthread_mutex_destroy(&m); pthread_cond_destroy(&c); } אך שוב אוהד ודנית התאכזבו כאשר המתרגל רמז להם כי הפתרון הנ"ל שגוי. תארו בפירוט מדוע הקוד שגוי, כלומר תארו תרחיש מסוים שבו הקוד הנ"ל לא יפעל כנדרש.

    libc syscall wrapper caching pitfallsSignal delivery and handler timingPipe IPC semanticsMutex correctness and deadlock avoidance
  4. (6 נקודות) השלימו את המימוש המוצע מטה, תוך שימוש במשתנה תנאי יחיד ומנעול mutex יחיד. pthread_cond_t c; pthread_mutex_t m; int done; void initialize() { done = 0; pthread_mutex_init(&m, NULL); pthread_cond_init(&c, NULL); } void* first(void* p) { printf("I should run first.\n"); return NULL; } void* second(void* p) { printf("I should run second.\n"); return NULL; } void finalize() { pthread_mutex_destroy(&m); pthread_cond_destroy(&c); }

    Pipe IPC semanticsMutex correctness and deadlock avoidance
  5. (5 נקודות) הציעו מימוש תקין העושה שימוש בסמפור יחיד. sem_t s; void initialize() { sem_init(&s, 0, ); /* the 3rd argument is the initial value */ } void* first(void* p) { printf("I should run first.\n"); } return NULL; void* second(void* p) { } printf("I should run second.\n"); return NULL; void finalize() { sem_destroy(&s); } הסבר:

    Semaphore blocking and busy-wait

The exam question — original PDF

pages 6, 7, 8, 9, 10, 11

Exactly as it appears on the exam paper.

loading page 6
loading page 7
loading page 8
loading page 9
loading page 10
loading page 11

Built from these components

Ordered basic → advanced. Master the earlier ones first.

L3pthread create/cancel/join lifecycleL3libc syscall wrapper caching pitfallsL3Signal delivery and handler timingL3Pipe IPC semanticsL3Semaphore blocking and busy-waitL3Mutex correctness and deadlock avoidance

Review the material

Read these before you answer — each verified slide teaches a component this question tests, and nothing from an unrelated topic is included. Tutorial slides show the actual slide image.

Lecture 3slide 25Shared memory communication – example

Shared memory communication – example // 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"); pthread_create(&t2, NULL, thread_fill, “second"); // wait for both threads to finish pthread_join(t1, NULL); pthread_join(t2, NULL); } OS (234123) – threads vs. processes 25

Lecture slide — text above is the material (no raster available).
Tutorial 2slide 22קריאות המערכת getpid(), getppid()pid_t getpid();קריאת מערכת המחזירה לתהליך הקורא את ה-pid של עצמו

קריאות המערכת getpid(), getppid()pid_t getpid();קריאת מערכת המחזירה לתהליך הקורא את ה-pid של עצמו.pid_t getppid();קריאת מערכת המחזירה את ה-PID של תהליך האב של התהליך הקורא.שאלה: מה המשמעות של getppid() == 1 עבור תהליך משתמש טיפוסי?תשובה: תהליך האב הוא init. קורה למשל אם תהליך הבן יתום.מערכות הפעלה - תרגול 222

Tutorial 3slide 8קריאת המערכת kill#include <sys/types

קריאת המערכת kill#include <sys/types.h>#include <signal.h>int kill(pid_t pid, int sig);פעולה: שולחת את הסיגנל שמספרו sig לתהליך המזוהה ע"י pid.אם הערך של sigהוא 0, אז הפעולה רק בודקת שהתהליך pid קיים, מבלי לשלוח signal (שימושי לבדיקת תקפות pid).ערך מוחזר:0 בהצלחה-1 בכישלון (למשל, אם אין תהליך בעל מזהה pid)מערכות הפעלה - תרגול 38

Tutorial 3slide 9העברת סיגנלים בשני שלביםרישום – מערכת ההפעלה רושמת ב-PCB של תהליך היעד שיש לו סיגנל ממתין (pending signal)

העברת סיגנלים בשני שלביםרישום – מערכת ההפעלה רושמת ב-PCB של תהליך היעד שיש לו סיגנל ממתין (pending signal).הרישום מתבצע במערך בינארי בין 31 ביטים, ולכן לכל תהליך יכול להיות לכל היותר סיגנל ממתין אחד מכל מספר.טיפול – בכל פעם שהתהליך חוזר ממצב גרעין למצב משתמש, מערכת ההפעלה בודקת אם יש סיגנלים ממתינים ומטפלת בהם.בסיום הטיפול בסיגנל, מערכת ההפעלה תאפס את הביט המתאים במערך. במידה ויש מספר סיגנלים ממתינים, סדר הטיפול מתחילת המערך לסופו.מערכות הפעלה - תרגול 39

Tutorial 3slide 20FD (file descriptors)כל פעולות קלט/פלט של תהליך בלינוקס מבוצעות דרך "קבצים":קבצים "רגילים" לאחסון מידע (/usr/file

FD (file descriptors)כל פעולות קלט/פלט של תהליך בלינוקס מבוצעות דרך "קבצים":קבצים "רגילים" לאחסון מידע (/usr/file.txt) נמצאים בדיסק.התקני חומרה גם כן מיוצגים כקבצים, אבל נמצאים בזיכרון.למשל, העכברים המחוברים למחשב מיוצגים כ- /dev/input/mouseN .גם ערוצי תקשורת כמו pipes מיוצגים ע"י קבצים שנמצאים בזיכרון.הקשר בין תהליך לבין קובץ שהוא ניגש אליו נשמר, ברמת המשתמש, ע"י מספר שלם שנקרא file descriptor (FD).לדוגמה: קריאת המערכת open() מחזירה FD.המשתמש מעביר את ה-FD לקריאות מערכת כמו read(), write() כדי לקרוא ולכתוב לקובץ.מערכות הפעלה - תרגול 320

Tutorial 3slide 42שחרור file objectשאלה: מי מבצע את שחרור הזיכרון של file object? מתי ניתן לשחררו? ייתכנו מצבים בהם תהליכים שונים מצביע...

שחרור file objectשאלה: מי מבצע את שחרור הזיכרון של file object? מתי ניתן לשחררו? ייתכנו מצבים בהם תהליכים שונים מצביעים לאותו file object, לכן שחרור ה-file object יכול להתבצע רק לאחר ביצוע close() מכל התהליכים החולקים את אותו ה-file object. זכרו של-file object יש מונה (f_count) הסופר את כמות התהליכים המצביעים עליו בכל רגע נתון. המונה קטן באחד עם כל פעולת close() על האובייקט. כאשר המונה מתאפס, ה-file object ישוחרר.מערכות הפעלה - תרגול 342

Tutorial 7slide 14יצירת חוט חדשפרמטרים:thread – מצביע למקום בו יאוחסן מזהה החוט החדש במקרה של סיום הפונקציה בהצלחה

יצירת חוט חדשפרמטרים:thread – מצביע למקום בו יאוחסן מזהה החוט החדש במקרה של סיום הפונקציה בהצלחה.attr – מאפיינים המתארים את תכונות החוט החדש, כגון האם החוט הוא חוט גרעין או חוט משתמש, האם ניתן לבצע לו join, כלומר להמתין לסיומו, וכו'. בד"כ נספק ערך NULL המציין חוט ברירת המחדל של המערכת, שניתן להמתין לסיומו.void* (*start_routine)(void*) מצביע לפונקציה שתהווה את קוד החוט. הערך המוחזר מפונקציה זו במקרה של סיומה הטבעי הינו ערך הסיום של החוט.arg – פרמטר שיסופק לפונקציה עם הפעלתה.מערכות הפעלה - תרגול 714

Tutorial 8slide 2TL;DRבתרגול הקודם למדנו לכתוב קוד מקבילי באמצעות חוטים

TL;DRבתרגול הקודם למדנו לכתוב קוד מקבילי באמצעות חוטים.ראינו שבכל בעיה לא טריוויאלית יש צורך בסנכרון בין החוטים.היום נלמד על מנגנוני סנכרון נוספים של ממשק pthreads :לבסוף, נלמד דוגמה נוספת של קוד מקבילי חשוב: גרעין לינוקס.קוד הגרעין לא משתמש בחוטים, אבל ניגש לזיכרון משותף מתוך מספר מסלולי בקרה שרצים במקביל, ולכן העקרונות שלמדנו תקפים גם עבורו.2מערכות הפעלה - תרגול 8להבטחת סדרלהבטחת אטומיותמשתני תנאי(condition variables)מנעולים(mutexes)סמפורים (semaphores)

Tutorial 8slide 6משתנה תנאי (condition variable)משתנה תנאי הוא אובייקט סנכרון המאפשר לחוט לצאת להמתנה בתוך קטע קריטי

משתנה תנאי (condition variable)משתנה תנאי הוא אובייקט סנכרון המאפשר לחוט לצאת להמתנה בתוך קטע קריטי.כלומר, לפנות את המעבד ולצאת לתור המתנה.ההמתנה תתבצע עד לקיום תנאי כלשהו.ההמתנה מאפשרת לאכוף סדר בביצוע של החוטים. שימוש תכנותי נכון במשתני תנאי מחייב להגדיר גם:משתנה מצב – החוט עובר להמתנה או חוזר מהמתנה בהתאם לערכו של משתנה המצב.מנעול mutex – מבטיח לנו אטומיות והגנה על הקטע הקריטי.מערכות הפעלה - תרגול 86

Tutorial 8slide 7סכימה כללית למשתני תנאיcond_t c; // should be initializedmutex_t m; // should be initializedint state_var = 0;החוט המ...

סכימה כללית למשתני תנאיcond_t c; // should be initializedmutex_t m; // should be initializedint state_var = 0;החוט הממתין לאירוע יקרא ל:while (!condition_holds(state_var)) cond_wait(&c, &m);החוט שמסמן לחוטים הממתינים להמשיך יקרא ל:if (condition_holds(state_var)) cond_signal(&c);מערכות הפעלה - תרגול 87מדוע cond_wait() מקבלתגם את המנעול?

Tutorial 8slide 11המתנה על משתני תנאיint pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);פעולה: משחררת את המנעול ומעביר...

המתנה על משתני תנאיint pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);פעולה: משחררת את המנעול ומעבירה את החוט להמתין על משתנה התנאי באופן אטומי (ראינו קודם מדוע זה הכרחי).החוט הממתין חייב להחזיק במנעול mutex לפני הקריאה.בחזרה מהמתנה על משתנה התנאי, החוט עובר להמתין על המנעול. החוט יחזור מהקריאה ל-pthread_cond_wait() רק לאחר שינעל מחדש את ה-mutex.ערך מוחזר: הפעולה תמיד מצליחה ומחזירה 0.11מערכות הפעלה - תרגול 8

Tutorial 8slide 12שחרור חוטים ממתיניםint pthread_cond_signal(pthread_cond_t *cond); משחררת את אחד החוטים הממתינים (הגינות לא מובטחת)

שחרור חוטים ממתיניםint pthread_cond_signal(pthread_cond_t *cond); משחררת את אחד החוטים הממתינים (הגינות לא מובטחת).int pthread_cond_broadcast(pthread_cond_t *cond);משחררת את כל החוטים הממתינים.כל החוטים מפסיקים להמתין על משתנה התנאי ועוברים להמתין על המנעול. החוטים יחזרו לפעילות בזה אחר זה (בסדר כלשהו, לאו דווקא הוגן) לאחר שינעלו מחדש את ה-mutex.שימו לב: אם אין אף חוט שממתין באותו רגע על משתנה התנאי cond, הפעולות חסרות השפעה (הסיגנל הולך לאיבוד ואינו נזכר הלאה).ערך מוחזר: הפונקציות תמיד מצליחות ומחזירות 0.מערכות הפעלה - תרגול 812

Tutorial 8slide 38מועד א', אביב 2008, שאלה 1sem_t sem; // Global semaphore, with initial value 1 int writer_lock() { sem_wait(sem);} in...

מועד א', אביב 2008, שאלה 1sem_t sem; // Global semaphore, with initial value 1 int writer_lock() { sem_wait(sem);} int writer_unlock() { sem_post(sem);} int reader_lock() { while(sem_getvalue(sem) <= 0) sleep(1); sem_wait(sem);} int reader_unlock() { sem_post(sem);}סעיף ב: להלן הצעה לפתרון בעיית קוראים/כותבים עם עדיפות לכותבים, המשתמשת בסמפורים.תארו 3 בעיות שונות של נכונות ו/או יעילות שיש בפתרון הנ"ל. הניחו כי הסמפור הינו הוגן.מערכות הפעלה - תרגול 838

Tutorial 8slide 39מועד א', אביב 2008, שאלה 1sem_t sem; // Global semaphore, with initial value 1 int writer_lock() { sem_wait(sem);} in...

מועד א', אביב 2008, שאלה 1sem_t sem; // Global semaphore, with initial value 1 int writer_lock() { sem_wait(sem);} int writer_unlock() { sem_post(sem);} int reader_lock() { while(sem_getvalue(sem) <= 0) sleep(1); sem_wait(sem);} int reader_unlock() { sem_post(sem);}בעיית נכונות: הפתרון לא מאפשר ליותר מקורא אחד להיכנס לקטע קריטי.בעיית נכונות: אם יש גם קוראים וגם כותבים, הכותבים לא בהכרח יקבלו עדיפות ועלולים להיות מורעבים בניגוד לדרישה.בעיית יעילות: קוראים מבצעים busy wait.מערכות הפעלה - תרגול 839

Ask Gemini
2017B_Spring_B · Q3 — question + its material already loaded
Pick a shortcut above or ask anything about this question.
The exam text, the skills it tests, and the exact slides are already in context.