Использование системных вызовов и управление процессами
В прошлой статье мы научились виртуозно жонглировать файлами и перенаправлять реки данных с помощью конвейеров. Но представьте жизненную ситуацию: вы запустили скрипт резервного копирования базы данных размером 500 гигабайт. Процесс займет около трех часов. Внезапно пропадает интернет, SSH-сессия обрывается, и ваш скрипт умирает на половине пути, оставляя поврежденный архив и переполненный диск.
Чтобы предотвращать подобные катастрофы, необходимо понимать, как операционная система управляет запущенными программами. Переход от написания простых линейных скриптов к созданию надежных фоновых служб требует погружения в архитектуру Unix.
Анатомия процесса и системные вызовы
Когда вы вводите команду в терминале и нажимаете Enter, программа не начинает выполняться сама по себе. Оболочка Bash обращается к ядру операционной системы с просьбой выделить ресурсы.
Процесс — это программа в стадии выполнения. Он включает в себя не только сам машинный код, но и выделенную оперативную память, переменные окружения и список открытых файлов.
Для взаимодействия между пользовательскими программами и ядром ОС используются системные вызовы (system calls). Это строго регламентированный интерфейс, через который скрипт просит ядро выполнить привилегированную операцию: прочитать файл, выделить память или отправить данные по сети.
Создание нового процесса в Unix опирается на два фундаментальных системных вызова:
fork — создает точную копию текущего процесса (родителя). Новый процесс называется дочерним. Он получает копию памяти родителя, но имеет собственный уникальный идентификатор.
exec — заменяет память дочернего процесса новой программой. > Программа — это заклинание, а процесс — это дух, которого вы вызвали этим заклинанием.
>
> Эрик Рэймонд
Каждый процесс в системе получает уникальный числовой идентификатор — (Process ID). Значение идентификатора всегда строго положительное, то есть . Максимальное значение зависит от настроек ядра, но в классических системах . У каждого процесса также есть (Parent Process ID) — идентификатор процесса, который его породил.
Мониторинг: поиск затерянных программ
Как найти процесс, который потребляет 100% ресурсов процессора и тормозит весь сервер? Для этого системные администраторы используют утилиты мониторинга.
Команда ps делает моментальный снимок текущих процессов. Чаще всего она используется с флагами aux:
Этот конвейер выведет все процессы, в имени которых есть слово nginx. Вы увидите пользователя, запустившего процесс, его , процент использования процессора и памяти, а также текущее состояние.
Процессы не всегда активно выполняются. Они постоянно меняют свои состояния в зависимости от системных событий.
| Код состояния | Название | Описание |
| :--- | :--- | :--- |
| R | Running | Процесс выполняется в данный момент или ожидает очереди на процессоре. |
| S | Sleeping | Процесс спит, ожидая события (например, ввода от пользователя или ответа от диска). |
| D | Uninterruptible Sleep | Глубокий сон, обычно связанный с ожиданием ввода-вывода. Процесс нельзя убить. |
| Z | Zombie | Процесс завершился, но родитель еще не считал код его возврата. Он не потребляет память, но занимает . |
| T | Stopped | Процесс приостановлен (например, комбинацией Ctrl+Z). |
Для наблюдения за системой в реальном времени применяется утилита top (или ее улучшенная версия htop). Она выводит динамически обновляемый список процессов, отсортированный по потреблению ресурсов. Если сервер начал тормозить, запуск top — это первый шаг к диагностике проблемы.
Управление фоновыми задачами
Вернемся к проблеме долгого резервного копирования. Если запустить скрипт обычным образом, он привязывается к текущему терминалу. Закрытие терминала отправляет всем привязанным процессам сигнал об отключении, и они завершаются.
Чтобы освободить терминал для других команд, процесс можно запустить в фоне, добавив амперсанд & в конец команды:
Система вернет номер фоновой задачи и ее , например: [1] 4592. Теперь вы можете вводить новые команды, пока бэкап создается в фоне.
Однако, если вы закроете SSH-сессию, процесс 4592 все равно будет уничтожен. Чтобы сделать процесс полностью независимым от терминала, используется системная утилита nohup (no hangup):
В этом примере мы отвязали скрипт от терминала, запустили его в фоне и перенаправили весь вывод (включая ошибки) в файл backup.log. Даже если вы выключите свой рабочий компьютер, сервер продолжит выполнять скрипт. Если скрипт обрабатывает 500 гигабайт данных со скоростью 50 мегабайт в секунду, он будет безопасно работать в фоне около 170 минут.
Для управления фоновыми задачами в рамках одной сессии используются команды:
* jobs — показывает список фоновых задач текущего терминала.
fg %1 (foreground*) — возвращает задачу номер 1 на передний план.
bg %1 (background*) — возобновляет выполнение приостановленной задачи в фоне.
Сигналы: язык общения с процессами
Как операционная система заставляет процесс остановиться? Она не выдергивает виртуальный шнур из розетки, а отправляет процессу сигнал. Сигналы — это простейшая форма межпроцессного взаимодействия (IPC).
Когда вы нажимаете Ctrl+C в терминале, оболочка отправляет активному процессу сигнал прерывания. Процесс получает его и, как правило, завершает свою работу.
Для ручной отправки сигналов используется системный вызов и одноименная утилита kill. Несмотря на пугающее название, она умеет отправлять любые сигналы, а не только убивать.
Самые важные сигналы, которые должен знать каждый инженер:
* SIGINT (код 2) — прерывание с клавиатуры (Ctrl+C). Программа может перехватить его и корректно завершить работу.
* SIGTERM (код 15) — мягкое требование завершения. Это сигнал по умолчанию для команды kill. Программа получает время на сохранение данных и закрытие файлов.
* SIGKILL (код 9) — жесткое убийство. Этот сигнал обрабатывается не самой программой, а ядром ОС. Процесс уничтожается мгновенно, без возможности сохранить данные.
Если веб-сервер завис, правильный алгоритм действий выглядит так:
Использование kill -9 должно быть исключительной мерой. Если применить его к базе данных во время записи транзакции, высок риск повредить файлы на диске.
Перехват сигналов внутри скрипта
Профессиональные скрипты умеют реагировать на сигналы. Представьте, что ваш скрипт скачивает временные файлы в директорию /tmp. Если пользователь нажмет Ctrl+C до завершения работы, временные файлы останутся лежать на диске мертвым грузом, постепенно съедая свободное место.
Чтобы этого избежать, в Bash существует встроенная команда trap. Она позволяет назначить функцию-обработчик, которая выполнится при получении определенного сигнала.
``bash
#!/bin/bash
TEMP_DIR="/tmp/my_script_cache"
mkdir -p "TEMP_DIR; exit 1" SIGINT SIGTERM
echo "Скрипт работает. Нажмите Ctrl+C для отмены."
Имитация долгой работы
for i in {1..100}; do
touch "i.tmp"
sleep 1
done
Нормальное завершение
rm -rf "PID$.
* Утилиты ps
и top
позволяют находить ресурсоемкие процессы и анализировать их состояния (от активного выполнения до "зомби").
* Для запуска долгих скриптов, устойчивых к закрытию терминала, используется комбинация утилиты nohup
и оператора фонового выполнения &
.
* Управление процессами осуществляется через сигналы. SIGTERM
(15) просит программу завершиться корректно, а SIGKILL
(9) принудительно уничтожает ее на уровне ядра.
* Команда trap` позволяет скриптам перехватывать сигналы прерывания, обеспечивая безопасное завершение работы и очистку временных файлов.