در توسینسو تدریس کنید

و

با دانش خود درآمد کسب کنید

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

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 به تصویر کشیده شده است.

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


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

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

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

#تابع_getpid #فرآیندها_در_لینوکس #دستور_kill_در_command_prompt #تابع_getppid #تابع_fork #init_چیست #zombie_process_چیست #رابطه_parent_-child
عنوان
1 ایجاد و مدیریت فرآیندها در لینوکس - قسمت اول رایگان
2 ایجاد و مدیریت فرآیندها در لینوکس - قسمت دوم رایگان
3 ایجاد و مدیریت فرآیندها در لینوکس - قسمت سوم رایگان
زمان و قیمت کل 0″ 0
7 نظر
بیات

بادرود فراوان

مهندس فرض کن فرایند پدر اینچنین باشه

int main(){

cout<<"hello";
pid_t child=fork();

cout<<" bye";
return 0;
}

پس طبق گفته شما فرایند فرزند هرگز hello رو چاپ نمیکنه درسته؟

حالا داستان رو به گونه ای دیگر میگم فرض کن فرایند پدر این باشه

int main(){

int x=12;
pid_t child=fork();

cout<<"x="<<x;
return 0;
}

پس فرایند فرزند نباید مقدار 12 رو در X بتونه چاپ کنه درسته؟

بیات

بادرود فراوان

مهندس فرض کن فرایند پدر اینچنین باشه

int main(){

cout<<"hello";
pid_t child=fork();

cout<<" bye";
return 0;
}

پس طبق گفته شما فرایند فرزند هرگز hello رو چاپ نمیکنه درسته؟

حالا داستان رو به گونه ای دیگر میگم فرض کن فرایند پدر این باشه

int main(){

int x=12;
pid_t child=fork();

cout<<"x="<<x;
return 0;
}

پس فرایند فرزند نباید مقدار 12 رو در X بتونه چاپ کنه درسته؟

بیات

در کد نخست تابع فرزند خط hello رونمیبنه

اما درکد دوم میتونه ببینه مقدار x برابر 12 هست وچاپش میکنه

درمجموع هرکدام ازکدهای بالا سه خط خروجی دارن و بدون error اجرامیشن

رامین تقی زاده

با سلام.

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

یکی از چیزایی که فرآیند فرزند از پدرش به ارث میبره، مقادیر متفیرها هست. پس در کد دوم شاهد چاپ دو تا 12 (یکی از طرف پدر و یکی هم از طرف فرزند) هستیم.

بیات
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main ()
{
pid_t child_pid;
printf(" main program  %d \n ", (int) getpid());
child_pid = fork ();
printf ("child_pid %d\n +++++++++++++++\n", (int)child_pid);

if (child_pid != 0) {
printf ("this is the parent process, with id %d\n", (int) getpid());
printf ("the child’s process ID is %d\n", (int) child_pid);
}
else
printf ("this is the child process, with id %d\n", (int) getpid() );
return 0;
}


نظرت درباره یا این کدچیه؟؟؟؟؟؟؟؟؟؟؟؟؟؟؟؟؟؟؟؟؟؟؟؟؟؟؟؟؟؟؟؟؟؟؟؟؟؟؟

دراینجا child_pid حتما مخالف صفراست

پس چرا در دستورif وقتی که شرط درست درمیاید ان رابه حساب فرایند والدمیگذارد؟؟؟؟؟؟؟؟

چرا child_pid در فرزند برابر صفر میشود؟؟؟

رامین تقی زاده

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

بیات

ببین این سوالمه pid فرزند درون والدش صفره؟؟؟؟پس چرا printf دوم مقداری رو چاپ میکنه؟؟؟؟؟؟؟؟؟؟؟

نظر شما
برای ارسال نظر باید وارد شوید.
از سرتاسر توسینسو
تنظیمات حریم خصوصی
تائید صرفنظر
×

تو می تونی بهترین نتیجه رو تضمینی با بهترین های ایران بدست بیاری ، پس مقایسه کن و بعد خرید کن : فقط توی جشنواره پاییزه می تونی امروز ارزونتر از فردا خرید کنی ....