Что такое процесс
В 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 получает команду «запусти эту программу», оно:
- Загружает файл в память (через
mmapдля текста кода и черезreadдля данных). - Выделяет адресное пространство — отдельный виртуальный мир памяти для этого процесса.
- Присваивает уникальный номер — PID (Process ID).
- Запоминает, кто его запустил — PPID (Parent Process ID).
- Ставит в очередь планировщика на исполнение.
Один файл-программа может быть запущен много раз — это будут разные процессы с разными PID. Например, на сервере с Airflow обычно работает один webserver, один scheduler, и десятки worker — все из одного бинаря airflow, но как отдельные процессы.
Файл на диске и его инстансы в исполнении — разные сущности
Запомните различие. На собеседовании могут спросить: «Сколько процессов запускается, когда вы написали 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 в корне
Жизненный цикл процесса: от 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. Ноль — это не реальный процесс, это маркер «нет родителя, я первый».
В 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:
Процесс переходит между состояниями в зависимости от того, что он делает
Подробнее по состояниям
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>/. Это виртуальная файловая система — ядро генерирует её содержимое на лету. Что там лежит:
Виртуальная файловая система — окно в ядро для просмотра состояния процесса
# Узнать состояние процесса
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-сценарии где это применимо
- «Airflow worker подвис» —
ps aux | grep airflow, видите состояние D — значит, висит на disk I/O, проверяете диск (df -h,iostat). - «У нас на сервере 200 zombie-процессов» — это значит, что какой-то родитель не делает
wait(). Идёте по PPID до родителя, лечите его или его перезапускаете. - «PID-ы зомби пухнут» —
ps aux | awk '$8=="Z"'найдёт всех зомби, дальше смотрите их PPID. - «Где живёт спарк 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