
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 '\0' ' ' # exe: показывает, какой файл запущен. Если обновили пакет, а процесс не перезагрузили – будет (deleted, означает что этот процесс был запущен, которого больше нет на диске.) ls -la /proc/1/exe # cwd: текущий рабочий каталог процесса. ls -la /proc/1/cwd # environ: переменные окружения tr '\0' '\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 ООО «МТ ФИНАНС»
Granulex
Статья сама предупреждает про «чувствительные данные» в env-переменных – но не договаривает: они затем лежат в /proc/PID/environ и читаются любым процессом с нужными правами. Docker по умолчанию это и делает.
opensophy Автор
Здравствуйте! Согласен, это хороший нюанс. В статье я только кратко упомянул, что в env могут лежать чувствительные данные, но не стал углубляться в вопросы доступа через /proc и особенности контейнерных окружений, счёл эту информацию на другую часть статьи по безопасности Linux.