آموزش لینوکس | وب‌داده

پردازش زامبی چیست؟
راهنمای پیدا کردن و رفع آن در لینوکس

پردازش تمام شده، اما چرا هنوز در فهرست پردازش‌های سرور دیده می‌شود؟ در این راهنما یاد می‌گیرید زامبی‌ها و برنامه والدشان را پیدا کنید، دلیل بی‌اثر بودن 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

علت ایجاد پردازش زامبی در لینوکس چیست؟

ایجاد یک 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 بررسی کنید

ابتدا دستور زیر را اجرا کنید:
top
Bash
در بخش بالای خروجی، معمولاً خطی مشابه این می‌بینید:
Tasks: 210 total, 1 running, 207 sleeping, 0 stopped, 2 zombie
Plaintext
این خروجی نمونه، وجود دو زامبی را نشان می‌دهد. چند نوبت وضعیت را نگاه کنید تا متوجه شوید تعداد ثابت می‌ماند، کم می‌شود یا افزایش پیدا می‌کند. برای خروج از top کلید q را بزنید.
این ابزار برای دیدن وضعیت کلی مناسب است. در مرحله بعد، شناسه و والد زامبی‌ها را دقیق‌تر استخراج می‌کنیم.

مرحله ۲: زامبی‌ها را با ps پیدا کنید

دستور زیر فقط پردازش‌هایی را نمایش می‌دهد که وضعیتشان با Z شروع می‌شود:
ps -eo pid,ppid,stat,comm | awk 'NR == 1 || $3 ~ /^Z/'
Bash
خروجی نمونه:
    PID    PPID STAT COMMAND
  24817   24001 Z    image-worker
  24818   24001 Z    image-worker
Plaintext
در این مثال، دو زامبی با شناسه‌های 24817 و 24818 به والد 24001 مربوط‌اند. ستون PID شناسه خود پردازش، PPID شناسه والد، STAT وضعیت و COMMAND نام برنامه را نشان می‌دهد.
اگر تنها عنوان ستون‌ها را دیدید، در لحظه اجرای دستور زامبی‌ای در پردازش‌های قابل مشاهده پیدا نشده است. این دستور برخلاف جست‌وجوی ساده نام برنامه‌ها، بر اساس ستون وضعیت فیلتر می‌کند.
برای شمارش زامبی‌ها هم می‌توانید از این دستور استفاده کنید:
ps -eo stat= | awk '$1 ~ /^Z/ {n++} END {print n+0}'
Bash
جزئیات ستون‌ها و کدهای وضعیت در راهنمای دستور ps آمده است.
شناسایی پردازش زامبی در خروجی ps با وضعیت Z و شناسه والد PPID

مرحله ۳: والد و برنامه ایجادکننده را شناسایی کنید

پس از پیدا کردن PPID، اطلاعات والد را ببینید:
ps -p 24001 -o pid,ppid,user,stat,lstart,args
Bash
این خروجی کمک می‌کند نام برنامه، کاربر اجراکننده و زمان شروع والد را بررسی کنید. برای دیدن فرزندان مستقیم همان والد نیز می‌توانید دستور زیر را اجرا کنید:
ps --ppid 24001 -o pid,ppid,stat,comm
Bash
اگر می‌خواهید رابطه پردازش والد و فرزندها را به شکل درختی مشاهده کنید، می‌توانید از دستور pstree استفاده کنید:
pstree -p 24001
Bash
این دستور ساختار پردازش‌های زیرمجموعه Parent را نمایش می‌دهد و در برنامه‌هایی که چند Worker یا Child Process دارند، پیدا کردن منشأ زامبی‌ها را ساده‌تر می‌کند.
اگر چند زامبی والد مشترک دارند، همان برنامه نقطه شروع مناسبی برای عیب‌یابی است. برای مثال، ارتباط زامبی‌ها با یک Worker خاص به شما می‌گوید ابتدا لاگ و نسخه همان Worker را بررسی کنید.
در سیستم مبتنی بر systemd، دستور زیر ممکن است سرویس یا واحد شامل آن PID را نشان دهد:
systemctl status 24001
Bash
واحد نمایش‌داده‌شده را بررسی کنید؛ ممکن است یک سرویس مشخص، یک نشست کاربری یا یک Scope باشد. صرف دیدن نام یک واحد به معنی مناسب‌بودن ری‌استارت آن نیست. این قابلیت در مستندات systemctl توضیح داده شده است.

مرحله ۴: مشکل والد را از مسیر سرویس برطرف کنید

پس از شناسایی سرویس واقعی، پیش از هر تغییری وضعیت و لاگ آن را ببینید:
sudo systemctl status example-worker.service
sudo journalctl -u example-worker.service --since "30 minutes ago" --no-pager
Bash
زمان شروع مشکل را با تغییرات اخیر برنامه، خطاهای لاگ و اجرای کارهای زمان‌بندی‌شده مقایسه کنید. هدف فقط خالی‌کردن فهرست زامبی‌ها نیست؛ باید بفهمید چه چیزی آن‌ها را ایجاد می‌کند.
اگر ری‌استارت همان سرویس مناسب است و وقفه آن را در نظر گرفته‌اید، اجرا کنید:
sudo systemctl restart example-worker.service
Bash
ری‌استارت ممکن است کارهای در حال اجرای سرویس را قطع کند. اگر برنامه روش توقف تدریجی یا تخلیه صف دارد، از همان روش مستند استفاده کنید. برای برنامه‌هایی که زیر نظر systemd نیستند نیز به مدیر پردازش یا سازوکار اجرای خود برنامه مراجعه کنید.
متوقف‌کردن والد می‌تواند باعث انتقال مسئولیت جمع‌آوری فرزندان پایان‌یافته شود، اما نتیجه به عملکرد والد جدید هم وابسته است. اگر والد PID 1 است، آن را برای حذف زامبی‌ها متوقف نکنید؛ در این حالت باید سازوکار init یا محیط کانتینری بررسی شود.
برای آشنایی با توقف پردازش‌های فعال و تفاوت سیگنال‌ها، مقاله Kill Process در لینوکس را بخوانید.

مرحله ۵: نتیجه را دوباره بررسی کنید

بعد از اقدام اصلاحی، فهرست زامبی‌ها را دوباره بگیرید:
ps -eo pid,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 اضافه کنید:
docker run --init YOUR_IMAGE
Bash
در Docker Compose، گزینه زیر را به تعریف سرویس مربوط اضافه کنید:
services:
  app:
    image: your-image:your-tag
    init: true
YAML
این نمونه‌ها را با ایمیج و تنظیمات واقعی برنامه تطبیق دهید. تغییر Compose نیز برای اثرگذاری روی کانتینر موجود به بازسازی همان سرویس نیاز دارد؛ زمان اجرای آن را با وقفه احتمالی برنامه هماهنگ کنید.
init سبک به مدیریت سیگنال‌ها و جمع‌آوری فرزندان یتیم‌شده کمک می‌کند، اما نمی‌تواند فرزندِ یک والد زنده و معیوب را به‌جای همان والد جمع‌آوری کند. بنابراین، جایگزین اصلاح باگ مدیریت فرزندان نیست. برای جزئیات، راهنمای مدیریت پردازش‌ها در Docker و گزینه init در Docker Compose را ببینید.
مدیریت فرزندان یتیم‌شده در کانتینر با init سبک

چگونه از ایجاد دوباره زامبی‌ها جلوگیری کنیم؟

رفع پایدار این مشکل به برنامه ایجادکننده فرزندان مربوط است. در کد برنامه، همه مسیرهای پایان فرزند، از اجرای موفق تا خطا و لغو کار، باید مدیریت شوند.
برای نمونه، اگر برنامه در حالت عادی نتیجه فرزند را دریافت می‌کند اما در مسیر خطا این مرحله را رد می‌کند، مشکل ممکن است فقط هنگام شکست یک کار مشخص دیده شود. ثبت زمان و نوع آن کار، تشخیص علت را آسان‌تر می‌کند.
برای نگهداری بهتر، تعداد زامبی‌ها و والدهای پرتکرار را پایش کنید، نسخه‌های اصلاح‌شده برنامه را بررسی کنید و ری‌استارت دوره‌ای را جایگزین عیب‌یابی نکنید. در گزارش‌ها، شناسه‌ها، زمان مشاهده و لاگ مرتبط را کنار هم نگه دارید؛ PID به‌تنهایی سرنخ ماندگاری نیست.

جمع‌بندی

پردازش زامبی در لینوکس پردازشی نیست که همچنان در حال اجرا باشد؛ اجرای آن تمام شده و تنها اطلاعات محدودی از آن تا زمان Reaping توسط Parent باقی مانده است. به همین دلیل، اجرای kill -9 روی خود Zombie Process راه‌حل مشکل نیست.
برای رفع اصولی، ابتدا با top یا ps وجود زامبی‌ها را بررسی کنید، سپس از طریق PPID پردازش والد را پیدا کرده و وضعیت سرویس و لاگ‌های آن را بررسی کنید. اگر زامبی‌ها پس از Restart دوباره ایجاد می‌شوند، علت اصلی معمولاً در نحوه مدیریت Child Processها توسط برنامه قرار دارد و باید همان بخش اصلاح شود.
برای اجرای سایت، اپلیکیشن یا سرویس‌های لینوکسی به یک سرور مناسب نیاز دارید؟ پلن‌های سرور مجازی و اختصاصی وب‌داده را بررسی کنید، منابع و قیمت‌ها را مقایسه کنید و متناسب با نیازتان سرور موردنظر را سفارش دهید.

پرسش‌های متداول درباره پردازش زامبی

آیا Zombie Process همان ویروس است؟

خیر. واژه زامبی در اینجا نام یک وضعیت پردازش است و به‌تنهایی نشانه آلودگی نیست. برای تشخیص فعالیت مشکوک باید خود برنامه و شواهد دیگری بررسی شوند.

معمولاً ابتدا باید برنامه والد را شناسایی و همان سرویس را بررسی کرد. ریبوت کل سرور ممکن است وضعیت فعلی را پاک کند، اما اگر ایراد برنامه باقی بماند، مشکل می‌تواند دوباره رخ دهد.

افزایش رم، ایراد دریافت‌نشدن وضعیت خروج فرزندان را اصلاح نمی‌کند. اگر تعداد زامبی‌ها رو به افزایش است، باید علت ایجاد آن‌ها در برنامه والد بررسی شود.

این عبارت معمولاً کنار نام پردازشی دیده می‌شود که پایان یافته اما هنوز جمع‌آوری نشده است. وضعیت Z در ستون مربوط، معیار مستقیم تشخیص زامبی است.

نام و نسخه سرویس، زمان ایجاد زامبی‌ها، والد مشترک و خطاهای همان بازه را ثبت کنید. سپس مسیر ایجاد و پایان فرزندان را در برنامه بررسی کنید؛ تکرار ری‌استارت به‌تنهایی اصلاح دائمی محسوب نمی‌شود.

Setareh Mirzaei
Setareh Mirzaei

فعال حوزه فناوری و زیرساخت، علاقه‌مند به سرورها، لینوکس، توسعه وب و تجربه‌های جدید در دنیای تکنولوژی.

مقاله‌ها: 18
پاسخی بگذارید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *