420 likes | 644 Views
מערכות הפעלה. תרגול 3 קלט פלט בסיסי תקשורת בין תהליכים. אדמיניסטרציה. אתר הקורס: http://www.cs.biu.ac.il/~shpiget/OS / הגשות: תרגיל ראשון - תאריך הגשה 14.03.2005 שאלות – ראו FAQ באתר הקורס האם הערך החוזר בתרגיל1 הוא בהכרח Short ? לאן יש להגיש?
E N D
מערכות הפעלה תרגול 3קלט פלט בסיסי תקשורת בין תהליכים
אדמיניסטרציה • אתר הקורס: • 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 - מערכות הפעלה
נושאים שיכוסו • קלט/פלט של תהליכים • תקשורת וסנכרון בין תהליכים • מבוא • pipes • הכוונת קלט ופלט • FIFOs – Named Pipes תירגול 3 - מערכות הפעלה
קלט/פלט של תהליכים ב-Linux (1) • ק/פ של תהליך מול התקן נעשה תוך שימוש במערך של descriptors • לכל תהליך ישנה טבלה, ששמה Process Descriptor Table - PDT אשר מנוהל ע"י ה-OS. • כל כניסה בטבלה מצביעה על אובייקט ניהול file) object) מטעם התהליך עבור ההתקן המקושר ל-descriptor • קוד תהליך ניגש להתקן דרך ה-descriptor.אובייקט הניהול מתופעל ע"י גרעין מערכת ההפעלה בלבד • "מחוון הקובץ" הוא חלק מאובייקט הניהול ומצביע למקום ספציפי בקובץ הפתוח, כלומר הנתונים הבאים לקריאה / כתיבה מההתקן • התקן המופעל באמצעות ה-descriptor יכול להיות קובץ, התקן חומרה, ערוץ תקשורת או כל דבר שניתן לכתוב ו/או לקרוא נתונים אליו וממנו • ממשק ההתקשרות עם ההתקן דרך ה-descriptor הינו אחיד, ללא תלות בסוג ההתקן תירגול 3 - מערכות הפעלה
קלט/פלט של תהליכים ב-Linux (2) • ערכי ה-descriptors הבאים מקושרים להתקנים הבאים כברירת מחדל: • 0 (stdin) - מקושר לקלט הסטנדרטי, בדרך-כלל המקלדת • פעולות הקלט המוכרות, כדוגמת scanf() ודומותיה, הן למעשה פעולות של קריאה מהתקן הקלט הסטנדרטי דרך stdin • 1 (stdout) - מקושר לפלט הסטנדרטי, בדרך-כלל תצוגת טקסט במסוף • פעולות הפלט המוכרות, כדוגמת printf() ודומותיה, הן למעשה פעולות של כתיבה להתקן הפלט הסטנדרטי דרך stdout • 2 (stderr) - מקושר לפלט השגיאות הסטנדרטי, בדרך-כלל גם הוא תצוגת טקסט במסוף • ניתן לשנות את קישור ה-descriptors להתקנים באופן דינמי, כפי שנראה בהמשך תירגול 3 - מערכות הפעלה
קלט/פלט של תהליכים ב-Linux (3) • תחילת עבודה עם התקן היא ע"י קישור ההתקן ל-descriptor בפעולת open() • ה-descriptor "פתוח", כלומר מקושר להתקן • ה-descriptors 0,1,2 פתוחים מתחילת ביצוע התהליך • בסיום העבודה עם ההתקן מנותק ה-descriptor מההתקן בפעולת close() • ה-descriptor "סגור", כלומר לא מקושר להתקן • כל ה-descriptors הפתוחים של תהליך נסגרים באופן אוטומטי ע"י מערכת ההפעלה בסיום התהליך • עם זאת, מומלץ לבצע סגירה מסודרת בקוד בכל סיום שימוש ב-descriptor • אין צורך לסגור descriptors פתוחים המקושרים לערוצי הקלט / פלט / שגיאות הסטנדרטיים (אלא אם רוצים לחבר אותם להתקנים אחרים) תירגול 3 - מערכות הפעלה
קריאות מערכת בסיסיות (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 - מערכות הפעלה
קריאות מערכת בסיסיות (2) • flags – תכונות לאפיון פתיחת הקובץ. חייב להכיל אחת מהאפשרויות הבאות: • O_RDONLY – הקובץ נפתח לקריאה בלבד • O_WRONLY – הקובץ נפתח לכתיבה בלבד • O_RDWR – הקובץ נפתח לקריאה ולכתיבה כמו כן, ניתן לצרף תכונות אופציונליות שימושיות נוספות באמצעות OR (|) עם הדגל המתאים, למשל: • O_CREAT – צור את הקובץ אם אינו קיים • O_APPEND – שרשר מידע בסוף קובץ קיים • mode – פרמטר אופציונלי המגדיר את הרשאות הקובץ, במקרה שפתיחת הקובץ גורמת ליצירתקובץ חדש. הסבר על הערכים בשקף הבא. • ערך מוחזר: • במקרה של הצלחה – ה-descriptor המקושר להתקן שנפתח. ערך זה הוא ה-descriptor הנמוך ביותר שהיה סגור לפני פתיחת ההתקן • במקרה של כישלון – (-1) תירגול 3 - מערכות הפעלה
mode_t • קביעת הרשאות הקבצים ע"י קוד תיעשה לפי המפתח הבא: • כך שלמעשה הרשאות קריאה בלבד יהיו: 0400 • הרשאות כתיבה בלבד: 0200 • כך שעבור R+W עבור עצמי ניתן את ההרשאה 0600 תירגול 3 - מערכות הפעלה
קריאות מערכת בסיסיות (3) • סגירת גישה להתקן – close() #include <unistd.h> int close(int fd); • פעולה: סגירת ה-descriptorfd. לאחר הסגירה לא ניתן לגשת להתקן דרך ה-fd שצויין • פרמטרים: • fd – ה-descriptor המיועד לסגירה • ערך מוחזר: הצלחה – 0. כישלון – (-1) תירגול 3 - מערכות הפעלה
קריאות מערכת בסיסיות (4) • קריאת נתונים מהתקן – read() #include <unistd.h> ssize_t read(int fd, void *buf, size_t count); • פעולה: מנסה לקרוא עד count בתים מתוך ההתקן המקושר ל-fd לתוך החוצץ buf • ייתכן שייקראו פחות מ-count בתים • למשל, אם נותרו פחות מ-count בתים בקובץ ממנו קוראים • ייתכן אף שלא ייקראו בתים כלל • למשל, אם בקריאה מקובץ, מחוון הקובץ הגיע לסוף הקובץ (EOF) • מחוון הקובץ מקודם בכמות הבתים שנקראו בפועל מההתקן, כך שבפעולת הגישה הבאה לקובץ (קריאה, כתיבה וכד') ניגש לנתונים שאחרי הנתונים שנקראו בפעולה הנוכחית • פעולת הקריאה תחסום את התהליך הקורא (תוריד אותו להמתנה) עד שיהיו נתונים זמינים לקריאה בהתקן • למשל: בקובץ, תהליך ימתין עד שהנתונים ייקראו מהדיסק תירגול 3 - מערכות הפעלה
קריאות מערכת בסיסיות (5) • פרמטרים: • fd – ה-descriptor המקושר להתקן ממנו מבקשים לקרוא • buf – מצביע לחוצץ בו יאוחסנו הנתונים שייקראו מההתקן • count – מספר הבתים המבוקש • ערך מוחזר: • בהצלחה – מספר הבתים שנקרא מההתקן לתוך buf. אם read() נקראה עם count = 0, יוחזר 0 ללא קריאה • אם משתמשים בפעולה זו על pipe עשויה להיווצר חסימה • בכישלון – (-1) תירגול 3 - מערכות הפעלה
קריאות מערכת בסיסיות (6) • כתיבת נתונים להתקן – write() #include <unistd.h> ssize_t write(int fd, const void *buf, size_t count); • פעולה: מנסה לכתוב עד count בתים מתוך buf להתקן המקושר ל-fd • בתלות בהתקן, ייתכן שייכתבו בין 0 ל-count בתים • למשל, אם יש מספיק מקום פנוי • בדומה ל-read(), מחוון הקובץ מקודם בכמות הבתים שנכתבו בפועל, והגישה הבאה לקובץ תהיה לנתונים שאחרי אלו שנכתבו • גם פעולת write() יכולה לחסום את התהליך בפעולה על התקנים מסוימים – למשל, עד שייתפנה מקום לכתיבה תירגול 3 - מערכות הפעלה
קריאות מערכת בסיסיות (7) • פרמטרים: • fd – ה-descriptor המקושר להתקן אליו מבקשים לכתוב • buf – מצביע לחוצץ בו מאוחסנים הנתונים שייכתבו להתקן • count – מספר הבתים המבוקש לכתיבה • ערך מוחזר: • בהצלחה – מספר הבתים שנכתב להתקן מתוך buf. אם write() נקראה עם count = 0, יוחזר 0 ללא כתיבה • אם משתמשים בפעולה זו על pipe עשויה להיווצר חסימה • בכישלון – (-1) תירגול 3 - מערכות הפעלה
קריאות מערכת בסיסיות (8) • הערות ל-read() : • ייתכן שייקראו פחות מ-count בתים (למשל, אם נותרו פחות מ-count בתים בקובץ ממנו קוראים), וייתכן אף שלא ייקראו בתים כלל (למשל, אם בקריאה מקובץ, מחוון הקובץ הגיע לסוף הקובץ (EOF)) • מחוון הקובץ מקודם בכמות הבתים שנקראו בפועל מההתקן, כך שבפעולת הגישה הבאה לקובץ (קריאה, כתיבה וכד') ניגש לנתונים שאחרי הנתונים שנקראו בפעולה הנוכחית • פעולת הקריאה תחסום את התהליך הקורא (תוריד אותו להמתנה) עד שיהיו נתונים זמינים לקריאה בהתקן • הערות ל-write() : • כתלות בהתקן, ייתכן שייכתבו בין 0 ל-count בתים (למשל, אם אין מספיק מקום פנוי) • בדומה ל-read(), מחוון הקובץ מקודם בכמות הבתים שנכתבו בפועל, והגישה הבאה לקובץ תהיה לנתונים שאחרי אלו שנכתבו • גם פעולת write() יכולה לחסום את התהליך בפעולה על התקנים מסוימים – למשל, עד שיתפנה מקום לכתיבה תירגול 3 - מערכות הפעלה
קלט ופלט – סגנון עבודה (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 - מערכות הפעלה
קלט ופלט – סגנון עבודה (2) • ניתן לעבוד עם קבצים והתקנים גם דרך ה-API של ספריית libc, המוכר משיעורי מבוא לתכנות: • fopen(), fclose(), fread(), fwrite(), [f]printf(), [f]scanf(), וכו' • עם זאת, כדאי מאוד להימנע מלערבב קריאות מערכת יחד עם קריאות לפונקציות libc בעבודה על אותו התקן • הסיבה: libc מנהלת מנגנוני בקרה משלה הכוללים חוצצים וכו' מעל לקריאות המערכת הבסיסיות, ו"עקיפת" מנגנונים אלו יכולה לגרום לשיבוש הנתונים תירגול 3 - מערכות הפעלה
שיתוף ק/פ בין חוטים ותהליכים (1) • חוטים: טבלת ה-descriptors גלובלית לתהליך, כלומר משותפת לכל החוטים שבו • מתארי התהליכים של כל החוטים מצביעים על אותו PDT • תהליכים: פעולת fork() יוצרת עותק נוסף של טבלת ה-descriptors אצל תהליך הבן • תהליך הבן יכול לגשת לאותם התקנים אליהם ניגש האב • ה-descriptors ששוכפלו הינם שותפים, כלומר מצביעים לאותו אובייקט ניהול, ובפרט, חולקים את אותו מחוון קובץ • לדוגמה: אם האב קורא 10 בתים מה-descriptor ואחריו הבן קורא 3 בתים, אז הבן יקרא את 3 הבתים שאחרי ה-10 של האב. הכתיבה מושפעת באופן דומה תירגול 3 - מערכות הפעלה
שיתוף ק/פ בין חוטים ותהליכים (2) • תהליכים (וחוטים) המשתמשים ב-descriptors שותפים או משותפים צריכים לתאם את פעולות הגישה להתקן על-מנת שלא לשבש זה את פעולת זה • הבעיה נפוצה במיוחד ב"שושלות משפחתיות" של תהליכים: אבות ובנים, אחים, נכדים וכו' • פעולת execve() ודומותיה אינן משנות את טבלת ה-descriptors של התהליך, למרות שהתהליך מאותחל מחדש • קבצים והתקנים פתוחים אינם נסגרים • שימושי ביותר להכוונת קלט ופלט של תוכניות אחרות תירגול 3 - מערכות הפעלה
תקשורת בין תהליכים ב-Linux (1) • בדומה למערכות הפעלה מודרניות אחרות, Linux מציעה מגוון מנגנונים לתקשורת וסנכרון בין תהליכים • IPC = Inter-Process Communication • לכאורה, ניתן היה להסתפק בתקשורת בין תהליכים דרך קבצים משותפים, אך צורת תקשורת זו יקרה מבחינת משאבים ואיטית • שימוש במשאבים של מערכת הקבצים למטרות שאינן עבודה עם קבצים • המנגנונים ש-Linux מציעה למטרת תקשורת בין תהליכים כוללים: • pipes ו-FIFOs (named pipes): ערוצי תקשורת בין תהליכים באותה מכונה בסגנון יצרן-צרכן: תהליכים מסוימים מייצרים נתונים לתוך ה-pipe ותהליכים אחרים צורכים את הנתונים • signals: איתותים - הודעות אסינכרוניות הנשלחות בין תהליכים באותה מכונה (וגם ממערכת ההפעלה לתהליכים) על-מנת להודיע לתהליך המקבל על אירוע מסוים תירגול 3 - מערכות הפעלה
תקשורת בין תהליכים ב-Linux (2) • System V IPC: שם כולל לקבוצה של מנגנוני תקשורת שהופיעו לראשונה בגרסאות UNIX של חברת AT&T ואומצו במהרה על-ידי מרבית סוגי ה-UNIX הקיימים כולל Linux. במסגרת קבוצה זו נכללים: • סמפורים • תורי הודעות (message queues) – מנגנון המאפשר להגדיר "תיבות דואר" וירטואליות הנגישות לכל התהליכים באותה מכונה. תהליכים יכולים לתקשר באמצעות הכנסה והוצאה של הודעות מאותה תיבת דואר • זיכרון משותף – יצירת איזור זיכרון מיוחד המשותף למרחבי הזיכרון של מספר תהליכים. זו צורת התקשורת היעילה ביותר והמקובלת ביותר עבור יישומים הדורשים כמות גדולה של IPC תירגול 3 - מערכות הפעלה
תקשורת בין תהליכים ב-Linux (3) • sockets: מנגנון סטנדרטי המאפשר יצירת ערוץ תקשורת דו-כיווני (duplex) בין תהליכים היכולים להמצא גם במכונות שונות. מנגנון זה מסתיר את התפעול הפיזי של התקשורת בין המחשבים (רשת, כבלים וכו'). • על sockets ניתן ללמוד בהרחבה במסגרת הקורס "תקשורת באינטרנט" וכן דרך מגוון אתרים, כדוגמת: http://www.ecst.csuchico.edu/~beej/guide/net • במהלך תרגול זה אנו נסקור את מנגנוני ה-pipes וה-signals כדוגמאות למנגנוני IPC תירגול 3 - מערכות הפעלה
pipes ב-Linux (1) • pipes (צינורות) הם ערוצי תקשורת חד-כיווניים המאפשרים העברת נתונים לפי סדר FIFO (First-In-First-Out) • הנתונים נקראים בסדר בו הם נכתבים • pipes משמשים גם לסנכרון תהליכים, כפי שנראה בהמשך • המימוש של pipes ב-Linux הוא כאובייקטים של מערכת הקבצים, למרות שאינם צורכים שטח דיסק כלל ואינם מופיעים בהיררכיה של מערכת הקבצים • הגישה ל-pipe היא באמצעות שני descriptors: אחד לקריאה ואחד לכתיבה תירגול 3 - מערכות הפעלה
pipes ב-Linux (2) • היצירה של pipe היא באמצעות קריאת המערכת pipe(), וה-pipe הנוצר הינו פרטי לתהליך, כלומר אינו נגיש לתהליכים אחרים במערכת • לפיכך, הדרך היחידה לשתף pipe בין תהליכים שונים היא באמצעות קשרי משפחה • תהליך אב יוצר pipe ואחריו יוצר תהליך בן באמצעות fork() – לאב ולבן יש גישה ל-pipe באמצעות ה-descriptors שלו, המצויים בשניהם • לאחר סיום השימוש ב-pipe מצד כל התהליכים (סגירת כל ה-descriptors) מפונים משאבי ה-pipe באופן אוטומטי תירגול 3 - מערכות הפעלה
pipes ב-Linux (3) • למי מהתהליכים הבאים יש גישה ל-pipe שיוצר תהליך A? • לכל התהליכים פרט ל-B Process A fork() pipe() fork() Process B Process C fork() fork() Process D Process E תירגול 3 - מערכות הפעלה
קריאת המערכת pipe() • תחביר: #include <unistd.h> int pipe(int filedes[2]); • פעולה: יוצרת pipe חדש עם שני descriptors: אחד לקריאה מה-pipe ואחד לכתיבה אליו • פרמטרים: • filedes – מערך בן שני תאים. ב-filedes[0] יאוחסן ה-descriptorלקריאה מה-pipe שנוצר וב-filedes[1] יאוחסן ה-descriptorלכתיבה • ערך מוחזר: 0 בהצלחה ו- (-1) בכישלון תירגול 3 - מערכות הפעלה
קריאה וכתיבה ל-pipe (1) • פעולות קריאה וכתיבה מתבצעות באמצעות read() ו-write() על ה-descriptors של ה-pipe • ניתן להסתכל על pipe כמו על תור FIFO עם מצביע קריאה יחיד (להוצאת נתונים) ומצביע כתיבה יחיד (להכנסת נתונים) • כל קריאה (מכל תהליך שהוא) מה-pipe מקדמת את מצביע הקריאה. באופן דומה, כל כתיבה מקדמת את מצביע הכתיבה • בדרך-כלל, תהליך מבצע רק אחת מהפעולות (קריאה או כתיבה) ולכן נהוג לסגור את ה-descriptor השני שאינו בשימוש • ה-descriptors של הקריאה בכל התהליכים הם שותפים, ולכן יש לתאם בין הקוראים. כנ"ל לגבי ה-descriptors של הכתיבה תירגול 3 - מערכות הפעלה
קריאה וכתיבה ל-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 - מערכות הפעלה
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 - מערכות הפעלה
הכוונת קלט ופלט (1) • אחד השימושים הנפוצים ביותר ב-descriptors בכלל וב-pipes בפרט הינו הכוונת הקלט והפלט של תכניות (Input/Output Redirection) • ניתוב המידע המיועד להתקן פלט לתוך התקן אחר (למשל קובץ) או החלפת מקור המידע מהתקן קלט אחד באחר • תוכנות ה-shell ב-Linux (ובמערכות הפעלה רבות אחרות) תומכות בהכוונת קלט ופלט באופן מובנה בפקודות שלהן • לדוגמה: התכנית ls מדפיסה את רשימת הקבצים בספרייה הנוכחית לתוך stdout. ניתן להריץ את ls דרך פקודת ה-shell הבאה: $ ls • הפקודה הבאה תגרום ל-shell להכניס את הפלט של ls לקובץ myfile $ ls > myfile תירגול 3 - מערכות הפעלה
הכוונת קלט ופלט (2) • מה למעשה ביצע ה-shell בתגובה לפקודה הקודמת? (בערך ..) status = fork(); if (status == 0) { close(1); fd = open(“myfile”, O_WRONLY…); execve(“/bin/ls”, …); { • באופן דומה ניתן לכוון את הקלט של תכנית להגיע מקובץ • לדוגמה: התכנית more קוראת נתונים מ-stdin ומדפיסה אותם עם הפסקה בין עמוד לעמוד. הפקודה הבאה תגרום לה להדפיס את הקובץ myfile more < myfile תירגול 3 - מערכות הפעלה
הכוונת קלט ופלט (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 - מערכות הפעלה
הכוונת קלט ופלט (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 - מערכות הפעלה
דוגמא נוספת ל-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 - מערכות הפעלה
דוגמא המשך - 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 - מערכות הפעלה
FIFOs ב-Linux (1) • FIFO הוא למעשה pipe בעל "שם" גלובלי שדרכו יכולים כל התהליכים במכונה לגשת אליו – pipe "ציבורי" • נקרא גם named pipe • השימוש העיקרי תקשורת מבלי שיהיה ביניהם קשרי משפחה • למשל, כאשר תהליכי לקוח צריכים לתקשר עם תהליך שרת • אכן יש להסכים מראש על שם ה"מקשר" • FIFO נוצר באמצעות קריאת המערכת mkfifo() • שם ה-FIFO הוא כשם קובץ במערכת הקבצים,למרות שאיננו קובץ כלל • למשל "/home/yossi/myfifo" • ה-FIFO מופיע במערכת הקבצים בשם שנבחר תירגול 3 - מערכות הפעלה
FIFOs ב-Linux (2) • פתיחת FIFO לשימוש נעשית ע"י open() • ניתן לבצע הן קריאה והן כתיבה ל-FIFO דרך אותו descriptor (ערוץ תקשורת דו-כיווני) • תהליך שפותח את ה-FIFO לקריאה בלבד נחסם עד שתהליך נוסף יפתח את ה-FIFO לכתיבה, וההפך • פתיחת ה-FIFO לכתיבה וקריאה (O_RDWR) איננה חוסמת. עם זאת, יש לוודא שלכל קריאה יש כותב (אחרת תהליך יחיד ייחסם - קיפאון) • כאובייקטים ציבוריים רבים, FIFO אינו מפונה אוטומטית לאחר שהמשתמש האחרון בו סוגר את הקובץ, ולכן יש לפנותו בצורה מפורשת באמצעות פקודות או קריאות מערכת למחיקת קבצים (למשל, פקודת rm) תירגול 3 - מערכות הפעלה
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 - מערכות הפעלה
דוגמת ריצה (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 - מערכות הפעלה
דוגמת ריצה (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 - מערכות הפעלה
דוגמת ריצה (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 - מערכות הפעלה
שימוש בקוד הדוגמא • למעשה אנו רואים כאן "שרת" שפותח את ה-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 - מערכות הפעלה