5

Я использую сценарии оболочки, чтобы реагировать на системные события и обновлять отображение статуса в моем оконном менеджере. Например, один скрипт определяет текущее состояние Wi-Fi, прослушивая несколько источников:

  1. ассоциировать / отсоединять события от wpa_supplicant
  2. изменение адреса от ip (так что я знаю, когда dhcpcd назначил адрес)
  3. процесс таймера (поэтому уровень сигнала обновляется время от времени)

Чтобы добиться мультиплексирования, я в конечном итоге порождаю фоновые процессы:

{ wpa_cli -p /var/run/wpa_supplicant -i wlan0 -a echo &
ip monitor address &
while sleep 30; do echo; done } |
while read line; do update_wifi_status; done &

то есть настройка такова, что всякий раз, когда какой-либо из источников событий выводит строку, мой статус Wi-Fi обновляется. Весь конвейер запускается в фоновом режиме (окончательный '&'), потому что я также смотрю другой источник событий, который вызывает завершение моего скрипта:

wait_for_termination
kill $!

Предполагается, что kill убирает фоновые процессы, но в этой форме он не совсем справляется. Процессы 'wpa_cli' и 'ip' всегда выживают, по крайней мере, и не умирают при следующем событии (теоретически они должны получить SIGPIPE; я думаю, что процесс чтения должен быть еще жив).

Вопрос в том, как надежно [и элегантно!] очистить все фоновые процессы, порожденные?

3 ответа3

7

Супер простое решение - добавить это в конец скрипта:

kill -- -$$

Объяснение:

$$ дает нам PID запущенной оболочки. Таким образом, kill $$ отправит SIGTERM процессу оболочки. Однако, если мы отрицаем PID, kill отправляет SIGTERM каждому процессу в группе процессов. Нам нужно -- заранее, чтобы kill знал, что -$$ - это идентификатор группы процессов, а не флаг.

Обратите внимание, что это зависит от того, является ли запущенная оболочка лидером группы процессов! В противном случае $$ (PID) не будет совпадать с идентификатором группы процессов, и вы в конечном итоге отправляете сигнал тому, кто знает, где (ну, вероятно, нигде, поскольку вряд ли будет группа процессов с совпадающим идентификатором, если мы не лидер группы).

Когда оболочка запускается, она создает новую группу процессов [1]. Каждый разветвленный процесс становится членом этой группы процессов, если они явно не изменяют свою группу процессов с помощью системного вызова (setpgid).

Самый простой способ гарантировать выполнение определенного скрипта в качестве лидера группы процессов - это запустить его с помощью setsid . Например, у меня есть несколько таких сценариев состояния, которые я запускаю из родительского сценария:

#!/bin/sh
wifi_status &
bat_status &

Написанные так, сценарии wifi и батареи работают с той же группой процессов, что и родительский сценарий, и kill -- -$$ не работает. Исправление:

#!/bin/sh
setsid wifi_status &
setsid bat_status &

Я нашел pstree -p -g полезным для визуализации идентификаторов процессов и групп процессов.

Спасибо всем, кто внес свой вклад и заставил меня копать немного глубже, я научился чему-то! :)

[1] Есть ли другие обстоятельства, когда оболочка создает группу процессов? например. на запуск подоболочки? я не знаю...

1

Хорошо, я придумала довольно приличное решение, которое не использует cgroups. Как отметил Леонардо Дагнино, это не сработает, несмотря на процессы разветвления.

Одна из проблем с ручным отслеживанием идентификаторов процессов через $! уничтожить их позже - это неотъемлемое условие гонки - если процесс завершится до того, как вы его уничтожите, скрипт отправит сигнал несуществующему или, возможно, некорректному процессу.

Мы можем проверить завершение процесса в оболочке с помощью встроенного ожидания , но мы можем ждать только завершения всех фоновых процессов или одного pid. В обоих случаях ожидают блоки, что делает его непригодным для проверки того, работает ли данный PID.

В поисках решения вышеизложенного я наткнулся на команду jobs , которая, как я думал ранее, была доступна только для интерактивных оболочек. Оказывается, он отлично работает с помощью сценариев и автоматически отслеживает фоновые процессы, которые мы запустили - если процесс завершился, он больше не будет отображаться в списке заданий.

Поэтому команда:

trap 'kill $(jobs -p)' EXIT

Достаточно обеспечить - в простых случаях - завершение фоновых процессов при выходе из текущей оболочки.

В моем случае этого недостаточно, потому что я запускаю также фоновый процесс из подоболочки, и ловушки очищаются для каждой новой подоболочки. Итак, мне нужно сделать ту же ловушку внутри subshell:

{ trap 'kill $(jobs -p)' EXIT
wpa_cli -p /var/run/wpa_supplicant -i wlan0 -a echo &
ip monitor address &
while echo; do sleep 30; done } |
while read line; do update_wifi_status; done &

Наконец, jobs -p выдает только pid последнего процесса в конвейере (как и $!). Как видите, я порождаю фоновые процессы в первом процессе фонового конвейера, поэтому я хочу сигнализировать об этом pid.

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

$ sleep 20 | sleep 20 &
$ jobs -l
[1]+ 25180 Running                 sleep 20
     25181                       | sleep 20 &

Итак, используя слегка измененную команду kill из родительского скрипта, я могу сигнализировать все процессы в конвейере:

wait_for_termination
kill $(jobs -l |awk '$2 == "|" {print $1; next} {print $2}')
0

Вы можете получить PID каждого из них, поставив что-то вроде

PID1=$!

после wpa_cli,

PID2=$!

после ip и в конце скрипта вы убиваете их обоих:

kill $PID1
kill $PID2

Однако, это не сработает, если процессы разветвляются. Тогда cgroups будет лучшим решением.

Всё ещё ищете ответ? Посмотрите другие вопросы с метками .