Linux, управление дочерними процессами, выполняющимися одновременно
Параллельная обработка сложна, по возможности ее лучше избегать, но когда есть приоритет для более быстрого выполнения или есть необходимость иметь несколько функций (программ) одновременно, мы должны пойти на это. Используя символ амперсанда (&), мы можем запускать процессы в фоновом режиме или в подоболочке, таким образом, они работают одновременно. При запуске нескольких процессов мы должны отслеживать состояние всех процессов, таких как их коды выхода, stdout, stderr и т. д., а также мы должны иметь возможность контролировать их жизненный цикл, например, возможность их убивать, останавливать, приостанавливать и т. д.
Итак, чтобы узнать коды выхода дочернего процесса, просто сохраните код выхода, используя «$?» и чтобы узнать stdout, stderr, мы должны перенаправить вывод в файл.
«$?» содержит код состояния последней выполненной программы.
Пример: мониторинг состояния дочернего процесса:
Рассмотрим сценарий, который просто спит и выдает код выхода в файл.
Сохраните файл с именем «script1.sh», затем сделайте его исполняемым.
Точно так же создайте другой файл с script2.sh со следующим содержимым.
Теперь запустите эти два сценария одновременно, используя символ амперсанда.
Таким образом, родительский процесс (здесь запущен сеанс оболочки) может узнать коды выхода, прочитав файлы proc1 и proc2 об этих фоновых процессах, точно так же мы можем перенаправить stdout и stderr в файлы и узнать статус этих дочерних процессов. процессы.
Пример: управление дочерним процессом:
Представьте, что мы хотим убить дочерний процесс, мы можем сделать это, если у нас есть pid дочернего процесса, чтобы мы могли отправлять сигналы SIGTERM или SIGKILL, чтобы убить его. Чтобы сохранить pid каждого дочернего процесса, который будет создан, используйте «$!» обозначение, это содержит pid последней запущенной программы.
Итак, в приведенных выше скриптах замените $? с $!, и запустите ту же команду, после чего pids можно будет прочитать из файлов, поскольку у нас есть pids дочернего процесса, который мы можем остановить, убить и т. д.
Всегда завершать дочерний процесс от прямого родителя:
Linux будет повторно использовать идентификаторы мертвых процессов, поэтому, когда мы сохраняем идентификаторы дочерних процессов дочерних процессов, эти внуки (не уверен в этой терминологии) могут быть мертвы некоторое время назад, и вы пытаетесь убить их, что может быть фатальным, потому что ядро могли назначить эти pids другим процессам.
Однако для прямых дочерних процессов дело обстоит иначе: дочерний процесс, который умирает до того, как родитель, будет считаться зомби-процессом. Он ничего не делает, но его pid не получает ядро, поэтому прямой родитель может отправить сигнал уничтожения любой нет. раз.
Следующий скрипт создает дочерний процесс, который работает в течение длительного времени.
echo $$ ›› pid.txt # текущий pid скрипта
sleep 315360000 & # Я буду жив 10 лет…
эхо $! ›› pid.txt # 10 лет команды сна pid
Назовите скрипт long_script.sh и сделайте его исполняемым.
1125 — Текущий идентификатор сеанса терминала.
6981 — Дитя 6980, долго спящий командный pid
Здесь не пытайтесь убить 6981 из процесса 1125, думая, что 6981 является потомком 6980, потому что, когда процессы 6980 и 6981 завершатся, ядро получит pid 6981 и назначит его какому-то другому процессу, но оно не будет пожинать 6980, потому что этот процесс является прямой потомок 1125, который все еще жив, поэтому 6980 помечен как зомби-процесс. Таким образом, управляйте 6980 дочерними процессами, используя родительский процесс 1125 и дочерний процесс 6981 из родительского процесса 6980.
Исполнительная команда:
Когда программа запускается с помощью «exec», текущая оболочка заменяется программой без создания нового процесса. Таким образом, когда вы запускаете «exec ‹some_command›» в терминале, сеанс оболочки терминала завершается и заменяется этой командой some_command. Это помогает в приведенном выше сценарии, когда вы хотите управлять процессом 6981 из 1125, который не является прямым родителем. Чтобы это произошло, запустите команду long sleep с exec, таким образом команда sleep не создаст новый процесс, а вместо этого получит 6980 в качестве своего pid, т. е. сеанс оболочки 6980 заменяется процессом 6981.
echo $$ ›› pid.txt # текущий pid скрипта
exec sleep 315360000 # Я буду жив 10 лет…
7414 — текущий pid сеанса терминала, 7696 — pid скрипта, затем процесс скрипта заменяется командой sleep, поэтому 7696 — это pid команды sleep. Теперь 7696 является прямым потомком родителя 7414, поэтому 7696 может управляться 7414.
Когда родительский процесс умирает:
Когда родительский процесс умирает, дочерние процессы считаются процессами-сиротами, процесс инициализации примет эти процессы-сироты, поэтому процесс инициализации становится новым pid родительского процесса.
Чтобы убедиться, что все дочерние процессы умирают, когда родительский умирает, мы можем использовать prctl PR_SET_PDEATHSIG, чтобы сообщить дочернему, когда родительский умирает, чтобы дочерний процесс знал, когда родитель умирает и завершает работу.
Завершение дочерних процессов
Программа на С запускает дочерний процесс с помощью функций fork() и execlp(). Получить pid основного процесса не представляется сложным. Убить его также не сложно. Возник вопрос, можно ли убить все возникшие в ходе выполнения программы дочерние процессы. При этом, чтобы программа продолжила спокойно выполнятся дальше.
А если не секрет, подскажите как. Буду очень благодарен.
сейчас структура программы такая:
Программа TEST . if (условие) pid=fork(); if (pif==0) exec(вызов программы); else вот тут надо убить дочерние процессы
Проверял по PID, структура примерно такая: PID PPID Имя проццеса 2 1 TEST 3 2 процесс вызванный в EXEC (omxplayer) 10 3 процесс созданный omxplayer
Нужно «убить» PID 10. PID 2 и 3 в программе получить могу. PID последнего потомка нет. Беда
как ты «убиваешь» процесс omxplayer? если дочерний его процесс в той же группе (pgroup), он тоже получит сигнал. сделай ps -eo sess,pgrp,ppid,pid,comm чтобы увидеть группу процессов. если его дочерний процесс сознательно помещен в отдельную группу, то «чистого» способа добиться желаймого нет — есть разной степени хаки. можно загнать их в cgroup/session и убить все в cgroup/session; можно подменить через ldpreload fork для omxplayer, с тем чтобы записывать его результат; можно в конце-концов искать всех «детей» твоего дочернего omxplayer через /proc.
а что ты вообще пытаешься сделать?
Закидывай в список все пиды дочерних процессов, потом отсылай им сигналы. И еще…
Задача стоит следующая: 1. есть RaspberryPI 2. с помощью omxplayer при загрузке системы запустить аудиодорожку №1 (это сделано) 3. сделать обработку состояния входов RBPI — при подаче на один вход сигнала высокого уровня: 3.1. запускается omxplayer, который начинает проигрывать аудиодорожку №2 (первая работает). при подаче сигнала низкого уровня 3.2. аудиодорожка №2 останавливается
Аудиодорожка №1 должна остаться нетронутой.
Я предположил, что это моно решить: 1. запустить аудиодорожку №1 прописав команду на е запуск в автозагрузку 2. написать на С программку с применение wiringPi библиотеки для обработка состояния входов 3. В цикле опрашивать состояние входа и при изменении уровня сигнала сначала запустить воспроизведение аудио с помощью комбинации fork(), exec() 4. Убить запущенный дочерний процесс с помощью kill(PID,-9)
По факту получается: 1. обрабатываю состояние входа 2. запускаю аудиод. При этом вызов fork()+execelp(«omxplayer»,) привод к тому, что в основном процессе (TEST) создается дочерний процесс omxplayer, а к этому процессу создается еще один дочерний, который проигрывает аудио (или что-то делает с этим аудиофайлом). Убить omxplayer удается без проблем. Но при этом его потомок, который собственно и отвечает за работу аудио продолжает работать.
Как сделать так, чтобы дочерний процесс завершался после завершения родительского
Предположим, у меня есть процесс, который порождает ровно один дочерний процесс. Теперь, когда родительский процесс завершается по какой-либо причине (нормально или ненормально, по kill, ^C, assert failure или чему-либо еще), я хочу, чтобы дочерний процесс также завершался. Как сделать это правильно?
Ответ 1
В Linux вы можете установить для дочернего процесса сигнал завершения родительского процесса, например так:
#include // prctl(), PR_SET_PDEATHSIG
#include // signals
#include // fork()
#include // perror()
// .
pid_t ppid_before_fork = getpid();
pid_t pid = fork();
if (pid == -1)
if (pid)
; // продолжить выполнение родительского процесса
> else
int r = prctl(PR_SET_PDEATHSIG, SIGTERM);
if (r == -1)
// проверка на случай, если исходный родитель завершился непосредственно
// перед вызовом prctl()
if (getppid() != ppid_before_fork)
exit(1);
// продолжить выполнение дочернего .
Обратите внимание, что сохранение идентификатора родительского процесса перед fork и его тестирование в дочернем процессе после prctl() устраняет состояние гонки между вызовом prctl() и завершением процесса, вызвавшего дочерний процесс.
Также обратите внимание, что сигнал завершения родителя сбрасывается во вновь созданных собственных дочерних элементах. На него не влияет файл execve() .
Этот тест можно упростить, если вы уверены, что системный процесс, отвечающий за привязку всех дочерних процессов, имеет PID 1:
pid_t pid = fork();
if (pid == -1)
if (pid)
; // продолжить выполнение родительского процесса
> else
int r = prctl(PR_SET_PDEATHSIG, SIGTERM);
if (r == -1)
// проверка на случай, если исходный родитель завершился непосредственно
// перед вызовом prctl()
if (getppid() == 1)
exit(1);
// продолжить выполнение дочернего .
Однако полагаться на то, что этот системный процесс имеет PID 1, не совсем корректно.
В POSIX.1-2008 указано :
Идентификатор родительского процесса всех существующих дочерних процессов и зомби-процессов вызывающего процесса должен быть установлен на идентификатор процесса системного процесса, определенного реализацией. То есть эти процессы должны быть унаследованы специальным системным процессом.
Традиционно системный процесс, принимающий всех дочерних, — это PID 1, то есть init, который является предком всех процессов.
В современных системах, таких как Linux или FreeBSD , эту роль может выполнять другой процесс. Например, в Linux процесс может вызывать, prctl(PR_SET_CHILD_SUBREAPER, 1), чтобы установить себя как системный процесс, который наследует все дочерн ие процессы и все процессы своих потомков.
Ответ 2
Для полноты картины : в macOS вы можете использовать kqueue:
void noteProcDeath(
CFFileDescriptorRef fdref,
CFOptionFlags callBackTypes,
void* info)
// LOG_DEBUG(@»noteProcDeath. «);
struct kevent kev;
int fd = CFFileDescriptorGetNativeDescriptor(fdref);
kevent(fd, NULL, 0, &kev, 1, NULL);
// принять меры в случае завершения процесса здесь
unsigned int dead_pid = (unsigned int)kev.ident;
CFFileDescriptorInvalidate(fdref);
CFRelease(fdref); // CFFileDescriptorRef больше не используется в этом примере
int our_pid = getpid();
// когда завершается наш родитель, завершаемся и мы.
LOG_INFO(@»exit! parent process (pid %u) died. no need for us (pid %i) to stick around», dead_pid, our_pid);
exit(EXIT_SUCCESS);
>
void suicide_if_we_become_a_zombie(int parent_pid)
// int parent_pid = getppid();
// int our_pid = getpid();
// LOG_ERROR(@»suicide_if_we_become_a_zombie(). parent process (pid %u) that we monitor. our pid %i», parent_pid, our_pid);
int fd = kqueue();
struct kevent kev;
EV_SET(&kev, parent_pid, EVFILT_PROC, EV_ADD|EV_ENABLE, NOTE_EXIT, 0, NULL);
kevent(fd, &kev, 1, NULL, 0, NULL);
CFFileDescriptorRef fdref = CFFileDescriptorCreate(kCFAllocatorDefault, fd, true, noteProcDeath, NULL);
CFFileDescriptorEnableCallBacks(fdref, kCFFileDescriptorReadCallBack);
CFRunLoopSourceRef source = CFFileDescriptorCreateRunLoopSource(kCFAllocatorDefault, fdref, 0);
CFRunLoopAddSource(CFRunLoopGetMain(), source, kCFRunLoopDefaultMode);
CFRelease(source);
>
Ответ 3
- е сли вы намеренно завершаете промежуточный процесс, то потомок не будет завершен, когда родительский процесс остановится ;
- е сли дочерний процесс завершается раньше, чем родительский, то промежуточный процесс попытается завершить исходный дочерний по pid, который теперь может относиться к другому процесс у (э то можно исправить, добавив больше кода в промежуточный процес с).
Мы будем очень благодарны
если под понравившемся материалом Вы нажмёте одну из кнопок социальных сетей и поделитесь с друзьями.