Вопрос или проблема
Я пытаюсь написать утилиту, похожую на pkill
, которая находит процессы, ища определённую строку в /proc/pid/environ
, и завершает их. Но совпавший процесс может завершиться до того, как я успею его убить, и другой процесс может занять его pid. В этом случае я в конечном итоге убью новый процесс. Есть ли способ избежать этого состояния гонки?
-
Пересмотрите то, что вы делаете. Вместо того чтобы искать и убивать процессы силовым методом, найдите способ предотвратить запуск программы, которая их запускает. (Например, если это нежелательная служба, отключите службу.)
-
Узнайте максимальное значение PID и рассчитайте вероятность повторного использования PID. Идентификаторы процессов в Linux выделяются инкрементально, пока не произойдёт сброс, а
kernel.pid_max
равен 2^16 в старых дистрибутивах и 2^22 в более новых, а ваша утилита выполняет оба действия примерно в одно и то же время (с несколькими командами разницы) – поэтому, чтобы это произошло, системе нужно запустить 2^22 новых процессов, не запланировав выполнение вашей утилиты в это время, что довольно маловероятно, если процесс работает с нормальным приоритетом. -
Обновите ядро до новой версии и используйте функции pidfd, которые работают с дескрипторами файлов процессов, такие как
pidfd_open()
иpidfd_send_signal()
.
Если вы работаете на достаточно новой версии Linux, вы можете использовать pidfd_open()
(требуется Linux 5.3 или новее) или открыть каталог для PID в /proc
(как файл с использованием обычной функции open()
, этот подход требует Linux 5.1 или новее), чтобы получить дескриптор файла, связанный с конкретным процессом, на который ссылается данный PID в момент вызова системного вызова. Затем вы можете использовать pidfd_send_signal()
, чтобы отправить сигнал этому процессу. Открывая дескриптор PID файла, а затем используя его для взаимодействия с каталогом процессов (вместо того чтобы проверять файлы в /proc
по отдельности), вы можете закрыть тревогу о состоянии гонки.
Но в общем случае, это имеет смысл только для инструментов, таких как pkill
. Если вы просто пытаетесь убить процесс, который вы управляете, вам следует использовать правильный менеджер процессов, такой как systemd или runit, или, если вы не можете этого сделать, хотя бы использовать cgroups, оба из которых вообще не имеют этой проблемы.
Ответ или решение
Чтобы избежать случайного завершения не того процесса, вызванного повторным использованием PID в Linux, следует конечно учесть несколько ключевых моментов и предложить более надежные решения. Проблема заключается в том, что вы можете найти процесс, который соответствует вашему критерию, и, пока вы пытаетесь его завершить, он может завершиться, а затем другой процесс может занять его идентификатор. Вот несколько стратегий, которые помогут вам минимизировать риск этой ситуации.
Переосмысление подхода
Прежде всего, вам стоит задуматься над основной логикой вашего инструмента. Вместо механического завершения процессов, следует рассмотреть возможность предотвращения их запуска на начальном уровне. Если вы сталкиваетесь с нежелательной службой, лучшее решение — отключить её в настройках системы или использовать соответствующие инструменты управления службами, такие как systemctl
или runit
. Это поможет избежать появления проблемных процессов в первый раз.
Оценка вероятностей повторного использования PID
Важно понимать, что процессные идентификаторы (PID) в Linux присваиваются инкрементально. При достижении максимального значения (в настройках kernel.pid_max
, которое может составлять 2^16 или 2^22 в зависимости от дистрибутива) система начинает снова использовать уже завершённые идентификаторы. Чтобы ваша утилита уместно работала, должна произойти настоящая гонка: система должна создать 2^22 новых процессов, прежде чем ваша утилита сможет выполниться. Это маловероятно, если ваша утилита работает с нормальным приоритетом, так как планировщик будет распределять ресурсы между процессами.
Использование современных возможностей ядра Linux
Если вы используете современную версию ядра Linux (от 5.3 и выше), у вас есть доступ к функциям pidfd
, которые позволяют удобно работать с файловыми дескрипторами процессов. Функция pidfd_open()
позволяет открыть дескриптор для процесса по его PID на момент выполнения системного вызова. После этого вы можете использовать pidfd_send_signal()
для отправки сигнала безопасно через дескриптор. Это устраняет риск ситуации, в которой PID может быть повторно использован между выполнением вашего запроса и отправкой сигнала.
Кроме того, вы можете открыть каталог процесса в /proc
как файл, что также доступно начиная с версии 5.1. Это позволяет вашему инструменту более безопасно взаимодействовать с процессами.
Альтернатива: Использование систем управления процессами
Если ваша утилита предназначена для управления процессами, которые вы создаёте и контролируете, рекомендуется использовать полнофункциональный менеджер процессов, такой как systemd
или runit
. Эти системы управления процессами не имеют проблемы повторного использования PID и предоставляют более устойчивые механизмы для управления службами, что значительно снижает вероятность столкновения процессов.
Заключение
Включение вышеописанных методов и подходов в вашу утилиту поможет свести к минимуму риск завершения ошибочного процесса из-за повторного использования PID. Основное внимание должно быть сосредоточено на предотвращении ненужного создания процессов и использования современных функциональных возможностей ядра Linux для повышения надежности вашего инструмента. Такой подход не только повысит его эффективность, но и сделает управляемый процесс более безопасным.