برنامه نویسی Pipe در گنو/لینوکس (قسمت دوم)
مسیر دهی جریان های استاندارد ورودی ، خروجی ، خطا
معمولاً شما یک پروسه ی فرزند ایجاد میکنید و میخواهید که یک سر pipe را به عنوان ورودی یا خروجی استاندارد آن قرار دهید. با فراخوانی تابع dup2 میتوانید از یک شاخص فایــل ، شـــاخصی جــدید معادل با شاخص اصلی ایجاد کنید. مثلاً برای این که از شاخص فایلی به نام fd ، به عنـــوان مســیر ورودی استاندارد پروسه ای استفاده کنید ، کد زیر را به کار ببرید:
dup2 (fd, STDIN_FILENO);
سمبل ثابت STDIN_FILENO نمایانگر شاخص فایل برای ورودی استاندارد با مقدار صفر میباشــــد. فـراخوانی خط بـالا، ورودی استاندارد را میبندد ،سپس به عنوان کپی جدید fd ،آن را بازگشایی میکند طوری کـــه fd و ورودی استاندارد به جای یکدیگر قابل به کار گیری باشند. شاخص های متناظر یعنی شاخص اولیه و شاخصی که با دستور dup2 ساختیم ، در مکان فایل و سایر پرچم های وضعیتی آن مشترک هستند؛ از این رو کاراکترهایی که از fd خوانده میشود ، از ورودی استاندارد بازخوانی نخواهد شد.
نمونه برنامه ۲ با استفاده از dup2 ، خروجی استاندارد pipe را به دستور sort میفرستد. دستور sort خطوطی از متن را از ورودی استاندارد میخواند ، آنها را بــــه ترتیب الفبا مرتب میکنـــد و نتیــجه را به خروجی استاندارد، چاپ میکند. برنامه پس از ایجاد pipe ، یک پروسه فرزند ایجاد میکند. پروسه پدر، تعــــدادی رشتــه متـنی درون پایپ میریزد. پروسه فرزند با استفاده از dup2 شاخص فایل خواندنی pipe را به ورودی استاندارد خود ضمیمه و سپس برنامه sort را اجرا میکند.
نمونه برنامه ۲: مسیر دهی خروجی استاندارد pipe با استفاده از dup2
#include <stdio.h> #include <sys/types.h> #include <sys/wait.h> #include <unistd.h> 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) { /* This is the child process. Close our copy of the write end of the file descriptor. */ close (fds[1]); /* Connect the read end of the pipe to standard input. */ dup2 (fds[0], STDIN_FILENO); /* Replace the child process with the “sort” program. */ execlp (“sort”, “sort”, 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”); fprintf (stream, “This is a test.\n”); fprintf (stream, “Hello, world.\n”); fprintf (stream, “Linux & Pipes.\n”); fprintf (stream, “This program is great.\n”); fprintf (stream, “One fish, two fish.\n”); fflush (stream); close (fds[1]); /* Wait for the child process to finish. */ waitpid (pid, NULL, 0); } return 0; }
popen و pclose دو تابع سودمند
یکی از کاربرد های معمول pipe ، فرستادن دیتا بـــه یک برنامه ی در حال اجرا درون یک زیر پروسه و یا گرفتن دیتا از آن میباشد. توابع popen و pclose پیاده سازی این الگــــو را با حـــذف نیاز بـــه فـراخوانی توابع pipe ،dup2 ،exec، fork و fdopen ، آسان میکنند. نمونه برنامه ۳ را با نمونه ی قبلی ،از این نظر مقایسه کنید :
نمونه برنامه ۳ : مثالی از popen
#include <stdio.h> #include <unistd.h> int main () { FILE* stream = popen (“sort”, “w”); fprintf (stream, “This is a test.\n”); fprintf (stream, “Hello, world.\n”); fprintf (stream, “Linux & Pipes.\n”); fprintf (stream, “This program is great.\n”); fprintf (stream, “One fish, two fish.\n”); return pclose (stream); } فراخوانی popen با ایجاد یک پروسه فرزند که دستور sort را اجرا میکند ، جایگزینی برای دستورات pipe, fork ,dup2 و execlp میباشد. آرگومان دوم "w” نشان میدهد که این پروسه میخواهد به سوی پروسه ی فرزند بنویسد. مقدار برگشتی popen یکی از دو سر pipe میباشد. سر دیگر به ورودی استاندارد پروسه ی فرزند ، متصل میشود.
پس از اتمام نوشتن ، pclose جریان پروسه ی فرزند را میبندد ؛ سپس منتظر پایان اجرای آن میشود و مقدار وضعیتی اش را باز میگرداند. اولین آرگومان popen به عنــــوان یک دستور شـــل درون یــــک زیرپروسه و با اجرای bin/sh/ ، اجرا میشود. شل برای یافتن برنامه اجرا شونده ، متغیر محیطی PATH را به روش معمول ، جست و جو میکند.
اگر آرگومان دوم "r” باشد، تـــابع خروجی استاندارد پـروسه فرزند را باز میگرداند تا پروسه ی پدر بتواند خروجی فرزند را بخواند. اما اگر آرگومان دوم "w” باشد ، تابع ورودی استاندارد پـــروسه فرزند را باز میگرداند تا پروسه پدر بتواند داده ها را به سوی فرزند بفرستد.
اگر در این میان خطایی بروز کند ، تابع یک اشاره گر null برمیگرداند.
از فراخوانی pclose برای بستن جریانی که توسط popen باز گردانده شده ، استفاده کنید. pclose پس از بستن جریان مشخص ، منتظر میماند تا اجرای پروسه فرزند پایان یابد.
FIFO ،پایپ دارای نام
یک فایل first-in,first-out یا به اختصار (FIFO) پایپی است که در سیستم فایل ،دارای نام میباشد. هر پروسه ای میتواند FIFO را باز کند یا ببندد. نیازی به این نیست که پروسه های دوسر FIFO به هم مربوط باشند. از FIFO ها به named pipes نیز نام برده میشود.
برای ساختن FIFO از دستور mkfifo استفاده کنید. مسیر آن را هم در خط فرمان مشخص کنید. برای نمونه با دستور زیر در مسیر tmp/fifo/ یک FIFO ایجاد کنید:
$ mkfifo /tmp/fifo $ ls -l /tmp/fifo prw-r--r-- 1 nsaba nsaba 0 2005-07-14 08:51 /tmp/fifo
اولین کاراکتر در خروجی دستور ls حرف p است که نشان میدهد این فایل یک FIFO یا named pipe میباشد. با دستور زیر در یک ترمینال شروع به خواندن از FIFO میکنیم:
$ cat < /tmp/fifo در ترمینال دیگر با دستور زیر شروع نوشتن در FIFO میکنیم:
$ cat > /tmp/fifo اکنون شروع به تایپ کردن چند خط متن در ترمینال دوم میکنیم. هر بار پس از فشردن کلید Enter، متنی که نوشته ایم از طریق FIFO فرستاده و در ترمینال اول نمایش داده میشود.
برای بستن FIFO در ترمینال دوم کلید های Ctrl+D را فشار دهید. برای پاک کردن آن هم از دستور زیر استفاده کنید:
$ rm /tmp/fifo
FIFO بسازیم
برای ساختن FIFO ابتدا باید <sys/stat.h> و<sys/types.h> را ضمیمه برنامه کنید تا بتوانید از دستور mkfifo استفاده کنید. mkfifo دو پارامتر میپذیرد : اولی مسیر ایجاد FIFO است و دومیمشخص کننده مجوز های دسترسی به FIFO ازقبیل صاحب و گروه آن میباشد. اگر در ساخت FIFO خطایی بروز کند مثلاً فایلی با نام مورد نظر ما برای FIFO در حال حاضر موجود باشد ،دستور mkfifo عدد 1- برمیگرداند.
نمونه برنامه ۳ : ساختن یک FIFO در زبان C
#include <unistd.h> #include <stdlib.h> #include <stdio.h> #include <sys/types.h> #include <sys/stat.h> int main(){ // Create FIFO int res = mkfifo(“/tmp/fifo”, 0777); if (res == 0) printf(“FIFO created\n”); exit(EXIT_SUCCESS); }
FIFO و روش دسترسی به آن
دسترسی در مورد FIFO مشابه دسترسی به فایل است. برای برقراری ارتباط از طریق FIFO یک برنامه باید آن را برای خواندن باز کند و برنامه ی دیگر برای نوشتن. بدین منظور میتوان هم از توابع ورودی-خروجی سطح پایین مانند open ,write, read, close و هم از توابع ورودی-خروجی کتابخانه ای زبان C مانند fopen , fprintf , fscanf , fclose استفاده نمود.
مثلاً برای نوشتن محتوای دیتای یک بافر درون FIFO با استفاده از دستورات سطح پایین ، میتوان کد زیر را به کار برد:
int fd = open (fifo_path, O_WRONLY); write (fd, data, data_length); close (fd);
و برای خواندن یک رشته از FIFO با استفاده از توابع ورودی-خروجی کتابخانه ای زبان C کد زیر را :
FILE* fifo = fopen (fifo_path, “r”); fscanf (fifo, “%s”, buffer); fclose (fifo);
یک FIFO میتواند بـه طور هم زمان چندین خواننده و نویسنده داشته باشد. بایت هایی که از هر نویسنده میرسد به طور اتوماتیک تا رسیدن به حجم بیشینه PIPE_BUF که در لینوکس ۴ کیلو بایت است ، در FIFO نوشته میشود. قطعاتی که به طور همزمان از نویسنده های مختلف میرسد ممکن است بین هم قرار گیرد. در مورد خواننده ها قوانین مشابه بر قرار است.
نقاط تمایز با name pipe های ویندوز
pipe ها در سیستم های عامل ۳۲ بیتی Windows به pipe های لینوکس بسیار شبیه هستند. تفاوت عمده در مورد named pipes است که در Win32 بیشتر شبیه به سوکت ها عمل میکنند. named pipe های ویندوز میتواند پروسه های مختلف در حال اجرا بر روی ماشین های جدا از هم ، که توسط شبکه به هم وصل هستند را به هم مرتبط کند. در حالی که در لینوکس از سوکت ها برای این کار استفاده میشود. همچنین Win32 امکان خواندن ونوشتن چند تایی همزمان را بدون از دست رفتن ترتیب آنها فراهم میکند. pipe ها همچنین در Win32 برای ارتباطات دو طرفه میتواند به کار برده شود. برای مطالعه بیشتر و استفاده از نمونه برنامه های کاربردی تر، میتوانید به منابع زیر رجوع فرمایید:
Mark Mitchel, Jeffrey Oldham, and Alex Samuel : Advanced Linux Programming , New York : New Riders Publishing , 2001. ------------------------------------------------------------------------------------------------- Neil Matthew, Richard Stones : Beginning Linux Programming Third Edition, Indianapolis : Wiley Publishing Inc , 2004 ------------------------------------------------------------------------------------------------- Kurt Wall, Mark Watson, and Mark Whitis : Linux Programming Unleashed , United States of America : Sams Publishing , 1999 ------------------------------------------------------------------------------------------------------------- Rafeeq Ur Rehman, Christopher Paul : The Linux Development Platform, New Jersey : Prentice Hall PTR , 2003 ------------------------------------------------------------------------------------------------- Eric Steven Raymond : The Art of Unix Programming :Pearson Education, Inc , 2003