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. Сбой в одном потоке затрагивает весь процесс — именно потому что CLONE_VM и CLONE_SIGHAND разделяют адресное пространство и обработчики сигналов на всех потоков процесса. Это прямое архитектурное следствие: nginx, например, использует модель multi-process вместо multi-thread, чтобы ошибка в одном worker не убила master и остальные воркеры.

У каждого процесса есть своё окружение — набор ресурсов и атрибутов, которые ядро выделяет при создании:

  1. Виртуальная память, где у процесса своё адресное пространство. Он не видит и не может напрямую изменить память другого процесса.

  2. Таблица открытых файлов, через неё процесс работает с файлами, сокетами, пайпами и др. По умолчанию открыты 3 дескриптора (stdin), 1 (stdout), 2 (stderr). В остальном процесс открывается сам по ситуации.

  3. Каталог (cwd), директория, в которой процесс сейчас находится, от неё отсчитываются все относительные пути.

  4. Переменные окружения, набор пар ключей (или по-другому KEY=VALUE), которые процесс получил от родителя. Через них передают конфиги: PATH, HOME, LANG и другие (к примеру иногда чувств. данные (токены, пароли). Меняются до запуска процесса, внутри только если процесс сам их перезапишет. Важный нюанс: переменные окружения процесса доступны через /proc/PID/environ — любому процессу с соответствующими правами на чтение этого файла. Поэтому хранить секреты в env стоит с пониманием этого факта, особенно в контейнерных окружениях.

  5. UID / GID, идентификаторы пользователя и группы, от имени которых запущен процесс. По ним идет проверка прав: может ли процесс прочитать файл, открыть порт, выполнить какую-то операцию и т. д.

  6. Код возврата, целое число 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

Процесс приостановлен сигналом SIGSTOP или SIGTSTP (Ctrl+Z). Не выполняется и не потребляет CPU. Можно возобновить через SIGCONT.

Z

Zombie

Завершён, но родитель не забрал код возврата через wait()

I

Idle

Неактивный поток ядра

Модификаторы состояния:

  1. < — высокий приоритет

  2. N — низкий приоритет

  3. s — лидер сессии

  4. l — многопоточный

  5. + — на переднем плане

Пример: 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) — 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>

6. Сигналы

Сигнал — это асинхронное уведомление процессу от ядра или другого процесса. Процесс может поймать сигнал, проигнорировать его или выполнить действие по умолчанию.

Главные сигналы

Сигнал

Номер

По умолчанию

Когда использовать

SIGTERM

15

Завершить

Первый выбор для остановки, процесс может перехватить и завершиться корректно: сохранить данные, закрыть соединения.

SIGKILL

9

Убить

Крайняя мера. Процесс не успевает сохранить состояние

SIGHUP

1

Завершить

Для демонов (перечитывать к примеру конфиг nginx без перезапуска)

SIGINT

2

Завершить

Прерывание (Ctrl+C)

SIGCONT

18

Приостановить/Продолжить

Возобновить приостановленный процесс

SIGCHLD

17

Игнорировать

Уведомление родителю о завершении дочернего процесса

SIGSTOP

19

Остановить

Приостановить процесс

Наверное появится вопрос "почему SIGKILL это крайняя мера?" Потому что процесс не успеет сделать свои нужды.(сохранить данные/записать файлы итд). Поэтому сначала пробуйте SIGTERM.

Что процесс может сделать с сигналом? Поймать, игнорировать, принять действие по умолчанию. Это означает следующие:

  1. Поймать, значит выполнить свой обработчик (например, nginx перечитывает конфиг на SIGHUP)

  2. Игнорировать (ну тут и так понятно)

  3. Принять действие по умолчанию, обычное завершение.

Стоит ещё отметить что поймать или проигнорировать 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> и другие примеры

# 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
# ⚠️ Файл читается любым процессом с правами на него.
# В контейнерных окружениях Docker открывает его по умолчанию — учитывайте это при передаче секретов через env.

# 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. Более точное измерение через smaps_rollup
cat /proc/1/smaps_rollup | grep -E "Rss|Pss"

# 3. Статистика ввода-вывода
cat /proc/1/io

VmRSS (Resident Set Size) — физическая память процесса, но с нюансом: если процесс использует разделяемые библиотеки, их страницы учитываются в RSS каждого процесса, хотя физически они одни. Если два процесса делят одну библиотеку на 10 МБ, суммарный RSS покажет 20 МБ — но в RAM занято только 10 МБ. PSS (Proportional Set Size) решает это: разделяемые страницы делятся пропорционально между процессами, поэтому сумма PSS всех процессов точнее отражает реальное потребление системы. Берите PSS когда хотите понять сколько памяти тратит система в целом, RSS — когда оцениваете пиковое потребление одного процесса в изоляции (например, если бы все библиотеки были статическими). Разница между rchar и read_bytes в /proc/PID/io показывает насколько эффективен 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

# 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

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"

Важное различие, которое легко упустить: ulimit ограничивает виртуальную память, а cgroups — физическую. Это принципиально для некоторых рантаймов. Например, сборщик мусора Go любит резервировать под гигабайт виртуальной памяти, используя при этом лишь несколько десятков мегабайт реальной — ulimit здесь либо сработает ложно, либо не сработает вообще, а cgroup будет контролировать процесс корректно.

cgroups более низкоуровневый механизм ядра, который ограничивает ресурсы для произвольной группы процессов независимо от сессий. systemd автоматически создаёт cgroups для каждого сервиса, поэтому это рекомендуемый (моя рекомендация) способ ограничения ресурсов: прописал условный MemoryMax в unit-файлах и всё, ядро следит за лимитами даже если сервис форкает сотню таких процессов/дочерних процессов.

Но вот вопрос, когда что использовать?

Давайте как пример пару ситуаций:

  1. Лимит для пользователя или сессии: достаточно ulimit и настройка конфига.

  2. Лимит для systemd-сервиса: systemd + cgroups

  3. Лимит для контейнера, к примеру в 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 ООО «МТ ФИНАНС»