Пара 6: Процессы, сигналы, IPC и наблюдение ресурсов

90 минут · 3 курс, ML.

Содержание и результат

Находить свой процесс и завершать его штатно; различать память и дисковое пространство.

План занятия

0–10: что осталось после закрытия терминала. 10–30: программа/процесс, fork/exec/wait и память. 30–45: потоки, состояния и сигналы. 45–65: реальный опыт fork/exec и чтение ps. 65–80: исходная лабораторная с ресурсами и штатным завершением. 80–90: различить zombie/orphan и объяснить наблюдения. COW и детали runtime — углубление, если группа успевает.

Практика выполняется в своей учебной папке и на localhost. Подготовка окружения описана в lab/README.md.

PID и жизненный цикл

Процесс — выполняющаяся программа с ресурсами и состоянием.

PID идентифицирует процесс в текущем пространстве имён. У процесса есть родитель, окружение, открытые файлы, память и код завершения. Один исполняемый файл может породить несколько процессов. ps показывает снимок, top — обновляемое представление. Процесс может работать, ждать I/O, спать или завершиться. Не делаем вывод «завис» только по нулевому CPU: сервис часто ждёт запрос. Фоновый запуск & возвращает shell управление, но не создаёт надёжный системный сервис. После закрытия сеанса процессы могут получить SIGHUP; для постоянного запуска позже используем менеджер служб.

Аналогия: рецепт — программа, повар за работой — процесс.

Сигналы и корректное завершение

Сначала просим остановиться, потом применяем крайние меры.

Ctrl+C обычно отправляет SIGINT группе процессов переднего плана. kill PID по умолчанию посылает SIGTERM; приложение может обработать его, завершить текущую работу и закрыть файлы. SIGKILL не даёт выполнить очистку и используется как крайний шаг после диагностики. Номер PID нужно проверить непосредственно перед сигналом: нельзя бездумно копировать чужой PID. Работаем только с созданным учебным sleep. Не используем широкие pkill python, которые могут остановить чужие задачи. Студент сначала показывает команду процесса, затем делает адресное завершение.

Аналогия: просьба закрыть магазин и аварийное отключение электричества имеют разные последствия.

CPU, RAM и диск

Ресурсы отвечают на разные диагностические вопросы.

free -h показывает память, df -h — свободное место файловых систем, du -sh — размер дерева файлов. Высокое использование RAM не всегда плохо: Linux использует кеш, поэтому обращаем внимание на available и характер нагрузки. При недостатке памяти ядро может убить процесс; нужно искать доказательство в доступных журналах, а не угадывать. Заполненный диск мешает записывать результаты, хотя памяти может быть достаточно. WSL2 имеет собственные ограничения ресурсов; не переносим числа со студенческого ноутбука на сервер. Объясните разницу единиц и то, что проценты зависят от числа ядер и инструмента.

Аналогия: повар, рабочий стол и кладовая — разные ограничения кухни.

Подробный разбор: Процесс изнутри: создание, выполнение, ожидание и завершение; IPC, наблюдение процессов и параметры ядра

Программа лежит на диске, процесс живёт в системе

Один и тот же код может иметь несколько независимых экземпляров.

Программа лежит на диске, процесс живёт в системе

Процесс — экземпляр выполнения с PID, виртуальным адресным пространством, открытыми дескрипторами, текущим каталогом, окружением и идентичностью пользователя. Код программы на диске и память работающего процесса — разные объекты: изменение app.py не обязано изменить уже запущенный процесс. Если дважды запустить одну программу, у экземпляров будут разные PID и своё состояние, даже когда они читают тот же исходник. PID имеет смысл внутри пространства имён и может быть повторно использован после завершения; не воспринимайте его как вечный уникальный идентификатор. PPID показывает текущего родителя. Процесс имеет хотя бы один поток исполнения. Команда ps показывает снимок, поэтому результат может поменяться между проверкой и действием. Перед отправкой сигнала сверяем PID и команду собственного учебного процесса.

Аналогия: две копии рецепта готовят два повара с разными рабочими столами. Общие файлы всё ещё могут создавать конфликты.

Практическое следствие: Обычно нет: у разных процессов отдельные адресные пространства. Общую память нужно организовать отдельно.

fork, exec и wait делают разные вещи

Новый PID появляется при создании ребёнка; exec сохраняет PID.

fork, exec и wait делают разные вещи

В учебной Unix-модели shell создаёт ребёнка через fork. В родителе возвращается PID ребёнка, в ребёнке — 0, поэтому один исходный участок кода продолжает работу по двум веткам. Ребёнок затем вызывает exec: ядро заменяет его образ программы, загружает новый код и исходное состояние. При успешном exec выполнение к прежнему коду не возвращается. PID остаётся тем же: это не создание третьего процесса. Родитель может ждать ребёнка через waitpid и получить статус завершения. Современные runtime и shell могут использовать posix_spawn или другие механизмы; наша цель — понять модель, а не обещать одинаковый syscall trace любого запуска. В демонстрации используем однопоточный Python и простую дочернюю команду, чтобы не переносить сложные ограничения fork многопоточных приложений в вводную практику.

Аналогия: созданному сотруднику назначили новую инструкцию; его табельный номер остался прежним. Память процесса заменяется намного строже, чем человеческая инструкция.

Практическое следствие: Нет. Изменяется выполняемый образ программы в том же процессе.

fork не обязан сразу копировать всю RAM

Copy-on-write откладывает копирование страниц до изменения.

fork не обязан сразу копировать всю RAM

В Linux fork использует copy-on-write: виртуальные адресные пространства различаются, но неизменённые страницы могут ссылаться на одни физические данные. Когда одна сторона пишет в такую страницу, ядро организует отдельную копию. Это объясняет, почему логическая копия процесса не всегда немедленно удваивает всю фактическую память. Изменение переменной в ребёнке не меняет переменную родителя. Общие описания открытых файлов — другое дело: наследованные дескрипторы могут ссылаться на один open file description и общий offset. RSS двух процессов нельзя механически складывать и считать уникальной потреблённой RAM; в более глубоком анализе учитывают разделяемые страницы и PSS. На первом курсе по DevOps важнее понять предел аналогии «fork копирует всё», чем вычислять память по одному ps.

Аналогия: две версии документа сначала ссылаются на общие страницы, но правки создают отдельную страницу. Это не общий изменяемый список.

Практическое следствие: Нет при обычной частной памяти. COW сохраняет отдельность адресных пространств.

Процесс владеет ресурсами, потоки выполняют код

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

Процесс владеет ресурсами, потоки выполняют код

Потоки имеют собственный стек, регистры и контекст исполнения, но в пределах процесса обычно разделяют адресное пространство и таблицу дескрипторов. Два потока могут читать и изменять один объект — появляются гонки, которые не устраняются самим фактом «оба потока в Python». Планировщик распределяет runnable-потоки по CPU; ожидание сети или диска освобождает возможность выполнить других. На одном ядре параллельность ощущается через чередование, на нескольких ядрах возможна реальная одновременность. Число потоков не равно числу используемых CPU: runtime, блокировки, I/O и ограничения контейнера меняют картину. Версии CPython и режимы исполнения различаются, поэтому не формулируем вечное правило про любой Python. Для урока достаточно разделить процесс, поток и ядро CPU.

Аналогия: сотрудники работают за общим столом с общими бумагами. Несколько рук ускоряют не каждую операцию и требуют согласования.

Практическое следствие: Потоки могут ждать I/O, блокировки, runtime или CPU; есть накладные расходы.

Процесс может существовать и не использовать CPU

Ожидание, остановка и завершение — разные состояния.

Процесс может существовать и не использовать CPU

В Linux ps STAT показывает состояние и дополнительные признаки. R объединяет выполнение и готовность к исполнению; S означает прерываемое ожидание, часто таймер, сеть или ввод. D — непрерываемое ожидание, часто связанное с I/O; сигнал может не проявиться до выхода из этого ожидания. T — остановленное состояние: код пока не выполняется, но ресурсы остаются. Z — процесс уже завершился, а запись со статусом ещё ожидает родителя. Состояния меняются быстро, поэтому ps даёт лишь моментальный снимок. Сон не равен сбою, а CPU 0% не доказывает исправность: процесс может ждать навсегда. На опыте sleep обычно S; SIGSTOP переводит собственный процесс в T, SIGCONT разрешает выполнение снова. Не отправляем сигналы PID 1 или произвольным процессам группы.

Аналогия: работник ждёт доставку, а не исчезает из штата. Зомби уже закончил работу и хранится только учётная запись.

Практическое следствие: Нет. Он может штатно ожидать таймер или данные; проверяем ожидаемое поведение.

kill — запрос ядру отправить сигнал

SIGTERM позволяет обработку; SIGKILL не даёт программе выполнить очистку.

kill — запрос ядру отправить сигнал

Сигнал — асинхронное уведомление процессу с определённым действием. SIGTERM обычно просит завершение; программа может установить обработчик, закончить текущую работу и выйти. Если обработчика нет, действует стандартное поведение. SIGKILL и SIGSTOP нельзя поймать или игнорировать. Ctrl+C в обычном терминале инициирует SIGINT для foreground process group, поэтому это не всегда сигнал только одному PID. Утилита kill не гарантирует, что процесс немедленно исчез: сигнал может быть обработан позже, а непрерываемое ожидание задерживает реакцию. Штатная остановка важна для сохранения данных, но сам SIGTERM ничего автоматически не сохраняет — очистка должна быть реализована приложением. В лабораторной начинаем с обычного TERM собственного sleep, затем ждём его через shell.

Аналогия: попросить закрыть магазин и выдернуть питание — разные действия. SIGKILL не является безопасной процедурой сохранения данных.

Практическое следствие: Она лишает приложение возможности штатно завершить работу и может скрыть причину отказа.

Зомби уже завершился, сирота потерял родителя

Лечение зависит от того, какая связь нарушена.

Зомби уже завершился, сирота потерял родителя

Когда ребёнок выходит, ядро сохраняет минимальную запись, чтобы родитель получил статус через wait. Пока этого не произошло, ps может показывать Z. Такой процесс уже не исполняет код; отправка kill не заменяет обязанности родителя собрать статус. Если первым завершился родитель, живой ребёнок становится сиротой. В Linux его подхватывает соответствующий reaper или subreaper, с учётом PID namespace; упрощение «любой сирота всегда получает host PID 1» неверно для всех контейнерных случаев. Сирота может продолжать полезную работу и не обязан стать зомби. Корректный менеджер процессов следит за детьми и собирает статусы. Поэтому PID 1 внутри контейнера должен правильно обрабатывать сигналы и дочерние процессы; небольшой init полезен, если приложение не выполняет эту роль.

Аналогия: зомби — незакрытая карточка законченной работы, сирота — продолжающий работу сотрудник с новым руководителем.

Практическое следствие: Нет. Это завершённый процесс с ещё не собранным статусом; сирота — другая ситуация.

POSIX-сигналы: уведомление и стандартное действие

Используем имена сигналов; их номера и обработка зависят от платформы.

POSIX-сигналы: уведомление и стандартное действие

Сигнал имеет имя и стандартное действие, если приложение не установило собственный обработчик или не изменило disposition. SIGINT связан с обычным Ctrl+C, SIGTERM используется для завершения, SIGKILL принудительно завершает без обработчика, SIGSTOP приостанавливает, SIGCONT продолжает. SIGCHLD сообщает об изменении состояния детей: сбор статуса выполняется wait. SIGPIPE может возникнуть при записи в pipe/socket без получателя; программа может обрабатывать ошибку другим способом. SIGHUP исторически связан с hangup; многие daemon используют его для reload только по собственному соглашению. SIGUSR1/2 также имеют смысл, установленный конкретной программой. SIGSEGV связан с ошибкой доступа к памяти, SIGALRM — с таймером. Не все сигналы означают смерть процесса. Стандартные сигналы могут объединяться при повторной доставке, поэтому их нельзя воспринимать как надёжную очередь произвольного числа событий; real-time сигналы имеют другую механику. Номера Linux и macOS могут различаться, особенно у USR и CHLD. Shell kill -l показывает локальные имена/номера.

Практическое следствие: SIGHUP не перезагружает любую программу автоматически. SIGKILL и SIGSTOP невозможно поймать или игнорировать.

IPC: взаимодействие отдельных адресных пространств

Канал и формат сообщения определяют, как процессы согласуют работу.

IPC: взаимодействие отдельных адресных пространств

Pipe передаёт поток байтов, FIFO даёт похожий механизм с именем в файловой системе. Unix-domain socket связывает локальные процессы, TCP/UDP socket — процессы через сетевой стек. Shared memory позволяет отображать общие страницы, но требует locks, semaphores или другого протокола синхронизации, иначе появляются гонки. Сигналы удобны для уведомлений вроде terminate/reload; большие данные лучше передавать через явный канал. Файлы тоже используются для взаимодействия, но тогда надо определить атомарность записи и обнаружение изменений. Протокол сообщает, где заканчивается сообщение, какая версия формата используется и что делать при ошибке. TCP даёт поток без встроенных границ JSON: один write не гарантирует один read. В нашем API HTTP задаёт рамки запроса и ответа, а серверные процессы читают данные через socket.

Практическое следствие: Отдельная память процессов не исключает общую память; она появляется только при специально организованном mapping и согласовании доступа.

Socket — объект ядра и программный интерфейс

IP-адрес и порт относятся к сетевому варианту, Unix socket может быть локальным.

Socket — объект ядра и программный интерфейс

Сервер создаёт socket, делает bind к выбранному адресу и порту, listen переводит TCP socket в режим приёма соединений, accept возвращает отдельный socket для клиента. Клиент делает connect, затем стороны читают и пишут. Listening socket и connected socket — разные объекты. TCP-соединение идентифицируется адресами/портами и протоколом, поэтому множество клиентов может приходить на один порт 8000. UDP не использует тот же listen/accept lifecycle; это datagram транспорт. Unix-domain socket работает локально и может использовать pathname, например /run/service.sock, либо другие namespace-механизмы. Сокет доступен через file descriptor, но это не обычный файл с сохранённой историей пакетов. Ss показывает Linux sockets и их состояние; macOS обычно используют lsof. Bind 127.0.0.1 принимает локальные обращения, 0.0.0.0 расширяет область слушания и требует понимания сетевого доступа.

Практическое следствие: LISTEN подтверждает слушателя. HTTP 200 дополнительно подтверждает конкретный ответ приложения; это разные уровни проверки.

top и htop — начало диагностики

Загрузка CPU, ожидание I/O и нехватка RAM имеют разные признаки.

top и htop — начало диагностики

Top периодически показывает нагрузку, CPU, память и процессы. Htop предоставляет другой интерактивный интерфейс, дерево и удобный выбор колонок; его установка не меняет механизм планирования Linux. %CPU относится к интервалу и выбранному режиму отображения; многопоточный процесс может использовать больше одного ядра. Linux load average учитывает runnable и задачи в непрерываемом ожидании, поэтому не равен проценту CPU. Высокий load при низком CPU может быть связан с I/O, но нужно подтвердить это состояниями и измерениями. RSS, VSZ и swap нельзя трактовать как одно число «памяти программы». Процесс D требует исследования устройства и kernel journal, S часто штатно ждёт. Для API связываем PID, состояние, журнал и запрос, иначе даже красивый график не доказывает причину. Режим top -b удобен для сохранения короткого снимка.

Практическое следствие: Ubuntu: top -b -n 1; ps -eo pid,ppid,stat,pcpu,pmem,comm --sort=-pcpu; free -h; vmstat 1 3.

sysctl — настройки работающего ядра

Текущие значения и сохранённая конфигурация — разные состояния.

sysctl — настройки работающего ядра

В Linux многие runtime-параметры доступны через /proc/sys. Sysctl читает или записывает эти значения; запись часто требует прав и может изменить сеть, память или безопасность немедленно. Файлы /etc/sysctl.d/*.conf описывают значения для применения системной инфраструктурой, но простое редактирование файла не равняется изменению текущего параметра. Не все свойства ядра настраиваются через sysctl, а набор ключей зависит от версии и сборки. На курсе используем чтение отдельных известных ключей, например vm.swappiness и net.ipv4.ip_forward. Копирование «ускоряющего набора sysctl» из блога не является диагностикой: нужны нагрузка, ожидаемый эффект и план возврата. Sysctl macOS имеет другой набор ключей и значения. Windows имеет другие механизмы конфигурации; Linux ключи туда не переносятся.

Практическое следствие: Чтение безопасно для наблюдения; изменение параметра — отдельное управляемое действие, а не универсальный ремонт.

Опыт: связать рисунок с наблюдением

python3 lab/experiments/process_lifecycle.py
sleep 30 &
pid=$!
ps -p "$pid" -o pid,ppid,stat,comm
kill -STOP "$pid"
ps -p "$pid" -o pid,ppid,stat,comm
kill -CONT "$pid"
kill -TERM "$pid"
wait "$pid"
printf "wait status: %s\n" "$?"

top -b -n 1 | head -n 15
ps -eo pid,ppid,stat,pcpu,pmem,comm --sort=-pcpu | head
ss -lnt
sysctl vm.swappiness net.ipv4.ip_forward
python3 lab/experiments/local_ipc.py

Перед опытом нарисуйте два PID и место exec. Сравните печать child-before-exec и child-after-exec: PID должен совпасть. Родитель получает exit status 7; сам учебный сценарий завершается успешно после проверки. В опыте sleep запишите S/T после сигналов; wait для TERM даёт ненулевой статус (численное представление shell может различаться). Работает в Ubuntu и macOS с python3. Запускайте блок в интерактивном Bash/zsh без режима set -e. Не сохраняйте PID для следующего занятия: его могли переиспользовать.

Снимите три коротких наблюдения собственного сервиса: PID/STAT, LISTEN и ответ HTTP. IPC-опыт использует socketpair и не открывает внешний порт. На Mac доступны ps, top, lsof; Linux sysctl ключи заменяются только после проверки документации.

Источники подробного разбора

Команды и наблюдения

Окружение: Ubuntu / Bash.

sleep 300 &
DEMO_PID=$!
ps -p "$DEMO_PID" -o pid,ppid,stat,comm
kill -TERM "$DEMO_PID"
wait "$DEMO_PID"
free -h
df -h .
du -sh ~/devops-course

$! сохраняет PID именно запущенного фонового процесса. Не подставляем PID из чужого примера.

Ожидаемый результат: sleep виден до сигнала и завершается после него; wait может вернуть 143 при SIGTERM, это ожидаемая демонстрация.

Вариант для macOS

ps, сигналы, df и du похожи. free отсутствует: vm_stat показывает страницы памяти; размер страницы указан в первой строке, поэтому не предполагайте 4 KiB. Activity Monitor удобен для общей картины памяти/CPU. Linux-сведения OOM и /proc не переносятся напрямую.

sleep 300 &
DEMO_PID=$!
ps -p "$DEMO_PID" -o pid,ppid,stat,comm
kill -TERM "$DEMO_PID"
wait "$DEMO_PID"
vm_stat
sysctl hw.memsize
df -h .
du -sh ~/devops-course

Практика: Паспорт ресурсов и процесса

  1. Запустите собственный sleep, найдите его через ps и запишите PID.
  2. Покажите команду и родительский PID; завершите SIGTERM.
  3. Сравните размер учебной папки с доступным местом файловой системы.
  4. Объясните, какие данные нужны, если сервис исчез при обработке большого входа.

Результат: Таблица CPU / RAM / диск и наблюдения процесса.

Проверка: Ни один чужой процесс не остановлен; память и диск не смешаны.

Неисправность для разбора: Студент смотрит df вместо free и неверно объясняет нехватку RAM.

Решение и диагностика

Для процесса достаточно ps -p "$DEMO_PID" -o pid,ppid,args и kill -TERM "$DEMO_PID". После wait повторный ps не должен показывать этот процесс. Для исчезнувшего сервиса соберите exit code, журнал, ограничения памяти, размер входа и данные о рестартах. Наличие кода 137 — признак SIGKILL, но само по себе ещё не доказывает OOM.

Основные выводы

Самостоятельная работа

Написать диагностическую памятку для трёх симптомов: медленно, исчезает, не пишет файл.

Источники