Процессы и сигналы

Материал из llmdocs — справочник разработчика
Процессы и сигналы
ИдентификаторPID; у родителя — PPID
Созданиеfork() и execve()
Первый процессinit/systemd (PID 1)
СостоянияR (выполняется), S (спит), D (ожидает ввода-вывода), T (остановлен), Z (зомби)
Частые сигналыSIGTERM (15), SIGKILL (9), SIGINT (2), SIGHUP (1), SIGCHLD
Инструментыps, top, htop, pgrep, kill, pkill, /proc

Процесс — экземпляр выполняющейся программы: код, загруженный в память, вместе с данными, открытыми файлами, переменными окружения и правами пользователя. У каждого процесса в Linux есть уникальный номер — PID — и родитель (PPID). Сигнал — короткое асинхронное уведомление, которое ядро или другой процесс посылает процессу: «завершись», «перечитай конфигурацию», «ты обратился к чужой памяти», «твой дочерний процесс закончил работу».

Понимание процессов и сигналов нужно, чтобы корректно останавливать службы, искать зависшие программы, писать скрипты и правильно завершать приложения в Docker и Kubernetes.

Жизнь процесса

Новые процессы в Unix создаются двумя системными вызовами: fork() создаёт копию текущего процесса, а execve() заменяет программу в этой копии на другую.[2] Так оболочка запускает каждую команду: делает fork, в дочернем процессе вызывает exec нужной программы, а сама ждёт её завершения через wait.

Все процессы образуют дерево с корнем в процессе с PID 1 — сегодня это обычно systemd. Завершившись, процесс возвращает код выхода (0 — успех) и становится зомби — записью в таблице процессов, ожидающей, пока родитель прочитает код выхода. Если родитель умер раньше, «сирот» усыновляет PID 1 и сам их «хоронит».

Состояние Обозначение Смысл
Выполняется R работает или готов к работе
Спит S ждёт события (сеть, таймер, ввод)
Непрерываемый сон D ждёт ввода-вывода, не реагирует даже на SIGKILL
Остановлен T приостановлен (Ctrl+Z, отладчик)
Зомби Z завершён, родитель не забрал код выхода

Просмотр процессов

ps aux                           # все процессы: пользователь, PID, CPU, память, команда
ps -ef --forest                  # дерево процессов
pgrep -a nginx                   # найти по имени
top                              # интерактивно; htop — удобнее
cat /proc/1234/status            # всё о процессе из виртуальной ФС /proc
ls -l /proc/1234/fd              # открытые файлы и сокеты
lsof -i :8080                    # какой процесс занял порт

Каталог /proc — окно в ядро: для каждого процесса есть каталог с его командной строкой, окружением, картой памяти и открытыми файлами.[3][4]

Сигналы

Сигнал Номер Действие по умолчанию Когда приходит
SIGHUP 1 завершение закрыт терминал; демоны часто перечитывают конфигурацию
SIGINT 2 завершение Ctrl+C в терминале
SIGQUIT 3 завершение с дампом Ctrl+\
SIGKILL 9 немедленное завершение kill -9; нельзя перехватить
SIGSEGV 11 завершение с дампом обращение к недопустимой памяти
SIGPIPE 13 завершение запись в закрытый канал
SIGTERM 15 завершение вежливая просьба завершиться — kill по умолчанию
SIGCHLD 17 игнорируется завершился дочерний процесс
SIGSTOP, SIGCONT 19, 18 остановка, продолжение приостановить и возобновить; SIGSTOP нельзя перехватить

Процесс может перехватить большинство сигналов и обработать их по-своему — например, по SIGTERM закрыть соединения и сохранить данные. SIGKILL и SIGSTOP перехватить нельзя: их обрабатывает ядро.[1]

kill 1234                        # SIGTERM — попросить завершиться
kill -HUP $(pgrep nginx)         # перечитать конфигурацию
kill -9 1234                     # SIGKILL — крайняя мера
pkill -f "python worker.py"      # по шаблону командной строки

Корректное завершение

Правильный порядок остановки — сначала SIGTERM, дать процессу время закончить работу и только затем SIGKILL. Так поступают systemd (TimeoutStopSec), docker stop (по умолчанию 10 секунд)[5] и Kubernetes (terminationGracePeriodSeconds). Приложение должно обрабатывать SIGTERM: перестать принимать новые запросы, дождаться текущих, закрыть соединения с базой.

import signal, sys

def shutdown(signum, frame):
    print("получен SIGTERM, завершаемся")
    server.stop(grace=10)
    sys.exit(0)

signal.signal(signal.SIGTERM, shutdown)

В Bash обработчик ставится командой trap 'cleanup' TERM INT EXIT.

Процессы в контейнерах

В контейнере приложение часто становится PID 1, а у PID 1 особые правила: ядро не применяет к нему действия сигналов по умолчанию, поэтому процесс, не установивший обработчик SIGTERM, просто его проигнорирует, и контейнер будет убит по таймауту. Кроме того, PID 1 обязан собирать зомби. Решения — запускать команду в exec-форме (CMD ["python", "app.py"], а не через sh -c, см. Dockerfile), обрабатывать сигналы в приложении или использовать маленький init вроде tini (docker run --init).[6]

Приоритеты и ресурсы

Приоритет планирования задаётся значением nice от −20 (высший) до 19: nice -n 10 ./backup.sh, renice. Ограничения ресурсов — ulimit (число открытых файлов, размер стека) и cgroups (память, процессор; см. Namespaces и cgroups). Когда память кончается, ядро запускает OOM killer, который завершает процесс с наибольшим «счётом» потребления — частая причина внезапной гибели баз данных и Java-приложений.

См. также

Источники

  1. signal(7) — Linux manual page
  2. fork(2) — Linux manual page
  3. proc(5) — Linux manual page
  4. ps(1) — Linux manual page
  5. Docker Docs — docker container stop
  6. tini — A tiny but valid init for containers