رفتن به محتوا
LoopX

پروسه‌ها

توی این درس یاد می‌گیری هر برنامه‌ی در حال اجرا یک پروسه (process) است با شماره‌ی یکتای PID؛ با ps aux، top و htop می‌بینی روی سیستم چه می‌گذرد؛ با سیگنال‌ها (kill یعنی SIGTERM و kill -9 یعنی SIGKILL) پروسه را مؤدبانه یا اجباری متوقف می‌کنی؛ کار طولانی را با & به پس‌زمینه می‌بری و با jobs، fg و bg کنترل می‌کنی؛ و با nohup کاری را بعد از بستن ترمینال زنده نگه می‌داری. تمرین: یک پروسه‌ی طولانی را به پس‌زمینه می‌بری، دوباره می‌آوری و متوقفش می‌کنی.

مسئله: کامپیوتر همزمان صدها کار می‌کند

Section titled “مسئله: کامپیوتر همزمان صدها کار می‌کند”

روی هر سیستم لینوکسی همزمان ده‌ها تا صدها برنامه در حال اجراست: وب‌سرور، دیتابیس، ssh، و shell خودت. وقتی سرور کند می‌شود، یک اسکریپت گیر می‌کند یا باید یک کار چندساعته را رها کنی و بروی، باید بتوانی ببینی چه چیزی اجراست، کدام‌ش مشکل دارد و چطور کنترلش کنی.

تشبیه: سفارش‌های یک آشپزخانه

Section titled “تشبیه: سفارش‌های یک آشپزخانه”

هر سفارش در آشپزخانه یک شماره دارد (PID)؛ سرآشپز (هسته) تصمیم می‌گیرد کدام سفارش چقدر از اجاق (CPU) بگیرد. kill مثل این است که به آشپز بگویی «لطفاً این را تمام کن و تمیز کن» (او می‌تواند ظرف‌هایش را جمع کند)؛ kill -9 مثل این است که گاز را قطع کنی: همان لحظه می‌ایستد، بدون هیچ تمیزکاری.

دو سیگنال اصلی توقف: SIGTERM مؤدبانه است و پروسه می‌تواند تمیزکاری کند؛ SIGKILL را هسته اعمال می‌کند و پروسه فرصت واکنش ندارد.

مثال ۱: ps aux، همه‌ی پروسه‌ها

Section titled “مثال ۱: ps aux، همه‌ی پروسه‌ها”

اول چند پروسه‌ی پس‌زمینه می‌سازم تا چیزی برای دیدن باشد (علامت & آخر دستور یعنی «در پس‌زمینه اجرا کن»؛ مثال ۵):

Terminal window
sleep 600 &
sleep 601 &
python3 -c 'import time; time.sleep(602)' &
sleep 1
ps aux
خروجی
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.0 0.0 872 504 ? Ss 21:00 0:00 /sbin/docker-init -- sleep infinity
root 6 0.0 0.0 2280 1220 ? S 21:00 0:00 sleep infinity
ali 350 0.0 0.0 4472 3264 ? Ss 21:07 0:00 bash
ali 359 0.0 0.0 2708 1664 ? S 21:07 0:00 sleep 600
ali 360 0.0 0.0 2708 1640 ? S 21:07 0:00 sleep 601
ali 361 0.0 0.2 14396 8336 ? S 21:07 0:00 python3 -c import time; time.sleep(602)
ali 363 0.0 0.0 8032 3840 ? R 21:07 0:00 ps aux

روی یک سرور واقعی ده‌ها تا صدها خط می‌بینی؛ این آزمایشگاه یک کانتینر کوچک است. ستون‌ها:

ستون معنی
USER صاحب پروسه
PID شماره‌ی یکتای پروسه
%CPU / %MEM سهم CPU (از شروع پروسه) و حافظه‌ی فیزیکی
VSZ / RSS حافظه‌ی مجازی و حافظه‌ی واقعی مصرفی (KB)
TTY ترمینال متصل؛ ? یعنی بدون ترمینال (مثل این آزمایش؛ در ترمینال واقعی pts/0 است)
STAT وضعیت: S خواب (منتظر رویداد)، R در حال اجرا، T متوقف، Z زامبی، s سرپرست session، + پیش‌زمینه
START / TIME زمان شروع / مجموع زمان CPU
COMMAND دستور با آرگومان‌ها

دو پروسه‌ی اول از خود کانتینرند: PID 1 اولین پروسه است و والد همه‌ی پروسه‌ها (در سرور واقعی systemd). حالا ببین هر پروسه والدش (PPID) کیست، و چطور ستون‌های دلخواه بخواهی:

Terminal window
echo "--- ps -ef (سبک UNIX؛ ستون PPID = پروسه‌ی والد؛ فقط سه خط اول):"
ps -ef | head -3
echo "--- ستون‌های دلخواه با -o، مرتب بر اساس حافظه‌ی واقعی (RSS):"
ps -eo pid,user,%mem,rss,etime,args --sort=-rss | head -6
خروجی
--- ps -ef (سبک UNIX؛ ستون PPID = پروسه‌ی والد؛ فقط سه خط اول):
UID PID PPID C STIME TTY TIME CMD
root 1 0 0 21:00 ? 00:00:00 /sbin/docker-init -- sleep infinity
root 6 1 0 21:00 ? 00:00:00 sleep infinity
--- ستون‌های دلخواه با -o، مرتب بر اساس حافظه‌ی واقعی (RSS):
PID USER %MEM RSS ELAPSED COMMAND
361 ali 0.2 8336 00:01 python3 -c import time; time.sleep(602)
366 ali 0.0 3812 00:00 ps -eo pid,user,%mem,rss,etime,args --sort=-rss
350 ali 0.0 3264 00:01 bash
359 ali 0.0 1664 00:01 sleep 600
360 ali 0.0 1640 00:01 sleep 601

ps aux (سبک BSD، بدون خط‌تیره) و ps -ef (سبک UNIX) تقریباً هم‌ارزند؛ با -o هر ستونی را انتخاب می‌کنی و با --sort=-ستون مرتب می‌کنی (- یعنی نزولی). برای پیدا کردن PID از روی اسم، pgrep ساده‌تر از ps | grep است:

Terminal window
echo "--- PID پروسه‌های sleep خودم:"
pgrep -u ali sleep
echo "--- با دستور کامل (-a):"
pgrep -u ali -a sleep
echo "--- جستجو در کل خط فرمان (-f):"
pgrep -a -f time.sleep
echo "--- تعداد (-c):"
pgrep -c -u ali sleep
خروجی
--- PID پروسه‌های sleep خودم:
359
360
--- با دستور کامل (-a):
359 sleep 600
360 sleep 601
--- جستجو در کل خط فرمان (-f):
361 python3 -c import time; time.sleep(602)
--- تعداد (-c):
2

مثال ۲: من، والد و فرزندانم

Section titled “مثال ۲: من، والد و فرزندانم”

برای دیدن ساختار درختی، یک shell واقعی در ترمینال مجازی (tmux، مثل درس‌های قبل) باز می‌کنم. $$ شماره‌ی PID خود shell و $PPID والد اوست:

Terminal window
run_in_tmux() { tmux -u kill-server 2>/dev/null; tmux -u new-session -d -s t -x "${TW:-76}" -y "${TH:-24}" "$@"; sleep 1; }
screen() { tmux capture-pane -t t -p | sed -e :a -e '/^[[:space:]]*$/{$d;N;ba' -e '}'; }
run_in_tmux "bash --norc"
tmux send-keys -t t 'echo "PID من: $$ | PID والد: $PPID"' Enter; sleep 1
tmux send-keys -t t 'ps -o pid,ppid,stat,tty,comm -p $$,$PPID' Enter; sleep 1
tmux send-keys -t t 'sleep 300 &' Enter; sleep 1
tmux send-keys -t t 'ps -o pid,ppid,stat,tty,cmd --ppid $$' Enter; sleep 1
tmux send-keys -t t 'pstree -p $PPID' Enter; sleep 1
screen
خروجی
bash-5.2$ echo "PID من: $$ | PID والد: $PPID"
PID من: 376 | PID والد: 375
bash-5.2$ ps -o pid,ppid,stat,tty,comm -p $$,$PPID
PID PPID STAT TT COMMAND
375 1 Ss ? tmux: server
376 375 Ss pts/0 bash
bash-5.2$ sleep 300 &
[1] 385
bash-5.2$ ps -o pid,ppid,stat,tty,cmd --ppid $$
PID PPID STAT TT CMD
385 376 S pts/0 sleep 300
388 376 R+ pts/0 ps -o pid,ppid,stat,tty,cmd --ppid 376
bash-5.2$ pstree -p $PPID
tmux: server(375)───bash(376)─┬─pstree(391)
└─sleep(385)
bash-5.2$

shell من فرزند tmux: server است، و sleep 300 که الان ساختم فرزند shell است (PPID آن برابر PID shell). pstree -p همین رابطه‌ی درختی را نشان می‌دهد (از بسته‌ی psmisc). هر پروسه‌ای که اجرا می‌کنی فرزند پروسه‌ای است که او را راه انداخته؛ این زنجیره به PID 1 می‌رسد.

مثال ۳: top و htop، نمای زنده

Section titled “مثال ۳: top و htop، نمای زنده”

ps یک عکس لحظه‌ای است؛ top همان را هر چند ثانیه به‌روز می‌کند. برای اینکه چیزی پرمصرف ببینیم، یک پروسه‌ی عمداً شلوغ راه می‌اندازم (yes تا ابد «y» چاپ می‌کند؛ خروجی را دور می‌ریزم) و top را در ترمینال مجازی باز می‌کنم:

Terminal window
yes > /dev/null &
TW=100
run_in_tmux "top"
sleep 2
screen
tmux send-keys -t t q; sleep 1
خروجی
top - 21:07:12 up 1 day, 2:05, 0 user, load average: 0.56, 3.94, 3.39
Tasks: 10 total, 2 running, 8 sleeping, 0 stopped, 0 zombie
%Cpu(s): 3.7 us, 9.8 sy, 0.0 ni, 86.6 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
MiB Mem : 3916.4 total, 409.1 free, 2301.0 used, 1394.7 buff/cache
MiB Swap: 1024.0 total, 774.4 free, 249.6 used. 1615.4 avail Mem
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
395 ali 20 0 2708 1572 1468 R 90.9 0.0 0:00.22 yes
1 root 20 0 872 504 460 S 0.0 0.0 0:00.08 docker-init
6 root 20 0 2280 1220 1140 S 0.0 0.0 0:00.00 sleep
350 ali 20 0 4472 3280 2984 S 0.0 0.1 0:00.02 bash
359 ali 20 0 2708 1664 1568 S 0.0 0.0 0:00.00 sleep
360 ali 20 0 2708 1640 1548 S 0.0 0.0 0:00.00 sleep
361 ali 20 0 14396 8336 5620 S 0.0 0.2 0:00.00 python3
400 ali 20 0 9496 4044 2980 S 0.0 0.1 0:00.01 tmux: server
401 ali 20 0 9072 4960 2888 R 0.0 0.1 0:00.00 top
403 ali 20 0 2708 1684 1592 S 0.0 0.0 0:00.00 sleep

خط‌های بالای top:

  • load average: میانگین تعداد پروسه‌های منتظر CPU در ۱، ۵ و ۱۵ دقیقه‌ی اخیر (درس منابع سیستم).
  • Tasks: تعداد پروسه‌ها به تفکیک وضعیت.
  • %Cpu(s): us (برنامه‌های کاربر)، sy (هسته)، id (بیکار)، wa (منتظر دیسک).
  • MiB Mem و MiB Swap: حافظه‌ی کل، آزاد، مصرفی و کش.

yes با ۱۰۰٪ CPU بالای فهرست است. کلیدهای مفید top: q خروج، P مرتب‌سازی با CPU، M با حافظه، k فرستادن سیگنال به یک PID، 1 نمایش هر هسته جدا، h راهنما. htop همین کار را رنگی و با منوی کلیدهای F انجام می‌دهد (اگر نصب نبود: sudo apt install htop):

Terminal window
run_in_tmux "htop"
sleep 2
screen
tmux send-keys -t t q; sleep 1
tmux -u kill-server 2>/dev/null
خروجی
0[|| 1.3%] 4[ 0.0%]
1[ 0.0%] 5[ 0.0%]
2[||||||||||||||||||||||||||||||||||||100.0%] 6[ 0.0%]
3[| 0.7%] 7[ 0.0%]
Mem[|||||||||||||||||||||||||||||||2.09G/3.82G] Tasks: 10, 0 thr, 0 kthr; 2 running
Swp[|||||||||||| 151M/1024M] Load average: 0.60 3.89 3.37
Uptime: 1 day, 02:06:03
[Main] [I/O]
PID USER PRI NI VIRT RES SHR S CPU%▽MEM% TIME+ Command
395 ali 20 0 2708 1572 1468 R 100.3 0.0 0:05.64 yes
1 root 20 0 872 504 460 S 0.0 0.0 0:00.07 /sbin/docker-init -- sleep infini
6 root 20 0 2280 1220 1140 S 0.0 0.0 0:00.00 sleep infinity
350 ali 20 0 4472 3284 2984 S 0.0 0.1 0:00.02 bash
359 ali 20 0 2708 1664 1568 S 0.0 0.0 0:00.00 sleep 600
360 ali 20 0 2708 1640 1548 S 0.0 0.0 0:00.00 sleep 601
361 ali 20 0 14396 8336 5620 S 0.0 0.2 0:00.00 python3 -c import time; time.slee
413 ali 20 0 9496 4060 2980 S 0.0 0.1 0:00.00 tmux -u new-session -d -s t -x 10
414 ali 20 0 4992 3524 2960 R 0.0 0.1 0:00.00 htop
417 ali 20 0 2708 1692 1596 S 0.0 0.0 0:00.00 sleep 2
F1Help F2Setup F3SearchF4FilterF5Tree F6SortByF7Nice -F8Nice +F9Kill F10Quit

پایین htop منوی کلیدها هست: F3 جستجو، F4 فیلتر، F5 نمای درختی، F6 ترتیب مرتب‌سازی، F9 فرستادن سیگنال (kill) و F10 یا q خروج. با کلیدهای جهت‌نما روی یک پروسه می‌روی و F9 می‌زنی.

مثال ۴: سیگنال‌ها، kill و kill -9

Section titled “مثال ۴: سیگنال‌ها، kill و kill -9”

سیگنال یک پیام کوچک است که هسته (یا یک پروسه‌ی دیگر) به یک پروسه می‌فرستد. فهرست مهم‌ترین‌ها با شماره‌شان:

Terminal window
for s in HUP INT QUIT KILL TERM STOP CONT TSTP; do printf '%s=%s ' "$s" "$(kill -l $s)"; done; echo
echo "--- ابتدای فهرست کامل (kill -l):"
kill -l | head -3
خروجی
HUP=1 INT=2 QUIT=3 KILL=9 TERM=15 STOP=19 CONT=18 TSTP=20
--- ابتدای فهرست کامل (kill -l):
1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP
6) SIGABRT 7) SIGBUS 8) SIGFPE 9) SIGKILL 10) SIGUSR1
11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM
سیگنال شماره چطور می‌رسد معنی
SIGHUP 1 بسته‌شدن ترمینال «ارتباط قطع شد»؛ پیش‌فرض: توقف
SIGINT 2 Ctrl+C قطع کردن
SIGTERM 15 kill PID درخواست مؤدبانه‌ی توقف
SIGKILL 9 kill -9 PID توقف اجباری؛ قابل‌گرفتن نیست
SIGSTOP 19 kill -STOP PID مکث (قابل‌گرفتن نیست)
SIGTSTP 20 Ctrl+Z مکث از طرف ترمینال
SIGCONT 18 kill -CONT PID، fg، bg ادامه

فرق عملی kill و kill -9 را با یک «سرویس» نمونه ببین. این اسکریپت یک فایل lock می‌سازد (نشان «من در حال اجرا هستم»، که برنامه‌های واقعی مثل دیتابیس‌ها هم می‌سازند) و اگر SIGTERM بگیرد، تمیزکاری می‌کند:

worker.sh
#!/bin/bash
# یک «سرویس» نمونه: lock می‌سازد و با SIGTERM تمیزکاری می‌کند
if [ -e /tmp/worker.lock ]; then
echo "[worker] خطا: فایل lock هست؛ نمونه‌ی دیگری در حال اجراست؟"
exit 1
fi
cleanup() { echo "[worker] SIGTERM گرفتم: lock را پاک می‌کنم و خارج می‌شوم"; rm -f /tmp/worker.lock; exit 0; }
trap cleanup TERM
touch /tmp/worker.lock
echo "[worker] شروع شد (PID $$)"
while true; do sleep 1; done

اول با kill (همان SIGTERM پیش‌فرض):

Terminal window
chmod +x worker.sh
./worker.sh > worker.log 2>&1 &
pid=$!
sleep 2
echo "قبل از kill: $(ls /tmp/worker.lock)"
kill $pid
wait $pid
echo "کد خروج: $?"
cat worker.log
ls /tmp/worker.lock 2>&1
خروجی
قبل از kill: /tmp/worker.lock
کد خروج: 0
[worker] شروع شد (PID 438)
[worker] SIGTERM گرفتم: lock را پاک می‌کنم و خارج می‌شوم
ls: cannot access '/tmp/worker.lock': No such file or directory

سرویس سیگنال را گرفت، پیام تمیزکاری را چاپ کرد، lock را پاک کرد و با کد ۰ (موفق) خارج شد. حالا همان را با kill -9:

Terminal window
./worker.sh > worker.log 2>&1 &
pid=$!
sleep 2
kill -9 $pid
wait $pid 2>/dev/null
echo "کد خروج: $? (یعنی 128+9)"
cat worker.log
echo "--- lock هنوز هست:"
ls -l /tmp/worker.lock
echo "--- و اجرای بعدی سرویس:"
./worker.sh
rm -f /tmp/worker.lock
خروجی
کد خروج: 137 (یعنی 128+9)
[worker] شروع شد (PID 448)
--- lock هنوز هست:
-rw-r--r-- 1 ali ali 0 Oct 3 21:07 /tmp/worker.lock
--- و اجرای بعدی سرویس:
[worker] خطا: فایل lock هست؛ نمونه‌ی دیگری در حال اجراست؟

بدون فرصت تمیزکاری، lock مانده و اجرای بعدی سرویس شکست می‌خورد (در برنامه‌های واقعی مثل PostgreSQL همین شکل «فایل pid/lock مانده» دردسر می‌سازد). قانون: اول kill، چند ثانیه صبر، و فقط اگر نمُرد kill -9. دو سیگنال دیگر برای مکث و ادامه‌ی یک پروسه:

Terminal window
sleep 300 &
pid=$!
sleep 1
echo "اجرا: $(ps -o stat= -p $pid)"
kill -STOP $pid; sleep 1
echo "مکث: $(ps -o stat= -p $pid) (T = Stopped)"
kill -CONT $pid; sleep 1
echo "ادامه: $(ps -o stat= -p $pid)"
kill $pid; wait $pid 2>/dev/null
خروجی
اجرا: S
مکث: T (T = Stopped)
ادامه: S
⚡ بررسی سریع

کدام سیگنال را پروسه نمی‌تواند بگیرد و نادیده بگیرد؟

مثال ۵: پس‌زمینه و پیش‌زمینه، &، jobs، fg و bg

Section titled “مثال ۵: پس‌زمینه و پیش‌زمینه، &، jobs، fg و bg”

پیش‌فرض: دستوری که می‌زنی در پیش‌زمینه اجرا می‌شود و ترمینال تا پایانش قفل است. با & آخر دستور آن را به پس‌زمینه می‌فرستی و دستور بعدی را می‌زنی. کارهای shell را jobs فهرست می‌کند، و fg (foreground) و bg (background) آن‌ها را جابه‌جا می‌کنند. اگر کاری در پیش‌زمینه است و می‌خواهی ترمینال را پس بگیری، Ctrl+Z متوقفش می‌کند و بعد bg ادامه‌اش را در پس‌زمینه می‌دهد. این کنترل فقط در shell تعاملی هست، پس دوباره از ترمینال مجازی استفاده می‌کنم:

چرخه‌ی یک کار در shell: Ctrl+Z پروسه را متوقف می‌کند، bg آن را در پس‌زمینه ادامه می‌دهد و fg دوباره به پیش‌زمینه می‌آورد.
Terminal window
TW=76
TH=28
run_in_tmux "bash --norc"
tmux send-keys -t t "sleep 300" Enter; sleep 1
tmux send-keys -t t C-z; sleep 1
tmux send-keys -t t "bg" Enter; sleep 1
tmux send-keys -t t "sleep 400 &" Enter; sleep 1
tmux send-keys -t t "jobs -l" Enter; sleep 1
tmux send-keys -t t "fg %1" Enter; sleep 1
tmux send-keys -t t C-c; sleep 1
tmux send-keys -t t "jobs" Enter; sleep 1
tmux send-keys -t t "kill %2" Enter; sleep 1
tmux send-keys -t t "jobs" Enter; sleep 1
screen
خروجی
bash-5.2$ sleep 300
^Z
[1]+ Stopped sleep 300
bash-5.2$ bg
[1]+ sleep 300 &
bash-5.2$ sleep 400 &
[2] 479
bash-5.2$ jobs -l
[1]- 472 Running sleep 300 &
[2]+ 479 Running sleep 400 &
bash-5.2$ fg %1
sleep 300
^C
bash-5.2$ jobs
[2]+ Running sleep 400 &
bash-5.2$ kill %2
bash-5.2$ jobs
[2]+ Terminated sleep 400
bash-5.2$

قدم‌به‌قدم:

  1. sleep 300 پیش‌زمینه را قفل کرد؛ Ctrl+Z آن را متوقف کرد (Stopped) و shell برگشت.
  2. bg همان کار را در پس‌زمینه ادامه داد ([1]+ sleep 300 &).
  3. sleep 400 & مستقیم در پس‌زمینه شروع شد و شماره‌ی کار ([2]) و PID را داد.
  4. jobs -l هر دو کار را با PID و وضعیت نشان داد (+ کار «جاری» و - کار قبلی).
  5. fg %1 کار شماره‌ی ۱ را به پیش‌زمینه آورد (%1 یعنی «کار شماره‌ی ۱»)، و Ctrl+C (SIGINT) آن را کشت.
  6. kill %2 کار دوم را با شماره‌ی کار (نه PID) کشت؛ jobs بعدش «Terminated» را گزارش کرد.

مثال ۶: nohup، بعد از بستن ترمینال زنده بمان

Section titled “مثال ۶: nohup، بعد از بستن ترمینال زنده بمان”

وقتی ترمینال (یا اتصال ssh) بسته می‌شود، به shell سیگنال SIGHUP («ارتباط قطع شد») می‌رسد و shell آن را به همه‌ی کارهایش می‌رساند؛ پس کارهای پس‌زمینه هم می‌میرند. nohup (no hangup) SIGHUP را برای آن پروسه نادیده می‌گیرد. سه کار می‌سازم: یکی ساده، یکی با nohup، و یکی با disown (شل او را از فهرست کارهایش حذف می‌کند و دیگر SIGHUP نمی‌فرستد). بعد ترمینال را می‌بندم:

Terminal window
TW=76
TH=14
run_in_tmux "bash --norc"
tmux send-keys -t t "sleep 701 &" Enter; sleep 1
tmux send-keys -t t "nohup sleep 702 &" Enter; sleep 1
tmux send-keys -t t "sleep 703 &" Enter; sleep 1
tmux send-keys -t t "disown" Enter; sleep 1
tmux send-keys -t t "ls -l nohup.out" Enter; sleep 1
screen
echo "--- ترمینال را می‌بندم (shell SIGHUP می‌گیرد)..."
tmux -u kill-server
sleep 1
echo "--- کدام زنده ماند؟"
pgrep -a -f 'sleep 70[123]'
خروجی
bash-5.2$ sleep 701 &
[1] 504
bash-5.2$ nohup sleep 702 &
[2] 507
bash-5.2$ nohup: ignoring input and appending output to 'nohup.out'
sleep 703 &
[3] 510
bash-5.2$ disown
bash-5.2$ ls -l nohup.out
-rw------- 1 ali ali 0 Oct 3 21:07 nohup.out
bash-5.2$
--- ترمینال را می‌بندم (shell SIGHUP می‌گیرد)...
--- کدام زنده ماند؟
507 sleep 702
510 sleep 703

فقط 702 (با nohup) و 703 (با disown) زنده ماندند؛ 701 با بسته‌شدن ترمینال مُرد. nohup وقتی خروجی ترمینالی باشد پیام «ignoring input and appending output to nohup.out» را می‌دهد و خروجی را در فایل nohup.out می‌ریزد (برای کنترل بهتر: nohup دستور > log.txt 2>&1 &). روی سرور برای کارهای تعاملی طولانی، ابزار tmux (که در این درس‌ها دیدی) بهتر است، چون بعداً دوباره به همان جلسه وصل می‌شوی.

مثال ۷: پیدا کردن و کشتن پروسه‌ی پرمصرف

Section titled “مثال ۷: پیدا کردن و کشتن پروسه‌ی پرمصرف”

همان سناریوی «سرور کند شده». yes از مثال ۳ هنوز CPU می‌خورد. پرمصرف‌ترین‌ها را مرتب‌شده بگیر، با pgrep PID را پیدا کن و بکش:

Terminal window
echo "--- پرمصرف‌ترین پروسه‌ها بر اساس CPU:"
ps -eo pid,user,%cpu,%mem,etime,comm --sort=-%cpu | head -4
echo "--- PID با نام:"
pgrep -a yes
echo "--- pkill: کشتن با نام (SIGTERM):"
pkill yes
sleep 1
pgrep -a yes || echo "yes دیگر نیست"
خروجی
--- پرمصرف‌ترین پروسه‌ها بر اساس CPU:
PID USER %CPU %MEM ELAPSED COMMAND
395 ali 99.9 0.0 00:34 yes
350 ali 0.0 0.0 00:41 bash
1 root 0.0 0.0 07:34 docker-init
--- PID با نام:
395 yes
--- pkill: کشتن با نام (SIGTERM):
yes دیگر نیست

pkill نام (و killall نام) به همه‌ی پروسه‌هایی که اسمشان منطبق است سیگنال می‌فرستد؛ قبل از زدن، با pgrep -a ببین چه کسانی می‌خورند.

هر پروسه با دو قدم ساخته می‌شود: fork() پروسه‌ی فعلی را کپی می‌کند (فرزند، با PID تازه)، و فرزند با exec() برنامه‌ی دیگری را به‌جای خودش لود می‌کند. بعد والد با wait() منتظر پایان فرزند می‌ماند و کد خروجی‌اش را می‌گیرد. وقتی در shell ls می‌زنی همین اتفاق می‌افتد:

shell برای اجرای هر دستور خودش را کپی (fork) می‌کند، کپی با exec به برنامه‌ی دستور تبدیل می‌شود، و shell با wait منتظر می‌ماند.

و ps اطلاعاتش را از کجا می‌آورد؟ هسته برای هر پروسه یک پوشه‌ی مجازی در /proc/PID می‌سازد (درس ساختار پوشه‌ها):

Terminal window
sleep 603 &
pid=$!
echo "--- بعضی از چیزهای داخل /proc/$pid:"
ls /proc/$pid | head -14 | tr '\n' ' '; echo
echo "--- وضعیت:"
grep -E '^(Name|State|Pid|PPid|Uid)' /proc/$pid/status
echo "--- خط فرمان:"
tr '\0' ' ' < /proc/$pid/cmdline; echo
echo "--- برنامه‌ی اجراشده و فایل‌های باز (fd 0 و 1 و 2 = stdin، stdout، stderr):"
ls -l /proc/$pid/exe /proc/$pid/fd
kill $pid; wait $pid 2>/dev/null
خروجی
--- بعضی از چیزهای داخل /proc/529:
attr autogroup auxv cgroup clear_refs cmdline comm coredump_filter cpuset cwd environ exe fd fdinfo
--- وضعیت:
Name: sleep
State: S (sleeping)
Pid: 529
PPid: 350
Uid: 1001 1001 1001 1001
--- خط فرمان:
sleep 603
--- برنامه‌ی اجراشده و فایل‌های باز (fd 0 و 1 و 2 = stdin، stdout، stderr):
lrwxrwxrwx 1 ali ali 0 Oct 3 21:07 /proc/529/exe -> /usr/bin/sleep
/proc/529/fd:
total 0
lr-x------ 1 ali ali 64 Oct 3 21:07 0 -> /dev/null
l-wx------ 1 ali ali 64 Oct 3 21:07 1 -> pipe:[3088651]
l-wx------ 1 ali ali 64 Oct 3 21:07 2 -> pipe:[3088651]

ps و top کاری جز خواندن همین فایل‌های /proc و مرتب‌کردنشان ندارند. وضعیت‌های پروسه (ستون STAT):

حرف معنی توضیح
R Running در حال اجرا یا آماده‌ی اجرا روی CPU
S Sleeping منتظر یک رویداد (ورودی، شبکه، زمان)
D Disk sleep منتظر دیسک یا I/O (قابل‌قطع نیست؛ زیاد بودنش نشانه‌ی مشکل دیسک)
T Stopped با Ctrl+Z یا SIGSTOP متوقف شده
Z Zombie تمام شده ولی والدش هنوز wait() نکرده
دستور کار
ps aux / ps -ef همه‌ی پروسه‌ها
ps -eo pid,ppid,user,%cpu,%mem,args --sort=-%cpu ستون‌های دلخواه، مرتب‌شده
pgrep -a نام PID و دستور پروسه‌های منطبق
pstree -p درخت پروسه‌ها
top / htop نمای زنده (خروج: q)
kill PID SIGTERM، درخواست مؤدبانه
kill -9 PID SIGKILL، اجباری (آخرین راه)
kill -l فهرست سیگنال‌ها
pkill نام / killall نام سیگنال به همه‌ی پروسه‌های هم‌نام
دستور & اجرا در پس‌زمینه
jobs / jobs -l کارهای این shell
fg %N / bg %N آوردن کار به پیش‌زمینه / ادامه در پس‌زمینه
Ctrl+Z / Ctrl+C متوقف کردن (SIGTSTP) / قطع کردن (SIGINT)
nohup دستور & زنده ماندن بعد از بسته‌شدن ترمینال
disown جدا کردن کار از shell

۱) kill -9 به‌عنوان اولین راه

Section titled “۱) kill -9 به‌عنوان اولین راه”

مثال ۴ را دیدی: بدون فرصت تمیزکاری، lock یا فایل موقت می‌ماند و اجرای بعدی خراب می‌شود؛ برای دیتابیس‌ها می‌تواند به خرابی داده هم برسد. راه‌حل: اول kill، ۵ تا ۱۰ ثانیه صبر، بعد اگر لازم بود kill -9.

۲) سیگنال به PID اشتباه یا مال دیگران

Section titled “۲) سیگنال به PID اشتباه یا مال دیگران”
Terminal window
echo "--- پروسه‌ی root را می‌خواهم بکشم (PID 1):"
kill 1
echo "--- PID که وجود ندارد:"
kill 999999
خروجی
--- پروسه‌ی root را می‌خواهم بکشم (PID 1):
bash: kill: (1) - Operation not permitted
--- PID که وجود ندارد:
bash: kill: (999999) - No such process

Operation not permitted: فقط صاحب پروسه (یا root) می‌تواند به آن سیگنال بفرستد. No such process: PID مرده یا اشتباه است (PID ها بعد از مدتی دوباره استفاده می‌شوند؛ قبل از kill با ps -p PID مطمئن شو همان است که فکر می‌کنی). راه‌حل: ps یا pgrep -a را بزن و فقط PID درست را بکش؛ برای پروسه‌ی دیگران، sudo kill.

Terminal window
sleep 9998 &
sleep 9999 &
echo "--- آنچه pkill sleep می‌گرفت (پیش‌نمایش با pgrep):"
pgrep -a -u ali sleep | grep -E 'sleep (600|601|9998|9999)$'
echo "--- دقیق: فقط یکی را با -f و الگوی کامل:"
pkill -f 'sleep 9999'
sleep 1
pgrep -a -f 'sleep 999[89]'
pkill -f 'sleep 9998'
خروجی
--- آنچه pkill sleep می‌گرفت (پیش‌نمایش با pgrep):
359 sleep 600
360 sleep 601
536 sleep 9998
537 sleep 9999
--- دقیق: فقط یکی را با -f و الگوی کامل:
536 sleep 9998

pkill sleep همه‌ی sleep‌ها را می‌کشت، از جمله کارهای مهم دیگر. راه‌حل: اول pgrep -a برای پیش‌نمایش، و برای دقت pkill -f 'الگوی کامل' یا خود PID.

Terminal window
bash -c 'sleep 1 & exec sleep 30' &
parent=$!
sleep 2
echo "--- فرزند تمام‌شده‌ای که والدش wait نکرده (Z):"
ps -o pid,ppid,stat,cmd --ppid $parent
zpid=$(ps -o pid= --ppid $parent | tr -d ' ')
kill -9 $zpid
sleep 1
echo "--- بعد از kill -9 روی خود zombie:"
ps -o pid,ppid,stat,cmd --ppid $parent
echo "--- راه‌حل: والدش را متوقف کن (zombie به PID 1 می‌رسد و جمع می‌شود):"
kill $parent; wait $parent 2>/dev/null
sleep 1
ps -o pid,ppid,stat,cmd --ppid $parent
echo "(خالی: zombie رفت)"
خروجی
--- فرزند تمام‌شده‌ای که والدش wait نکرده (Z):
PID PPID STAT CMD
546 544 Z [sleep] <defunct>
--- بعد از kill -9 روی خود zombie:
PID PPID STAT CMD
546 544 Z [sleep] <defunct>
--- راه‌حل: والدش را متوقف کن (zombie به PID 1 می‌رسد و جمع می‌شود):
PID PPID STAT CMD
(خالی: zombie رفت)

zombie پروسه‌ی «مرده» است که فقط یک ردیف در جدول پروسه‌ها اشغال کرده تا والد کد خروجی‌اش را بخواند؛ نمی‌شود با سیگنال کشتش (قبلاً مرده). تعداد کم بی‌خطر است، ولی اگر هزاران تا جمع شوند جدول پروسه‌ها پر می‌شود. راه‌حل: والد را درست کن (باگ در wait()) یا متوقفش کن تا PID 1 آن‌ها را جمع کند.

Terminal window
TW=70
TH=6
run_in_tmux "bash --norc"
tmux send-keys -t t "fg" Enter; sleep 1
tmux send-keys -t t "jobs" Enter; sleep 1
screen
tmux -u kill-server
خروجی
bash-5.2$ fg
bash: fg: current: no such job
bash-5.2$ jobs
bash-5.2$

fg: current: no such job، یعنی این shell کار متوقف یا پس‌زمینه‌ای ندارد. دقت کن کارها مال همین shell هستند: در یک ترمینال دیگر، یا بعد از بسته‌شدن shell، jobs خالی است و برای پیدا کردن پروسه باید از ps یا pgrep استفاده کنی.

✎ تمرینآسان

سه پروسه‌ی sleep در پس‌زمینه بساز، با pgrep -c تعدادشان را بشمار، با pkill همه را بکش و دوباره بشمار.

دیدن جواب
Terminal window
pkill -u ali sleep
sleep 801 & sleep 802 & sleep 803 &
sleep 1
echo "تعداد: $(pgrep -c -u ali sleep)"
pkill -u ali sleep
sleep 1
echo "بعد از pkill: $(pgrep -c -u ali sleep)"
خروجی
تعداد: 3
بعد از pkill: 0
✎ تمرینمتوسط

تمرین اصلی: یک پروسه‌ی طولانی را به پس‌زمینه ببر، دوباره بیاور و متوقفش کن. sleep 500 را در پیش‌زمینه اجرا کن، با Ctrl+Z متوقفش کن، با bg به پس‌زمینه بفرست، با jobs ببین، با fg برگردان و با Ctrl+C تمامش کن.

دیدن جواب
Terminal window
TW=70
TH=16
run_in_tmux "bash --norc"
tmux send-keys -t t "sleep 500" Enter; sleep 1
tmux send-keys -t t C-z; sleep 1
tmux send-keys -t t "bg" Enter; sleep 1
tmux send-keys -t t "jobs" Enter; sleep 1
tmux send-keys -t t "fg" Enter; sleep 1
tmux send-keys -t t C-c; sleep 1
tmux send-keys -t t "jobs" Enter; sleep 1
screen
tmux -u kill-server
خروجی
bash-5.2$ sleep 500
^Z
[1]+ Stopped sleep 500
bash-5.2$ bg
[1]+ sleep 500 &
bash-5.2$ jobs
[1]+ Running sleep 500 &
bash-5.2$ fg
sleep 500
^C
bash-5.2$ jobs
bash-5.2$
✎ تمرینسخت

پروسه‌ای که SIGTERM را عمداً نادیده می‌گیرد (trap "" TERM) را اجرا کن. نشان بده kill معمولی کارش نمی‌کند و kill -9 می‌کند. کد خروجی را هم ببین.

دیدن جواب
Terminal window
bash -c 'trap "" TERM; while true; do sleep 1; done' &
pid=$!
sleep 1
kill $pid
sleep 2
echo "بعد از kill (SIGTERM): $(ps -o stat= -p $pid) (زنده است)"
kill -9 $pid
wait $pid 2>/dev/null
echo "کد خروج بعد از kill -9: $?"
ps -p $pid > /dev/null || echo "پروسه تمام شد"
خروجی
بعد از kill (SIGTERM): S (زنده است)
کد خروج بعد از kill -9: 137
پروسه تمام شد
؟ آزمونک
  1. PID 1 چیست؟

  2. تفاوت kill PID و kill -9 PID؟

  3. چطور دستوری را از ابتدا در پس‌زمینه اجرا می‌کنی؟

  4. کاری در پیش‌زمینه است. چطور ترمینال را پس بگیری و کار ادامه یابد؟

  5. چرا کار پس‌زمینه با بسته‌شدن ssh می‌میرد و nohup چه می‌کند؟

  6. پروسه‌ای با وضعیت Z (zombie) را چطور از بین می‌بری؟

  • هر برنامه‌ی در حال اجرا یک پروسه است با PID، والد (PPID)، کاربر و وضعیت؛ هسته اطلاعاتشان را در /proc/PID می‌گذارد و ps/top همان را می‌خوانند.
  • ps aux عکس لحظه‌ای، top/htop نمای زنده؛ pgrep -a نام برای پیدا کردن PID؛ ps -eo ... --sort=-%cpu برای پرمصرف‌ها.
  • kill PID = SIGTERM (مؤدبانه، تمیزکاری)، kill -9 PID = SIGKILL (اجباری، آخرین راه). Ctrl+C = SIGINT، Ctrl+Z = SIGTSTP.
  • & اجرا در پس‌زمینه؛ jobs، fg %N، bg %N کارهای همان shell را کنترل می‌کنند.
  • بسته‌شدن ترمینال = SIGHUP برای کارها؛ با nohup (یا disown، یا tmux) زنده می‌مانند.
  • zombie (Z) را نمی‌شود کشت؛ والدش را درست کن.
برگه‌ی تقلب این درس
دستورکاری که می‌کند
ps aux / ps -efهمه‌ی پروسه‌ها
ps -eo pid,ppid,user,%cpu,%mem,args --sort=-%cpuستون دلخواه، مرتب‌شده
pgrep -a نامPID و دستور پروسه‌های منطبق
top / htopنمای زنده (q = خروج)
kill PIDSIGTERM (مؤدبانه)
kill -9 PIDSIGKILL (اجباری، آخرین راه)
pkill -f "الگو"سیگنال به پروسه‌های منطبق
دستور &اجرا در پس‌زمینه
jobs / fg %1 / bg %1کنترل کارهای shell
Ctrl+Z / Ctrl+Cمتوقف کردن / قطع کردن
nohup دستور > log 2>&1 &زنده ماندن بعد از بسته‌شدن ترمینال
kill -lفهرست سیگنال‌ها