1 / 42

מערכות הפעלה

מערכות הפעלה. תרגול 3 קלט פלט בסיסי תקשורת בין תהליכים. אדמיניסטרציה. אתר הקורס: http://www.cs.biu.ac.il/~shpiget/OS / הגשות: תרגיל ראשון - תאריך הגשה 14.03.2005 שאלות – ראו FAQ באתר הקורס האם הערך החוזר בתרגיל1 הוא בהכרח Short ? לאן יש להגיש?

anahid
Télécharger la présentation

מערכות הפעלה

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. מערכות הפעלה תרגול 3קלט פלט בסיסי תקשורת בין תהליכים

  2. אדמיניסטרציה • אתר הקורס: • http://www.cs.biu.ac.il/~shpiget/OS/ • הגשות: • תרגיל ראשון - תאריך הגשה 14.03.2005 • שאלות – ראו FAQ באתר הקורס • האם הערך החוזר בתרגיל1 הוא בהכרח Short? • לאן יש להגיש? • כל סטודנט מגיש באופן עצמאי ע"י שימוש ב- Submitex • תרגיל1 מכיל 2 תוכנות שיש להגיש בנפרד לשני תרגילים! • שימו לב לשמות הספריות להגשה Ex1_Fork Ex1_Shell • שם הקבצים להגשה הם exec1.c עבור Fork, ו-exec2.c עבור Shell תירגול 3 - מערכות הפעלה

  3. נושאים שיכוסו • קלט/פלט של תהליכים • תקשורת וסנכרון בין תהליכים • מבוא • pipes • הכוונת קלט ופלט • FIFOs – Named Pipes תירגול 3 - מערכות הפעלה

  4. קלט/פלט של תהליכים ב-Linux (1) • ק/פ של תהליך מול התקן נעשה תוך שימוש במערך של descriptors • לכל תהליך ישנה טבלה, ששמה Process Descriptor Table - PDT אשר מנוהל ע"י ה-OS. • כל כניסה בטבלה מצביעה על אובייקט ניהול file) object) מטעם התהליך עבור ההתקן המקושר ל-descriptor • קוד תהליך ניגש להתקן דרך ה-descriptor.אובייקט הניהול מתופעל ע"י גרעין מערכת ההפעלה בלבד • "מחוון הקובץ" הוא חלק מאובייקט הניהול ומצביע למקום ספציפי בקובץ הפתוח, כלומר הנתונים הבאים לקריאה / כתיבה מההתקן • התקן המופעל באמצעות ה-descriptor יכול להיות קובץ, התקן חומרה, ערוץ תקשורת או כל דבר שניתן לכתוב ו/או לקרוא נתונים אליו וממנו • ממשק ההתקשרות עם ההתקן דרך ה-descriptor הינו אחיד, ללא תלות בסוג ההתקן תירגול 3 - מערכות הפעלה

  5. קלט/פלט של תהליכים ב-Linux (2) • ערכי ה-descriptors הבאים מקושרים להתקנים הבאים כברירת מחדל: • 0 (stdin) - מקושר לקלט הסטנדרטי, בדרך-כלל המקלדת • פעולות הקלט המוכרות, כדוגמת scanf() ודומותיה, הן למעשה פעולות של קריאה מהתקן הקלט הסטנדרטי דרך stdin • 1 (stdout) - מקושר לפלט הסטנדרטי, בדרך-כלל תצוגת טקסט במסוף • פעולות הפלט המוכרות, כדוגמת printf() ודומותיה, הן למעשה פעולות של כתיבה להתקן הפלט הסטנדרטי דרך stdout • 2 (stderr) - מקושר לפלט השגיאות הסטנדרטי, בדרך-כלל גם הוא תצוגת טקסט במסוף • ניתן לשנות את קישור ה-descriptors להתקנים באופן דינמי, כפי שנראה בהמשך תירגול 3 - מערכות הפעלה

  6. קלט/פלט של תהליכים ב-Linux (3) • תחילת עבודה עם התקן היא ע"י קישור ההתקן ל-descriptor בפעולת open() • ה-descriptor "פתוח", כלומר מקושר להתקן • ה-descriptors 0,1,2 פתוחים מתחילת ביצוע התהליך • בסיום העבודה עם ההתקן מנותק ה-descriptor מההתקן בפעולת close() • ה-descriptor "סגור", כלומר לא מקושר להתקן • כל ה-descriptors הפתוחים של תהליך נסגרים באופן אוטומטי ע"י מערכת ההפעלה בסיום התהליך • עם זאת, מומלץ לבצע סגירה מסודרת בקוד בכל סיום שימוש ב-descriptor • אין צורך לסגור descriptors פתוחים המקושרים לערוצי הקלט / פלט / שגיאות הסטנדרטיים (אלא אם רוצים לחבר אותם להתקנים אחרים) תירגול 3 - מערכות הפעלה

  7. קריאות מערכת בסיסיות (1) פתיחת התקן לגישה – open() #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> //Last Arg. is Opt. for O_CREAT only int open(const char *path, int flags [,mode_t mode]); • פעולה: ההתקן המבוקש (דרך path) נפתח לגישה לפי התכונות המוגדרות ב-flags ולפי הרשאות המוגדרות ב-mode • ערך מוחזר: במקרה של הצלחה – ה-descriptor המקושר להתקן שנפתח (ערך זה הוא ה-descriptor הנמוך ביותר שהיה סגור לפני פתיחת ההתקן), במקרה של כישלון – (-1) תירגול 3 - מערכות הפעלה

  8. קריאות מערכת בסיסיות (2) • flags – תכונות לאפיון פתיחת הקובץ. חייב להכיל אחת מהאפשרויות הבאות: • O_RDONLY – הקובץ נפתח לקריאה בלבד • O_WRONLY – הקובץ נפתח לכתיבה בלבד • O_RDWR – הקובץ נפתח לקריאה ולכתיבה כמו כן, ניתן לצרף תכונות אופציונליות שימושיות נוספות באמצעות OR (|) עם הדגל המתאים, למשל: • O_CREAT – צור את הקובץ אם אינו קיים • O_APPEND – שרשר מידע בסוף קובץ קיים • mode – פרמטר אופציונלי המגדיר את הרשאות הקובץ, במקרה שפתיחת הקובץ גורמת ליצירתקובץ חדש. הסבר על הערכים בשקף הבא. • ערך מוחזר: • במקרה של הצלחה – ה-descriptor המקושר להתקן שנפתח. ערך זה הוא ה-descriptor הנמוך ביותר שהיה סגור לפני פתיחת ההתקן • במקרה של כישלון – (-1) תירגול 3 - מערכות הפעלה

  9. mode_t • קביעת הרשאות הקבצים ע"י קוד תיעשה לפי המפתח הבא: • כך שלמעשה הרשאות קריאה בלבד יהיו: 0400 • הרשאות כתיבה בלבד: 0200 • כך שעבור R+W עבור עצמי ניתן את ההרשאה 0600 תירגול 3 - מערכות הפעלה

  10. קריאות מערכת בסיסיות (3) • סגירת גישה להתקן – close() #include <unistd.h> int close(int fd); • פעולה: סגירת ה-descriptorfd. לאחר הסגירה לא ניתן לגשת להתקן דרך ה-fd שצויין • פרמטרים: • fd – ה-descriptor המיועד לסגירה • ערך מוחזר: הצלחה – 0. כישלון – (-1) תירגול 3 - מערכות הפעלה

  11. קריאות מערכת בסיסיות (4) • קריאת נתונים מהתקן – read() #include <unistd.h> ssize_t read(int fd, void *buf, size_t count); • פעולה: מנסה לקרוא עד count בתים מתוך ההתקן המקושר ל-fd לתוך החוצץ buf • ייתכן שייקראו פחות מ-count בתים • למשל, אם נותרו פחות מ-count בתים בקובץ ממנו קוראים • ייתכן אף שלא ייקראו בתים כלל • למשל, אם בקריאה מקובץ, מחוון הקובץ הגיע לסוף הקובץ (EOF) • מחוון הקובץ מקודם בכמות הבתים שנקראו בפועל מההתקן, כך שבפעולת הגישה הבאה לקובץ (קריאה, כתיבה וכד') ניגש לנתונים שאחרי הנתונים שנקראו בפעולה הנוכחית • פעולת הקריאה תחסום את התהליך הקורא (תוריד אותו להמתנה) עד שיהיו נתונים זמינים לקריאה בהתקן • למשל: בקובץ, תהליך ימתין עד שהנתונים ייקראו מהדיסק תירגול 3 - מערכות הפעלה

  12. קריאות מערכת בסיסיות (5) • פרמטרים: • fd – ה-descriptor המקושר להתקן ממנו מבקשים לקרוא • buf – מצביע לחוצץ בו יאוחסנו הנתונים שייקראו מההתקן • count – מספר הבתים המבוקש • ערך מוחזר: • בהצלחה – מספר הבתים שנקרא מההתקן לתוך buf. אם read() נקראה עם count = 0, יוחזר 0 ללא קריאה • אם משתמשים בפעולה זו על pipe עשויה להיווצר חסימה • בכישלון – (-1) תירגול 3 - מערכות הפעלה

  13. קריאות מערכת בסיסיות (6) • כתיבת נתונים להתקן – write() #include <unistd.h> ssize_t write(int fd, const void *buf, size_t count); • פעולה: מנסה לכתוב עד count בתים מתוך buf להתקן המקושר ל-fd • בתלות בהתקן, ייתכן שייכתבו בין 0 ל-count בתים • למשל, אם יש מספיק מקום פנוי • בדומה ל-read(), מחוון הקובץ מקודם בכמות הבתים שנכתבו בפועל, והגישה הבאה לקובץ תהיה לנתונים שאחרי אלו שנכתבו • גם פעולת write() יכולה לחסום את התהליך בפעולה על התקנים מסוימים – למשל, עד שייתפנה מקום לכתיבה תירגול 3 - מערכות הפעלה

  14. קריאות מערכת בסיסיות (7) • פרמטרים: • fd – ה-descriptor המקושר להתקן אליו מבקשים לכתוב • buf – מצביע לחוצץ בו מאוחסנים הנתונים שייכתבו להתקן • count – מספר הבתים המבוקש לכתיבה • ערך מוחזר: • בהצלחה – מספר הבתים שנכתב להתקן מתוך buf. אם write() נקראה עם count = 0, יוחזר 0 ללא כתיבה • אם משתמשים בפעולה זו על pipe עשויה להיווצר חסימה • בכישלון – (-1) תירגול 3 - מערכות הפעלה

  15. קריאות מערכת בסיסיות (8) • הערות ל-read() : • ייתכן שייקראו פחות מ-count בתים (למשל, אם נותרו פחות מ-count בתים בקובץ ממנו קוראים), וייתכן אף שלא ייקראו בתים כלל (למשל, אם בקריאה מקובץ, מחוון הקובץ הגיע לסוף הקובץ (EOF)) • מחוון הקובץ מקודם בכמות הבתים שנקראו בפועל מההתקן, כך שבפעולת הגישה הבאה לקובץ (קריאה, כתיבה וכד') ניגש לנתונים שאחרי הנתונים שנקראו בפעולה הנוכחית • פעולת הקריאה תחסום את התהליך הקורא (תוריד אותו להמתנה) עד שיהיו נתונים זמינים לקריאה בהתקן • הערות ל-write() : • כתלות בהתקן, ייתכן שייכתבו בין 0 ל-count בתים (למשל, אם אין מספיק מקום פנוי) • בדומה ל-read(), מחוון הקובץ מקודם בכמות הבתים שנכתבו בפועל, והגישה הבאה לקובץ תהיה לנתונים שאחרי אלו שנכתבו • גם פעולת write() יכולה לחסום את התהליך בפעולה על התקנים מסוימים – למשל, עד שיתפנה מקום לכתיבה תירגול 3 - מערכות הפעלה

  16. קלט ופלט – סגנון עבודה (1) • הפעלה יחידה של קריאה או כתיבה מ-descriptor אינה מבטיחה העברת כל כמות הנתונים הרצויה, ולכן יש לבצע מספר הפעלות לפי הצורך • לדוגמה: אם רוצים לקרוא k תוים לתוך החוצץ buf, נבצע את הקוד הבא: total=0;while (k > 0) { r = read(fd, buf [total], k); if (r < 0) { /* handle error */ } else{ total+=r k –= r; } } • ניתן בקלות להתאים את דוגמת הקוד שלעיל לטפל במצבים נוספים (EOF, סוף מחרוזת וכו') • כדי לעבוד בצורה מסודרת מומלץ להגדיר פונקציות קריאה וכתיבה המכילות קוד כנ"ל ולהשתמש בהן תירגול 3 - מערכות הפעלה

  17. קלט ופלט – סגנון עבודה (2) • ניתן לעבוד עם קבצים והתקנים גם דרך ה-API של ספריית libc, המוכר משיעורי מבוא לתכנות: • fopen(), fclose(), fread(), fwrite(), [f]printf(), [f]scanf(), וכו' • עם זאת, כדאי מאוד להימנע מלערבב קריאות מערכת יחד עם קריאות לפונקציות libc בעבודה על אותו התקן • הסיבה: libc מנהלת מנגנוני בקרה משלה הכוללים חוצצים וכו' מעל לקריאות המערכת הבסיסיות, ו"עקיפת" מנגנונים אלו יכולה לגרום לשיבוש הנתונים תירגול 3 - מערכות הפעלה

  18. שיתוף ק/פ בין חוטים ותהליכים (1) • חוטים: טבלת ה-descriptors גלובלית לתהליך, כלומר משותפת לכל החוטים שבו • מתארי התהליכים של כל החוטים מצביעים על אותו PDT • תהליכים: פעולת fork() יוצרת עותק נוסף של טבלת ה-descriptors אצל תהליך הבן • תהליך הבן יכול לגשת לאותם התקנים אליהם ניגש האב • ה-descriptors ששוכפלו הינם שותפים, כלומר מצביעים לאותו אובייקט ניהול, ובפרט, חולקים את אותו מחוון קובץ • לדוגמה: אם האב קורא 10 בתים מה-descriptor ואחריו הבן קורא 3 בתים, אז הבן יקרא את 3 הבתים שאחרי ה-10 של האב. הכתיבה מושפעת באופן דומה תירגול 3 - מערכות הפעלה

  19. שיתוף ק/פ בין חוטים ותהליכים (2) • תהליכים (וחוטים) המשתמשים ב-descriptors שותפים או משותפים צריכים לתאם את פעולות הגישה להתקן על-מנת שלא לשבש זה את פעולת זה • הבעיה נפוצה במיוחד ב"שושלות משפחתיות" של תהליכים: אבות ובנים, אחים, נכדים וכו' • פעולת execve() ודומותיה אינן משנות את טבלת ה-descriptors של התהליך, למרות שהתהליך מאותחל מחדש • קבצים והתקנים פתוחים אינם נסגרים • שימושי ביותר להכוונת קלט ופלט של תוכניות אחרות תירגול 3 - מערכות הפעלה

  20. תקשורת בין תהליכים ב-Linux (1) • בדומה למערכות הפעלה מודרניות אחרות, Linux מציעה מגוון מנגנונים לתקשורת וסנכרון בין תהליכים • IPC = Inter-Process Communication • לכאורה, ניתן היה להסתפק בתקשורת בין תהליכים דרך קבצים משותפים, אך צורת תקשורת זו יקרה מבחינת משאבים ואיטית • שימוש במשאבים של מערכת הקבצים למטרות שאינן עבודה עם קבצים • המנגנונים ש-Linux מציעה למטרת תקשורת בין תהליכים כוללים: • pipes ו-FIFOs (named pipes): ערוצי תקשורת בין תהליכים באותה מכונה בסגנון יצרן-צרכן: תהליכים מסוימים מייצרים נתונים לתוך ה-pipe ותהליכים אחרים צורכים את הנתונים • signals: איתותים - הודעות אסינכרוניות הנשלחות בין תהליכים באותה מכונה (וגם ממערכת ההפעלה לתהליכים) על-מנת להודיע לתהליך המקבל על אירוע מסוים תירגול 3 - מערכות הפעלה

  21. תקשורת בין תהליכים ב-Linux (2) • System V IPC: שם כולל לקבוצה של מנגנוני תקשורת שהופיעו לראשונה בגרסאות UNIX של חברת AT&T ואומצו במהרה על-ידי מרבית סוגי ה-UNIX הקיימים כולל Linux. במסגרת קבוצה זו נכללים: • סמפורים • תורי הודעות (message queues) – מנגנון המאפשר להגדיר "תיבות דואר" וירטואליות הנגישות לכל התהליכים באותה מכונה. תהליכים יכולים לתקשר באמצעות הכנסה והוצאה של הודעות מאותה תיבת דואר • זיכרון משותף – יצירת איזור זיכרון מיוחד המשותף למרחבי הזיכרון של מספר תהליכים. זו צורת התקשורת היעילה ביותר והמקובלת ביותר עבור יישומים הדורשים כמות גדולה של IPC תירגול 3 - מערכות הפעלה

  22. תקשורת בין תהליכים ב-Linux (3) • sockets: מנגנון סטנדרטי המאפשר יצירת ערוץ תקשורת דו-כיווני (duplex) בין תהליכים היכולים להמצא גם במכונות שונות. מנגנון זה מסתיר את התפעול הפיזי של התקשורת בין המחשבים (רשת, כבלים וכו'). • על sockets ניתן ללמוד בהרחבה במסגרת הקורס "תקשורת באינטרנט" וכן דרך מגוון אתרים, כדוגמת: http://www.ecst.csuchico.edu/~beej/guide/net • במהלך תרגול זה אנו נסקור את מנגנוני ה-pipes וה-signals כדוגמאות למנגנוני IPC תירגול 3 - מערכות הפעלה

  23. pipes ב-Linux (1) • pipes (צינורות) הם ערוצי תקשורת חד-כיווניים המאפשרים העברת נתונים לפי סדר FIFO (First-In-First-Out) • הנתונים נקראים בסדר בו הם נכתבים • pipes משמשים גם לסנכרון תהליכים, כפי שנראה בהמשך • המימוש של pipes ב-Linux הוא כאובייקטים של מערכת הקבצים, למרות שאינם צורכים שטח דיסק כלל ואינם מופיעים בהיררכיה של מערכת הקבצים • הגישה ל-pipe היא באמצעות שני descriptors: אחד לקריאה ואחד לכתיבה תירגול 3 - מערכות הפעלה

  24. pipes ב-Linux (2) • היצירה של pipe היא באמצעות קריאת המערכת pipe(), וה-pipe הנוצר הינו פרטי לתהליך, כלומר אינו נגיש לתהליכים אחרים במערכת • לפיכך, הדרך היחידה לשתף pipe בין תהליכים שונים היא באמצעות קשרי משפחה • תהליך אב יוצר pipe ואחריו יוצר תהליך בן באמצעות fork() – לאב ולבן יש גישה ל-pipe באמצעות ה-descriptors שלו, המצויים בשניהם • לאחר סיום השימוש ב-pipe מצד כל התהליכים (סגירת כל ה-descriptors) מפונים משאבי ה-pipe באופן אוטומטי תירגול 3 - מערכות הפעלה

  25. pipes ב-Linux (3) • למי מהתהליכים הבאים יש גישה ל-pipe שיוצר תהליך A? • לכל התהליכים פרט ל-B Process A fork() pipe() fork() Process B Process C fork() fork() Process D Process E תירגול 3 - מערכות הפעלה

  26. קריאת המערכת pipe() • תחביר: #include <unistd.h> int pipe(int filedes[2]); • פעולה: יוצרת pipe חדש עם שני descriptors: אחד לקריאה מה-pipe ואחד לכתיבה אליו • פרמטרים: • filedes – מערך בן שני תאים. ב-filedes[0] יאוחסן ה-descriptorלקריאה מה-pipe שנוצר וב-filedes[1] יאוחסן ה-descriptorלכתיבה • ערך מוחזר: 0 בהצלחה ו- (-1) בכישלון תירגול 3 - מערכות הפעלה

  27. קריאה וכתיבה ל-pipe (1) • פעולות קריאה וכתיבה מתבצעות באמצעות read() ו-write() על ה-descriptors של ה-pipe • ניתן להסתכל על pipe כמו על תור FIFO עם מצביע קריאה יחיד (להוצאת נתונים) ומצביע כתיבה יחיד (להכנסת נתונים) • כל קריאה (מכל תהליך שהוא) מה-pipe מקדמת את מצביע הקריאה. באופן דומה, כל כתיבה מקדמת את מצביע הכתיבה • בדרך-כלל, תהליך מבצע רק אחת מהפעולות (קריאה או כתיבה) ולכן נהוג לסגור את ה-descriptor השני שאינו בשימוש • ה-descriptors של הקריאה בכל התהליכים הם שותפים, ולכן יש לתאם בין הקוראים. כנ"ל לגבי ה-descriptors של הכתיבה תירגול 3 - מערכות הפעלה

  28. קריאה וכתיבה ל-pipe (2) • read מ-pipe תחזיר: • את כמות הנתונים המבוקשת אם היא נמצאת ב-pipe • פחות מהכמות המבוקשת אם זו הכמות הזמינה ב-pipe בזמן הקריאה • 0 (EOF) כאשר כל ה-write descriptors נסגרו וה-pipe ריק • תחסום את התהליך אם יש כותבים (write descriptors) ל-pipe וה-pipe ריק. כאשר תתבצע כתיבה, יוחזרו הנתונים שנכתבו עד לכמות המבוקשת • write ל-pipe תבצע: • כתיבה של כל הכמות המבוקשת אם יש מספיק מקום פנוי ב-pipe • אם יש קוראים (read descriptors) ואין מספיק מקום פנוי ב-pipe, הכתיבה תחסום את התהליך עד שניתן יהיה לכתוב את כל הכמות הדרושה • ה-pipe מוגבל בגודלו (כ-4K) ולכן כתיבה ל-pipe שאין בו מספיק מקום פנוי ושאין עבורו קוראים (read descriptors פתוחים) תיכשל תירגול 3 - מערכות הפעלה

  29. pipe – תכנית דוגמה #include <stdio.h> #include <unistd.h> int main() { int my_pipe[2]; int status; char father_buff[6]; int index = 0; status = pipe(my_pipe); if (status == -1) { printf("Unable to open pipe\n"); exit(-1); } status = fork(); if (status == -1) { printf("Unable to fork\n"); exit(-1); } if (status == 0) { /* son process */ close(my_pipe[0]); write(my_pipe[1], "Hello", 6 * sizeof(char)); exit(0); } else { /* father process */ close(my_pipe[1]); wait(&status); /* wait until son process finishes */ read(my_pipe[0], father_buff, 6); printf("Got from pipe: %s\n", father_buff); exit(0); } } תירגול 3 - מערכות הפעלה

  30. הכוונת קלט ופלט (1) • אחד השימושים הנפוצים ביותר ב-descriptors בכלל וב-pipes בפרט הינו הכוונת הקלט והפלט של תכניות (Input/Output Redirection) • ניתוב המידע המיועד להתקן פלט לתוך התקן אחר (למשל קובץ) או החלפת מקור המידע מהתקן קלט אחד באחר • תוכנות ה-shell ב-Linux (ובמערכות הפעלה רבות אחרות) תומכות בהכוונת קלט ופלט באופן מובנה בפקודות שלהן • לדוגמה: התכנית ls מדפיסה את רשימת הקבצים בספרייה הנוכחית לתוך stdout. ניתן להריץ את ls דרך פקודת ה-shell הבאה: $ ls • הפקודה הבאה תגרום ל-shell להכניס את הפלט של ls לקובץ myfile $ ls > myfile תירגול 3 - מערכות הפעלה

  31. הכוונת קלט ופלט (2) • מה למעשה ביצע ה-shell בתגובה לפקודה הקודמת? (בערך ..) status = fork(); if (status == 0) { close(1); fd = open(“myfile”, O_WRONLY…); execve(“/bin/ls”, …); { • באופן דומה ניתן לכוון את הקלט של תכנית להגיע מקובץ • לדוגמה: התכנית more קוראת נתונים מ-stdin ומדפיסה אותם עם הפסקה בין עמוד לעמוד. הפקודה הבאה תגרום לה להדפיס את הקובץ myfile more < myfile תירגול 3 - מערכות הפעלה

  32. הכוונת קלט ופלט (3) • קריאת המערכת dup() - שימושית במיוחד לפעולות הכוונת קלט ופלט #include <unistd.h> int dup(int oldfd); • פעולה: מעתיקה את ה-descriptoroldfd ל-descriptor אחר פנוי (סגור) בטבלה. • ה-descriptor החדש הינו ה-descriptor הסגור בעל הערך הנמוך ביותר בטבלה • לאחר פעולה מוצלחת, oldfd וה-descriptor החדש הם שותפים • פרמטרים: • oldfd – ה-descriptor המיועד להעתקה – חייב להיות פתוח לפני ההעתקה • ערך מוחזר: • בהצלחה, מוחזר הערך של ה-descriptor החדש • בכישלון מוחזר (-1) • קריאה דומה: int dup2(oldfd, newfd) – סוגרת את newfd אם צריך ומעתיקה את oldfd ל-newfd ומחזירה את newfd תירגול 3 - מערכות הפעלה

  33. הכוונת קלט ופלט (4) • כאשר רוצים לכוון את הקלט או הפלט לבוא מתוך או להשלח אל תהליך אחר, משתמשים ב-pipe בין התהליכים בתור התקן המחליף את התקן הקלט או הפלט • לדוגמה: אם נרצה שהפלט של ls יודפס בעמודים עם הפסקות: $ ls | more • מה יבצע ה-shell בתגובה לפקודה הנ"ל? (בערך..) pipe(MyFd); status = fork(); if (status == 0) { /* first child */ close(1); //Def. Out dup(MyFd[1]); close(MyFd[0]); close(MyFd[1]); execve(“/bin/ls”,…); } status = fork(); if (status == 0) { /* second child */ close(0); //Def. In dup(MyFd[0]); close(MyFd[0]); close(MyFd[1]); execve(“/bin/more”,..); } close(MyFd[0]); close(MyFd[1]); תירגול 3 - מערכות הפעלה

  34. דוגמא נוספת ל-pipe (pipe4_6.c) #include <stdio.h> #include <unistd.h> int main(int argc, char *argv[]) { int i = 0, status, fd[2]; if (argc < 3) { // To be used for CMD | CMD “a.out whosort” fprintf(stderr, "usage: %s <command> <command>\n", argv[0]); exit(1); } if (pipe(fd) < 0) // create pipe { perror("pipe call"); exit(2); } //… next slide תירגול 3 - מערכות הפעלה

  35. דוגמא המשך - pipe switch (fork()) { case -1: // error perror("fork call"); exit(1); case 0: // child - the writing process dup2(fd[1], 1); // make stdout go to pipe close(fd[0]); close(fd[1]); execlp(argv[1], argv[1], NULL); exit(3); default: // parent - the reading process dup2(fd[0], 0); // make stdin come from pipe close(fd[0]); close(fd[1]); execlp(argv[2], argv[2], NULL); exit(3); } return (0); } שימו לב: אם אחד התהליכים נכשל אזי האחר יקבל התראת סיגנל על כישלון ב-pipe כלומרSIGPIPE תירגול 3 - מערכות הפעלה

  36. FIFOs ב-Linux (1) • FIFO הוא למעשה pipe בעל "שם" גלובלי שדרכו יכולים כל התהליכים במכונה לגשת אליו – pipe "ציבורי" • נקרא גם named pipe • השימוש העיקרי תקשורת מבלי שיהיה ביניהם קשרי משפחה • למשל, כאשר תהליכי לקוח צריכים לתקשר עם תהליך שרת • אכן יש להסכים מראש על שם ה"מקשר" • FIFO נוצר באמצעות קריאת המערכת mkfifo() • שם ה-FIFO הוא כשם קובץ במערכת הקבצים,למרות שאיננו קובץ כלל • למשל "/home/yossi/myfifo" • ה-FIFO מופיע במערכת הקבצים בשם שנבחר תירגול 3 - מערכות הפעלה

  37. FIFOs ב-Linux (2) • פתיחת FIFO לשימוש נעשית ע"י open() • ניתן לבצע הן קריאה והן כתיבה ל-FIFO דרך אותו descriptor (ערוץ תקשורת דו-כיווני) • תהליך שפותח את ה-FIFO לקריאה בלבד נחסם עד שתהליך נוסף יפתח את ה-FIFO לכתיבה, וההפך • פתיחת ה-FIFO לכתיבה וקריאה (O_RDWR) איננה חוסמת. עם זאת, יש לוודא שלכל קריאה יש כותב (אחרת תהליך יחיד ייחסם - קיפאון) • כאובייקטים ציבוריים רבים, FIFO אינו מפונה אוטומטית לאחר שהמשתמש האחרון בו סוגר את הקובץ, ולכן יש לפנותו בצורה מפורשת באמצעות פקודות או קריאות מערכת למחיקת קבצים (למשל, פקודת rm) תירגול 3 - מערכות הפעלה

  38. FIFOs ב-Linux (3) • קריאת המערכת mkfifo() #include <sys/types.h> #include <sys/stat.h> int mkfifo(const char *pathname, mode_t mode); • פעולה: יוצרת FIFO המופיע במערכת הקבצים במסלול pathname והרשאות הגישה שלו הן mode • פרמטרים: • pathname – שם ה-FIFO וגם המסלול לקובץ במערכת הקבצים • mode – הרשאות הגישה ל-FIFO שנוצר. בדומה לכפי שהוסבר בשקף ההרחבה על open. ניתן להכניס ערך 0600. • ערך מוחזר: 0 בהצלחה, (-1) בכישלון תירגול 3 - מערכות הפעלה

  39. דוגמת ריצה (1) (Filename : half_duplex.h) #define HALF_DUPLEX "/tmp/halfduplex“ #define MAX_BUF_SIZE> 255 (hd_server.c) #include <stdio.h> #include <errno.h> #include <ctype.h> #include <unistd.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <halfduplex.h> /* For name of the named-pipe */ int main(int argc, char *argv[]) { int fd, ret_val, count, numread; char buf[MAX_BUF_SIZE]; /* Create the named - pipe */ ret_val = mkfifo(HALF_DUPLEX, 0600); // See Next slide תירגול 3 - מערכות הפעלה

  40. דוגמת ריצה (2) if ((ret_val == -1) && (errno != EEXIST)) { perror("Error creating the named pipe"); exit (1); } /* Open the named pipe for reading */ fd = open(HALF_DUPLEX, O_RDONLY); /* Read from the Named pipe */ numread = read(fd, buf, MAX_BUF_SIZE); buf[numread] = '0'; printf("Half Duplex Server : Read From the pipe : %sn", buf); /* Convert to the string to upper case */ count = 0; while (count < numread) { buf[count++] = toupper(buf[count]); } printf("Half Duplex Server : Converted String : %sn", buf); } תירגול 3 - מערכות הפעלה

  41. דוגמת ריצה (3) (hd_client.c) /* like prev prog #include <stdio.h> #include <errno.h> #include <ctype.h>#include <unistd.h> #include <sys/types.h>#include <sys/stat.h>#include <fcntl.h>*/ #include <halfduplex.h> /* For name of the named-pipe */ int main(int argc, char *argv[]) { int fd; /* Check if an argument was specified. */ if (argc != 2) { printf("Usage : %s <string to be sent to the server>n", argv[0]); exit (1); } /* Open the Named pipe for writing */ fd = open(HALF_DUPLEX, O_WRONLY); /* Write to the pipe */ write(fd, argv[1], strlen(argv[1])); } תירגול 3 - מערכות הפעלה

  42. שימוש בקוד הדוגמא • למעשה אנו רואים כאן "שרת" שפותח את ה-Named-Pipe ולקוח שיעשה בו שימוש, ראשית ניצור את ה-FIFO ע"י תוכנת ה"שרת" % hd_server & • בנק' זו ייחסם השרת בקריאה משום שאין כותב ל- Named-Pipe % hd_client hello • כעת יפתח ה"משתמש" – clientאת צד הכתיבה ויזין את המילה hello ל-FIFO ויסיים את פעולתו • השרת, יקרא וידפיס את השורות הבאות: Half Duplex Server : Read From the pipe : hello Half Duplex Server : Converted String : HELLO • מה שכחנו? • למחוק את הקובץ מ -"/tmp/halfduplex" • מה אם נרצה תקשורת דו-סטרית? • נצטרך לפתוח Named-pipe נוסף! תירגול 3 - מערכות הפעלה

More Related