یک پردازه چیزی نیست جز نمونهای در حال اجرا شدن از یک برنامه. آن را برنامهی در حال کار نیز تعریف کردهاند. ایدهی پشت ساختار و روابط پردازهها همان مفهوم کلی سیستم لینوکسی است. در این نوشتار دربارهی چرخهی عمر پردازهها و انواع آنها بحث خواهیم نمود و نیمنگاهی نیز داریم به مسیری که پردازهها در چرخهی عمرشان طی میکنند.
۱. کد؛ برنامه؛ پردازهبیایید ابتدا تفاوت این سه مفهوم را درک کنیم.
کد: مثال زیر را در نظر بگیرید.
#include <stdio.h>
#include <unistd.h>
int main(void)
{
printf("\n Hello World\n");
sleep(10);
return 0;
}
این چند خط را درون فایلی با نام helloWorld.c ذخیره کرده و به آن گوییم «کد».
برنامه: اگر کد بالا را با استفاده از دستور زیر کامپایل کنیم، فایلی قابل اجرا خواهیم داشت با نام helloWorld.
$ gcc -Wall helloWorld.c -o helloWorld
این فایل اجرایی، یک «برنامه» است.
پردازه: حال برنامهی helloWorld را اجرا میکنیم:
$ ./helloWorld
Hello World
زمانی که فایل اجرایی (یا برنامه) اجرا شد، «پردازه»ای متناظر با آن ساخته شده و کدهای ماشین درون برنامه را به کار میاندازد. به همین دلیل است که پردازه را به عنوان نمونهای از برنامه میشناسند.
برای دیدن مشخصات پردازهی ساخته شده، دستور زیر را وارد کنید.
$ ps -aef | grep hello*
1000 6163 3017 0 18:15 pts/0 00:00:00 ./helloWorld
۲. پردازهی والد و پردازهی فرزندهر پردازه یک والد دارد و ممکن است فرزند هم داشته باشد. به خروجی دستور ps روی اوبونتوی من توجه کنید:
اعداد صحیح ستونهای دوم و سوم خروجی بالا، نمایانگر شناسهی پردازه و شناسهی پردازهی والد آن است. به آنهایی که با فونت درشت مشخص شدهاند توجه کنید. زمانیکه من دستور ps -aef را اجرا کردم، پردازهای با شناسهی 6191 ساخته شد. شناسهی پردازهی والد آن را ببینید که 3079 است. اگر بالاتر و ابتدای خروجی را بنگرید، خواهید دید که 3079 شناسهی پردازهی bash است. پس میفهمیم پردازهی bash، والد تمام دستوراتی است که داخلش اجرا میشوند.
به همین نحو، حتی پردازههایی که خارج از شل ساخته شدهاند نیز والد دارند. هیچ گاه خانهای در ستون PPID (parent process ID) دستور ps -aef خالی نمیماند. بنابراین تمام پردازهها، والد مخصوص به خود را دارند.
و اما پردازهی فرزند. هر گاه پردازهای یک پردازهی دیگر بسازد، قبلی والد و دومی فرزند نام میگیرند. به لحاظ فنی، پردازههای فرزند توسط تابع ()fork موجود در کد ساخته میشوند. معمولا زمانی که دستوری را درون شل اجرا میکنید، ()fork با دنبالهای از توابع ()exec همراه میگردد.
پیش از این گفتیم هر پردازهای یک والد دارد. سؤالی که پیش میآید این است که چه بر سر فرزندی میآید که والدش کشته شده؟ سؤال خوبی است. اما اجازه دهید فعلا آن را همینجا رها کنیم.
۳. پردازهی initهنگام بوت شدن یک سیستم لینوکسی، اولین چیزی که وارد حافظه میشود vmlinuz است که همان فایل اجرایی و فشرده شدهی کرنل لینوکس است. نتیجه، ساخته شدن اولین پردازه، یعنی init است. PID آن هست 1 و والد تمام پردازهها در یک سشن لینوکسی است. اگر ساختار درختی پردازهها را در نظر بگیرید، init بالاترین جایگاه را به خود اختصاص داده است. دستور pstree را اجرا کنید. خروجی چنین چیزی خواهد بود:
pstree را پیدا کرده و ساختار کامل والد و فرزندی آن را تا init دنبال کنید.
حال بر میگردیم سر و وقت سؤالی که بدون پاسخ رهایش کرده بودیم. دربارهی وقایعی که بر سر فرزند زندهای که والدش کشته شده نازل میشود. خب، در چنین شرایط مشهود است که پردازهی فرزند یتیم میشود. اما اتفاقی که خواهد افتاد این است که پردازهی init سرپرستی آن را قبول میکند. پس init، والد جدید فرزندانی میشود که والدهایشان terminate شدهاند.
۴. چرخهی عمر پردازهدر این بخش، به بررسی چرخهی عمر یک پردازهی معمولی لینوکسی پیش از کشته شدن و حذف شدن از جدول پردازههای کرنل خواهیم پرداخت.
- همانطور که قبلا بیان کردیم، پردازهی جدید با فراخوانی تابع ()fork ساخته میشود و اگر قرار باشد فایل اجرایی دیگری هم به کار افتد، خانوادهی توابع ()exec نیز ()fork را همراهی خواهند نمود. به محض ساخته شدن یک پردازه، به صف شده و درون صف پردازههای آمادهبهکار قرار خواهد گرفت.
- اگر فقط تابع ()fork فراخوانده شود، پردازهی ساخته شده، در حالت عادی، در mode کاربر اجرا میگردد. اما اگر ()exec فراخوانده شود، پردازهی جدید تا زمانیکه یک فضای آدرس پردازهی تازه برایش ساخته نشود، در mode کرنل کار خواهد کرد.
- اگر پردازهای در حال اجرا باشد، پردازهای دیگر با اولویت بالاتر اجازه دارد آن را با یک وقفه (interrupt)، قبضه کند. پردازهی قبضه شده دوباره درون صف پردازههای آماده به کار قرار گرفته و منتظر رسیدن نوبت میماند.
- یک پردازه میتواند حین اجرا، به mode کرنل برود. این کار زمانی ممکن است که نیاز به منابعی مانند یک فایل متنی ذخیره شده روی هارد دیسک داشته باشد. از آنجایی که انجام عملیات درگیر کنندهی سختافزار ممکن است زمانبر باشد، احتمال آن بالا است که پردازهی مورد نظر به خواب رود و تنها زمانی که دادهی درخواست شده در دسترس میشود، بیدار شود. بیدار شدنش بدان معنی نیست که بلافاصله باید پردازش شود. بلکه مانند دیگران دوباره در صف قرار گرفته و منتظر رسیدن نوبت میماند.
- برای تمام کردن پردازهها راههای بسیار وجود دارد مانند فراخواندن اعلان ()exit یا فرستادن سیگنالها.
- با کشته شدن یک پردازه، آن پردازه به طور کامل از بین نمی رود و مدخلی کوچک حاوی اطلاعاتی دربارهاش، درون جدول پردازههای کرنل باقی میماند. این مدخل که به آن process descriptor گفته میشود، تا پیش از زمانی که اعلان ()wait یا ()waitpid صریحا توسط والد صادر نشده، باقی مانده و به چنین پردازهای، پردازهی زامبی گویند.
۵. پردازهی زامبیزامبی، یکی از انواع پردازهها است. شما نمیتوانید زامبیها را بکشید زیرا آنها همین الآن هم مردهاند! درست مانند زامبیهای واقعی. یک زامبی در اصل بقایای پردازهای کشته شده است که به طور صحیح پاکسازی نشده. برنامهی استاندارد نباید به زامبیها اجازهی عرض اندام بدهد.
هنگامی که یک پردازه میمیرد، به طور کامل از حافظه حذف نمیشود؛ بلکه توصیفگر آن درون حافظه باقی میماند. وضعیت پردازه به EXIT_ZOMBIE تغییر یافته و والدش توسط سیگنال SIGCHLD از این واقعه مطلع میگردد. اکنون از پردازهی والد انتظار میرود اعلان سیستمی ()wait را اجرا کند تا دربارهی پردازهی مرده و وضعیت آن، اطلاعات کسب کند. پس از اجرای این اعلان است که پردازهی مرده کاملا از حافظه حذف میشود.
این مراحل معمولا خیلی سریع طی میشوند و زامبیها روی هم انباشته نمیشوند. اما اگر پردازهی والد بطور صحیح برنامهنویسی نشده باشد، اعلان ()wait را صادر نکرده و فرزندان زامبیاش تا زمان پاکسازی، درون حافظه به انتظار مینشینند.
ابزارهایی مانند سیستم مانیتور محیط دسکتاپتان یا دستورات top و ps زامبیها را نشان می دهند.
آیا زامبیها خطری هم دارند؟یک پردازهی زامبی به جز فضایی بسیار کم از حافظه برای نگه داشتن توصیفگرش، از هیچ یک از منابع سیستمی استفاده نمیکند. اما به هر حال زامبیها نیز همانند دیگر پردازهها، شناسهی پردازهی مخصوص به خود را نگه میدارند. یک سیستم لینوکسی با معماری ۳۲ بیت، تعداد محدود ۳۲۷۶۷ شناسهی پردازه را پشتیبانی میکند. پس اگر زامبیها با سرعت روی هم انباشته شوند - مثلا اگر وبسروری که خوب نوشته نشده، زیر بار بالای درخواستها زامبی تولید کند - ممکن است ذخیرهی PIDهای قابل استفاده تمام شده و همگی به پردازههای زامبی اختصاص یابند. در چنین شرایطی پردازههای دیگر قادر به اجرا شدن نخواهند بود!
البته تعداد کم زامبیها مشکلی ایجاد نکرده تنها بیانگر باگ نرمافزاری است.
رهایی یافتن از دست زامبیهاهمانطور که بالاتر به آن اشاره شد، کشتن پردازههای زامبی همانند دیگران و با سیگنال SIGKILL ممکن نیست. زیرا آنها مردهاند اما زندهاند! با این حال، راههای کمی برای رهایی یافتن از دست آنها وجود دارد.
یک راه، فرستادن SIGCHLD به پردازهی والد است. این سیگنال به والد میگوید وقت آن است اعلان سیستمی ()wait را اجرا کرده و فرزندان زامبیاش را پاکسازی کند. برای فرستادن این سیگنال از دستور kill استفاده کنید.
kill -s SIGCHLD <pid>
با این حال اگر پردازهی والد درست برنامهنویسی نشده باشد، سیگنال SIGCHLD را نادیده میگیرد. در این شرایط باید خود پردازهی والد را بکشید. میدانیم با کشته شدن پردازهی والد، سرپرستی فرزندان به init داده میشود. init خود در فواصل معیین، اعلان سیستمی ()wait را صادر کرده و حساب فرزندان زامبیاش را یکسره میکند. اکنون دوباره پردازهی والد را راه اندازید. اگر به ساختن فرزندان زامبی ادامه داد، گزارش باگ برایش پر کنید.
این مطلب ترجمهی آزادی بود از: