پردازش زامبی چیست؟ راهنمای پیدا کردن و رفع آن در لینوکس
پردازش تمام شده، اما چرا هنوز در فهرست پردازشهای سرور دیده میشود؟ در این راهنما یاد میگیرید زامبیها و برنامه والدشان را پیدا کنید، دلیل بیاثر بودن kill -9 را بشناسید و مشکل را از ریشه برطرف کنید.
پردازش زامبی یا Zombie Process در لینوکس، پردازشی است که اجرای آن تمام شده اما پردازش والد هنوز وضعیت خروج آن را با سازوکاری مانند wait() یا waitpid() جمعآوری نکرده است. در نتیجه، اطلاعات محدودی از پردازش در جدول پردازشهای سیستم باقی میماند و معمولاً با وضعیت Z یا عبارت <defunct> دیده میشود.
مشاهده یک Zombie Process لزوماً به معنی وجود مشکل جدی در سرور نیست؛ اما اگر تعداد آنها مرتب افزایش پیدا کند، باید پردازش والد و برنامهای که این وضعیت را ایجاد میکند شناسایی شود. در ادامه، روش پیدا کردن زامبیها، بررسی Parent Process و رفع اصولی مشکل را مرحلهبهمرحله بررسی میکنیم.
خلاصه: پردازش زامبی قبلاً اجرای خود را تمام کرده است؛ بنابراین kill -9 روی خود Zombie Process مشکل را حل نمیکند. ابتدا باید PID و PPID زامبی را پیدا کنید، پردازش والد را بررسی کنید و در صورت وجود مشکل، سرویس یا برنامه والد را از روش صحیح مدیریت کنید.
پردازش زامبی چیست و چگونه ایجاد میشود؟
وقتی برنامهای در لینوکس اجرا میشود، سیستم به آن یک شناسه به نام PID میدهد. برنامه میتواند پردازشهای دیگری بسازد؛ در این رابطه، سازنده را «والد» و پردازش ایجادشده را «فرزند» مینامیم.
فرض کنید برنامه پردازش تصویر برای ساخت یک تصویر بندانگشتی، یک فرزند ایجاد میکند. فرزند کارش را تمام میکند و خارج میشود. حالا والد باید نتیجه پایان کار او را دریافت کند.
این کار معمولاً با توابع خانواده wait()، مثل waitpid() انجام میشود. دریافت وضعیت خروج و جمعآوری اطلاعات باقیمانده فرزند را Reaping مینامند. اگر والد این مرحله را انجام ندهد، فرزندِ پایانیافته به شکل زامبی باقی میماند.
بنابراین، مشکل همیشه این نیست که والد از پایان کار فرزند خبر ندارد؛ ممکن است برنامه درست نوشته نشده باشد یا در مسیر مدیریت فرزندان به مشکل خورده باشد. توضیح فنی این سازوکار را میتوانید در مستندات wait در لینوکس بخوانید.
علت ایجاد پردازش زامبی در لینوکس چیست؟
ایجاد یک Zombie Process معمولاً به نحوه مدیریت پردازشهای فرزند توسط برنامه والد مربوط میشود. زمانی که یک Child Process اجرای خود را تمام میکند، سیستمعامل اطلاعاتی مانند وضعیت خروج آن را تا زمانی که Parent Process آن را دریافت کند نگه میدارد.
اگر پردازش والد این اطلاعات را با توابعی مانند wait() یا waitpid() دریافت نکند، پردازش فرزند در وضعیت Zombie باقی میماند.
دلایل رایج ایجاد پردازش زامبی عبارتاند از:
وجود باگ در برنامهای که Child Process ایجاد میکند
مدیریت نادرست پردازشهای فرزند
اجرا نشدن صحیح wait() یا waitpid()
خطا در Workerهای یک سرویس
مدیریت نادرست Processها در اسکریپتهای Bash یا برنامههای سفارشی
استفاده از کانتینری که PID 1 آن وظیفه Reaping را بهدرستی انجام نمیدهد
برای مثال، فرض کنید یک برنامه برای انجام چند کار همزمان مرتب پردازشهای فرزند ایجاد میکند. اگر این پردازشها تمام شوند اما برنامه والد وضعیت خروج آنها را دریافت نکند، تعداد Zombie Processها بهمرور افزایش پیدا میکند.
به همین دلیل، مشاهده یک پردازش زامبی معمولاً بهتنهایی نگرانکننده نیست؛ اما افزایش مداوم تعداد آنها میتواند نشانه وجود مشکل در برنامه یا نحوه مدیریت Child Processها باشد.
تفاوت پردازش زامبی با پردازش خوابیده و یتیم
هر پردازشی که در حال استفاده از CPU نیست، زامبی محسوب نمیشود. برای تشخیص، باید وضعیت آن را بررسی کنید.
وضعیت
مفهوم
اجرای برنامه تمام شده است؟
R
در حال اجرا یا آماده دریافت CPU
خیر
S
منتظر یک رویداد؛ خواب قابل وقفه
خیر
D
انتظار غیرقابل وقفه، معمولاً مرتبط با I/O
خیر
T
متوقفشده، مثلاً با سیگنال کنترل اجرای کار
لزوماً خیر
Z
پایانیافته، اما وضعیت خروج هنوز جمعآوری نشده
بله
پردازش یتیم یا Orphan هم مفهوم متفاوتی دارد: والدش پایان یافته است. یک پردازش یتیم ممکن است همچنان کار کند؛ در مقابل، اجرای زامبی قبلاً تمام شده است.
وقتی والد خارج میشود، مسئولیت فرزندانش معمولاً به init یا یک Subreaper منتقل میشود. Subreaper پردازشی است که میتواند مسئولیت فرزندان یتیمِ زیرمجموعه خود را بپذیرد. در محیط کانتینری، نحوه مدیریت این مسئولیت اهمیت بیشتری دارد.
بنابراین، Zombie و Orphan مترادف نیستند؛ Zombie پردازشی است که اجرای آن تمام شده ولی هنوز Reap نشده است، در حالی که Orphan میتواند همچنان در حال اجرا باشد.
آیا پردازش زامبی باعث کندی سرور میشود؟
زامبی دیگر کدی اجرا نمیکند، بنابراین خودش مانند یک برنامه فعال CPU مصرف نمیکند. حافظه اجرایی معمول برنامه هم پس از پایان کار آزاد میشود؛ آنچه باقی میماند، اطلاعات محدودی مثل شناسه و وضعیت خروج است.
بااینحال، هر زامبی یک جایگاه در جدول پردازشها اشغال میکند. انباشتهشدن آنها میتواند در نهایت ایجاد پردازشهای جدید را با مشکل روبهرو کند. برای همین، روند افزایش تعداد مهمتر از دیدن یک مورد گذراست.
برای ارزیابی وضعیت، سه سؤال بپرسید: آیا تعداد زامبیها بیشتر میشود؟ آیا همگی والد مشترک دارند؟ آیا همزمان خطا در اجرای کارهای جدید یا اختلال سرویس دیده میشود؟ این اطلاعات کمک میکند تصمیم بگیرید فقط پایش لازم است یا باید برنامه را بررسی کنید.
اگر همزمان سایت یا سرویس شما در دسترس نیست، بررسی را به زامبیها محدود نکنید. راهنمای اقدامات ضروری هنگام داون شدن سرور برای بررسی سایر علتهای اختلال هم مفید است.
۵ مرحله شناسایی و رفع پردازش زامبی در لینوکس
دستورهای این بخش برای ابزارهای رایج GNU/Linux مانند Ubuntu، Debian و AlmaLinux نوشته شدهاند. بخش مربوط به systemctl مخصوص سیستمهایی است که سرویسهایشان با systemd مدیریت میشوند.
شمارههای 24817 و 24001 و نام example-worker.service در مثالها فرضیاند؛ آنها را با مقادیر واقعی سرور خود جایگزین کنید.
مرحله ۱: تعداد زامبیها را در top بررسی کنید
ابتدا دستور زیر را اجرا کنید:
Copy
top
Bash
در بخش بالای خروجی، معمولاً خطی مشابه این میبینید:
این خروجی نمونه، وجود دو زامبی را نشان میدهد. چند نوبت وضعیت را نگاه کنید تا متوجه شوید تعداد ثابت میماند، کم میشود یا افزایش پیدا میکند. برای خروج از top کلید q را بزنید.
این ابزار برای دیدن وضعیت کلی مناسب است. در مرحله بعد، شناسه و والد زامبیها را دقیقتر استخراج میکنیم.
مرحله ۲: زامبیها را با ps پیدا کنید
دستور زیر فقط پردازشهایی را نمایش میدهد که وضعیتشان با Z شروع میشود:
Copy
ps-eopid,ppid,stat,comm|awk'NR == 1 || $3 ~ /^Z/'
Bash
خروجی نمونه:
Copy
PID PPID STAT COMMAND 24817 24001 Z image-worker 24818 24001 Z image-worker
Plaintext
در این مثال، دو زامبی با شناسههای 24817 و 24818 به والد 24001 مربوطاند. ستون PID شناسه خود پردازش، PPID شناسه والد، STAT وضعیت و COMMAND نام برنامه را نشان میدهد.
اگر تنها عنوان ستونها را دیدید، در لحظه اجرای دستور زامبیای در پردازشهای قابل مشاهده پیدا نشده است. این دستور برخلاف جستوجوی ساده نام برنامهها، بر اساس ستون وضعیت فیلتر میکند.
برای شمارش زامبیها هم میتوانید از این دستور استفاده کنید:
این خروجی کمک میکند نام برنامه، کاربر اجراکننده و زمان شروع والد را بررسی کنید. برای دیدن فرزندان مستقیم همان والد نیز میتوانید دستور زیر را اجرا کنید:
Copy
ps--ppid24001-opid,ppid,stat,comm
Bash
اگر میخواهید رابطه پردازش والد و فرزندها را به شکل درختی مشاهده کنید، میتوانید از دستور pstree استفاده کنید:
Copy
pstree-p24001
Bash
این دستور ساختار پردازشهای زیرمجموعه Parent را نمایش میدهد و در برنامههایی که چند Worker یا Child Process دارند، پیدا کردن منشأ زامبیها را سادهتر میکند.
اگر چند زامبی والد مشترک دارند، همان برنامه نقطه شروع مناسبی برای عیبیابی است. برای مثال، ارتباط زامبیها با یک Worker خاص به شما میگوید ابتدا لاگ و نسخه همان Worker را بررسی کنید.
در سیستم مبتنی بر systemd، دستور زیر ممکن است سرویس یا واحد شامل آن PID را نشان دهد:
Copy
systemctlstatus24001
Bash
واحد نمایشدادهشده را بررسی کنید؛ ممکن است یک سرویس مشخص، یک نشست کاربری یا یک Scope باشد. صرف دیدن نام یک واحد به معنی مناسببودن ریاستارت آن نیست. این قابلیت در مستندات systemctl توضیح داده شده است.
مرحله ۴: مشکل والد را از مسیر سرویس برطرف کنید
پس از شناسایی سرویس واقعی، پیش از هر تغییری وضعیت و لاگ آن را ببینید:
زمان شروع مشکل را با تغییرات اخیر برنامه، خطاهای لاگ و اجرای کارهای زمانبندیشده مقایسه کنید. هدف فقط خالیکردن فهرست زامبیها نیست؛ باید بفهمید چه چیزی آنها را ایجاد میکند.
اگر ریاستارت همان سرویس مناسب است و وقفه آن را در نظر گرفتهاید، اجرا کنید:
Copy
sudosystemctlrestartexample-worker.service
Bash
ریاستارت ممکن است کارهای در حال اجرای سرویس را قطع کند. اگر برنامه روش توقف تدریجی یا تخلیه صف دارد، از همان روش مستند استفاده کنید. برای برنامههایی که زیر نظر systemd نیستند نیز به مدیر پردازش یا سازوکار اجرای خود برنامه مراجعه کنید.
متوقفکردن والد میتواند باعث انتقال مسئولیت جمعآوری فرزندان پایانیافته شود، اما نتیجه به عملکرد والد جدید هم وابسته است. اگر والد PID 1 است، آن را برای حذف زامبیها متوقف نکنید؛ در این حالت باید سازوکار init یا محیط کانتینری بررسی شود.
برای آشنایی با توقف پردازشهای فعال و تفاوت سیگنالها، مقاله Kill Process در لینوکس را بخوانید.
مرحله ۵: نتیجه را دوباره بررسی کنید
بعد از اقدام اصلاحی، فهرست زامبیها را دوباره بگیرید:
Copy
ps-eopid,ppid,stat,comm|awk'NR == 1 || $3 ~ /^Z/'
Bash
سپس سلامت سرویس را بررسی کنید و همان کاری را که قبلاً زامبی ایجاد میکرد، در شرایط کنترلشده تکرار کنید. اگر زامبیها برمیگردند، ریاستارت فقط اثر موقتی داشته است؛ گزارش خطا باید همراه با نام برنامه، نسخه، والد مشترک و زمان وقوع برای بررسی فنی ثبت شود.
ناپدیدشدن یک PID بهتنهایی کافی نیست. معیار بهتر، ادامه کار صحیح سرویس و متوقفشدن روند انباشت زامبیهاست.
چرا kill -9 روی پردازش زامبی کار نمیکند؟
دستور kill -9 سیگنال SIGKILL میفرستد تا اجرای یک پردازش پایان یابد. اما اجرای زامبی قبلاً تمام شده است؛ چیزی برای متوقفکردن باقی نمانده است.
در نتیجه، فرستادن این سیگنال به PID زامبی مرحله جمعآوری وضعیت خروج را انجام نمیدهد. مسئله باید از سمت والد یا پردازشی که مسئولیت فرزند را پذیرفته است، حل شود. نمونه آموزشی Zombie Process در کتاب The Linux Programming Interface همین بیاثر بودن SIGKILL روی زامبی را نشان میدهد.
ارسال دستی SIGCHLD به والد هم راهحل قطعی نیست. فقط در صورتی ممکن است کمکی کند که برنامه برای دریافت آن، رسیدگی مناسبی پیاده کرده باشد. این سیگنال بهخودیخود جای اجرای wait() را نمیگیرد.
به همین دلیل، اجرای دستورهای گروهی که همه والدها را با kill -9 متوقف میکنند، روش مناسبی برای نگهداری سرور نیست؛ ممکن است چند سرویس سالم هم قطع شوند.
رفع پردازش زامبی در Docker
داخل کانتینر، برنامه اصلی ممکن است نقش PID 1 را هم داشته باشد. اگر برنامه یا اسکریپت راهانداز، مدیریت مناسبی برای فرزندان نداشته باشد، باید هم کد برنامه و هم نحوه اجرای کانتینر بررسی شوند.
برای اجرای کانتینر با یک init سبک، گزینه --init را به دستور docker run اضافه کنید:
Copy
dockerrun--initYOUR_IMAGE
Bash
در Docker Compose، گزینه زیر را به تعریف سرویس مربوط اضافه کنید:
Copy
services:app:image:your-image:your-taginit:true
YAML
این نمونهها را با ایمیج و تنظیمات واقعی برنامه تطبیق دهید. تغییر Compose نیز برای اثرگذاری روی کانتینر موجود به بازسازی همان سرویس نیاز دارد؛ زمان اجرای آن را با وقفه احتمالی برنامه هماهنگ کنید.
init سبک به مدیریت سیگنالها و جمعآوری فرزندان یتیمشده کمک میکند، اما نمیتواند فرزندِ یک والد زنده و معیوب را بهجای همان والد جمعآوری کند. بنابراین، جایگزین اصلاح باگ مدیریت فرزندان نیست. برای جزئیات، راهنمای مدیریت پردازشها در Docker و گزینه init در Docker Compose را ببینید.
چگونه از ایجاد دوباره زامبیها جلوگیری کنیم؟
رفع پایدار این مشکل به برنامه ایجادکننده فرزندان مربوط است. در کد برنامه، همه مسیرهای پایان فرزند، از اجرای موفق تا خطا و لغو کار، باید مدیریت شوند.
برای نمونه، اگر برنامه در حالت عادی نتیجه فرزند را دریافت میکند اما در مسیر خطا این مرحله را رد میکند، مشکل ممکن است فقط هنگام شکست یک کار مشخص دیده شود. ثبت زمان و نوع آن کار، تشخیص علت را آسانتر میکند.
برای نگهداری بهتر، تعداد زامبیها و والدهای پرتکرار را پایش کنید، نسخههای اصلاحشده برنامه را بررسی کنید و ریاستارت دورهای را جایگزین عیبیابی نکنید. در گزارشها، شناسهها، زمان مشاهده و لاگ مرتبط را کنار هم نگه دارید؛ PID بهتنهایی سرنخ ماندگاری نیست.
جمعبندی
پردازش زامبی در لینوکس پردازشی نیست که همچنان در حال اجرا باشد؛ اجرای آن تمام شده و تنها اطلاعات محدودی از آن تا زمان Reaping توسط Parent باقی مانده است. به همین دلیل، اجرای kill -9 روی خود Zombie Process راهحل مشکل نیست.
برای رفع اصولی، ابتدا با top یا ps وجود زامبیها را بررسی کنید، سپس از طریق PPID پردازش والد را پیدا کرده و وضعیت سرویس و لاگهای آن را بررسی کنید. اگر زامبیها پس از Restart دوباره ایجاد میشوند، علت اصلی معمولاً در نحوه مدیریت Child Processها توسط برنامه قرار دارد و باید همان بخش اصلاح شود.
برای اجرای سایت، اپلیکیشن یا سرویسهای لینوکسی به یک سرور مناسب نیاز دارید؟ پلنهای سرور مجازیو اختصاصی وبداده را بررسی کنید، منابع و قیمتها را مقایسه کنید و متناسب با نیازتان سرور موردنظر را سفارش دهید.
پرسشهای متداول درباره پردازش زامبی
آیا Zombie Process همان ویروس است؟
خیر. واژه زامبی در اینجا نام یک وضعیت پردازش است و بهتنهایی نشانه آلودگی نیست. برای تشخیص فعالیت مشکوک باید خود برنامه و شواهد دیگری بررسی شوند.
آیا برای حذف زامبی باید سرور را ریبوت کنیم؟
معمولاً ابتدا باید برنامه والد را شناسایی و همان سرویس را بررسی کرد. ریبوت کل سرور ممکن است وضعیت فعلی را پاک کند، اما اگر ایراد برنامه باقی بماند، مشکل میتواند دوباره رخ دهد.
آیا افزایش RAM مشکل را برطرف میکند؟
افزایش رم، ایراد دریافتنشدن وضعیت خروج فرزندان را اصلاح نمیکند. اگر تعداد زامبیها رو به افزایش است، باید علت ایجاد آنها در برنامه والد بررسی شود.
عبارت defunct در خروجی ps یعنی چه؟
این عبارت معمولاً کنار نام پردازشی دیده میشود که پایان یافته اما هنوز جمعآوری نشده است. وضعیت Z در ستون مربوط، معیار مستقیم تشخیص زامبی است.
اگر بعد از ریاستارت دوباره زامبی ایجاد شد چه کنیم؟
نام و نسخه سرویس، زمان ایجاد زامبیها، والد مشترک و خطاهای همان بازه را ثبت کنید. سپس مسیر ایجاد و پایان فرزندان را در برنامه بررسی کنید؛ تکرار ریاستارت بهتنهایی اصلاح دائمی محسوب نمیشود.