دسته بندی:

Kill کردن پروسه‌ها در لینوکس؛ راهنمای کامل kill، pkill و سیگنال‌ها در CentOS و Ubuntu

Kill کردن پروسه‌ها در لینوکس؛ راهنمای کامل kill، pkill و سیگنال‌ها در CentOS و Ubuntu

حدود ۱۱ دقیقه مطالعه

یک پروسه روی سرور گیر کرده است: yum نیمه‌کاره متوقف شده و هیچ نصب دیگری اجرا نمی‌شود، یک اسکریپت PHP تمام CPU را گرفته، یا سرویسی به دستور stop جواب نمی‌دهد و load سرور بالا رفته است. کار در این وضعیت سه مرحله دارد: شناسایی دقیق پروسه، بستن آن با کم‌خطرترین سیگنال ممکن، و اطمینان از این‌که واقعا بسته شده است. دستورهای زیر روی CentOS، AlmaLinux، Rocky Linux، Debian و Ubuntu یکسان هستند و تفاوت‌ها هر جا وجود داشته باشد جداگانه گفته شده است.

پیش‌نیازها

ابتدا PuTTY یا هر برنامه دیگری که با آن به شل لینوکس متصل می‌شوید را باز کنید و با SSH وارد سرور شوید؛ روی لینوکس و macOS همان ssh user@server-ip کافی است.

هر کاربر فقط می‌تواند پروسه‌های متعلق به خودش را kill کند. برای بستن پروسه‌های کاربران دیگر یا سرویس‌های سیستمی حتما باید با یوزر root لاگین کنید یا دستور را با sudo اجرا کنید؛ در غیر این صورت خطای Operation not permitted می‌گیرید و پروسه دست‌نخورده باقی می‌ماند.

گام اول: شناسایی پروسه و پیدا کردن PID

برای kill کردن هر پروسه ابتدا لازم است process id (PID) آن را بیابید:

ps aux | grep yum
ps aux | grep apt-get
ps aux | grep named
ps aux | grep php-fpm

فرمان ps aux فهرست تمام پروسه‌های در حال اجرای سیستم را برمی‌گرداند و grep ابزاری است که از آن خروجی متنی فقط سطرهای مطابق الگو را نشان می‌دهد. عدد ستون دوم همان PID است و ستون آخر، دستور کاملی که پروسه با آن اجرا شده. به جای yum در این دستور نام هر پروسه دیگری را که دنبالش هستید بگذارید؛ الگو می‌تواند هر رشته‌ای باشد.

یک به‌روزرسانی درباره yum لازم است: yum ابزار نصب پیش‌فرض CentOS نسخه ۶ و ۷ است (معادل apt و apt-get در Debian و Ubuntu)، اما از CentOS 8 به بعد و در AlmaLinux، Rocky Linux و Fedora جای آن را dnf گرفته و yum فقط یک نام مستعار برای dnf باقی مانده است. روی سرورهای امروزی دنبال پروسه dnf بگردید:

ps aux | grep dnf

نکته‌ای که همیشه تازه‌کارها را گمراه می‌کند: خودِ grep هم یک پروسه است و در خروجی ظاهر می‌شود؛ اگر PID آن سطر را kill کنید عملا هیچ کاری نکرده‌اید. ساده‌ترین راه، استفاده از pgrep است که فقط پروسه‌های واقعی را برمی‌گرداند:

pgrep -a yum

یا ترفند قدیمی قرار دادن یک حرف داخل کروشه، که باعث می‌شود الگوی grep با خودش مطابقت نکند. الگو را حتما داخل کوتیشن بگذارید، وگرنه شل خودِ کروشه را به‌عنوان الگوی فایل تفسیر می‌کند و اگر در دایرکتوری جاری فایلی به نام yum وجود داشته باشد ترفند از کار می‌افتد:

ps aux | grep "[y]um"

run-ps-aux-to-fetch-process-list

اگر نام پروسه مقصر را نمی‌دانید و فقط می‌دانید سرور کند شده، فهرست را بر اساس مصرف CPU مرتب کنید:

ps -eo pid,ppid,user,%cpu,%mem,etime,args --sort=-%cpu | head

ستون etime نشان می‌دهد پروسه چه مدت در حال اجراست؛ اسکریپتی که قرار بوده چند ثانیه طول بکشد و حالا چند ساعت زنده مانده، معمولا همان چیزی است که دنبالش هستید. ابزارهای تعاملی top و htop هم همین اطلاعات را زنده نشان می‌دهند و امکان kill مستقیم دارند: در top کلید k و در htop کلید F9.

گام دوم: سیگنال‌ها و تفاوت آن‌ها

دستور kill برخلاف نامش پروسه را «نمی‌کشد»؛ فقط یک سیگنال به آن می‌فرستد و واکنش به آن سیگنال به خود پروسه بستگی دارد. سینتکس کار:

kill [-سیگنال] PID

PID می‌بایست با ID که در مرحله قبل به دست آورده‌ایم جایگزین شود و سیگنال یک پارامتر اختیاری است. سه سیگنالی که در عمل بیشتر به کار می‌آیند:

  • ۱۵ یا SIGTERM — ایمن‌ترین روش و سیگنال پیش‌فرض دستور kill. به پروسه می‌گوید کارش را تمام کند و برود؛ پروسه فرصت دارد فایل‌ها را ببندد، تراکنش‌ها را کامل کند و فایل‌های موقت و قفل خود را پاک کند. همیشه از این‌جا شروع کنید.
  • ۹ یا SIGKILL — پروسه را به‌صورت force و بدون هیچ فرصتی از بین می‌برد. این سیگنال قابل نادیده گرفتن یا مدیریت شدن توسط پروسه نیست و دقیقا به همین دلیل آخرین راه‌حل است نه اولین: هیچ پاک‌سازی‌ای انجام نمی‌شود و فایل قفل، سوکت یا داده نیمه‌نوشته باقی می‌ماند.
  • ۱ یا SIGHUP — در اصل به معنای «قطع شدن ترمینال» است. برنامه‌های معمولی با دریافت آن بسته می‌شوند، اما بیشتر سرویس‌ها و دیمن‌ها آن را «فایل کانفیگ را دوباره بخوان» تفسیر می‌کنند؛ یعنی راهی برای reload سرویس بدون قطع شدن آن. اصلاح نسبت به نسخه قبلی این مطلب: SIGHUP دستور kill را دوباره اجرا نمی‌کند؛ اگر پروسه‌ای به kill معمولی جواب نداد، پاسخ درست ارسال SIGKILL است نه SIGHUP.

فهرست کامل سیگنال‌ها را با kill -l می‌بینید و در صورت نیاز به اطلاعات بیشتر در خصوص سیگنال‌ها، می‌توانید این مقاله را مطالعه نمایید.

چند مثال از نحوه kill کردن پروسه‌ها

پیش‌فرض، توقف امن:

kill 25632

Force kill:

kill -9 25632

Hangup (در سرویس‌ها معمولا reload کانفیگ):

kill -1 25632

به جای شماره می‌توانید نام سیگنال را بنویسید که خواناتر است:

kill -TERM 25632
kill -HUP 25632
kill -KILL 25632

ترتیب درست کار این است: اول kill PID، چند ثانیه صبر، بررسی این‌که پروسه رفته یا نه، و تنها اگر هنوز زنده بود kill -9 PID. رفتن مستقیم سراغ -9 عادت بدی است که روی دیتابیس‌ها و سرویس‌های حساس به داده گران تمام می‌شود.

بستن چند پروسه با هم: pkill و killall

اگر ده‌ها worker از یک برنامه در حال اجراست، گرفتن تک‌تک PIDها منطقی نیست:

pkill httpd
killall httpd
pkill -u baduser

تفاوت این دو مهم است: killall با نام دقیق پروسه مطابقت می‌کند، اما pkill با بخشی از نام هم مطابقت می‌گیرد و pkill -f کل خط فرمان را جست‌وجو می‌کند. همین pkill را خطرناک می‌کند؛ مثلا pkill php می‌تواند پروسه‌هایی را ببندد که اصلا در نظر نداشتید. مثال سوم هم خطرناک است: pkill -u تمام پروسه‌های آن کاربر را می‌بندد، از جمله جلسه SSH او؛ اگر نام کاربر را اشتباه بنویسید و به کاربر خودتان برسد، ارتباطتان با سرور قطع می‌شود. قبل از هر pkill، همان الگو را یک بار با pgrep -a امتحان کنید تا ببینید دقیقا چه چیزی قرار است بسته شود.

یک نکته عملی: killall و fuser هر دو از بسته psmisc می‌آیند و روی نصب‌های minimal ممکن است موجود نباشند؛ در آن صورت با dnf install psmisc یا apt install psmisc نصبشان کنید. pkill و pgrep بخشی از procps-ng هستند و تقریبا همیشه نصب‌اند.

گام سوم: بررسی این‌که پروسه واقعا بسته شد

دستور kill معمولا هیچ خروجی نمی‌دهد و سکوت آن به معنای موفقیت نیست، فقط یعنی سیگنال ارسال شد. برای بررسی:

ps -p 25632
pgrep -a yum

دقت کنید که ps -p حتی وقتی پروسه‌ای پیدا نکند هم سطر عنوان ستون‌ها را چاپ می‌کند؛ اگر زیر آن سطر چیزی نبود یعنی پروسه رفته است. روش دقیق‌تر، ارسال «سیگنال صفر» است که چیزی نمی‌فرستد و فقط وجود پروسه را بررسی می‌کند:

kill -0 25632; echo $?

خروجی 0 یعنی پروسه هنوز زنده است و خروجی 1 یعنی دیگر وجود ندارد. تنها استثنا این است که اگر پروسه متعلق به کاربر دیگری باشد و شما root نباشید، خروجی هم 1 می‌شود ولی پیام Operation not permitted هم چاپ می‌شود؛ یعنی پروسه هنوز زنده است. برای همین این بررسی را با root انجام دهید.

پروسه‌های yum و dnf که گیر می‌کنند

چنانچه پروسه‌های yum یا dnf گیر کنند، اغلب برای این منظور force kill نیاز است و در برخی موارد لازم است حتما با یوزر root لاگین نمایید؛ چنانچه با user root لاگین نباشید نمی‌توانید این دسته از پروسه‌ها را kill کنید.

root-user-force-kills-process

اما کار با kill تمام نمی‌شود. پکیج‌منیجرها یک فایل قفل نگه می‌دارند تا دو نصب هم‌زمان اجرا نشود و اگر پروسه با -9 بسته شود این قفل باقی می‌ماند و دستور بعدی با پیامی مثل Another app is currently holding the yum lock متوقف می‌شود.

روی سیستم‌های مبتنی بر yum فایل قفل /var/run/yum.pid است. پیش از حذف آن مطمئن شوید هیچ پروسه‌ای واقعا در حال کار نیست:

ps aux | grep "[y]um"
rm -f /var/run/yum.pid

روی dnf قفل‌ها در دو مسیر ساخته می‌شوند: قفل دانلود و متادیتا در /var/cache/dnf و قفل دیتابیس rpm در /var/lib/dnf. آن‌ها را ببینید و تنها در صورتی که هیچ پروسه dnf در حال اجرا نیست حذف کنید:

pgrep -a dnf
ls -l /var/cache/dnf/*.pid /var/lib/dnf/*.pid

اگر تراکنش نصب نیمه‌کاره مانده باشد، پس از آزاد شدن قفل وضعیت پکیج‌ها را بررسی کنید: روی dnf با dnf check و روی سیستم‌های قدیمی yum با ابزار yum-complete-transaction از بسته yum-utils که تراکنش ناتمام را کامل می‌کند.

معادل همین ماجرا در Debian و Ubuntu، قفل‌های apt و dpkg هستند. ابتدا ببینید چه پروسه‌ای قفل را نگه داشته:

fuser -v /var/lib/dpkg/lock-frontend

اگر پروسه‌ای وجود نداشت و فقط قفل بی‌صاحب مانده بود، فایل‌های /var/lib/dpkg/lock-frontend، /var/lib/dpkg/lock و /var/cache/apt/archives/lock را حذف و سپس نصب نیمه‌کاره را جمع کنید:

dpkg --configure -a

هشدار مهم: قفل را تنها وقتی حذف کنید که مطمئن شده‌اید هیچ پروسه‌ای در حال کار نیست. حذف قفل در حالی که نصب واقعا در جریان است می‌تواند دیتابیس پکیج‌ها را خراب کند. ضمنا بسیاری از «هنگ»های apt در واقع اجرای خودکار unattended-upgrades در پس‌زمینه است که چند دقیقه بعد خودش تمام می‌شود.

وقتی حتی kill -9 هم جواب نمی‌دهد

سه حالت وجود دارد که در آن‌ها SIGKILL هم پروسه را از فهرست حذف نمی‌کند. ابتدا وضعیت پروسه را ببینید:

ps -o pid,stat,cmd -p 25632
  • حرف D در ستون STAT (uninterruptible sleep). پروسه منتظر پاسخ سخت‌افزار یا فایل‌سیستم است؛ معمولا یک دیسک کند، یک RAID خراب یا یک mount شبکه‌ای مثل NFS که پاسخ نمی‌دهد. در این حالت هیچ سیگنالی به پروسه نمی‌رسد و kill کردن آن ممکن نیست؛ باید ریشه را برطرف کنید. خروجی dmesg معمولا علت را نشان می‌دهد.
  • حرف Z در ستون STAT (zombie). پروسه در واقع تمام شده و فقط یک ردیف در جدول پروسه‌ها باقی مانده تا والدش نتیجه را بخواند. زامبی جز همان یک خانه در جدول پروسه‌ها نه حافظه مصرف می‌کند نه CPU، و kill کردنش بی‌معناست چون پروسه‌ای باقی نمانده که سیگنال را بگیرد؛ اگر تعدادشان زیاد شد، پروسه والد (ستون PPID) را ری‌استارت کنید.
  • پروسه بلافاصله برمی‌گردد. یعنی سرویسی است که systemd آن را مدیریت می‌کند و با تنظیم Restart= بعد از هر بار بسته شدن دوباره اجرایش می‌کند. راه‌حل، بخش بعدی است.

اگر پروسه یک سرویس systemd است، مستقیم kill نکنید

روی CentOS 7 به بعد، AlmaLinux، Rocky Linux و همه نسخه‌های امروزی Debian و Ubuntu، سرویس‌ها را systemd مدیریت می‌کند. kill مستقیم آن‌ها یا بی‌اثر است (چون دوباره اجرا می‌شوند) یا وضعیت داخلی systemd را با واقعیت ناهماهنگ می‌کند. روش درست:

systemctl stop httpd
systemctl restart httpd
systemctl status httpd

اگر سرویس به stop جواب نداد، خودِ systemd ابزار force دارد که تمام پروسه‌های آن سرویس را با هم می‌بندد:

systemctl kill --signal=SIGKILL httpd

دستورهای قدیمی service httpd restart و /etc/init.d/httpd restart که در راهنماهای قدیمی زیاد دیده می‌شوند امروز فقط یک لایه سازگاری هستند و درخواست را به systemd منتقل می‌کنند؛ روی سرور جدید مستقیم از systemctl استفاده کنید. نام سرویس وب‌سرور در CentOS و خانواده آن httpd و در Debian و Ubuntu apache2 است.

چه پروسه‌هایی را نباید kill کنید

  • PID 1. این پروسه systemd (یا init در سیستم‌های قدیمی) است و کل سیستم به آن وابسته است. کرنل PID 1 را ویژه در نظر می‌گیرد و سیگنالی که خود این پروسه برایش handler نصب نکرده باشد را به آن تحویل نمی‌دهد، اما اصل ماجرا این است که اصلا نباید سراغش بروید.
  • پروسه‌های کرنل. هر چیزی که نامش در خروجی ps داخل کروشه است، مثل [kworker/0:1] یا [kswapd0]، یک نخ کرنل است و با kill بسته نمی‌شود.
  • دیتابیس‌ها با سیگنال ۹٫ بستن mysqld، mariadbd یا postgres با -9 فرصت نوشتن داده‌های بافرشده را از آن‌ها می‌گیرد و در بهترین حالت یک crash recovery طولانی در راه‌اندازی بعدی به همراه دارد. همیشه systemctl stop و کمی صبر.
  • جلسه SSH خودتان. یک pkill -u یا الگوی بیش از حد کلی می‌تواند ارتباط خودتان با سرور را قطع کند؛ روی سروری که کنسول جایگزین ندارید این یعنی دردسر جدی.

پیدا کردن پروسه‌ای که یک پورت یا فایل را اشغال کرده

پرتکرارترین نمونه، خطای Address already in use هنگام استارت وب‌سرور است. برای دیدن این‌که چه پروسه‌ای پورت را گرفته:

ss -ltnp | grep :80
lsof -i :80

دستور ss جایگزین امروزی netstat است؛ بسته net-tools که netstat را فراهم می‌کرد روی توزیع‌های جدید به‌صورت پیش‌فرض نصب نیست. برای دیدن نام پروسه در خروجی ss به دسترسی root نیاز دارید.

اگر فایل یا مسیری هم به دلیل باز بودن توسط یک پروسه قابل unmount یا حذف نیست:

fuser -v /path/to/file
fuser -k -TERM /path/to/file

دقت کنید که fuser -k بدون مشخص کردن سیگنال، به‌صورت پیش‌فرض SIGKILL می‌فرستد؛ به همین دلیل در مثال بالا سیگنال TERM صریحا ذکر شده است.

خلاصه مسیر

  • PID را با pgrep -a یا ps aux | grep پیدا کنید و مراقب باشید سطر مربوط به خود grep را انتخاب نکنید.
  • با kill PID شروع کنید؛ این همان SIGTERM و امن‌ترین حالت است.
  • چند ثانیه صبر و سپس بررسی با ps -p PID.
  • فقط اگر هنوز زنده بود، kill -9 PID.
  • اگر سرویس systemd است، به جای همه این‌ها systemctl stop.
  • بعد از force kill کردن پکیج‌منیجر، حتما فایل قفل و وضعیت تراکنش را بررسی کنید.

آموزش‌های مرتبط