
1. Введение
Продолжаем серию о Linux. В прошлой части разбирали права доступа, а теперь переходим к одной из самых важных тем в Linux — процессам. Любая программа в системе в конечном итоге существует как процесс: nginx, postgres, docker, sshd, systemd, ваш shell и даже потоки ядра. Понимание того, как процессы создаются, живут, взаимодействуют с ядром и завершаются, — это база для понимания и диагностики Linux-систем. Цель этой статьи — рассказать кратко и простым языком всю нужную информацию как для начинающих, так и для опытных пользователей и админов, чтобы освежить знания. Важно: для практики, если обучаетесь, лучше всего использовать виртуалку с Linux.
Серия в основном под Ubuntu/Debian.
2. Что такое процесс и поток
Процесс — это запущенная программа. Когда вы запускаете контейнер nginx, или python app.py, или браузер, ядро Linux создаёт процесс: выделяет ему память, даёт номер (PID) и начинает его выполнять. Пока программа не запущена, это программный файл. Отличается он только наличием доступа x (т. е. возможности исполнения), но, как только вы его запускаете, он становится процессом под управлением ядра Linux.
Про поток (thread) достаточно запомнить, что это задача внутри процесса, разделяющая с ним адресное пространство и файловые дескрипторы. В ядре он представлен той же структурой task_struct, что и процесс, но с флагами CLONE_VM, CLONE_FILES, CLONE_SIGHAND. В утилитах (ps, htop) потоки отображаются как LWP (Light Weight Process) с общим PID и уникальным TID. Сбой в одном потоке затрагивает весь процесс.
У каждого процесса есть своё окружение — набор ресурсов и атрибутов, которые ядро выделяет при создании:
-
Виртуальная память, где у процесса своё адресное пространство. Он не видит и не может напрямую изменить память другого процесса.
-
Таблица открытых файлов, через неё процесс работает с файлами, сокетами, пайпами и др. По умолчанию открыты дескрипторы: 0 (stdin), 1 (stdout), 2 (stderr). Остальные процесс открывает сам по мере необходимости.
-
Каталог (cwd) — директория, в которой процесс сейчас находится. От неё отсчитываются все относительные пути.
-
Переменные окружения — набор пар ключей (или, по-другому, KEY=VALUE), которые процесс получил от родителя. Через них передают конфиги: PATH, HOME, LANG и другие (к примеру, иногда чувствительные данные: токены, пароли). Меняются до запуска процесса, внутри — только если процесс сам их перезапишет.
-
UID / GID — идентификаторы пользователя и группы, от имени которых запущен процесс. По ним идёт проверка прав: может ли процесс прочитать файл, открыть порт, выполнить какую-то операцию и т. д.
-
Код возврата — целое число от 0 до 255, которое процесс возвращает при завершении: 0 = успех, 1–255 = ошибка. Сохраняется в ядре до тех пор, пока родитель не считает его через команду wait() / waitpid(). Если родитель не забирает код, процесс остаётся зомби до очистки.
3. Жизненный цикл: рождение и смерть
Как рождается процесс: в Linux новый процесс создаётся клонированием существующего через команды fork() и exec(). fork() создаёт точную копию родителя с новым PID, а exec() заменяет её память кодом новой программы, сохраняя PID и наследуя ресурсы.
Что наследует дочерний процесс: открытые файловые дескрипторы, переменные окружения, текущий каталог, UID/GID, обработчики сигналов. Не наследует: PID, очередь сигналов и статистику выполнения.
Как умирает процесс: процесс вызывает exit() (т. е. мы сообщаем ядру информацию о том, что программа внутри процесса завершила все свои действия), и, соответственно, освобождаются ресурсы (файлы, память, сокеты), процесс переходит в состояние зомби (Z), ядро отправляет родителю сигнал (SIGCHLD), родитель вызывает wait() и забирает код возврата — зомби удаляется. Если родитель завершается раньше, то дочерний процесс усыновляется systemd (PID 1), который сам выполнит wait() при его завершении.
Когда зомби становится проблемным: по ситуации. Несколько зомби, к примеру, — это норма, если они живут миллисекунды, пока родитель не вызовет wait(). Основная проблема возникает, когда он не вызывает его никогда, из-за чего происходит накопление зомби, исчерпание PID-пространства и т. д. В итоге система перестаёт создавать новые процессы.
В качестве примеров команды:
# Найти зомби
ps aux | awk '$8 == "Z"'
# Найти родителя зомби и «напомнить» ему убраться
kill -CHLD <PPID зомби>
# Если родитель мёртво завис – убить его (дети усыновятся systemd)
kill -TERM <PPID>
4. Состояния процесса
Каждый процесс находится в одном из состояний, отображаемых в колонке STAT (ps aux).
Основные состояния:
|
Символ |
Название |
Описание |
|---|---|---|
|
R |
Running |
Выполняется или в очереди на CPU. |
|
S |
Sleeping |
Прерываемое ожидание (ввод, сеть, таймер). Реагирует на сигналы. |
|
D |
Uninterruptible sleep |
Ожидание I/O, который не прерывается сигналами. |
|
T |
Stopped |
Процесс приостановлен сигналом |
|
Z |
Zombie |
Завершён, но родитель не забрал код возврата через wait() |
|
I |
Idle |
Неактивный поток ядра |
Модификаторы состояния:
-
<— высокий приоритет -
N— низкий приоритет -
s— лидер сессии -
l— многопоточный -
+— на переднем плане
Пример: Ssl — спит, лидер сессии, многопоточный.
5. Идентификаторы: PID, PPID, UID и другие
PID (Process ID) — уникальный номер процесса, который назначает ядро. Не повторяется, пока не исчерпается диапазон.
PPID (Parent PID) — PID процесса, который создал данный. Каждый процесс (кроме systemd) имеет ровно 1 родителя. При завершении родителя его дочерние процессы переходят под управление systemd (PID 1).
# Посмотреть PID/PPID/Имя процесса
ps -ef | head -10
# Дерево процессов (наглядная иерархия. Рекомедую посмотреть данный пример)
pstree -p
# Максимальный PID в системе
cat /proc/sys/kernel/pid_max
Особые процессы: PID 1 и PID 2. Эти процессы создаются ядром сразу после загрузки. Они не запускаются пользователем и не появляются через команды как обычные программы. Про PID 1 уже несколько раз упомянули, но всё же стоит отдельно вынести его в терминологию.
PID 1 (systemd) — первый процесс в пользовательском пространстве. От него ведут начало все остальные процессы: от сессий до демонов. Если процесс теряет родителя, PID 1 его усыновляет, забирая коды возврата завершившихся процессов и предотвращая накопление зомби. Убить его нельзя, в основном завершить его можно только через перезагрузку системы.
PID 2 (kthreadd) — прародитель всех потоков ядра. От него создаются системные воркеры (к примеру, kworker). В ps и htop их видно по квадратным скобкам: [kworker/0:0]. Эти процессы работают в пространстве ядра и отвечают за фоновые задачи, такие как обработка прерываний, работа с диском и т. д.
# Увидеть иерархию от systemd
pstree -p | head -20
# Посмотреть потоки ядра (в квадратных скобках)
ps -eo pid,comm | grep '['
# Проверить родителя у любого процесса
ps -o pid,ppid,comm -p <PID>
6. Сигналы
Сигнал — это асинхронное уведомление процессу от ядра или другого процесса. Процесс может поймать сигнал, проигнорировать его или выполнить действие по умолчанию.
Главные сигналы
|
Сигнал |
Номер |
По умолчанию |
Когда использовать |
|---|---|---|---|
|
SIGTERM |
15 |
Завершить |
Первый выбор для остановки, процесс может перехватить и завершиться корректно: сохранить данные, закрыть соединения. |
|
SIGKILL |
9 |
Убить |
Крайняя мера. Процесс не успевает сохранить состояние |
|
SIGHUP |
1 |
Завершить |
Для демонов (перечитывать к примеру конфиг nginx без перезапуска) |
|
SIGINT |
2 |
Завершить |
Прерывание (Ctrl+C) |
|
SIGCONT |
18 |
Приостановить/Продолжить |
Возобновить приостановленный процесс |
|
SIGCHLD |
17 |
Игнорировать |
Уведомление родителю о завершении дочернего процесса |
|
SIGSTOP |
19 |
Остановить |
Приостановить процесс |
Наверное, появится вопрос: «Почему SIGKILL — это крайняя мера?» Потому что процесс не успеет завершить свои дела (сохранить данные, записать файлы и т. д.). Поэтому сначала пробуйте SIGTERM.
Что процесс может сделать с сигналом? Поймать, игнорировать, принять действие по умолчанию. Это означает следующие:
-
Поймать, значит выполнить свой обработчик (например, nginx перечитывает конфиг на SIGHUP).
-
Игнорировать (ну тут и так понятно).
-
Принять действие по умолчанию, обычное завершение.
Стоит ещё отметить, что поймать или проигнорировать SIGKILL и SIGSTOP нельзя, т. к. они всегда обрабатываются ядром.
Примеры, как отправить сигнал и завершить процесс.
# По PID
kill -TERM 1234
kill -9 1234 # то же что kill -KILL
# По имени процесса
pkill -TERM nginx
pkill -HUP sshd # перечитать конфиг
# По полной командной строке
pkill -f "python app.py"
# Всей группе процессов (минус перед номером)
kill -TERM -<PGID>
# Проверить, жив ли процесс (без отправки сигнала)
kill -0 1234 && echo "жив" || echo "мёртв"
PID=1234
TIMEOUT=30
# Шаг 1: попросить завершиться
kill -TERM "$PID"
# Шаг 2: подождать
for i in $(seq 1 $TIMEOUT); do
kill -0 "$PID" 2>/dev/null || { echo "завершился за ${i}с"; exit 0; }
sleep 1
done
# Шаг 3: если не вышел – убиваем
echo "не завершился за ${TIMEOUT}с, применяем SIGKILL"
kill -KILL "$PID"
7. /proc — заглянуть внутрь любого процесса
Виртуальная файловая система (/proc) — это интерфейс, через который ядро отдаёт информацию о процессах и системе. Файлы здесь не занимают место на диске: когда, к примеру, мы делаем cat /proc/1234/status, ядро прямо в этот момент формирует текстовый ответ и отдаёт его вам. Здесь лежат данные обо всём: какие процессы запущены, сколько памяти используется и т. д.
Главные файлы /proc/<PID> и другие примеры
# cmdline: командная строка запуска
cat /proc/1/cmdline | tr ' ' ' '
# exe: показывает, какой файл запущен. Если обновили пакет, а процесс не перезагрузили – будет (deleted, означает что этот процесс был запущен, которого больше нет на диске.)
ls -la /proc/1/exe
# cwd: текущий рабочий каталог процесса.
ls -la /proc/1/cwd
# environ: переменные окружения
tr ' ' 'n' </proc/1/environ
# status: состояние/память/UID/сигналы.
cat /proc/1/status
# или с выбором нужных строк
cat /proc/1/status | grep -E "Name|State|Uid|VmRSS|Threads"
# wchan: на каком системном вызове процесс ждёт / завис
cat /proc/1/wchan
# stack: стек ядра(требует рут)
sudo cat /proc/1/stack
Файловые дескрипторы
# Cписок открытых дескрипторов
ls -la /proc/1/fd/
# Cколько открыто
ls /proc/1/fd | wc -l
# Cравнить с лимитом
cat /proc/1/limits | grep "open files"
Если число в fd близко к лимиту, возможна утечка. Частая причина ошибки: too many open files.
Память и I/O
# Потребление памяти
cat /proc/1/status | grep -E "VmRSS|VmSize|VmSwap"
# Статистика ввода-вывода
cat /proc/1/io
VmRSS (Resident Set Size) — показатель реального потребления памяти. Разница между
rcharиread_bytesпоказывает, насколько эффективен page cache для этого процесса.
8. Планировщик и приоритеты
Linux делит время CPU между процессами через планировщик CFS (Completely Fair Scheduler). Планировщик решает, кто получает ресурсы и на сколько.
Nice: что это и как работает?
Nice — это атрибут процесса и утилита для его изменения.
Как атрибут: число от -20 до +19, которое хранится в ядре для каждого процесса. По умолчанию стоит 0, но чем меньше значение — тем выше приоритет. Как утилита: nice запускает команду с заданным приоритетом, renice меняет приоритет уже работающего процесса.
Правила Nice и Политика Планировщика
Правила nice довольно простые: уменьшать nice (что означает повышать приоритет) может только root. Обычный пользователь может только увеличивать его (т. е. снижать приоритет): с 0 до 10 — можно, с 0 до -5 — нельзя. Отмечу, что nice влияет только на распределение процессорного времени. Он не даёт гарантий, не резервирует CPU и не работает для real-time задач.
Примеры использования nice
# Запуск команды с низким приоритетом(не будет мешать другим)
nice -n 15 rsync -av /data /backup
# Запустить с высоким приоритетом (root)
sudo nice -n -10 ./service
# Просмотр nice всех процессов (колонка NI)
ps -eo pid,ni,comm | sort -k2 -n | head -20
Теперь про политику планировщика. Если сравнивать, то nice — это мягкое влияние, для более жёсткого контроля есть политики планировщика. Они определяют, как именно процесс конкурирует за CPU.
chrt — утилита для управления политиками планировщика.
|
Политика |
Описание |
Когда использовать |
|---|---|---|
|
SCHED_OTHER |
Стандартная, на основе nice. Процессы вытесняют друг друга. |
Обычные приложения, демоны, пользовательские задачи. |
|
SCHED_BATCH |
Как OTHER, но оптимизирована для пакетной обработки: не мешает интерактивным задачам. |
Фоновые расчёты, бэкапы, конвертация видео. |
|
SCHED_IDLE |
Самый низкий приоритет. Процесс получает CPU только когда система полностью свободна. |
Очень фоновые задачи, которые не должны влиять ни на что. |
|
SCHED_FIFO |
Real-time. Процесс выполняется, пока сам не уступит или не завершится. Не вытесняется другими. |
Аудио/видео обработка, промышленная автоматизация. |
|
SCHED_RR |
Real-time с квантами времени. Процесс выполняется фиксированными отрезками, затем уступает очередь. |
Задачи с жёсткими временными рамками, но без риска «зависнуть навсегда». |
Приоритет: для real-time политик (FIFO, RR) — число от 1 до 99. Для остальных политик приоритет игнорируется. Будьте осторожны: real-time политики могут повесить систему, если процесс войдёт в бесконечный цикл и не будет уступать CPU (тестировать и практиковаться рекомендуется через виртуальную машину!).
Примеры использования chrt
# Посмотреть политику и приоритет процесса
chrt -p 1234
# Изменить политику работающего процесса
sudo chrt -f -p 50 1234
# 1234 это пример pid
ionice — утилита для управления приоритетом доступа процесса к диску.
Классы.
-
1 (real-time): первый в очереди диска. Опасно: может заблокировать систему.
-
2 (best-effort): по умолчанию. Приоритет 0-7 внутри класса(0 = высший)
-
3 (idle): работает только когда никто другой не запрашивает диск.
Примеры использования ionice
# Запуск с минимальным i/o-приоритетом.
ionice -c 3 rsync -av /data /backup
# посмотреть i/o-приоритет процесса
ionice -p 1234
taskset — утилита для просмотра и установки привязки процесса к конкретным ядрам процессора. Зачем она нужна? К примеру, чтобы изолировать критичный сервис от других процессов, которые потребляют много ресурсов.
Примеры использования taskset
# Запуск процесс только на ядрах 0 и 1
taskset -c 0,1 ./myapp
# Запустить на ядрах 2-4 (диапазон)
taskset -c 2-4 ./worker
# Привязка рабочего процесса к ядрам 0,2,4
taskset -cp 0,2,4 1234
# Посмотреть текущую привязку.
taskset -cp 1234
Ещё пример: на 8-ядерном сервере привяжите базу данных к ядрам 0-3, а веб-сервер — к 4-7. Это снизит конкуренцию за кэш и память, уменьшит миграцию и повысит стабильность.
9. OOM Killer — кто умрёт первым
Когда памяти не хватает, ядро убивает кого-то. Жертву выбирает OOM Killer по очкам (oom_score): чем больше процесс жрёт памяти, тем ближе он к смерти. Примеры и частые практики, для которых нужно знать OOM Killer.
# Кто сейчас под угрозой
for pid in /proc/[0-9]*/oom_score; do
score=$(cat $pid 2>/dev/null)
[ "${score:-0}" -gt 100 ] 2>/dev/null || continue
comm=$(cat ${pid%/oom_score}/comm 2>/dev/null)
echo "$score $comm"
done | sort -rn | head -10
# Проверить, был ли кто убит
dmesg | grep -i oom
journalctl -k | grep "killed process"
# Защитить какой-то процесс от убийства
echo -500 | sudo tee /proc/$(pgrep postgres | head -1)/oom_score_adj
# Проверить, был ли кто убит
sudo dmesg | grep -i oom
# Защитить процесс от убийства
echo -500 | sudo tee /proc/$(pgrep postgres | head -1)/oom_score_adj
# Сделать процесс главной мишенью
echo 1000 | sudo tee /proc/1234/oom_score_adj
Кратко еще стоит добавить про ulimit и cgroups — ограничение ресурсов
ulimit задаёт ограничения для процессов в текущей сессии шелла и их потомков: сколько файлов можно открыть, сколько памяти занять, сколько процессов создать и т. д. Но, закрыв терминал, всё сбросится, если не прописать это постоянно в limits.conf (путь: /etc/security/limits.conf). Пропишите в конфиге все нужные вам ограничения, чтобы изменения пережили перезапуск. Проверить текущие ограничения можно примерно так: systemctl show myapp.service | grep -E "Memory|CPU|Tasks|IO".
cgroups — более низкоуровневый механизм ядра, который ограничивает ресурсы для произвольной группы процессов независимо от сессий. systemd автоматически создаёт cgroups для каждого сервиса, поэтому это рекомендуемый (моя рекомендация) способ ограничения ресурсов: прописал условный MemoryMax в unit-файлах — и всё, ядро следит за лимитами, даже если сервис форкает сотню процессов или дочерних процессов.
Но вот вопрос: когда что использовать?
Давайте как пример пару ситуаций:
-
Лимит для пользователя или сессии: достаточно ulimit и настройка конфига.
-
Лимит для systemd-сервиса: systemd + cgroups
-
Лимит для контейнера, к примеру в Docker это можно сделать через yaml (docker compose) у них под капотом cgroups.
Примеры команд
# посмотреть все лимиты
ulimit -a
# макс. открытых файлов
ulimit -n
# макс. процессов
ulimit -u
# макс. виртуальной памяти
ulimit -v
# макс. cpu
ulimit -t
# поднять лимит файлов прямо сейчас
ulimit -n 65536
# лимиты конкретного процесса
cat /proc/1/limits
# иерархия cgroups
systemd-cgls
# посмотреть потребление ресурсов по сервисам в реальном времени
systemd-cgtop
# найти в какой cgroup сидит процесс
cat /proc/1/cgroup
10. Мониторинг: top, htop и другие.
top — инструмент, показывающий процессы, нагрузку на процессор, память и среднюю загрузку системы. Можно запустить top сразу, а можно и написать флаги, к примеру обновление каждую секунду top -d 1 или следить за конкретным процессом top -p 1234 (несколь процессов через запятую писать).
Возможно есть ещё флаги, но полезные флаги я выделю вот такие:
Для запуска и отображения:
-
-d <секунды>— интервал обновления. По умолчанию 3 секунды. -
-n <число>— завершить после N обновлений. Полезно в скриптах. -
-b— пакетный режим, выводит результат в stdout без интерактива. Используется вместе с-n, -
-1— показать каждое ядро процессора отдельно.
Для фильтрации:
-
-p <PID>— следить только за указанным процессами, до 20 через запятую. -
-u <пользователь>— показывать только процессы конкретного пользователя. -
-U <пользователь>— то же, но включает процессы где пользователь указан в любом из полей.
Прочее:
-
-H— показывать потоки вместо процессов. Каждый поток отображается отдельной строкой. -
-c— показывать полную командую строку вместо короткого имени процесса. -
-i— скрыть простаивающие процессы, показывать только активные. -
-s— безопасный режим, отключает опасные команды(к примеру r и k).
Заключение
Мы разобрали базу процессов (за рамками остались межпроцессное взаимодействие, strace и т. д.), они войдут в следующие части серии. Самая лучшая практика для вас (если вы, к примеру, обучаетесь) — запустить виртуалку и попробовать создать сценарии, используя команды и знания из статьи. Удачи вам!
© 2026 ООО «МТ ФИНАНС»
Автор: opensophy
