Learning Platform
Глоссарий Troubleshooting
Урок 11.01 · 22 мин
Начальный
процессыPIDPPIDinitsystemdprocess stateszombiepstree

Что такое процесс

В Linux вы постоянно слышите слово «процесс». Airflow worker — процесс. Postgres сервер — процесс. Bash, в котором вы пишете команды — тоже процесс. Если для junior Python-разработчика «процесс» — это что-то абстрактное, для DE это вполне конкретный объект: с номером, родителем, состоянием, лимитами по памяти. И когда в проде ночью что-то падает — починка почти всегда начинается с вопроса «а какие сейчас процессы и что они делают».

В этом уроке разберём, что такое процесс с точки зрения ядра Linux, какие у него атрибуты, какие бывают состояния, и почему PID 1 — особая магическая штука.


Программа vs процесс

Анатомия процесса: task_struct и виртуальная память fork и exec: как рождаются процессы

Программа — это файл на диске: бинарный исполняемый файл /usr/bin/python3, скрипт ~/etl/run.sh, jar-ник /opt/airflow/lib/airflow.jar. Просто байты, лежащие где-то в файловой системе.

Процесс — это программа в исполнении. Когда ядро Linux получает команду «запусти эту программу», оно:

  1. Загружает файл в память (через mmap для текста кода и через read для данных).
  2. Выделяет адресное пространство — отдельный виртуальный мир памяти для этого процесса.
  3. Присваивает уникальный номер — PID (Process ID).
  4. Запоминает, кто его запустил — PPID (Parent Process ID).
  5. Ставит в очередь планировщика на исполнение.

Один файл-программа может быть запущен много раз — это будут разные процессы с разными PID. Например, на сервере с Airflow обычно работает один webserver, один scheduler, и десятки worker — все из одного бинаря airflow, но как отдельные процессы.

Программа -> процесс

Файл на диске и его инстансы в исполнении — разные сущности

/usr/bin/python3Бинарный файл на диске. Лежит, ничего не делает. Просто байты
exec
PID 1234Запущенный экземпляр программы. Имеет адресное пространство, файловые дескрипторы, состояние
/usr/bin/python3Тот же файл — но запущенный второй раз становится отдельным процессом
exec
PID 1235Второй инстанс. Тот же код, но изолирован от первого — отдельная память, отдельные открытые файлы
TIP

Запомните различие. На собеседовании могут спросить: «Сколько процессов запускается, когда вы написали ls | grep foo?» Правильный ответ — два: один для ls, один для grep. Хотя бинарника два (/usr/bin/ls, /usr/bin/grep), а в shell вы это написали в одну строку.


PID и PPID

Каждый процесс имеет PID — целое число от 1 до 4194304 (это kernel.pid_max на современных Linux 64-bit). PID уникален в каждый момент времени. Когда процесс умирает, его PID освобождается, и ядро может выдать его новому процессу.

Каждый процесс имеет PPID — PID того процесса, который его запустил. Это называется родительский процесс. Если bash запустил python script.py, то PPID процесса python — это PID процесса bash. Эта связь образует дерево процессов.

# Посмотреть свой PID и PPID
echo "Мой PID: $$"
echo "PID моего родителя: $PPID"

Если вы запустите эти команды в bash, $$ будет PID того bash, а $PPID — PID того, что запустил bash (обычно — терминал-эмулятор типа GNOME Terminal или alacritty).

Дерево процессов

Каждый процесс знает своего родителя, образуется дерево с PID 1 в корне

PID 1: systemdПервый процесс системы. Родитель всех остальных. Запускается ядром сразу после загрузки. Если умрёт — kernel panic
PID 1200: sshdSSH daemon. PPID = 1 (потомок systemd). Принимает входящие SSH-подключения
PID 1300: postgresPostgreSQL postmaster. PPID = 1. Главный процесс БД
PID 1400: airflow schedulerЗапущенный через systemd unit. PPID = 1
PID 2500: bashВаш интерактивный shell. PPID = sshd (1200), потому что shell породил sshd при принятии вашего подключения
PID 2800: pythonСкрипт, который вы запустили. PPID = 2500 (ваш bash)

Жизненный цикл процесса: от fork до exit Планировщик ядра: как выбирается следующий процесс

PID 1 — особый процесс

Когда Linux загружается, ядро монтирует root filesystem и запускает первый user-space процесс. Этот процесс получает PID 1 и называется init. На современных дистрибутивах (Ubuntu 26.04, Debian 13 Trixie, RHEL/Fedora) роль init выполняет systemd.

PID 1 — это родитель всех остальных процессов. Если процесс умирает, и его реальный родитель уже мёртв — ядро «переподписывает» сирот к PID 1. Это называется reparenting.

PID 1 нельзя убить обычным kill. У него игнорируются почти все сигналы — иначе можно было бы случайно положить всю систему. Если PID 1 умирает — ядро Linux паникует и встаёт целиком (kernel panic — attempted to kill init).

# Посмотреть PID 1
ps -p 1 -o pid,ppid,cmd
# PID PPID CMD
#   1    0 /sbin/init

PPID у PID 1 — это 0. Ноль — это не реальный процесс, это маркер «нет родителя, я первый».

WARNING

В Docker-контейнерах PID 1 — это ваш процесс, который вы указали в CMD/ENTRYPOINT. Если ваш Python-скрипт стартует первым — он становится PID 1. Это часто приводит к проблемам: PID 1 не reap-ит zombie-детей, не обрабатывает SIGTERM по дефолту, и контейнер не останавливается gracefully. Поэтому в Docker используют tini или dumb-init как промежуточный PID 1. Подробнее — в модуле systemd (модуль 15).


Состояния процесса

В каждый момент времени процесс находится в одном из состояний. Ядро Linux хранит это в поле state структуры task_struct. В выводе ps это колонка STAT:

Состояния процесса

Процесс переходит между состояниями в зависимости от того, что он делает

RRunning или Runnable. Процесс либо прямо сейчас выполняется на CPU, либо стоит в очереди готовых к выполнению. Это «здоровое» рабочее состояние
SSleeping (interruptible). Ждёт события — например, ответа от сокета, нажатия клавиши, прихода данных. Большинство idle-процессов — в S. Можно разбудить сигналом
DUninterruptible sleep. Ждёт ввода-вывода (обычно disk I/O) и не реагирует даже на kill -9. Если процессов в D много и надолго — диск умирает или NFS висит
ZZombie. Процесс уже завершился, но родитель ещё не вызвал wait() чтобы забрать его exit code. Висит в таблице процессов, занимая запись. Не потребляет CPU/память, но засоряет таблицу
TStopped. Процесс приостановлен — либо вы нажали Ctrl-Z, либо ему послали SIGSTOP. Не выполняется, ждёт SIGCONT чтобы продолжить
XDead. Промежуточное состояние при завершении. Видеть его почти невозможно — оно слишком короткое

Подробнее по состояниям

R (Running/Runnable). Процесс что-то активно делает на CPU или готов начать как только дойдёт очередь. Чем больше процессов в R одновременно — тем больше нагрузка на CPU. На уроке про top (следующий 03-урок) вы увидите, как load average отражает количество R+D-процессов.

S (Sleeping). Подавляющее большинство процессов на спокойной системе — в S. Они ждут чего-то: пришедшего пакета, нажатия клавиши, события таймера. Поэтому когда в top вы видите 200 процессов, но CPU usage 2% — это нормально, 198 из них в S.

D (Disk wait, uninterruptible). Это худшее состояние, в которое может попасть процесс. Он висит, ожидая дискового I/O, и не реагирует ни на какие сигналы, даже на SIGKILL. Это сделано для безопасности — если процесс прервать в середине записи на диск, можно повредить файловую систему. На практике если у вас процесс зависает в D — обычно у вас умирающий диск, висит NFS, или сломан iSCSI mount. Лечится перезагрузкой.

Z (Zombie). Когда процесс завершается, его запись в таблице остаётся, пока родитель не вызовет wait() чтобы получить exit code. Зомби сам ничего не делает, не ест CPU/память. Но если родитель забывает делать wait — зомби копятся. На системе с лимитом 32k процессов 32 тысячи зомби — это полная блокировка форка новых процессов.

T (Stopped). Процесс приостановлен. Это происходит когда вы нажали Ctrl-Z (модуль 10 урок 05) или кто-то послал SIGSTOP. Процесс не выполняется, но всё ещё занимает память. Возобновляется по SIGCONT.


Дерево процессов

Просмотр иерархии процессов — pstree или ps --forest:

# Утилита pstree (нужно установить: apt install psmisc)
pstree

# Только ваше дерево
pstree -p $$

# Аналог через ps
ps --forest -o pid,ppid,cmd

Типичный вывод pstree -p для интерактивного bash:

systemd(1)─┬─sshd(1200)───sshd(2400)───bash(2500)───python(2800)
           ├─postgres(1300)─┬─postgres(1301)
           │                ├─postgres(1302)
           │                └─postgres(1303)
           └─cron(1500)

Видно: вы (bash 2500) — потомок sshd(2400) — потомок главного sshd(1200) — потомок systemd(1). А python(2800) — ваш потомок.


Внутренности процесса в /proc/PID/

Каждый процесс представлен в виде директории /proc/<PID>/. Это виртуальная файловая система — ядро генерирует её содержимое на лету. Что там лежит:

Что внутри /proc/PID/

Виртуальная файловая система — окно в ядро для просмотра состояния процесса

statusТекстовая сводка: PID, PPID, состояние, кто owner (UID), количество тредов, memory usage. Самое читаемое
cmdlineПолная команда запуска, как массив null-separated. Полезно увидеть с какими аргументами запущен сервис
cwdСимлинк на текущую рабочую директорию процесса. Полезно понять «откуда» запущен скрипт
environВсе переменные окружения процесса как null-separated пары KEY=VALUE. Видно DATABASE_URL, AIRFLOW_HOME и т.д.
fd/Все открытые файловые дескрипторы. fd/0 -> stdin, fd/1 -> stdout, fd/2 -> stderr. Можно посмотреть «куда пишут логи»
mapsКарта виртуальной памяти процесса. Какие библиотеки загружены, какие mmap-нутые файлы
# Узнать состояние процесса
cat /proc/$$/status | head

# Команда запуска (null-separated, нужно tr)
cat /proc/$$/cmdline | tr '\0' ' '
echo

# Текущая рабочая директория
ls -l /proc/$$/cwd

# Куда указывает stdout
ls -l /proc/$$/fd/1

DE-сценарий: вы видите, что Airflow worker «потерял» соединение с Postgres. Заходите в /proc/<airflow-worker-pid>/fd/ и видите все открытые сокеты, файлы, pipe-ы. Можно найти конкретный сокет к Postgres, проверить его состояние через lsof -p <PID> или ss -p.


Команды для просмотра процессов

Кратко — что вам понадобится в дальнейших уроках:

  • ps — снапшот процессов в данный момент. Подробно в следующем уроке 02.
  • top / htop — интерактивный мониторинг. Урок 03.
  • pstree — дерево процессов.
  • pgrep — найти PID по имени. pgrep airflow вернёт PID-ы всех процессов, в имени которых есть «airflow».
  • pidof — упрощённый pgrep, точное совпадение. pidof postgres.
# Сколько всего процессов в системе
ps aux | wc -l

# Все PID-ы, в имени которых есть python
pgrep python

# Точный PID процесса postgres
pidof postgres

# Дерево от моего bash вниз
pstree -p $$

DE-сценарии где это применимо

  1. «Airflow worker подвис»ps aux | grep airflow, видите состояние D — значит, висит на disk I/O, проверяете диск (df -h, iostat).
  2. «У нас на сервере 200 zombie-процессов» — это значит, что какой-то родитель не делает wait(). Идёте по PPID до родителя, лечите его или его перезапускаете.
  3. «PID-ы зомби пухнут»ps aux | awk '$8=="Z"' найдёт всех зомби, дальше смотрите их PPID.
  4. «Где живёт спарк executor»ls -l /proc/<spark-pid>/cwd покажет рабочую директорию, /proc/<spark-pid>/environ — все env vars.

Cross-link: подробно про ps — следующий урок 02. Про сигналы (как убивать) — урок 04. Про jobs (фон/передний план) — урок 05.


Попробуй сам

# 1. Узнай свой PID и PID родителя
echo "Я: $$, мой родитель: $PPID"

# 2. Посмотри своё дерево вверх
ps -o pid,ppid,cmd -p $$
ps -o pid,ppid,cmd -p $PPID
ps -o pid,ppid,cmd -p $(awk '{print $4}' /proc/$PPID/stat)

# 3. Запусти sleep на 60 секунд в фоне и посмотри на него
sleep 60 &
SLEEP_PID=$!
ps -p $SLEEP_PID -o pid,ppid,state,cmd
# state будет S (sleeping)

# 4. Прибей и проверь, что освободился
kill $SLEEP_PID
sleep 1
ps -p $SLEEP_PID
# Сообщит, что процесса нет

# 5. Посмотри полное дерево системы
pstree | head -30

Проверка знанийKnowledge check
В чём разница между состоянием S (Sleeping) и D (Disk wait), и почему процесс в D не убивается через kill -9?
ОтветAnswer
S (Sleeping, interruptible) — процесс ждёт какого-то события (сетевой пакет, нажатие клавиши, таймер) и его можно разбудить сигналом. Например, можно прислать SIGKILL и процесс умрёт. D (Disk wait, uninterruptible) — процесс ждёт ввода-вывода (обычно дисковой операции) и ядро намеренно блокирует доставку любых сигналов, включая SIGKILL. Это сделано для безопасности: прервать процесс в середине I/O операции — значит риск повредить файловую систему. Если процесс долго висит в D — это обычно симптом умирающего диска, висящего NFS или сломанного iSCSI. Перезагрузка — единственный способ от него избавиться.

Проверьте понимание

Результат: 0 из 0
Концептуальный
Вопрос 1 из 5. В чём разница между программой и процессом?

Закончили урок?

Отметьте его как пройденный, чтобы отслеживать свой прогресс

Войдите чтобы оценить урок

Прогресс модуля
0 из 5