در بسیاری موارد پراسس پدر نیاز دارد تا بفهمد چه موقع یکی از پراسسهای فرزندش تغییر وضعیت میدهد، مثلا کی خاتمه مییابد و یا به وسیلهی یک سیگنال STOP میشود. در این فصل به بررسی دو تکنیک نظارت بر پراسسهای فرزند میپردازیم:
SIGCHLDسیستم کال wait(2) منتظر میشود تا یکی از فرزندان پراسس صدا زنندهی این سیستم کال terminate شود و termination status فرزند را در بافری که status به آن اشاره میکند قرار میدهد.
#include <sys/wait.h>
pid_t wait(int *status);
Returns process ID of terminated child, or -1 on error
در صورت خطا سیستم کال wait(2) مقدار -1 برمیگرداند. یک خطای محتمل این است که پراسس صدا زنندهی wait
یا فرزند نداشته باشد
یا فرزند داشته باشد ولی قبلا روی تمام آن wait کرده باشد. در این صورت متغیر errno با مقدار ECHILD پر میشود.
نکتهی بالا به این مفهوم است که با تکه کد زیر میتوانیم منتظر خاتمه یافتن تمام فرزندان یک پراسس پدر شویم:
while ((childPid = wait(NULL)) != -1)
continue;
if (errno != ECHILD) /* an unexpected error... */
err(EXIT_FAILURE, "wait");
اگر چندین پراسس فرزند zombie، یعنی پراسس تمام شدهی wait نشده، وجود داشته باشد اینکه سیستم کال wait کدام یک را برگرداند توسط SUSv3 تعریف نشده است و بستگی به پیادهسازی دارد. حتی در میان نسخههای متفاوت لینوکس رفتار wait در این مورد فرق میکند.
مثالی دیگر در مورد سیستم کال wait
مثال متفرقه در مورد غیر فعال کردن buffering در stdout. ترمینال به طور پیش فرض line-buffered است یعنی در صورت رسیدن به کاراکتر خط جدید بافر stdio در بافر کرنل کپی میشود. در این مثال ما بافر stdout را غیر فعال کردهایم و کاراکترهای نوشته شده توسط تابع printf فورا در بافر کرنل نوشته میشوند.
محدودیتهای سیستم کالwait(2) به قرار زیر است:
SIGSTOP و SIGTTIN و یا resume شدن پراسس فرزند در نتیجهی تحویل سیگنال SIGCONT نیز مطلع شویم.محدودیتهای فوق با سیستم کال waitpid(2) مرتفع میگردد.
#include <sys/wait.h>
pid_t waitpid(pid_t pid, int *status, int options);
Returns process ID of child, 0 (see text), or -1 on error
آرگومان pid میتواند بر مبنای قوانین زیر تفسیر شود:
wait(&status) == waitpid(-1, &status, 0)
آرگومان options یک bit mask است که میتواند از OR صفر یا ترکیبی از موارد زیر ساخته شود:
WUNTRACED: علاوه بر برگرداندن اطلاعات در مورد فرزندان terminate شده، اگر در نتیجهی رخداد یک سیگنال فرزندی STOP شد آن را هم اطلاع میدهد.WCONTINUED: از لینوکس ۲.۶.۱۰. اگر فرزند STOP شدهای در نتیجهی تحویل سیگنال SIGCONT اجرا را از سر گرفت و به اصطلاح resume شد آن را هم اطلاع بده.WNOHANG: اگر هنوز هیچ پراسس فرزندی که با pid مشخص شده است تغییر وضعیت نداده باشد به جای اینکه سیستم کال waitpid بلاک شود فورا برمیگردد (یعنی یک poll اجرا میکند. #Think) در این حالت waitpid صفر برمیگرداند.اگر پراسس فراخوان سیستم کال waitpid هیچ فرزندی که با مقدار مشخص شده در pid مطابقت داشته باشد، نداشته باشد در این حالت waitpid ناموفق تمام میشود و errno مقدار ECHILD میگیرد.
#Think

با بررسی مقدار status برگشت داده شده از سیستم کالهای wait و waitpid میتوان وقوع رویدادهای زیر را تشخیص داد:
در محیط shell با چاپ مقدار $? ، termination status آخرین دستور اجرا شده را خواهیم دید.
در سیستم کالهای wait و waitpid اگرچه متغیر status از نوع integer است ولی فقط ۲ بایت کم ارزش آن مورد استفاده قرار میگیرد. به خاطر بیاورید که در سیستم کال _exit(status) نیز فقط کم ارزشترین بایت status به پراسس فرزند ارسال میشد.
شکل زیر ساختار wait status را در linux/x86-32 نشان میدهد. جزئیات ماجرا در پیادهسازیهای مختلف فرق میکند و SUSv3 نیز هیچ استانداردی را برای بیان این اطلاعات تعیین نکرده است و حتی نگفته است که wait status حتما باید در ۲ بایت کم ارزش status قرار گیرد. برنامههای portable همیشه باید برای استخراج اطلاعات wait status از ماکروهایی که در ادامه معرفی میشوند استفاده کنند و از تفسیر مستقیم بیتها اجتناب کنند.

اگر ۴ ماکروی زیر را بر روی wait status ای که از سیستم کال wait و یا سیستم کال waitpid گرفته شده است استفاده کنیم فقط یکی از آنها مقدار true برمیگرداند. این ماکروها در سرفایل <sys/wait.h> تعریف شدهاند. ماکروهای استاندارد دیگری نیز در توضیحات این ۴ ماکروها معرفی شدهاند:
WIFEXITED(status): اگر پراسس فرزند به صورت طبیعی terminate شده باشد این ماکرو مقدار true برمیگرداند. در این حالت ماکروی WEXITSTATUS(status) مقدار exit status پراسس فرزند را برمیگرداند. همان طور که در فصل قبل خواندیم فقط بایت کمارزش exit status پراسس فرزند در معرض دید پراسس پدر است.WIFSIGNALED(status): اگر پراسس فرزند با دریافت یک سیگنال کشته شده باشد این ماکرو true برمیگرداند. در این حالت ماکروی:
WTREMSIG(status) شمارهی سیگنالی که باعث terminate شدن پراسس فرزند شده است را برمیگرداند.WCORDUMP(status) اگر پراسس فرزند فایل core dump ایجاد کرده باشد true برمیگرداند. ماکروی WCOREDUMP() در SUSv3 استاندارد نشده است ولی در بیشتر پیادهسازیهای UNIX وجود دارد.WIFSTOPPED(status): اگر پراسس فرزند با تحویل یک سیگنال STOP شده باشد این ماکرو true برمیگرداند. در این حالت ماکروی WSTOPSIG(status) شمارهی سیگنالی که باعث STOP شدن پراسس فرزند شده است را به دست میدهد.WIFCONTINUED(status) اگر پراسس فرزند با دریافت سیگنال SIGCONT اجرا را از سر گرفته یا resume شده باشد این ماکرو مقدار true برمیگرداند. این ماکرو از لینوکس ۲.۶.۱۰ اضافه شده است.برای بررسی مقدار wait status میتوانید از این مثال استفاده کنید. چند نمونه کاربرد این برنامه در خط فرمان در ابتدای کد منبع برنامه آورده شده است.
مثالی دیگر از سیستم کال waitpid و ماکروهای مربوطه
پراسس فرزند به صورت پیشفرض رویدادهای STOP شدن، resume شدن و terminate شدن خودش را با سیگنال SIGCHLD به پراسس پدر اطلاع میدهد. سیستم کال wait در سمت پراسس پدر فقط موقع terminate شدن پراسس فرزند برمیگردد. نکات گفته شده را در این مثال بررسی کنید.
کتاب در صفحهی ۵۴۸ میگوید که با اجرای دستور
$ ulimit -c unlimited
در shell اجازه میدهیم که فایل core dump به هر اندازهای که باشد در صورت لزوم ایجاد گردد. در مثال بالا با ساطع کردن سیگنال SIGABRT برای پراسس فرزند اگرچه خروجی میگوید که فایل core dump ایجاد شده است ولی در واقع در دایرکتوری فعلی چیزی وجود ندارد. اشکال کار کجاست؟ #Think
توجه کنید که اگر در داخل یک signal handler با استفاده از سیستم کال _exit(status) اقدام به خاتمهی برنامه کنیم این کار به معنای terminate شدن طبیعی و نرمال پراسس است و status ارسال شده به سیستم کال _exit() در سمت پراسس پدر با سیستم کالهای wait و waitpid قابل دسترسی است.
گاهی اوقات لازم است برای رخداد یک سیگنال یک cleanup handler بنویسیم. با توجه به نکتهی بالا نمیتوان در داخل signal handler بعد از clean up اقدام به فراخوانی سیستم کال _exit() کنیم چون پراسس پدر فکر میکند که فرزندش به صورت طبیعی خاتمه یافته است. برای اینکه هم cleanup انجام دهیم و هم به پدر بفهمانیم که پراسس فرزند در نتیجهی رخداد یک سیگنال، خاتمهی غیر طبیعی داشته است، بعد از عمل cleanup باید دوباره مجددا همان سیگنال را با استفاده از تابع کتابخانهای raise(3) ساطع کنیم در نتیجه سیگنال هندلر قالبی به شکل زیر خواهد داشت:
void
handler(int signo)
{
/* perform cleanup steps */
signal(signo, SIG_DFL); /* disestablish handler */
raise(sig); /* raise signal again */
}
مثال. چند نمونه از اجرای برنامه در بالای کد منبع آورده شده است. مثال نکتهای دارد که آن را در متن برنامه با #TIP نشان دادهایم.
سیستم کال waitid(2) هم مانند سیستم کال waitpid(2) مقدار status برگشتی از پراسس فرزند را به دست میدهد با این وجود امکانات بیشتری نسبت به waitpid(2) در اختیار برنامهنویس قرار میدهد. (برای مطالعهی مطالب مختصری در مورد تاریخچهی این سیستم کال ر.ک. صفحهی ۵۵۰)
#include <sys/wait.h>
int waitid(idtype_t idtype, id_t id, siginfo_t *infop, int options);
Returns 0 on success or if WNOHANG was specified and
there were no children to wait for, or -1 on error.
آرگومانهای idtype و id مشخص میکنند که عملیات wait باید روی کدام فرزندها انجام شود:
idtype == P_ALL: فیلد id نادیده گرفته میشود و wait for any child.idtype == P_PID: روی فرزندی wait میکند که process ID آن برابر id
داده شده به عنوان آرگومان دوم باشد.idtype == P_GID: منتظر فرزندی میشود که process group ID آن برابر
id فراهم شده باشد.برخلاف سیستم کال waitpid(2) در اینجا امکان اینکه id را مساوی صفر مقدار دهیم و منظور process group پراسس جاری باشد وجود ندارد. برای به دست آوردن process group ID پراسس جاری از getpgrp(3p) استفاده کنید.
صفحهی ۵۵۰ این فصل متوقف شد. نیاز به دانستن پیش نیازهای دیگری دارم.