تا %60 تخفیف خرید برای 4 نفر با صدور مدرک فقط تا
00 00 00

ایجاد و مدیریت فرآیندها در لینوکس - قسمت دوم

3. فراخوانی سیستمی fork و exec


از فراخوانی سیستمی fork برای ایجاد یک پردازه جدید استفاده می شود. در کرنل، fork عملا به وسیله ی یک فراخوانی سیستمی clone پیاده سازی می شود. این واسطه به طور موثر سطحی از انتزاع را در مورد اینکه کرنل لینوکس چگونه قادر به ایجاد پردازه ها است را به وجود می آورد. clone به شما این اجازه را می دهد که به طور صریح مشخص کنید که کدام قسمتها از پردازه درون پردازه جدید کپی شده و چه قسمت هایی بین آن ها به اشتراک گذاشته شود.دستور fork نحوی به صورت زیر دارد:

#include <sys/types.h>
#include <unistd.h>
pid_t fork();

پردازه فرزند که یک کپی از پردازه والد می باشد، دارای کد مشابه با والد است و اجرای خود را به همان طریقی که پردازه والد به اجرای خود ادامه می دهد شروع می کند (مثلا اگر والد در خط پنجم fork را فراخوانی کند، پردازه فرزند ایجاد شده اجرای خود را از خط پنجم به بعد از سر می گیرد). برای اینکه بین برگشت از تابع fork در پردازه والد و برگشت از تابع fork در پردازه ی فرزند تمایز قائل شویم، تابع PID پردازه فرزند را در حالت اول (در پردازه پدر) و 0 را در حالت دوم (در پردازه فرزند) برمی گرداند. وقتی اجرای تابع fork موفقیت آمیز نباشد، 1- برگردانده می شود. کد زیر مطالب بالا را با مثال نشان می دهد:

pid=fork();
/* source code executed by both processes */
switch (pid)
case -1:
/* Error! Unsuccesful fork! */
case 0 :
/* source code executed only by the child*/
break;
default:
/* source code executed only by the parent*/
} 
/*source code executed by both processes*/

به این نکته توجه کنید که مثلا اگر تعداد پردازه های ایجاد شده توسط کاربر و یا تعداد پردازه هایی که در سیستم می توانند به طور همزمان اجرا شوند به حد مشخصی برسد، ممکن است خطا اتفاق بیافتد. تمامی متغیرهای پردازه فرزند در ابتدا مقادیر خود را از پردازه والد به ارث می برند. همچنین تمامی توصیفگرهای فایل مشابه پردازه والد است. شایان ذکر است که سیگنال های معوق پاک شده و توسط فرزند به ارث برده نمی شوند. همچنین قفل های فایلی که پردازه والد به دست آورده است به وسیله پردازه فرزند به ارث برده نمی شود.حافظه فیزیکی که دو پردازه در آن قرار گرفته اند و منابعی که توسط سیستم عامل به آن ها تخصیص داده می شود متفاوت می باشد، و این یعنی اینکه دو پردازه مجزا هستند. در لحظه ای که دو پردازه از فراخوانی fork برمی گردند، فرزند و پدر به طور مستقل اجرا شده اند و حال برای به دست-گرفتن پردازنده و دیگر منابع موجود با همدیگر به رقابت می پردازند. نمی توان مشخص کرد که کدام یک از این دو پردازه پس از fork اول اجرا می شوند. تنها گزینه ممکن همان طور که در کد بالا نشان داده شد این است که با تست کردن مقدار برگشتی توسط fork، اجرا را بین دو پردازه (پردازه والد و پردازه فرزند) جدا کنیم.

ایجاد یک پردازه فرزند یکسان (از لحاظ محتوا) با پدرش زمانی منطقی است که بتوانیم بخش کد و داده ی پردازه ی تازه ایجاد شده را تغییر دهیم، درست مانند این که یک برنامه جدید را بار و اجرا کنیم. علت وجود فراخوانی سیستمی exec نیز همین است. وقتی از فراخوانی سیستمی exec استفاده می کنیم، بخش سیستمی پردازه تغییری نمی کند، به همین دلیل در PID تغییری ایجاد نمی شود. در این حالت پردازه فرزند چیزی متفاوت با آنچه پدرش اجرا می کند را می تواند اجرا کند. بعد از فراخوانی موفقیت آمیز exec به کد قدیمی برنمی گردیم. با ابن حال باید توجه شود که فایل هایی که در پردازه پدر باز می باشند، در پردازه فرزتد نیز باز هستند (چون محتوای جدول توصیفگر کپی شده است)؛ آن ها حتی بعد از فراخوانی exec نیز باز می مانند. اگر می خواهیم یک فایل باز را بعد از فراخوانی exec ببندیم باید به طور صریح اینکار را با استفاده از تابع fcnlt به صورت مقابل انجام دهیم. (fcnlt(fd, F__SETFT, 1. اگر فراخوانی exec با شکست مواجه شد، مقدار 1- برگردانده می شود. شکست در مواقعی رخ می دهد که مسیر فایل اجرایی را اشتباه داده باشیم یا فایل اجرایی درست اجرا نشود. یکی از راه های بکار گیری فراخوانی سیستمی exec در زیر ذکر شده است. در ادامه با یک مثال نحوه بکارگیری این دستور را نشان می دهیم.

#include <unistd.h>
int execl(const char * path, const char * arg0, ..., NULL);

4. فراخوانی سیستمی wait


این فراخونی سیستمی برای همزمان کردن اجرای پردازه های فرزند و پدر استفاده می شود: پردازه پدر تا زمان خاتمه پردازه فرزند صبر می کند. نحو استفاده از این دستور در زیر آمده است:

#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int* pstatus);

در صورت موفقیت، این تابع PID فرزند خاتمه یافته و در صورت بروز خطا 1- را برمی گرداند. آرگومان pstatus آدرس جایی است که کد خاتمه فرزند در آنجا کپی شده است. (فرندی که PID آن برگردانده شده است) پردازه ای که wait را فراخوانی می کند می تواند:

  • بلوکه شود. اگر تمامی فرزندان در حال اجرا باشند، این اتفاق می افتد.
  • حالت خاتمه فرزند را دریافت کند. اگر حداقل یکی از فرزندان قبل از فراخوانی wait به پایان رسیده باشند، این اتفاق می افتد.
  • یک خطا دریافت کند. اگر هیچ پردازه ی فرزندی موجود نباشد این اتفاق می افتد.

حالت خاتمه پردازه فرزند به صورت اکتال کد شده و در آدرسی که به وسیله pstatus مشخص شده ذخیره می شود. ما می توانیم به این اطلاعات به وسیله دستورات ماکروی زیر دسترسی پیدا کنیم:

  • (WIFEXITED(*pstatus: اگر پردازه فرزند به وسیله فراخوانی صریح یا ضمنی ( در پایان اجرا) exit و یا به وسیله فراخوانی دستور return در پایان تابع main خاتمه بیابد، مقدار true برگردانده می شود. در غیر این صورت false برگردانده می شود.
  • (WEXITSTATUS(*pstatus: کد خاتمه مشخص شده در پردازه ی فرزند را برمی گرداند.
  • (WIFSIGNALED(*pstatus: اگر پردازه فرزند در اثر دریافت یک سیگنال خاتمه بیابد true و در غیر این صورت false برگردانده می شود.
  • (WTERMSIG(*pstatus: کد سیگنالی که باعث خامته پردازه فرزند شده است را برمی گرداند. استفاده از این دستور زمانی عاقلانه است که ماکروی WIFSIGNALED مقدار true را برگردانده باشد.

در سه حالت یک پردازه خاتمه می یابد. 1- وقتی پردازه عمدا exit را فراخوانی کند 2- بعد از دریافت یک سیگنال خاتمه دهنده و یا دریافت سیگنالی که پردازه قادر به پردازش آن نیست. 3 -در اثر خرابی سیستم. کد حالتی که به وسیله متغیر pstatus برگردانده می شود، مشخص می کند که کدام یک از دو حالت اول اتفاق افتاده است.


5. فراخوانی سیستمی exit


این فراخوانی سیستمی باعث خاتمه پردازه فراخوانی کننده می شود. نحو این دستور به صورت زیر است:

void exit(int* status);

پارامتر فرستاده شده به تابع exit به عنوان کد خاتمه تفسیر شده و پردازه پدر می تواند برای تعیین روش خاتمه یکی از فرزندانش، از آن استفاده کند. طبق قرار داد کد 0 به معنی خاتمه نرمال و موفقیت آمیز پردازه است در حالی که مقدار غیر صفر یک خطا را سیگنال می دهد.برای پردازه هایی که عضوی از رابطه والد-فرزندی هستند، سه حالت مختلف که مرتبط با فراخوانی exit است، وجود دارد:

  • وقتی پدر قبل از فرزند خاتمه می یابد. در این حالت به همه فرزندان یک پدر جدید نسبت داده می شود. این پدر جدید پردازه init با شناسه 1 است. با این کار دیگر پروسه ی یتیمی در سیستم وجود نخواهد داشت.
  • وقتی فرزند قبل از پدر خود پایان می یابد. سیستم عامل بعضی از اطلاعات را راجع به پردازه خاتمه یافته ذخیره می کند. (PID ، دلیل خاتمه و غیره). پدر پردازه خاتمه یافته به این اطلاعات با استفاده از فراخوانی سیستمی wait دسترسی دارد. حالت zombie به پردازه ای گفته می شود که خاتمه یافته است و پدر آن نیز wait را فراخوانی نکرده است. به وسیله دستور ps می توان از پردازه های zombie اطلاع پیدا کرد. در این موقع در ستون حالت (‘S’) حرف Z چاپ می شود.
  • وقتی پردازه ای که از init ارث بری می کند، خاتمه بیابد. این پردازه ها وارد حالت zombie نمی شوند به این خاطر که پردازه ی init همیشه یکی از فراخوانی های wait یا waitpid را برای فرزندانش فراخوانی می کند. با این مکانیزم از سربار پیدا کردن سیستم با پردازه های zombie پرهیز می شود.

در شکل زیر خلاصه ای از استفاده از دستورات fork ، exit ، wait و exec به تصویر کشیده شده است.

ایجاد و مدیریت فرآیندها در لینوکس - قسمت دوم

نویسنده : رامین غلامی تقی زاده

منبع : انجمن تخصصی فناوری اطلاعات ایران

هرگونه نشر و کپی برداری بدون ذکر منبع و نام نویسنده دارای اشکال اخلاقی می باشد

نظر شما
برای ارسال نظر باید وارد شوید.
7 نظر
افرادی که این مطلب را خواندند مطالب زیر را هم خوانده اند