Линукс
Linux: Процессы
Четвёртая часть серии о Linux для DevOps и DevSecOps — разбираем процессы глубоко: жизненный цикл, состояния, /proc, сигналы, планировщик, ограничения, отладка и диагностика в production.
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. Сбой в одном потоке затрагивает весь процесс.
У каждого процесса есть своё окружение — набор ресурсов и атрибутов, которые ядро выделяет при создании:
- Виртуальная память, где у процесса своё адресное пространство. Он не видит и не может напрямую изменить память другого процесса.
- Таблица открытых файлов, через неё процесс работает с файлами, сокетами, пайпами и др. По умолчанию открыты 3 дескриптора (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-пространство и т. д. — система в итоге перестаёт создавать новые процессы.
В качестве примеров команды:
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 | Неактивный поток ядра |
Модификаторы состояния:
<— высокий приоритетN— низкий приоритетs— лидер сессииl— многопоточный+— на переднем плане
Пример: Ssl — спит, лидер сессии, многопоточный.
5. Идентификаторы: PID, PPID, UID и другие
PID (Process ID) — уникальный номер процесса, который назначает ядро. Не повторяется, пока не исчерпается диапазон.
PPID (Parent PID) — PID процесса, который создал данный. Каждый процесс (кроме systemd) имеет ровно 1 родителя. При завершении родителя его дочерние процессы переходят под управление systemd (PID 1).
Особые процессы: PID 1 и PID 2, эти процессы создаются ядром сразу после загрузки. Они не запускаются пользователем и не появляются через команды как обычные программы. Про PID 1 уже несколько раз упомянули, но всё же стоит отдельно вынести в терминологию.
PID 1 (systemd) — 1-ый процесс в пользовательском пространстве. От него ведут начало все остальные процессы от сессий до демонов, если процесс теряет родителя — PID 1 его усыновляет, забирая коды возврата завершившихся процессов, предотвращая накопление зомби. Убить их нельзя, в основном можно завершить/убить их через перезагрузку системы.
PID 2 (kthreadd) — прародитель всех потоков ядра. От него создаются системные воркеры (к примеру kworker). В ps и htop их видно по квадратным скобкам [kworker/0:0]. Эти процессы работают в пространстве ядра и отвечают за фоновые задачи, такие как обработку прерываний, работу с диском итд.
6. Сигналы
Сигнал — это асинхронное уведомление процессу от ядра или другого процесса. Процесс может поймать сигнал, проигнорировать его или выполнить действие по умолчанию.
Главные сигналы
| Сигнал | Номер | По умолчанию | Когда использовать |
|---|---|---|---|
| SIGTERM | 15 | Завершить | Первый выбор для остановки, процесс может перехватить и завершиться корректно: сохранить данные, закрыть соединения. |
| SIGKILL | 9 | Убить | Крайняя мера. Процесс не успевает сохранить состояние |
| SIGHUP | 1 | Завершить | Для демонов (перечитывать к примеру конфиг nginx без перезапуска) |
| SIGINT | 2 | Завершить | Прерывание (Ctrl+C) |
| SIGCONT | 18 | Приостановить/Продолжить | Возобновить приостановленный процесс |
| SIGCHLD | 17 | Игнорировать | Уведомление родителю о завершении дочернего процесса |
| SIGSTOP | 19 | Остановить | Приостановить процесс |
Наверное появится вопрос “почему SIGKILL это крайняя мера?” Потому что процесс не успеет сделать свои нужды.(сохранить данные/записать файлы итд). Поэтому сначала пробуйте SIGTERM.
Что процесс может сделать с сигналом? Поймать, игнорировать, принять действие по умолчанию. Это означает следующие:
- Поймать, значит выполнить свой обработчик (например, nginx перечитывает конфиг на SIGHUP)
- Игнорировать (ну тут и так понятно)
- Принять действие по умолчанию, обычное завершение.
Стоит ещё отметить что поймать или проигнорировать SIGKILL и SIGSTOP нельзя, т. к. они всегда обрабатываются ядром.
Примеры как отправить сигнал и завершить процесс.
7. /proc — заглянуть внутрь любого процесса
Виртуальная файловая система (/proc) — это интерфейс, через который ядро отдаёт информацию о процессах и системе. Файлы здесь не занимают место на диске: когда делаем, к примеру, cat /proc/1234/status, ядро прямо в этот момент формирует текстовый ответ и отдаёт его вам. Здесь лежат данные обо всём: какие процессы запущены, сколько памяти используется и т. д.
Главные файлы /proc/<PID> и другие примеры
Файловые дескрипторы
если числов fd близко к лимиту, возможно утечка. Частая причина: too many open files
Память и I/O
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 — это мягкое влияние, для более жёсткого контроля есть политики планировщика. Они определяют, как именно процесс конкурирует за 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
ionice — утилита для управления приоритетом доступа процесса к диску.
Классы.
- 1 (real-time): первый в очереди диска. Опасно: может заблокировать систему.
- 2 (best-effort): по умолчанию. Приоритет 0–7 внутри класса (0 = высший)
- 3 (idle): работает только когда никто другой не запрашивает диск.
Примеры использования ionice
taskset — утилита для просмотра и установки привязки процесса к конкретным ядрам процессора. Зачем он нужен? к примеру изолировать критичный сервис от других процессов, которые потребляют много ресурсов.
Примеры использования taskset
9. OOM Killer — кто умрёт первым
Когда памяти не хватает — ядро убивает кого-то, жертву выбирает OOM Killer по очкам (oom_score), чем больше жрёт памяти, тем ближе к смерти. Примеры и частые практики для чего нужно знать OOM Killer.
Кратко ещё стоит добавить про 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.
Примеры команд
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 и т. д.) они войдут в следующие части серии. Самая лучшая практика для вас (если вы обучаетесь к примеру) рекомендую запустить виртуалку и попробуйте создать сценарии используя команды и знания из статьи. Удачи вам!
