Любая команда, которую вы запускаете в оболочке - это процесс. Администратор каждый день решает три задачи вокруг процессов: запустить нужное (и не потерять, когда закроется терминал), увидеть, что вообще происходит в системе (кто ест CPU и память), и аккуратно остановить то, что зависло или мешает. В этом уроке разберём управление задачами оболочки, инструменты наблюдения от ps до htop, механику сигналов и отсоединяемые сессии screen/tmux.

Как это работает
Когда оболочка запускает программу, она делает fork (создаёт копию себя) и затем exec (загружает в эту копию код новой программы). Так рождается дочерний процесс со своим PID, и его родителем (PPID) становится оболочка. Все процессы выстроены в дерево, корень которого - systemd с PID 1.
Внутри оболочки запущенные команды группируются в задания (jobs). Задание может быть на переднем плане (foreground) - тогда оболочка ждёт его завершения и отдаёт ему клавиатуру, либо в фоне (background) - оболочка сразу возвращает приглашение. Символ & в конце команды отправляет её в фон. Это управление заданиями - функция самой оболочки, а не ядра.
Связь процесса с терминалом важна. У интерактивных процессов есть управляющий терминал. Когда вы закрываете терминал, ядро рассылает процессам этой сессии сигнал SIGHUP (hang up - исторически обрыв связи модема), и по умолчанию они умирают. Чтобы фоновая задача пережила выход, её отвязывают от терминала: nohup игнорирует HUP заранее, а disown убирает задание из таблицы заданий оболочки, чтобы та не слала ему HUP при выходе.
Сигналы - это асинхронные уведомления ядра процессу. У каждого есть номер и имя. TERM (15) - вежливая просьба завершиться, процесс может перехватить её и прибраться. KILL (9) и STOP - единственные, которые нельзя перехватить или проигнорировать: KILL убивает мгновенно силами ядра, STOP замораживает, CONT размораживает. HUP многие демоны трактуют как перечитать конфиг.
Команды и примеры
Управление заданиями в оболочке:
Код: Выделить всё
sleep 600 & # запуск в фоне, оболочка печатает [1] 12345
jobs -l # список заданий с PID
fg %1 # вернуть задание 1 на передний план
# Ctrl+Z останавливает текущий процесс (шлёт SIGTSTP)
bg %1 # продолжить остановленное задание в фоне
Код: Выделить всё
nohup ./backup.sh > backup.log 2>&1 & # вывод в файл, HUP игнорируется
# или для уже запущенного:
long_task &
disown -h %1 # снять задание с рассылки HUP
disown %1 # полностью убрать из таблицы заданий
Код: Выделить всё
ps aux # BSD-стиль: все процессы, с пользователями и ресурсами
ps -ef # UNIX-стиль: то же дерево с PPID
ps -eo pid,ppid,stat,%cpu,%mem,comm --sort=-%cpu | head
ps axjf # дерево процессов (forest)
Интерактивный мониторинг:
Код: Выделить всё
top # классика: e/E переключают единицы, P - сорт по CPU, M - по памяти
htop # дружелюбнее: F9 шлёт сигнал, F5 - дерево, мышь работает
Поиск и завершение по имени:
Код: Выделить всё
pgrep -a nginx # PID процессов nginx с командной строкой
pgrep -u www-data php # с фильтром по пользователю
kill 12345 # по умолчанию TERM
kill -9 12345 # KILL, крайняя мера
kill -HUP $(pgrep nginx) # перечитать конфиг
killall firefox # по точному имени
pkill -f 'python.*worker' # по шаблону всей командной строки
kill -STOP 12345; kill -CONT 12345 # заморозить и разморозить
Код: Выделить всё
uptime # аптайм и load average за 1/5/15 минут
free -h # память и swap в человекочитаемом виде
watch -n 2 'free -h; uptime' # повтор каждые 2 сек, -d подсвечивает изменения
Отсоединяемые сессии. Они держат процессы живыми независимо от вашего SSH-подключения и позволяют вернуться к ним позже:
Код: Выделить всё
tmux new -s work # новая сессия с именем work
# Ctrl+b затем d - отсоединиться (detach), процессы продолжают работать
tmux ls # список сессий
tmux attach -t work # вернуться
# screen аналогично: screen -S work, Ctrl+a d, screen -r work
- Привычка сразу бить kill -9. KILL не даёт процессу закрыть файлы и сбросить буферы - возможна потеря данных и битые временные файлы. Сначала TERM, KILL только если не реагирует.
- Процесс в состоянии D не убивается ничем, даже KILL: он ждёт ядро. Лечится устранением причины ввода-вывода, иногда только перезагрузкой.
- nohup сам по себе не отправляет в фон - без & команда займёт терминал. Нужны оба: nohup ... &.
- disown без -h убирает задание из таблицы, и вы больше не сможете обратиться к нему через fg/bg или %1.
- killall на разных системах ведёт себя по-разному и сопоставляет по имени процесса; легко зацепить не то. pkill -f надёжнее, но шаблоном можно по ошибке убить и сам grep/команду поиска.
- Зомби (Z) не едят ресурсы и не убиваются - их забирает родитель; чините или перезапускайте родителя, а не зомби.
- top показывает %CPU больше 100 на многоядерной машине - это нормально, суммируются ядра (режим Irix).
- Запустите sleep 300 &, затем yes > /dev/null &. Посмотрите оба через jobs -l.
- Командой ps -eo pid,stat,%cpu,comm --sort=-%cpu | head найдите, что грузит CPU (это будет ваш yes).
- Заморозьте его: kill -STOP %2, проверьте в top, что CPU освободился, потом kill -CONT %2.
- Верните sleep на передний план через fg %1 и остановите его Ctrl+Z, затем верните в фон bg.
- Запустите nohup ping -c 1000 127.0.0.1 > ping.log 2>&1 &, закройте терминал, откройте новый и убедитесь через pgrep -a ping, что процесс жив.
- Завершите его сначала pkill ping (TERM), при необходимости pkill -9 ping.
- Создайте сессию tmux new -s lab, запустите в ней top, отсоединитесь Ctrl+b d и вернитесь через tmux attach -t lab.
- Чем отличается завершение процесса сигналом TERM от KILL и почему KILL нельзя перехватить?
- Что делает disown -h и чем он отличается от nohup при защите фоновой задачи от закрытия терминала?
- Какие ключи ps относятся к BSD-стилю, а какие к UNIX-стилю, и чем выводы aux и -ef различаются по смыслу?
- Процесс висит в состоянии D и не реагирует на kill -9. Почему и что с этим делать?
- Как послать сигнал HUP всем процессам nginx одной командой и что обычно делает демон, получив HUP?
- Чем отсоединение сессии в tmux принципиально отличается от запуска команды через nohup ... &?