پردازههای Daemon
پردازه Daemon به پردازهای گفته میشود که در پسزمینه (Background) اجرا شده و به هیچ ترمینال کاربری متصل نیست. نداشتن ترمینال متصل، از ویژگیهای بارز این پردازههاست، چرا که معمولاً توسط اسکریپتهای راهاندازی اولیه سیستم آغاز به کار میکنند.
از آنجایی که پردازههای Daemon ترمینال کنترلی ندارند، هنگام وقوع هر رویدادی به روشی برای ثبت و نمایش پیامهای خود نیاز خواهند داشت. این پیامها ممکن است صرفاً جنبه اطلاعرسانی داشته باشند یا پیامهای اضطراری باشند که نیازمند رسیدگی فوری مدیر سیستم هستند. تابع syslog استانداردترین روش برای ثبت این پیامها به شمار میرود و آنها را برای پردازش به پردازه Daemon به نام syslogd ارسال میکند.
پردازه Daemon به نام syslogd
دیمن syslogd پردازهای است که وظیفه مدیریت لاگها را بر عهده دارد؛ این پردازه در زمان بوت سیستم شروع به کار کرده و تا زمان فعال بودن سیستم، پیوسته در حال اجرا میماند. نحوه عملکرد syslogd در سیستمهای Unix مشتقشده از برکلی (BSD) به شرح زیر است:
- ابتدا فایل پیکربندی (معمولاً
/etc/syslog.conf) را میخواند. این فایل تعیین میکند که با توجه به نوع پیام لاگِ دریافتی، چه اقدامی باید صورت گیرد؛ برای مثال، ممکن است پیام را به یک فایل خاص اضافه کند، آن را برای یک کاربر واردشده (Login) به سیستم بفرستد یا حتی به پردازهsyslogdدر یک میزبان (Host) دیگر ارسال نماید. - یک سوکت دامنه Unix ایجاد شده و به فایلی در مسیر
/var/run/log(یا در برخی سیستمها/dev/log) متصل (Bind) میشود. - یک سوکت UDP ایجاد شده و به پورت ۵۱۴ متصل میشود (سرویس
syslog). - فایل دستگاه
/dev/klogایجاد میشود؛ تمامی پیامهای خطای هسته (Kernel) به عنوان ورودی از طریق این دستگاه دریافت میشوند.
پردازه دیمن syslog یک حلقه بینهایت اجرا کرده و با فراخوانی تابع select، منتظر میماند تا یکی از سه توصیفگر فایل (File Descriptor) ذکر شده در مراحل قبل آماده خواندن شود. به محض خواندن پیام لاگ، اقدامات مشخصشده در فایل پیکربندی را انجام میدهد. همچنین در صورتی که این پردازه سیگنال SIGHUP را دریافت کند، فایل پیکربندی را مجدداً بازخوانی خواهد کرد.
ما میتوانیم با ایجاد یک سوکت دیتاگرام دامنه Unix و ارسال پیام به مسیری که دیمن به آن متصل شده، پیامها را از دیمنِ خود به پردازه syslogd بفرستیم. همچنین ایجاد یک سوکت UDP و ارسال پیام به آدرس Loopback و پورت ۵۱۴ نیز امکانپذیر است؛ با این حال، سادهترین راهکار، فراخوانی تابع syslog است که در ادامه به آن میپردازیم.
تابع syslog
روش اصلی برای ثبت لاگها در یک پردازه Daemon، فراخوانی تابع syslog است. این تابع در ابتدا برای سیستمهای BSD توسعه یافته بود، اما امروزه تقریباً در تمام سیستمهای مشتقشده از Unix پشتیبانی میشود.
آرگومان priority در واقع ترکیبی از دو پارامتر level و facility است که مفهوم هر یک در ادامه توضیح داده شده است:
level
پارامترlevelدر پیامهای لاگ مقداری بین ۰ تا ۷ دارد. در صورتی که مقداری برای آن تعیین نشود، مقدار پیشفرضLOG_NOTICEدر نظر گرفته خواهد شد.
facility
پارامترfacilityبرای شناسایی نوع و منشأ پردازهای که پیام را ارسال میکند به کار میرود. اگر مقداری برای آن مشخص نکنید، مقدار پیشفرضLOG_USERاعمال خواهد شد.
هنگامی که برنامه برای اولین بار تابع syslog() را فراخوانی میکند، یک سوکت دامنه Unix ایجاد کرده و با اجرای تابع connect() به سوکت ایجادشده توسط دیمن syslogd (مثلاً در مسیر /var/run/log) متصل میشود. این سوکت تا پایان کار برنامه باز خواهد ماند.
علاوه بر این، برنامه میتواند توابع openlog() و closelog() را به صورت صریح فراخوانی کند.
تابع openlog() پیش از فراخوانی syslog() استفاده میشود و آرگومانهای آن به شرح زیر هستند:
- آرگومان
ident: رشتهای است که توسطsyslogبه ابتدای هر پیام لاگ اضافه میشود و معمولاً نام برنامه را نشان میدهد. - آرگومان
options: ترکیبی از ثابتهای مشخصشده در تنظیمات سیستم است. - آرگومان
facility: مقدار پیشفرض این پارامتر را برای فراخوانیهای بعدی تابعsyslogتعیین میکند.
تابع daemon_init()
این تابع برای تبدیل یک پردازه معمولی به یک پردازه Daemon مورد استفاده قرار میگیرد.
مراحل عملکرد این تابع به ترتیب زیر است:
با فراخوانی تابع fork()، پردازه والد (Parent) خاتمه یافته و پردازه فرزند (Child) شروع به کار میکند. فرزند شناسه گروه پردازه (Process Group ID) را به ارث میبرد اما شناسه پردازه (PID) اختصاصی خود را دارد؛ بنابراین پردازه فرزند نمیتواند رهبر گروه پردازه (Process Group Leader) باشد.
سپس تابع POSIX به نام setsid() فراخوانی میشود تا یک نشست (Session) جدید ایجاد کند. با این کار، پردازه فرزند رهبر نشست و رهبر گروه پردازه جدید میشود.
سیگنال SIGHUP نادیده گرفته میشود. این اقدام برای فراخوانی دومین تابع fork() در مرحله بعد ضروری است.
با فراخوانی دومین fork()، پردازه فرزند جدیدی ایجاد شده و فرزند اول خاتمه مییابد. هدف این است که تضمین شود دیمن، حتی در صورت باز کردن یک دستگاه ترمینال (مانند /dev/tty یا /dev/console)، هرگز نمیتواند یک ترمینال کنترلی به دست آورد؛ زیرا اگر رهبر نشستی که ترمینال کنترلی ندارد دستگاه ترمینالی را باز کند، آن ترمینال به طور خودکار ترمینال کنترلی آن خواهد شد.
از آنجا که فرزند دوم رهبر نشست نیست، امکان تصاحب ترمینال کنترلی را نخواهد داشت. با این حال، هنگام خاتمه فرزند اول، سیگنال SIGHUP به فرزند دوم ارسال میشود و به همین دلیل، نادیده گرفتن این سیگنال در مرحله قبل الزامی بود.
مقدار ۱ به متغیر سراسری daemon_proc اختصاص مییابد. این متغیر در توابع err_XXX() این کتاب تعریف شده و اگر مقداری غیر از صفر داشته باشد، پیامهای خطا به جای fprintf() از طریق تابع syslog() ثبت میشوند.
دایرکتوری کاری تغییر میکند (معمولاً به دایرکتوری ریشه). البته برخی دیمنها مانند دیمن پرینتر ممکن است به مسیر دیگری نیاز داشته باشند. همچنین دایرکتوری اولیه دیمن را در حین اجرای آن نمیتوان Unmount کرد، بنابراین تغییر مسیر از قبل اهمیت دارد.
تمامی توصیفگرهای باز و به ارث رسیده بسته میشوند. چالش کار در این است که حداکثر تعداد توصیفگرهای در حال استفاده مشخص نیست و تابع استاندارد Unix برای به دست آوردن آن وجود ندارد؛ به همین دلیل در این کتاب، ۶۴ توصیفگر اول بسته میشوند.
سپس ورودی استاندارد، خروجی استاندارد و خطای استاندارد به مسیر /dev/null هدایت (Redirect) میشوند. این کار تضمین میکند که توصیفگرهای پایه باز هستند و از خطاهای غیرمنتظره در کتابخانههای وابسته جلوگیری میکند.
علاوه بر این، اگر توصیفگرهای پایه بسته باشند و سوکت جدیدی برای کلاینت باز شود، ممکن است شماره توصیفگر ۱ یا ۲ را بگیرد؛ در این حالت، فراخوانی توابعی مانند perror() ممکن است ناخواسته دادههایی را به سمت کلاینت ارسال کند که خطرساز خواهد بود.
در نهایت، تنظیمات لازم برای استفاده از syslogd انجام میشود و با فراخوانی تابع openlog()، نام برنامه و مقدار پیشفرض facility تعیین میگردد.
مثال کاربردی از تابع daemon_init(): سرور Daytime
کد زیر، نسخه ارتقایافته سرور Daytime از فصل ۱۱ است که با فراخوانی تابع daemon_init() به عنوان یک پردازه Daemon اجرا میشود. تغییرات اعمالشده شامل دو مورد زیر است:
- تابع
daemon_init()درست در ابتدای برنامه فراخوانی میشود. - برای نمایش آدرس IP و پورت کلاینت، به جای
printf()از تابعerr_msg()استفاده میشود.
پیش از اجرای daemon_init()، مقدار argc بررسی میشود تا راهنمای استفاده صحیح به کاربر نمایش داده شود. این بررسی زودهنگام باعث میشود کاربر در صورت وارد کردن آرگومانهای اشتباه فوراً مطلع شود؛ چرا که پس از اجرای daemon_init()، پیامها به syslogd منتقل شده و کاربر متوجه خطای لحظهای نخواهد شد.
پردازه inetd
در یک سیستم متداول Unix، معمولاً سرویسهای متعددی مانند FTP ،Telnet ،Rlogin و TFTP منتظر اتصال کلاینتها هستند. این پردازهها غالباً در زمان بوت سیستم اجرا شده و الگوی رفتاری بسیار مشابهی دارند.
- ابتدا یک سوکت ایجاد کرده،
- پورت مشخص و شناختهشدهی سرور را به سوکت متصل (Bind) نموده،
- در صورت استفاده از TCP، منتظر اتصال کلاینت و در صورت استفاده از UDP، منتظر دریافت دیتاگرام مانده و سپس
- تابع
fork()را فراخوانی میکند.
با این حال، چنین پیادهسازی و ساختاری با دو چالش اساسی همراه است:
- نخست اینکه تمامی این پردازههای Daemon، کدهای راهاندازی تقریباً یکسانی دارند.
- دوم اینکه هر پردازهی Daemon یک پردازه مجزا ایجاد میکند، اما بیشتر این پردازهها بخش عمدهای از زمان را در حالت انتظار و خواب (Sleep) به سر میبرند.
برای برطرف کردن این چالشها، در نگارش 4.3 BSD پردازهای موسوم به «ابر سرور» (Super-server) به نام inetd معرفی شد. پردازهی inetd این دو مشکل را به شیوه زیر حل میکند:
- از آنجا که سرویس
inetdمدیریت جزئیات مراحل راهاندازی را بر عهده میگیرد، توسعه و نوشتن پردازههای Daemon بسیار سادهتر میشود؛ در نتیجه، دیگر نیازی نیست که هر سرور به طور مجزا تابعdaemon_initرا فراخوانی کند. - به جای اینکه هر سرویس به طور جداگانه منتظر درخواستهای کلاینت بماند، پردازهی
inetdبه عنوان نماینده منتظر تمام درخواستها مینشیند. این کار باعث کاهش چشمگیر تعداد کل پردازههای فعال در سیستم میشود.
پردازهی inetd ابتدا با استفاده از همان روشهایی که در تابع daemon_init() توضیح داده شد، خود را به عنوان یک پردازهی Daemon اجرا میکند. سپس فایل پیکربندی (معمولاً /etc/inetd.conf) را میخواند و پردازش میکند؛ فایلی که مشخص میسازد چه سرویسهایی باید مدیریت شوند و در صورت رسیدن درخواست برای هر سرویس، چه واکنشی باید نشان داده شود. هر خط از این فایل شامل فیلدهایی است که در ادامه آمده است:
اگر به محتوای فایل inetd.conf نگاهی بیندازیم، با ساختاری به شکل زیر روبرو میشویم:
در اینجا، ویژگی مهمی که باید به آن دقت کرد، فلگ wait-flag است. این مشخصه تعیین میکند که آیا inetd باید پیش از پذیرش اتصالات جدید برای یک سرویس، منتظر اتمام کار پردازهی فرزند بماند یا خیر. در سرورهای معمولی TCP، این مقدار روی nowait تنظیم میشود؛ بدین معنی که سیستم بدون معطل ماندن برای پایان کار پردازهی فرزند (که در حال پاسخ به درخواست قبلی است)، اتصالات جدید را میپذیرد و فرزندان جدیدی ایجاد میکند.
مراحل عملکرد inetd
تصویر زیر چگونگی عملکرد پردازهی inetd را نشان میدهد. بیایید مراحل آن را گامبهگام بررسی کنیم.
- در زمان راهاندازی اولیه، این پردازه فایل
/etc/inetd.confرا میخواند و برای تمام سرویسهای مشخصشده در آن، سوکتهایی از نوع مناسب ایجاد میکند. حداکثر تعداد سرویسهایی کهinetdمیتواند مدیریت کند، به حداکثر تعداد دیسکریپتورهایی بستگی دارد که این پردازه قادر به ایجاد آنهاست. سپس هر سوکت جدیدی که ایجاد میشود، به مجموعهی دیسکریپتورهای تابعselectافزوده خواهد شد. - در ادامه، تابع
bind()فراخوانی میشود تا پورت سرور و آدرس IP عمومی (Wildcard) را مشخص کند. شماره پورت TCP/UDP مورد نظر نیز با ارسال نام سرویس و فیلد پروتکل از فایل پیکربندی به عنوان ورودی به تابعgetservbyname()به دست میآید. - در مورد سوکتهای TCP، تابع
listen()فراخوانی میشود تا امکان پذیرش درخواستهای اتصال ورودی فراهم گردد؛ البته برای سوکتهای UDP نیازی به طی کردن این مرحله نیست. - پس از ساخته شدن تمامی سوکتها، تابع
select()صدا زده میشود تا منتظر آماده شدن یکی از سوکتها برای خواندن بماند. همانطور که در فصل ۶ اشاره شد، سوکت TCP زمانی قابل خواندن میشود که آمادهی پذیرش اتصال جدید باشد، و سوکت UDP نیز با رسیدن یک دیتاگرام آمادهی خواندن میگردد. پردازهیinetdبیشتر زمان خود را در این تابعselect()میگذراند و منتظر آماده شدن یکی از سوکتها میماند. - هنگامی که تابع
select()سوکت آماده به خواندن را برمیگرداند، اگر آن سوکت از نوع TCP بوده و فلگnowaitبرای آن تنظیم شده باشد، با فراخوانیaccept()اتصال جدید پذیرفته میشود. - سپس پردازهی
inetdبا فراخوانی تابعfork()، یک پردازهی جدید برای پاسخگویی به درخواست سرویس ایجاد میکند که دقیقاً مشابه روند همزمانی (Concurrency) در سرورهای معمول است. - اگر سوکت از نوع UDP باشد، پردازهی والد باید ارتباط سوکت متصلشده را قطع کند (همانند آنچه در سرورهای همزمان معمول اتفاق میافتد). با فراخوانی مجدد تابع
select()توسط والد، سیستم دوباره تا زمان آماده شدن سوکتها برای خواندن به انتظار مینشیند.
پردازهی فرزندی که توسط inetd ایجاد شده است، درخواست کاربر را از سوکت خوانده و آن را پردازش میکند. بیایید مراحل کار این پردازهی فرزند را دقیقتر مرور کنیم.
- نخست تمامی دیسکریپتورها به جز دیسکریپتور سوکتی که قرار است پردازش شود، بسته میشوند.
- پردازهی فرزند سه بار تابع
dup2را فراخوانی میکند تا سوکت را روی دیسکریپتورهای 0، 1 و 2 کپی کند؛ سپس سوکت اصلی بسته میشود. بدین ترتیب، تنها دیسکریپتورهای باز در پردازهی فرزند، دیسکریپتورهای 0، 1 و 2 خواهند بود. - پردازهی فرزند با فراخوانی تابع
getpwnam()، اطلاعات رمز عبور مربوط به نام کاربری مشخصشده در فایل پیکربندی را به دست میآورد. اگر نام کاربری تعیینشده چیزی غیر ازrootباشد، پردازهی فرزند توابعsetgid()وsetuid()را اجرا میکند تا تحت هویت کاربر مشخصشده به فعالیت ادامه دهد. - از آنجا که پردازهی
inetdبا شناسه کاربری 0 (User ID 0 یا همان root) اجرا میشود، پردازهی فرزندی که با تابعfork()ایجاد شده، این امکان را دارد تا هویت هر کاربر دلخواهی را به خود بگیرد. - در نهایت، پردازهی فرزند با فراخوانی تابع
exec()برنامه سرور مورد نظر را برای رسیدگی به درخواست اجرا کرده و همزمان، آرگومانهای تعریفشده در فایل پیکربندی را نیز به آن منتقل میکند.
نمونهای از عملکرد inetd: سرویس FTP
برای درک عمیقتر نحوهی مدیریت دیسکریپتورها، بیایید شرایطی را بررسی کنیم که در آن یک درخواست اتصال جدید از سمت یک کلاینت FTP دریافت میشود.
درخواست اتصال مستقیماً به پورت ۲۱ ارسال میشود، اما با فراخوانی تابع accept()، یک سوکت متصلشدهی جدید ایجاد میگردد. تصویر زیر وضعیتی را نشان میدهد که پس از فراخوانی تابع fork()، تمامی دیسکریپتورها به جز سوکت متصلشده بسته شدهاند.
اکنون پردازهی فرزند تابع exec() را فراخوانی میکند. همانطور که در بخش ۴.۷ بیان شد، تمامی دیسکریپتورهایی که پیش از فراخوانی exec() باز بودهاند، پس از اجرای آن نیز باز باقی میمانند. بنابراین پردازهی اصلی سرور که توسط exec() فراخوانی شده است، میتواند از هر یک از دیسکریپتورهای 0، 1 و 2 برای ارتباط با کلاینت استفاده کند و این موارد باید تنها دیسکریپتورهای باز در پردازهی سرور باشند.
سناریویی که بررسی کردیم مربوط به زمانی بود که سرویس در فایل پیکربندی به صورت nowait تعریف شده باشد. اما اگر برای یک سرویس UDP فلگ wait تنظیم شود، بدین معناست که پردازهی فرزند باید پیش از فراخوانی مجدد select() روی سوکت توسط inetd، کار خود را به پایان برساند. در نتیجه، تغییرات زیر رخ میدهد:
- پس از بازگشت تابع
fork()، پردازهی والد شناسهی پردازهی فرزند (Process ID) را ذخیره میکند. این موضوع به والد امکان میدهد تا با بررسی مقدار بازگشتی تابعwaitpid()، از پایان کار فرزند مربوطه اطمینان حاصل کند. - پردازهی والد با استفاده از ماکروی
FD_CLR، بیت مربوط به آن سوکت را در مجموعهی دیسکریپتورها غیرفعال میکند؛ به این معنی که سوکت تا زمان اتمام کار پردازهی فرزند در حالت اشغال باقی میماند. - با به پایان رسیدن کار پردازهی فرزند، سیگنال
SIGCHLDبه والد ارسال میشود و Handler سیگنال، شناسهی فرزند خاتمهیافته را دریافت میکند. سپس با فعالسازی مجدد بیت مربوط به سوکت در مجموعهی دیسکریپتورها، آن سوکت برای تابعselect()مجدداً در دسترس قرار میگیرد.
علت اشغال ماندن سوکت تا پایان کار سرور دیتاگرام این است که از انتظار کشیدن inetd در تابع select() برای خواندن از سوکت (به امید دریافت دیتاگرام کلاینت دیگر) جلوگیری شود. بر خلاف سرورهای TCP که یک سوکت برای اتصال و به ازای هر کلاینت یک سوکت مجزا برای تبادل داده دارند، سرور UDP تنها از یک سوکت واحد بهره میبرد.
اگر پردازهی inetd سوکت UDP را از دامنهی پایش select() خارج نکند و خودِ inetd زودتر از پردازهی فرزند اجرا شود، دیتاگرامِ ورودی کلاینت جدید همچنان در بافر دریافت سوکت باقی خواهد ماند؛ در نتیجه inetd از حالت انتظار در select() خارج شده و یک پردازهی فرزند اضافی (و غیرضروری) دیگر ایجاد میکند. از این رو، inetd باید سوکت دیتاگرام را تا زمانی که مطمئن شود فرزند دیتاگرام را از صف دریافت خوانده است، نادیده بگیرد. دریافت سیگنال SIGCHLD دقیقاً به همین منظور است تا نشان دهد کلاینت کارش با سوکت تمام شده است؛ موضوعی که در بخش ۲۲.۷ به طور مفصل به آن خواهیم پرداخت.
پردازهی inetd معمولاً برای سرورهای با بار ترافیکی سنگین، بهویژه سرورهای ایمیل و وب به کار گرفته نمیشود. به عنوان مثال، سرور sendmail اغلب به عنوان یک سرور همزمان استاندارد اجرا میشود؛ چرا که در این حالت، هزینهی مدیریت هر اتصال کلاینت تنها به یک فراخوانی fork() محدود میشود، در حالی که inetd ملزم به فراخوانی هر دو تابع fork() و exec() است. وبسرورها نیز برای به حداقل رساندن سربار ناشی از پردازهها تکنیکهای متعددی را به کار میبندند که در فصل ۳۰ به شرح آنها میپردازیم.
تابع daemon_inetd
در ادامه، کد تابعی با نام daemon_inetd() را مشاهده میکنید که توسط سرورهای فراخوانیشده از طریق inetd قابل استفاده است.
از آنجا که تمام مراحل تبدیل شدن به Daemon از پیش توسط inetd انجام شده است، این تابع بسیار سرراستتر و سادهتر از تابع daemon_init() عمل میکند. تمام کاری که این تابع انجام میدهد، تغییر مقدار daemon_proc به ۱ و فراخوانی تابع openlog است.
بیایید با استفاده از این تابع، سرور Daytime را که قبلاً معرفی کردیم، به عنوان یک پردازهی Daemon اجرا کنیم. با جایگزین کردن تابع daemon_init() با daemon_inetd()، پیادهسازی به صورت زیر درمیآید:
در این برنامه دو تغییر بنیادین و مهم دیده میشود:
- نخست اینکه تمامی کدهای مربوط به ساخت سوکت حذف شدهاند؛ یعنی دیگر نیازی به فراخوانی توابع
tcp_listen()وaccept()نیست.
۲. از آنجا که این تابع به ازای هر اتصال کلاینت فقط یکبار فراخوانی میشود، حلقهٔ بینهایت از بین رفته است.
خلاصه
فرآیند دیمن (Daemon Process) فرآیندی است که مستقل از کنترل هرگونه ترمینال و در پسزمینه اجرا میشود. بسیاری از سرورهای شبکه بهصورت دیمن کار میکنند. معمولاً تمام خروجیهای یک فرآیند دیمن توسط تابع syslog به فرآیند دیمن syslogd منتقل میشوند.
برای اجرای هر برنامهای در قالب یک فرآیند دیمن، انجام چند مرحله ضروری است:
- فراخوانی
fork()به منظور اجرا در پسزمینه، - فراخوانی
setsid()برای تبدیل شدن به لیدر یک سشن جدید POSIX، - فراخوانی مجدد
fork()جهت جلوگیری از اختصاص ترمینال کنترلی جدید، - و در نهایت، تغییر دایرکتوری کاری، تنظیم ماسک ایجاد مود فایل (File Mode Creation Mask) و بستن فایلهای غیرضروری.
تابع daemon_init() تمامی این مراحل را مدیریت و اجرا میکند.
تمرینها
۱۳.۱ در مثال مربوط به تابعdaemon_init()، اگر فراخوانیdaemon_init()را به قبل از بررسی آرگومانهای خط فرمان منتقل کنیم بهطوریکهerr_quit()پس از آن قرار گیرد، چه اتفاقی رخ میدهد؟
پیام خطای مربوط به آرگومانهای نامعتبر توسط syslog ثبت (Log) خواهد شد.
۱۳.۲ سرویسهای TCP/IP موجود در جدول زیر، نمونههایی از سرویسهای رایجی هستند که توسطinetdمدیریت میشوند. با در نظر گرفتن نسخههای TCP و UDP هر سرویس، کدامیک از آنها با استفاده از fork() پیادهسازی شدهاند و کدام سرورها نیازی به fork() ندارند؟
- نسخههای TCP سرویسهای
echo،discardوchargenپس از اجرایfork()در فرآیند فرزند اجرا میشوند؛ چراکه این سه سرویس تا زمان قطع اتصال توسط کلاینت به فعالیت خود ادامه میدهند. - سرورهای باقیمانده یعنی
timeوdaytimeنیازی بهfork()ندارند؛ چراکه پیادهسازی سادهای دارند و مستقیماً توسطinetdپردازش میشوند. - تمامی سرویسهای
UDPبدون نیاز بهfork()پردازش میشوند، زیرا در پاسخ به هر دیتاگرام کلاینت، حداکثر یک دیتاگرام برمیگردانند.
۱۳.۳ اگر یک سوکت UDP ایجاد کنیم، آن را به پورت ۷ (سرور استاندارد echo) متصل (Bind) کرده و یک دیتاگرام UDP به سرورchargenارسال نماییم، چه رخ خواهد داد؟
- این سناریو یک روش برای حملهٔ DoS است. نخستین دیتاگرام ارسالشده از پورت ۷ باعث میشود سرویس
chargenدیتاگرامی را مجدداً به پورت ۷ ارسال کند. این فرآیند دوباره بازتاب یافته و دیتاگرام دیگری را به سرویسchargenمیفرستد (و این چرخه به طور مداوم تکرار میشود). - برای جلوگیری از بروز این مشکل، در FreeBSD اگر پورت مبدأِ دیتای دریافتی متعلق به یک سرویس داخلی باشد، از ارسال دیتاگرام به سرویسهای داخلی ممانعت به عمل میآید.
- روش دیگر این است که چنین سرویسهای داخلی را در
inetdهر هاست یا در روترهای اینترنت درونسازمانی غیرفعال کنیم.
۱۳.۴ در صفحهٔ راهنما (man page) مربوط بهinetدر سولاریس 2.x، فلگ-tتوضیح داده شده است که آدرس IP و پورت کلاینت را برای تمامی سرویسهای TCP تحت مدیریتinetdثبت میکند.inetdاین اطلاعات را چگونه به دست میآورد؟ همچنین در این صفحه ذکر شده که چنین امکانی برای سرویسهایUDPوجود ندارد؛ دلیل آن چیست؟ آیا راهکاری برای برطرف کردن این محدودیت در سرویسهای UDP وجود دارد؟
- آدرس IP و پورت کلاینت را میتوان از ساختار آدرس سوکت که توسط تابع
accept()برگردانده میشود، استخراج کرد. - اما در مورد UDP، از آنجا که تابع
recvfrom()برای خواندن دیتاگرام توسط فرآیند اصلی سرور (که با استفاده از تابعfork()ایجاد میشود) اجرا میگردد،inetdاز این اطلاعات بیخبر میماند. - در عوض، فرآیند
inetdمیتواند با مشخص کردن فلگMSG_PEEK، دیتاگرام را بخواند تا صرفاً آدرس IP و پورت کلاینت را به دست آورد و دیتاگرام را برای پردازش نهایی در اختیار فرآیند سرور باقی بگذارد.
منابع و مراجع
- https://stackoverflow.com/questions/881388/what-is-the-reason-for-performing-a-double-fork-when-creating-a-daemon
- تغییرات شناسههای Process/Group/Session در اثر Fork دوگانه (Double Fork)