Fifos – Named pipes
לאמצעי ה-pipe יש יתרונות מסוימים, אך איך שהם בנויים יש
מגבלות. אמצעי ה- fifo או
בשמו האחר named pipes הוא
אמצעי לממש את אמצעי ה-pipe תוך
עקיפת מגבלותיו.
יתרונותיו של ה-pipe הוא שהוא ממש העברת
זרימה של מידע אם צריך גם גולמי (לא עונה
לפורמט מסוים), מסונכרן ויעיל (תקורת ההמתנה מינימלית) גמישה (ננעלת על file descriptor ויכולה לנצל תוכניות שלא
נכתבו במיוחד למענה). המנגנון גם גמיש
במובן הזה שהוא מאפשר כתיבה של שטחים בגודל שונה ל-pipe, למרות של-pipe יש נפח מוגבל שצריך להביא אותו
בחשבון לפעמים.
המגבלות של pipe הוא שהוא יכול לשמש אך ורק בין
תהליכים בעלי אב קדמון משותף, כמו תהליך אב לבן או לנכד, או בין שני תהליכים אחים.
זה למשל מגביל את היכולת של האמצעי הזה לממש תהליך שרת. מנגנוני שרת לקוח אמורים
לתמוך במציעות שבו יש תהליך קבוע שמקבל משימות עבור תהליכי לקוח שבאים והולכים. מעבר לכך pipe הוא אמצעי די זמני, כל אימת שצריך
אותם יש ליצור אותם מחדש משום שרק תהליכים שנוצרו מהתהליך שיצר את ה-pipe.
בכדי לעקוף את הבעיות הללו נוצר מנגנון ה-fifo,
שהוא מעין שילוב של pipe
ו-קובץ. כאשר נוצר fifo נוצרת
כניסה בעץ הספריות של המערכת ותהליכים מתבייטים אליו
לפי השם הזה. בצורה כזו ניתן להשתמש
באמצעי הזה למימוש מערכת שרת לקוח מהמודל שלעיל, וזמן קיום ה-fifo
אינו מוגבל.
היסטורית,
אמצעי ה-fifo לא
היה קיים בכל גרסה של Unix,
והממשק אליו לא לגמרי אחידה. היום כמעט כל גרסה של Unix תומכת בזה. בכל גרסה שתומכת ב-fifo
ניתן ליצור fifo
בעזרת הקריאת המערכת
mknod, פקודה
שמשמת גם פקודות יצירת קבצים כמן open
ו-fopen. היום ברוב הגרסאות של Unix ישנה גם קריאת מערכת מפורשת
לעניין: mkfifo.
ניתן ליצור fifo גם משורת הפקודה:
למשל,
/tmp > mkfifo channel
/tmp > mknod channel2 p
/tmp > ls -l channel*
prw------- 1 ronn ypstaff 0 Oct
12 10:26 channel
prw------- 1 ronn ypstaff 0 Oct
12 10:26 channel2
/tmp >
כאשר p מציין שמדובר ב-pipe, הגם שלא מדובר ב-pipe הרגיל.
ניתן להשתמש
בזה משורת הפקודה. למשל:
בחלון אחד
/tmp > echo "Hello World!" > channel
התוכנית תעצר עד שבחלון אחר נעשה משהו כמו
cat < channel
Hello World!
/tmp >
זה גם יעבוד
בסר הפוך: אם פקודת ה-cat
תקדים את פקודת ה-echo היא
זו שתיעצר עד שפקודת ה-echo
תתבצע.
כמו ב-pipes, ניתן לנתב את stdin, stdout, stderr ל-fifo.
תכנות
עם fifos
יצירת fifo
אפשר ב-mknod או
ב-mkfifo.
כאן הדבר יעשה ע"י mkfifo:
int mkfifo(const char *path, mode_t mode);
כאשר path הוא המיקום והשם של ה-fifo בעץ
הספריות של המערכת.
ברגע ש-fifo
קיים, יש צורך לעשות לו open.
בכדי שבפועל יהיה אפשר לכתוב ל-fifo יש צורך לפתוח אותו לכתיבה ולכתוב
לתוכו יש צורך קודם לפתוח אותו לקריאה.
ברירת המחדל של open הוא
לעצור את התהליך הקורא לו לפתיחה עד הה-fifo
יפתח לסוג הכתיבה. אפשר לבצע קריאה ל-open
מבלי להיעצר ע"י שימוש בדגל O_NONBLOCK. במקרה כזה התהליך יכול להמשיך עד שיבוצע read ל-fifo אלא אם כן בינתיים
תהליך אחר הספיק לפתוח את ה-fifo לקריאה וכתב לו נתונים.
אם ב-open מתבצע פתיחה לכתיבה ל-fifo שכבר פתחו אותו לקריאה יתבצע חזרה
מיד בלי קשר לשאלה אם יש שימוש ב- O_NONBLOCK ולהיפך, אם הפתיחה לכתיבה
הקדימה את הפתיחה לקריאה. במידה ותהליך
עושה open לקריאה ל-fifo שלא
פתוח לקריאה, הוא יחזור מיד כקריאה שגויה עם ערך 1- וה-errno
יהיה בעל ערך ENXIO.
שרתים בדרך כלל פותחים את ה-fifo הן
לקריאה והן לכתיבה, בכדי להבטיח של-fifo תמיד יהיה לפחות תהליך אחד (השרת
עצמו) הפותח אותו הן לקריאה והן לכתיבה, כי אחרת זה יותר כשלון לקריאות ה-read שינסו לקרוא מה שבעצם יהיה pipe שאין לו צד קורא. במקרה כזה
התוכנית תצטרך בעצם לעשות לולאה של read עד
שתהליך אחר יפתח את ה-fifo לקריאה.
שחרור fifo
נעשה כמו שחרור קובץ, למשל ע"י קריאת המערכת remove.
int
remove(const char *path);
שתי התוכניות
הבאות מתקשרות דרך fifo.
תהליך השרת
יוצר את ה-fifo,
פותח אותו לקריאה וממתין להודעה, מדפיס אותו, מוחק את ה-fifo
וחוזר.
תהליך הלקוח fifosend
מניח שה-fifo כבר
קיים ושלח טקסט "Hello World!" ומסיים.
פלט ריצה של
השרת:
/tmp > cc fiforecv.c -o fiforecv
/tmp > ./fiforecv
received: Hello World!
/tmp >
בחלון אחר,
פלט ריצה של הלקוח
/tmp > cc fifosend.c -o fifosend
/tmp > ./fifosend
/tmp
תוכנית השרת:
/* fiforecv.c
*/
#include
<fcntl.h>
#include
<stdio.h>
#include
<errno.h>
#include
<stdlib.h>
#include
<string.h>
#define
MAX_MSGSIZE 80
char *fifo = "/tmp/newfifo";
void fatal(char str[])
{
fprintf(stderr, "%s\n", str);
exit(0);
}
/* fatal */
int main()
{
int
fd, i, nwrite;
char msgbuf[MAX_MSGSIZE];
/* Create fifo, unless it already exits */
if(mkfifo(fifo, 0600) == -1)
if (errno != EEXIST)
fatal("mkfifo failed");
if ( (fd = open(fifo, O_RDWR)) < 0)
fatal("fifo open failed");
if ( read(fd, msgbuf,MAX_MSGSIZE ) < 0)
fatal("fifo write failed");
printf("received: %s\n",
msgbuf);
if (remove(fifo) < 0)
perror("remove");
return 0;
}
/* main */
קוד הלקוח:
/* fifosend.c
*/
#include
<fcntl.h>
#include
<stdio.h>
#include
<errno.h>
#include
<stdlib.h>
#include
<string.h>
char *fifo = "/tmp/newfifo";
void fatal(char str[])
{
fprintf(stderr, "%s\n", str);
exit(0);
}
/* fatal */
int main()
{
int
fd, i, nwrite;
char msgbuf[] = "Hello World!\n";
if ( (fd = open(fifo, O_WRONLY |
O_NONBLOCK)) < 0)
fatal("fifo open failed");
if ((write(fd, msgbuf, strlen(msgbuf)+1)) < 0)
fatal("fifo write failed");
return 0;
}
/* main */