
Введение
Это четвёртая часть серии о Linux для DevOps и DevSecOps. В прошлой части разбирали права доступа и теперь переходим к одной из самых важных тем в Linux — процессам. Любая программа в системе в конечном итоге существует как процесс: nginx, postgres, docker, sshd, systemd, ваш shell и даже потоки ядра. Понимание того, как процессы создаются, живут, взаимодействуют с ядром и завершаются это база для понимания и диагностики Linux-систем. Цель этой статьи, рассказать кратко и на простом языке всю нужную информацию для начинающих так и для опытных пользователей и админов освежить их знания. Важно: для практики если обучаетесь то лучше всего использовать виртуалку с Linux…
Что такое процесс и поток
Процесс — это запущенная программа. Когда вы запускаете контейнер 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. Сбой в одном потоке затрагивает весь процесс.
У каждого процесса есть своё окружение — набор ресурсов и атрибутов, которые ядро выделяет при создании:
Виртуальная память, где у процесса своё адресное пространство. Он не видит и не может напрямую изменить память другого процесса.
Таблица открытых файлов, через неё процесс работает с файлами, сокетами, пайпами и др. По умолчанию открыты 3 дескриптора (stdin), 1 (stdout), 2 (stderr). В остальном процесс открывается сам по ситуации.
Каталог (cwd), директория, в которой процесс сейчас находится, от неё отсчитываются все относительные пути.
Переменные окружения, набор пар ключей (или по-другому KEY=VALUE), которые процесс получил от родителя. Через них передают конфиги: PATH, HOME, LANG и другие (к примеру иногда чувств. данные (токены, пароли). Меняются до запуска процесса, внутри только если процесс сам их перезапишет.
UID / GID, идентификаторы пользователя и группы, от имени которых запущен процесс. По ним идет проверка прав: может ли процесс прочитать файл, открыть порт, выполнить какую-то операцию и т. д.
Код возврата, целое число 0–255, которое процесс возвращает при завершении: 0 = успех, 1–255 = ошибка. Сохраняется в ядре до тех пор, пока родитель не считает его через команду
wait()/waitpid(). Если родитель не забирает код = процесс остаётся зомби до очистки
Жизненный цикл: рождение и смерть
Как рождается процесс: в 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>
Состояния процесса
Каждый процесс находится в одном из состояний, отображаемых в колонке STAT (ps aux).
Основные состояния:
Символ | Название | Описание |
|---|---|---|
R | Running | Выполняется или в очереди на CPU. |
S | Sleeping | Прерываемое ожидание (ввод, сеть, таймер). Реагирует на сигналы. |
D | Uninterruptible sleep | Ожидание I/O, который не прерывается сигналами. |
T | Stopped | Процесс приостановлен сигналом SIGSTOP или SIGTSTP (Ctrl+Z). Не выполняется и не потребляет CPU. Можно возобновить через SIGCONT. |
Z | Zombie | Завершён, но родитель не забрал код возврата через wait() |
I | Idle | Неактивный поток ядра |
Модификаторы состояния:
<— высокий приоритетN— низкий приоритетs— лидер сессииl— многопоточный+— на переднем плане
Пример: Ssl — спит, лидер сессии, многопоточный.
Идентификаторы: 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) — 1-ый процесс в пользовательском пространстве. От него ведут начало все остальные процессы от сессий до демонов, если процесс теряет родителя — 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>
Сигналы
Сигнал — это асинхронное уведомление процессу от ядра или другого процесса. Процесс может поймать сигнал, проигнорировать его или выполнить действие по умолчанию.
Главные сигналы
Сигнал | Номер | По умолчанию | Когда использовать |
|---|---|---|---|
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"
/proc — заглянуть внутрь любого процесса
Виртуальная файловая система (/proc) — это интерфейс, через который ядро отдаёт информацию о процессах и системе. Файлы здесь не занимают место на диске: когда делаем, к примеру, cat /proc/1234/status, ядро прямо в этот момент формирует текстовый ответ и отдаёт его вам. Здесь лежат данные обо всём: какие процессы запущены, сколько памяти используется и т. д.
Главные файлы /proc/<PID> и другие примеры
# 1. cmdline: командная строка запуска cat /proc/1/cmdline | tr '\0' ' ' # 2. exe: показывает, какой файл запущен. Если обновили пакет, а процесс не перезагрузили — будет (deleted, означает что этот процесс был запущен, которого больше нет на диске.) ls -la /proc/1/exe # 3. cwd: текущий рабочий каталог процесса. ls -la /proc/1/cwd # 4. environ: переменные окружения tr '\0' '\n' </proc/1/environ # 5. status: состояние/память/UID/сигналы. cat /proc/1/status # или с выбором нужных строк cat /proc/1/status | grep -E "Name|State|Uid|VmRSS|Threads" # 6. wchan: на каком системном вызове процесс ждёт / завис cat /proc/1/wchan # 7. stack: стек ядра (требует рут) sudo cat /proc/1/stack
Файловые дескрипторы
# 1. список открытых дескрипторов ls -la /proc/1/fd/ # 2. сколько открыто ls /proc/1/fd | wc -l # 3. сравнить с лимитом cat /proc/1/limits | grep "open files"
если числов fd близко к лимиту, возможно утечка. Частая причина: too many open files
Память и I/O
# 1. Потребление памяти cat /proc/1/status | grep -E "VmRSS|VmSize|VmSwap" #2. Статистика ввода-вывода cat /proc/1/io
VmRSS (Resident Set Size) показатель реального потребления памяти. Разница между
rcharиread_bytesпоказывает насколько эффективен page cache для этого процесса.
Планировщик и приоритеты
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
# 1. Запуск команды с низким приоритетом (не будет мешать другим) nice -n 15 rsync -av /data /backup # 2. Запустить с высоким приоритетом (root) sudo nice -n -10 ./service # 3. Просмотр 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
# 1. Посмотреть политику и приоритет процесса chrt -p 1234 # 2. Изменить политику работающего процесса sudo chrt -f -p 50 1234 # 1234 это пример pid
ionice — утилита для управления приоритетом доступа процесса к диску.
Классы.
1 (real-time): первый в очереди диска. Опасно: может заблокировать систему.
2 (best-effort): по умолчанию. Приоритет 0–7 внутри класса (0 = высший)
3 (idle): работает только когда никто другой не запрашивает диск.
Примеры использования ionice
# 1. Запуск с минимальным i/o-приоритетом. ionice -c 3 rsync -av /data /backup # 2. посмотреть i/o-приоритет процесса ionice -p 1234
taskset — утилита для просмотра и установки привязки процесса к конкретным ядрам процессора. Зачем он нужен? к примеру изолировать критичный сервис от других процессов, которые потребляют много ресурсов.
Примеры использования taskset
# 1. Запуск процесс только на ядрах 0 и 1 taskset -c 0,1 ./myapp # 2. Запустить на ядрах 2-4 (диапазон) taskset -c 2-4 ./worker # 3. Привязка рабочего процесса к ядрам 0,2,4 taskset -cp 0,2,4 1234 # 4. Посмотреть текущую привязку. taskset -cp 1234
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
Мониторинг: 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).
Шпаргалка
Базовые команды
Команда | Описание |
|---|---|
| все процессы в системе |
| альтернативный формат |
| дерево процессов с PID |
| мониторинг в реальном времени |
| интерактивный мониторинг |
| найти PID по имени |
| найти PID по шаблону |
Сигналы
Команда | Описание |
|---|---|
| отправить SIGTERM |
| отправить SIGKILL |
| список всех сигналов |
| перезагрузить конфигурацию |
| убить по имени |
| убить все процессы с этим именем |
Приоритеты
Команда | Описание |
|---|---|
| запустить с пониженным приоритетом |
| запустить с повышенным приоритетом (нужен root) |
| изменить приоритет работающего процесса |
| изменить класс планирования |
/proc
Команда | Описание |
|---|---|
| статус процесса |
| командная строка |
| переменные окружения |
| открытые файловые дескрипторы |
| карта памяти |
| cgroup процесса |
Мониторинг
Команда | Описание |
|---|---|
| обновление каждую секунду |
| следить за конкретным процессом |
| показывать потоки |
| потребление по cgroup |
`dmesg | grep -i oom` |
Заключение
Мы разобрали базу процессов (за рамками остались межпроцессное взаимодействие, strace и т. д.) — они войдут в следующие части серии. Самая лучшая практика для вас, если вы обучаетесь — запустить виртуалку и попробовать создать сценарии, используя команды и знания из статьи. Удачи вам!
© 2026 ООО «МТ ФИНАНС»


