برنامه نویسی Pipe در گنو/لینوکس (قسمت اول)

pipe یک ابزار ارتباطی است کــه امکان ارتباطات یک طرفه را فراهم می‌سازد. داده هایی کــه در طـــرف نــوشتنی pipe نوشته می‌شوند از طرف دیگر آن که طرف خواندنی نامیده می‌شود قابل خواندن است. در واقـــع شمـا باید لولـه‌ای را در نظـــر بگیرید که ارتباط دیتا بین دو ناحیه از طریق آن صورت می‌پذیرد. ایــن لــولــه داده‌ها را بـــه همـان ترتیبی که دریافت می‌کند، به ناحیه ی دیگر منتقل می‌کند، از این رو از آن به عنوان یک وسیله ی سریال یا ترتیبی یاد می‌شود. استفاده از pipe برای ایجاد ارتباط بین دو نخ در یک پروسه یا ارتباط بین پروسه پدر و پروسه فرزند ،‌صورت می‌پذیرد.
در پوسته‌های فرمان، نشانه‌ی | یک pipe ایجاد می‌کند. مثلاً دستور زیر باعـــث ایـجاد دو پروسه ی فرزند می‌شود؛ یکی برای ls و دیگری برای less:
$ ls | less
فرمان بالا همچنین یــک pipe برای مرتبط کردن خـــروجـی استاندارد پروسه ی فرزند ls به ورودی استاندارد پروسه فرزند less ایجاد می‌کند؛ نام فایل های لیست شده توسط ls، بــه less فرستاده می‌شود، بــه تــرتیبی کــه انـگار مستقیماً به ترمینال فرستاده شده‌اند.
ظرفیت یک pipe برای داده‌ها محدود است. اگر پروسه‌ای کـــه دیـتــا را درون pipe می‌نویسد با نرخی سریع تر از پروسه ای کــه دیتا را از pipe می‌خواند عمل کند و در صورتی که pipe ظــرفیت نگهداری دیتای بیشتر نداشته باشد، پـروسه ی دیتا نویس تا زمانی که ظرفیت بیشتر آزاد شود، مسدود یا به اصطلاح بلوکه می‌شود.
در صورتی که پروسه ی دیتا خوان درون pipe دیتایی برای خواندن نیابد ، مسدود میشود تا این که بالاخره دیتایی فراهم شود ؛ در نتیجه pipe باعث هم روند شدن دو پروسه می‌شود . (Synchronization) .
ساختن pipe هابرای ساختن pipe از دستــور pipe استفاده کنید. یــک آرایه با اندازه ۲ از نوع عدد صحیح هــم فـراهم کنید. دستور pipe شاخص فایل خواننده را در محل صفر و شاخص فایل نویسنده را در محل شماره یک آرایه ذخیره می‌کند. بـرای مثال، کــد زیر را در نظر بگیرید:
int pipe_fds[2]; int read_fd; int write_fd; pipe (pipe_fds); read_fd = pipe_fds[0]; write_fd = pipe_fds[1];
دیتای نوشته شده به شاخص فایل read_fd ، از شاخص فایل write_fd قابل باز خوانی است.
بر قراری ارتباط بین پروسه پدر و فرزندفـــراخــوانی pipe در یک پروسه ، شاخص‌های فایلی ایجاد می‌کند کــه فقط مابین آن پروسه و فرزندانش معتبر هستند. شاخص‌های فایل یک پروسه قابل فرستادن بـــه پروسه‌های نامــربوط نیست. امـا وقتی که یک پروسه fork را فراخوانی می‌کند، ایـن شاخص ها برای فرزندان جدید پروسه کپی می‌شود. در نتــیجه pipe فقط می‌تواند پروسه های مربوط را به هم وصل کند.
در نمونه برنامه ۱، fork پروسه فرزند جـــدیدی ایجاد می‌کند؛ فرزند جدید، شاخص های فایل را از پدر به ارث می‌برد . پدر رشته ای از کاراکتر ها را توی pipe می‌نویسد و فرزند، آنها را بازخوانی می‌کند.
نمونه برنامه ما شاخص‌های فایل را بـــا بــکـــارگیــری fdopen، به جـریان‌های * FILE تبدیل می‌کند (واژه جریان ، معادل stream است).
از آنجا کـــه به جای شاخص‌های فایل از جریان ها استفاده می‌کنیم، امکان استفاده از توابع ورودی-خروجی سطح بالاتر کتابخانه استاندارد زبان C، مانند printf و fgets وجود دارد.
نمونه برنامه ۱: کاربرد pipe برای ارتباط با پروسه فرزند
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h> /* Write COUNT copies of MESSAGE to STREAM, pausing for a second
between each. */ void writer (const char* message, int count, FILE* stream)
{
for (; count > 0; --count) {
/* Write the message to the stream, and send it off immediately. */
fprintf (stream, “%s\n”, message);
fflush (stream); /* Snooze a while. */
sleep (1);
}
}
/* Read random strings from the stream as long as possible. */
void reader (FILE* stream)
{
char buffer[1024]; /* Read until we hit the end of the stream. fgets reads until
either a newline or the end-of-file. */ while (!feof (stream) && !ferror (stream) && fgets (buffer, sizeof (buffer), stream) != NULL){
fputs (buffer, stdout);
} int main ()
{ int fds[2];
pid_t pid; /* Create a pipe. File descriptors for the two ends of the pipe are
placed in fds. */ pipe (fds); /* Fork a child process. */ pid = fork ();
if (pid == (pid_t) 0) {
FILE* stream; /* This is the child process. Close our copy of the write end of
the file descriptor. */
close (fds[1]); /* Convert the read file descriptor to a FILE object, and read
from it. */ stream = fdopen (fds[0], “r”);
reader (stream);
close (fds[0]);
} else { /* This is the parent process. */
FILE* stream; /* Close our copy of the read end of the file descriptor. */ close (fds[0]); /* Convert the write file descriptor to a FILE object, and write
to it. */ stream = fdopen (fds[1], “w”);
writer (“Hello, world.”, 5, stream);
close (fds[1]);
}
return 0;
} در ابتدای تابع main، آرایه‌ای از اعداد صحیح به نام fds و با اندازه ۲، اعلان شده است. فــراخوانی تابع pipe پس از ایجاد pipe ، شاخص های فایل خواندنی و نوشتنی را در آن آرایه ذخیره می‌کند.
برنامه‌ما در ادامه یک پروسه فرزند ایجاد می‌کند. پس از بستن سر خواندنی pipe، پروسه ی پدر شروع به نوشتن رشته ها به درون pipe کرده و پس از بستن سر نوشتنی pipe، پروسه فرزند رشته ها را از pipe می‌خواند.
منظور از بستن سر pipe، تعیین مرز برای فضایی از حافظه است که به عنوان pipe باید مورد استفاده قرار گیرد.
توجه کنید که پروسه‌ی پــدر در تابع writer پس از نوشتن در pipe، تابع fflush را فـــراخــوانی و بدین وسیله pipe را وادار می‌کند تا رشته را بدون درنگ ارسال نماید.
هنگامی کــه فرمان ls | less را اجرا می‌کنید، دو fork انجـــام می‌شود؛ یــکــی بــرای پـــروسه ی فرزند ls و دیگری برای پروسه‌ی فرزند less. هر دوی این فرزندان شاخص های فایل pipe را به ارث می‌برند طوری که بتوانند از طریق pipe ارتباط برقرار کنند. برای برقراری ارتباط بین پروسه های نا مرتبط از FIFO باید استفاده کرد که در ادامه بدان پرداخته خواهد شد.