שבוע 8# תהליכים : פרק משותף זיכרון : נושא Shared Memory – ` קורס
Transcription
שבוע 8# תהליכים : פרק משותף זיכרון : נושא Shared Memory – ` קורס
שבוע #8 פרק :תהליכים נושא :זיכרון משותף – Shared Memory קורס מערכות הפעלה א' מכללת הדסה /מכללה חרדית צבי מלמד [email protected] הרצאות הקורס מבוססות במידה רבה ביותר על ההרצאות של ד"ר יורם ביברמן © כל הזכויות שמורות לד"ר יורם ביברמן ולצבי מלמד ©צבי מלמד 1 זכרון משותף -מבוא • עד כה -כלי ה IPC -שראינו היו סדרתיים )תור = (FIFO – -כולל ה message-queue • זיכרון משותף: – מאפשר לתהליכים שחולקים אותו לפנות לאותו קטע-זיכרון )= מערך( בגישה ישירה :הם יכולים לקרוא /לכתוב על כל תא רצוי. – מהירות :המידע אינו עובר דרך תווך ביניים כלשהו בין הכותב לקורא • מאידך: – אתגר :כיצד לגרום לתהליכים לסנכרן\לתאם את הפניה לזכרון המשותף, כך שתהליך ב' לא יקרא נתונים לפני שתהליך א' סיים להכינם – תזכורת :במחשבים מודרניים אפילו פעולת השמה על הזיכרון אינה אטומית! – בדרך כלל הסינכרון יעשה באמצעות סמפורים )מנעולים( ]בסמסטר הבא[ ©צבי מלמד 2 זכרון משותף -מבוא • כזכור :מערכת ההפעלה מגינה עלינו ,בכך שהיא מפרידה את מרחב הכתובות של כל תהליך מכל האחרים. • בזכות זאת :תהליך ב' לא יכול לפגוע בנתונים של תהליך א' • כדי להשיג את שיתוף הזיכרון יש להשתמש באמצעים מיוחדים. • כפי שציינו ,זיכרון משותף ,תור הודעות וסמפור ,נקראים ,XSI IPCמקורם משותף ) (System Vויש ביניהם קווי דמיון ,בפרט ברעיון של מפתח חיצוני לעומת מזהה פנימי, באמצעותם פונים ל ַאובייקט. ©צבי מלמד 3 זכרון משותף – ייצור המפתח החיצוני • גם בזיכרון משותף נתחיל ביצור מפתח ,באמצעות )(:ftok ; key_t key { )if ((key = ftok("~yoramb/os", 'b')) == -1 ; )…(perror ; )…(exit } ©צבי מלמד 4 זכרון משותף -יצירת האוביקט • • • • נשתמש ב ַ מפתח כדי להקצות את האובייקט הדרוש ,ויוחזר לנו מזהה האובייקט: ; int shm_id shm_id = shmget(key, SHM_SIZE, ; )IPC_CREAT | IPC_EXCL | 0600 { )if (shm_id == -1 … } הדבר דומה ליצירת/קבלת האוביקט בתור ההודעות הארגומנט הראשון -והשלישי דומים לאלה בפונקציה )(,msgget הארגומנט השני -גודל שטח הזיכרון הרצוי בבתים – שטח הזיכרון המוקצה מאופס – בפועל ,מוקצים דפים שלמים ,חלק מהדף האחרון המוקצה לא יעמוד לרשותנו ©צבי מלמד 5 עוד על )(shmget • סוגי שגיאות: – ביקשנו גודל שקטן מהמינימום SHMMINאו גדול מהמקסימום SHMMAXהמותרים – האוביקט כבר קיים והעברנו IPC_CREAT | IPC_EXCL – עברנו את המכסות – כמות האוביקטים או כמות הזכרון המוקצית למטרה זאת – האוביקט לא קיים )וגם לא הדלקנו את (IPC_CREAT – אין לנו הרשאת גישה – המערכת לא הצליחה להקצות זיכרון ©צבי מלמד 6 זכרון משותף -קבלת האוביקט • • קטע זה לביצוע על ידי תהליכים נוספים שירצו להשתמש בזכרון המשותף שכבר הוקצה הארגומנט השלישי: – לא ליצור אוביקט/להקצות שטח – לא להכשל אם הוא כבר מוקצה – ההרשאות – 600קריאה /כתיבה למשתמש הנוכחי. – ההרשאות נקבעות בזמן היצירה, ונבדקות עבור התהליך שרק מקבל את האוביקט – האם כוונותיו תואמות את ההרשאות שנוצרו לקטע ;)key = ftok(...... ; int shm_id = shm_id shmget(key, 0, // OR: SHM_SIZE ; )0600 { )if (shm_id == -1 … } ©צבי מלמד 7 שקף גיבויIPC_PRIVATE : • • • • • במקום להשתמש ב KEY-חיצוני כלשהו ,ניתן להשתמש בערך .IPC_PRIVATE נוצר אזור "זכרון-משותף" פרטי לאותו תהליך .תהליך אחר שיבצע קריאה דומה, לא יקבל את אותו האזור ,אלא ייווצר עבורו אזור אחר )או תור אחר עבור (msgget לכאורה – למה זה טוב? – לשיתוף בין אבא ובניו – לדוגמא :זאת תופעה מקובלת שתהליך שרת יוצר את תהליכים הקליינטים כיצד מתבצע השיתוף? – האבא יצר את ה) shared-memory-הקריאה ל )( (shmgetלפני שביצע את ה- FORK – האב קיבל את המפתח הפנימי .המפתח עבר לבנים בתהליך שכפול הזכרון עלות /תועלת לעומת השיטה של מפתח חיצוני: – +חסכנו את הצורך במפגש rendezvousוהסכנה של קונפליקט במפתחות – -הגבלנו עצמנו לאב קדמון ולבניו ,צאצאיו ©צבי מלמד 8 סיכום ביניים (1מערכת ההפעלה הקצתה בזיכרון המחשב שטח לזכרון משותף (2ניתנה הרשאת גישה אליו לתהליכים שבצעו )(shmget (3קיים מזהה פנימי בשביל להתייחס לאזור הזכרון המשותף מה חסר? • לשלב את שטח הזיכרון במרחב הכתובות של תהליכים אלה; • כלומר :כרגע )עדיין( אין כתובות )עליהן מצביע פוינטר כלשהו( בתהליכים השונים באמצעותן הם יפנו לשטח הזיכרון המשותף. ©צבי מלמד 9 הקדמונת קצרצרה לזיכרון ווירטואלי • באופן ווירטואלי – התוכנית "רואה" - זכרון לינארי רציף • הזכרון מחולק לסגמנטים • לכל סגמנט יש כתובת בסיס ,גודל וקוד/אופן הגנה • למעשה חלוקה עדינה יותר – לדפים בגודל ) 4Kתלוי מכונה( • דף יכול להיות בזכרון הראשי )ירוק( ,או בזכרון המשני )צהוב( ,או ) invalidאדום( • מנגנון חומרה דואג לתרגומים המסובכים מכתובת ווירטואלית שקיימת בתוכנית לכתובת פיזית +בדיקת הרשאות גישה ©צבי מלמד 10 חיבור ) (attachהתהליך לזכרון המשותף • לאחר שמקצים את הזכרון המשותף ,צריך להוסיף אותו לטבלת דפי-הזכרון של התהליך. • מתבצע על ידי הקריאה ל) shmat -נשמע כמו שמטע " -סמרטוט" - אבל בעצם(shared-memory-attach : • הוספה כפולה ונשנית של אותו אזור – נקבל מספר כתובות ,אבל כולן מתייחסות לאותו מידע • הוספה עם הרשאות כתיבה/קריאה. • אם התוכנית צריכה רק לקרוא – ראוי להשתמש באופציה של ) READ-ONLYבמקרה של BUGבתוכנית שלנו – לא נקלקל את הזיכרון המשותף לתהליכים אחרים( ©צבי מלמד 11 חיבור ) (attachהתהליך לזכרון המשותף ;)void *shmat(int shmid, const void *shmaddr, int shmflg • • • NULLבארגומנט השני מורה שניתן לשלב את קטע הזכרון המשותף בכל מקום פנוי במרחב הכתובות .זה הערך המקובל מאוד לארגומנט) .בד"כ הוא ישולב בין המחסנית לערמה(. אפס בארגומנט השלישי מורה שברצוננו ,בעזרת המצביע הנוכחי גם לקרוא וגם לכתוב על הזכרון המשותף. אם נרצה רק לקרוא – נעביר את הדגל ) SHM_RDONLYאין אפשרות מקבילה ל"כתיבה בלבד"( ; char *shm_ptr )* shm_ptr = (char shmat(shm_id, NULL, ; )0 { )if (shm_ptr == (char *) -1 ;)"perror("shmat failed ;)exit(EXIT_FAILURE } ©צבי מלמד 12 עוד על shmat ... • הפעולה מגדילה מונה פנימי )בדסקריפטור( שסופר את מספר ה- ) attachmentsשימושי לפעולת ה(delete- • ערך מוחזר :המצביע לזכרון – במקרה הצלחה ,או -1במקרה של כשלון • שגיאות אפשריות: – אין הרשאות – קונפליקט בכתובות – בעיה בהקצאת הזכרון ©צבי מלמד 13 עבודה רגילה עם המצביע לזכרון . לטפל בשטח הזיכרון כבכל מערךshm_ptr נוכל באמצעות,• עתה :לדוגמה char input_str[MAX_STR_LEN] ; fgets(input_str, MAX_STR_LEN, stdin) ; strcpy(shm_ptr, input_str) ; :• או for (i=0; i< 10; i++) shm_ptr[i] = '?' ; 14 ©צבי מלמד סיום עבודה – ניתוק המצביע • • • • • בסיום העבודה עם הזכרון המשותף ,ננתק את המצביע על ידי קריאת המערכת shmdt(shm_ptr) : לא משחררים את שטח הזיכרון -אלא רק מנתקים את המצביע הזה ממנו. הפעולה הזאת מעדכנת מונה )מקטינה באחד( –shm_nattchחבר ב shared-memory descriptor ה shared-memory -ישוחרר ,רק לאחר ש:- – הודענו על רצוננו לשחררו באמצעות קריאה לshmctl - – מספר המצביעים אליו ירד והגיע לאפס – כלומר ,אחרי שכל התהליכים שעושים בו שימוש הסתיימו )או שחררו את המצביע( ©צבי מלמד 15 שיחרור הזכרון המשותף • • • כדי לשחרר את שטח הזיכרון יכול כל תהליך שיש לו הרשאת קריאה +כתיבה על השטח )ולא רק מי שהקצה אותו( להגדיר משתנה עזר: ; struct shmid_ds shm_desc ולבצע: )if (shmctl(shm_id, IPC_RMID, &shm_desc) == -1 { ; )"perror("shmctl failed ; )exit(EXIT_FAILURE } קטע זכרון משותף שלא שוחרר מסיבה כלשהי )למשל :התהליך עף (...ממשיך להתקיים )ולגזול משאבי מערכת(. – בפרט ,ניסיון הקצאה שני )הרצה חוזרת של תוכניתנו( – ההקצאה תכשל ©צבי מלמד 16 עבודה מהshell - • : ipcs -mמציג את משאבי הזכרונות המשותפים • > : ipcrm –m <shm-idמשחררת את הזכרון המשותף הזה ©צבי מלמד 17 Shared-memory descriptor struct shmid_ds { struct ipc_perm shm_perm; size_t shm_segsz; time_t shm_atime; time_t shm_dtime; time_t shm_ctime; unsigned short shm_cpid; unsigned short shm_lpid; short shm_nattch; ... }; 18 ©צבי מלמד /* /* /* /* /* /* /* /* operation perms */ size of segment (bytes) */ last attach time */ last detach time */ last change time */ pid of creator */ pid of last operator */ no. of current attaches */ זכרון משותף תקלה אפשרית • ניזכ ֵר כי הפעולה ) v++המגדילה את תא הזיכרון vבאחד( למעשה ,בד"כ ,הופכת לשלוש פקודות מכונה: 1. reg v 2. reg++ 3. v reg • עתה נניח כי אנו מריצים שני תהליכים ,החולקים זיכרון משותף בן שני בתים, אליו הם פונים כאל מערך .aכל אחד מהתהליכים סופר ,ומוסיף לזיכרון המשותף ,כמה אפסים וכמה אחדים הוא קרא )לתאים #1 ,#0במערך(. { )while ((c = getchar()) != EOF )'if (c == '0' || c == '1 ; a[c – '0']++ } ©צבי מלמד 19 זכרון משותף תקלה אפשרית • נניח התהליך הבא. תהליך ב' • ערכו של ] a[0הוא .17 • כל אחד משני התהליכים קרא אפס נוסף ,וברצונו על-כן להגדיל את התא #0במערך; • מערכת ההפעלה מריצה אותם, כולל החלפת הקשר ,באופן המתואר ממול. תהליך א' ]reg1 = a[0 ]reg2 = a[0 reg1++ reg2++ • מה תהיה התוצאה? a[0] = reg1 a[0] = reg2 ©צבי מלמד 20 זכרון משותף תקלה אפשרית • אם כאמור נניח שערכו של ]a[0 הוא .17 • התוצאה בסיוםa[0]18 : • ולא מה שהתכוונו שיקרה ,והוא ש: a[0]19 תהליך ב' תהליך א' ]reg1 = a[0 ]reg2 = a[0 reg1++ reg2++ a[0] = reg1 a[0] = reg2 ©צבי מלמד 21 פתרונות אפשריים למניעה הדדית הפתרון: שימוש בסמפור או במנעולים כלליים ,כפי שנכיר בסמסטר הבא .I או: .II בנעילת קטע הזכרון המשותף באמצעות מנעול יעודי כפי שלינוקס וסולאריס מאפשרות לנו לעשות )באופן שחורג מהסטנדרט. – בלינוקס רק החל מגרסה 2.6.10כל משתמש יכול לבצע פעולה זאת )בעבר רק משתמש מיוחס יכול היה לבצעה( ©צבי מלמד 22 shmctl() מניעה הדדית באמצעות while ((c = getchar()) != EOF) { עד.אנו מבקשים לנעול את הז"מ if (c == '0' || c == '1') שהמנעול לא יינתן לנו אנו { תקועים\מושהים if (shmctl(shm_id, SHM_LOCK, &shm_desc) == -1) { perror("shmctl(lock) failed"); רק אנו מטפלים,כאן exit(EXIT_FAILURE) ; בז"מ באופן בלבדי } a[ c - '0']++ ; if (shmctl(shm_id, שחרור הז"מ שנעלנו SHM_UNLOCK, &shm_desc) == -1) { perror("shmctl(unlock) failed") ; exit(EXIT_FAILURE) ; } } } ©צבי מלמד 23 דוגמה :יצרן-צרכן של מחרוזות • תכנית א' קוראת מחרוזות מ stdin-ו'-מייצרת' אותן – כלומר כותבת לז"מ. – • תכנית ב' 'צורכת' את המחרוזות שייצרה תכנית א'. – • • ייתכן שמספר תהליכים המריצים תכנית זאת ירוצו במקביל )כנ"ל( כדי לסנכרן את הגישה שלהן לז"מ הן משתמשות בתא #0בקטע הזיכרון: – סימן מינוס :אין מחרוזת בזיכרון. – סימן פלוס :יש מחרוזת בזיכרון. הערה: – בתכנית יש ליקויים מבחינת סנכרון הגישה לזיכרון המשותף ,נושא עליו נדון בפרק ,#7אך היא תקינה מהבחינה הטכנית ולכן תספק את צרכינו הנוכחיים. ©צבי מלמד 24 (צרכן של מחרוזות- "היצרן" )יצרן:דוגמה /* shm_create_n_produce.c * A program that allocates a block of shared memory, * then repeatedly 'produces' strings (it reads from stdin) * to the shm. * A second program: a 'consumer' consumes * these strings from the shm. * (shm_consumer.c) * * The programs do not utilize the random access * property of a shm */ 25 ©צבי מלמד (צרכן של מחרוזות- "היצרן" )יצרן:דוגמה #include #include #include #include #include #include #include <stdio.h> <stdlib.h> <string.h> <unistd.h> <sys/types.h> <sys/ipc.h> <sys/shm.h> // for exit() // for strcpy(), strcmp() // for sleep() #define MAX_STR_LEN 100 #define SHM_SIZE MAX_STR_LEN + 1 //shm_ptr[0] holds whether the shm //is empty (-) on full (+) #define END_STRING "END" 26 ©צבי מלמד (צרכן של מחרוזות- "היצרן" )יצרן:דוגמה int main() { key_t key ; int shm_id ; המפתח החיצוני והפנימי לז"מ המצביע בעזרתו נפנה לז"מ char *shm_ptr ; char input_string[MAX_STR_LEN] ; מבנה לצורך שחרור הז"מ struct shmid_ds shm_desc ; // create a key for the shm key = ftok("/tmp", 'y') ; if (key == -1) { perror("ftok failed: ") ; exit(EXIT_FAILURE) ; } 27 ©צבי מלמד (צרכן של מחרוזות- "היצרן" )יצרן:דוגמה if ((shm_id = shmget (key, SHM_SIZE, IPC_CREAT | IPC_EXCL | 0600))== -1) { נוריד את המרכיב הזה אם כמה תהליכים עשויים לנסות להקצות את הז"מ perror("shmget failed: ") ; exit(EXIT_FAILURE) ; } shm_ptr = (char *) shmat(shm_id, NULL, 0) ; if (!shm_ptr) { perror("shmat failed: ") ; exit(EXIT_FAILURE) ; } 28 ©צבי מלמד (צרכן של מחרוזות- "היצרן" )יצרן:דוגמה shm_ptr[0] = '-' ; // signals that shm is empty puts("Now, (and only now!) reader can start"); printf("Enter a series of strings to be written\ on the shm.\n\ Enter %s to finish\n", END_STRING) ; המתנה עסוקה for( ; ; ) { scanf(" %s", input_string) ; while (shm_ptr[0] == '+') // while shm // is not 'empty' sleep(1) ; if (shmctl(shm_id, SHM_LOCK, &shm_desc) == -1) { perror("shmctl LOCK failed: ") ; exit(EXIT_FAILURE) ; } 29 ©צבי מלמד (צרכן של מחרוזות- "היצרן" )יצרן:דוגמה strcpy(shm_ptr +1, input_string) ; shm_ptr[0] = '+' ; // signals that shm is not empty if (shmctl(shm_id, SHM_UNLOCK, &shm_desc) == -1 { perror("shmctl UNLOCK failed: ") ; exit(EXIT_FAILURE) ; } if (strcmp(input_string, END_STRING) == 0) { if (shmctl(shm_id, IPC_RMID, &shm_desc) == -1) { perror("shmctl IPC_RMID failed: ") ; exit(EXIT_FAILURE) ; } return( EXIT_SUCCESS ) ; } } // for (; ; ) return( EXIT_SUCCESS ) ; } 30 ©צבי מלמד (צרכן של מחרוזות- "הצרכן" )יצרן:דוגמה // file: shm_consumer.c // See documentation in: shm_create_n_produce.c #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/types.h> #include <unistd.h> #include <sys/ipc.h> #include <sys/shm.h> #define MAX_STR_LEN 100 #define SHM_SIZE MAX_STR_LEN +1 #define END_STRING "END" //---------------------------------------------- 31 ©צבי מלמד (צרכן של מחרוזות- "הצרכן" )יצרן:דוגמה int main() { key_t key ; int shm_id ; char *shm_ptr ; char output_string[MAX_STR_LEN] ; struct shmid_ds shm_desc ; // create THE SAME key for the shm as the producer key = ftok("/tmp", 'y') ; if (key == -1) { perror("ftok failed: ") ; exit(EXIT_FAILURE) ; } 32 ©צבי מלמד (צרכן של מחרוזות- "הצרכן" )יצרן:דוגמה // get the id of the block of memory // that was, hopefully, already created by the producer if ((shm_id = shmget(key, SHM_SIZE, // OR: 0 0600)) == -1) { perror("shmget failed: ") ; exit(EXIT_FAILURE) ; } shm_ptr = (char *) shmat(shm_id, NULL, 0) ; if (!shm_ptr) { perror("shmat failed: ") ; exit(EXIT_FAILURE) ; } for( ; ; ) { ............. 33 ©צבי מלמד (צרכן של מחרוזות- "הצרכן" )יצרן:דוגמה for( ; ; ) { while (shm_ptr[0] == '-') sleep(1) ; // lock the shm before you operate on it if (shmctl(shm_id, SHM_LOCK, &shm_desc) == -1) { perror("shmctl LOCK failed: ") ; exit(EXIT_FAILURE) ; } strcpy(output_string, shm_ptr +1) ; shm_ptr[0] = '-' ; // the shm is empty if (shmctl(shm_id, SHM_UNLOCK, &shm_desc) == -1) { perror("shmctl UNLOCK failed: ") ; exit(EXIT_FAILURE) ; } printf("Got: %s from the shm\n", output_string) ; 34 ©צבי מלמד (צרכן של מחרוזות- "הצרכן" )יצרן:דוגמה printf("Got: %s from the shm\n", output_string) ; if (strcmp(output_string, END_STRING) == 0) { if (shmctl(shm_id, IPC_RMID, &shm_desc) == -1) { perror("shmctl IPC_RMID failed: ") ; exit(EXIT_FAILURE) ; } return( EXIT_SUCCESS ) ; } } // for( ; ; ) return( EXIT_SUCCESS ) ; } 35 ©צבי מלמד זכרון משותף – הערות נוספות • גודלו של שטח זכרון משותף שהוקצה אינו ניתן לשינוי – • שלא כמו זיכרון )פרטי( שהוקצה דינאמית ע"י )(malloc וגודלו ניתן לשנותו ע"י )(. ,realloc ניתן לטפל במערך של מבנים על ידי שמגדירים: } … { struct S ; struct S *ptr ; )כרגיל(ptr = (struct S *) shmatt ........ = … ptr[0]. ©צבי מלמד 36 זכרון משותף – הערות נוספות • במידה ואנו מחזיקים מבנה נתונים כלשהו בז"מ יש להקפיד להחזיק את כל המידע אודותיו בז"מ )לדוגמה :כמה תאים כולל המערך ,וכמה נמצאים כרגע בשימוש(. • מספר שלם בן ארבעה בתים יש לשמור בכתובת שהינה כפולה של ארבע .בד"כ הקומפיילר דואג לכך עבורנו .עת אנו מקצים ז"מ, ורוצים לאחסן בו נתונים )בפרט מבנים הכוללים חברים מטיפוסים שונים( ,חובת הדאגה לכך עוברת אלינו )אם לא נקפיד על כך ישלח לתהליך הסיגנל .(SIGBUS ©צבי מלמד 37