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 */