как проверить успешность execl() (несколько процессов)

Я создал несколько дочерних процессов с помощью fork() и заставил их запускать исполняемый файл с помощью execl().

Я хочу проверить, есть ли какая-либо ошибка execl() (например, попытка выполнить несуществующий файл). Попытайтесь выполнить execl() все программы, и если одна из них не удалась, верните 1 до того, как start начнет общаться с любыми программами.

вот справочный код (предположим, что все настройки верны)

#DEFINE NUMBEROFCHILD 4
char** exeFile = {"./p1", "./p2", "./p3", "nonexist"); //"nonexist" is a non-exist program
int main(int argc, char** argv){
     pid_t childPID [numberOfChild];

     for (int i = 0; i<numberOfChild; i++) {
          //setting up pipes
          pid_t childPID[i] = fork();

          if(childPID[i] < 0) {
               //close all pipes and quit
          }else if (childPID[i] == 0) {
               //redirect pipe
               execl(exeFile[i],"args",(char*)0;
               return 1; 
               //I'm expecting this to return when it try to execute "nonexist"
               //but it didn't and keep running successed execl()
          }else {
               //close un-use pipes
          }
     }

     while(true) {
          for (int i = 0; i<numberOfChild; i++) {
               //communicate with childs through pipe
          }
     }

     for (int i = 0; i<numberOfChild; i++) {
          //close reminded pipes
          if (waitpid(childPID[i], NULL, 0) == -1){
               return 1;
          }
     }
     return 0;
}

Эта программа по-прежнему отправляла сообщение «несуществующему» (но ничего не получала от него, как и ожидалось).

Есть ли способ достичь моей цели? Благодарю вас!


person Tran Van Boi    schedule 29.09.2016    source источник
comment
Поставьте printf перед return 1, чтобы увидеть, что execl не удалось. Имейте в виду, что в этот момент вы находитесь в дочернем процессе, поэтому вам нужно будет использовать каналы для обратной связи с родителем, или отправить сигнал, например kill(getppid(), SIG_USER1), или использовать любое количество других средств межпроцессного взаимодействия.   -  person user3386109    schedule 29.09.2016
comment
Семейство функций exec* возвращает значение ошибки -1, а errno указывает на ошибку. Проверьте возвращаемое значение execl (в основном execl возвращается только при ошибке).   -  person Pablo    schedule 29.09.2016
comment
Если исполняемый файл не найден, execl вернет 2 (ENOENT)   -  person Pablo    schedule 29.09.2016
comment
Пожалуйста, опубликуйте компилируемый код (execl(exeFile[i],"args",(char*)0; неверно, не так ли?). Функция execl() возвращает значение только в случае сбоя процесса exec. Без работы по настройке единственным способом для родителя узнать о сбое дочернего процесса является ожидание процесса (возможно, с помощью waitpid(pid, &status, WNOHANG)) или попытка подать ему сигнал (kill(0, pid) видит, доступен ли pid). Оба они могут столкнуться с ложными срабатываниями планирования: ребенок еще не пробовал execl(), но когда он это сделает, он потерпит неудачу.   -  person Jonathan Leffler    schedule 29.09.2016
comment
@Jonathan: я написал двухстрочный тестовый код printf("errno = %d (%s)\n", errno, strerror(errno));, чтобы получить номер ошибки.   -  person Pablo    schedule 29.09.2016
comment
@Pablo: нет необходимости проверять возвращаемое значение из execl() или любой из exec*() функций. Если он вообще возвращается, значит, произошел сбой (и возвращаемое значение равно -1). Просто напишите код сообщения об ошибке после вызова exec*().   -  person Jonathan Leffler    schedule 29.09.2016
comment
@Pablo: это другое и соответствует тому, что я сказал, и тому, что вы хотели сказать (но на самом деле не сказали во втором комментарии) — значение самого execl() равно -1, а errno установлено в 2 или ENOENT.   -  person Jonathan Leffler    schedule 29.09.2016
comment
Да, это правда. Я хотел сделать акцент на том факте, что execl вернется только в случае сбоя. Кажется, я не правильно выразился.   -  person Pablo    schedule 29.09.2016
comment
user3386109: printf распечатывается в терминале и выходит через return, но все дочерние элементы все еще общаются при первом повороте (который нажал return1)   -  person Tran Van Boi    schedule 29.09.2016
comment
@Jonathan: не имеет значения, верен ли execl(exeFile[i],args,(char*)0) или нет, я просто хочу, чтобы эта программа возвращалась немедленно (без связи), если один из exeFile[i] не исполняемый.   -  person Tran Van Boi    schedule 29.09.2016
comment
Это имеет значение — или должно иметь значение как вопрос (вашей личной) профессиональной гордости. Этот вопрос может стоять годами. Если вы не исправите это, это покажет, что вы не заботитесь о качестве своей работы. Это глупо.   -  person Jonathan Leffler    schedule 29.09.2016
comment
Я понимаю, что вы хотите знать, все ли дети присутствуют и работают, прежде чем отправлять какие-либо данные любому из них. Нет тривиального способа сделать это, как я описал в другом комментарии. Если у вас есть двунаправленная канальная связь (один канал к каждому дочернему элементу, один канал от каждого дочернего элемента), то вы можете спроектировать дочерние элементы так, чтобы они записывали согласованный байт (или слово, или другое сообщение) во входящий канал, а ваша родительская программа этого не делает. не продолжаться до тех пор, пока не будет получен правильный ответ от каждого ребенка. Если дочерние элементы читают сообщение от родительского процесса после записи, они могут выйти на EOF.   -  person Jonathan Leffler    schedule 29.09.2016
comment
@Jonathan: о, теперь я тебя понял, я думаю, что знаю, как двигаться дальше; Спасибо за помощь. Кстати, я изучаю C/Linux, и это часть моего задания.   -  person Tran Van Boi    schedule 29.09.2016
comment
Я должен был отделить свой «бичевательный» комментарий о некомпилируемом коде в отдельный комментарий от последующего содержания; Мне жаль, что я этого не сделал.   -  person Jonathan Leffler    schedule 29.09.2016
comment
@TranVanBoid, вы имеете в виду, что вы хотите, чтобы дочерний процесс возвращал 1 родительскому в случае сбоя execl(), или вы хотите, чтобы родительский процесс прервал все это и вернуть 1 вызывающей стороне?   -  person John Bollinger    schedule 30.09.2016
comment
@JohnBollinger - родитель, который прервет все это и вернет 1 вызывающей стороне. Я просто понимаю, как глупо ставить return 1; в дочернем процессе.   -  person Tran Van Boi    schedule 30.09.2016
comment
return 1; после execl() — или, в функции, отличной от main(), вызов exit(1); или одного из его вариантов — совершенно необходим. Также должно быть напечатано сообщение об ошибке в стандартной ошибке. Но вы не можете позволить, чтобы потомок продолжал выполняться после того, как он не выполнился — это привело бы к хаосу и путанице. Проблема в том, как родитель распознает, что ребенок не смог выполнить. (Кстати, POSIX рекомендует exit(127);, когда команда не найдена — статус выхода для команды.)   -  person Jonathan Leffler    schedule 30.09.2016
comment
@JonathanLeffler мои коды работают, когда я заменяю return на exit...   -  person Tran Van Boi    schedule 30.09.2016


Ответы (2)


Вы можете договориться о том, чтобы дать ребенку один конец pipe(), установленный на закрытие при выполнении.

После сбоя execl() (т. е. если вызов возвращается), дочерний элемент write() перейдет в канал. Если родитель получает что-либо на своем конце канала (проверка с помощью poll() или аналогичного), то он знает, что дочерний процесс потерпел неудачу.

Если сообщение от дочернего элемента содержит идентификатор дочернего элемента, то канал может быть общим для всех дочерних элементов. Однако постарайтесь, чтобы write() было достаточно маленьким, чтобы быть атомарным!

person Toby Speight    schedule 29.09.2016
comment
Сохранять достаточно маленькую запись несложно; вам нужно будет записать более 4 КБ — возможно, более 64 КБ — чтобы превысить предел для атомарной записи в канале. - person Jonathan Leffler; 29.09.2016
comment
Да, я должен был сказать, что было бы довольно дико писать так много! - person Toby Speight; 29.09.2016
comment
Насколько надежно это при наличии произвольных задержек планирования? Как долго родительский процесс ждет, прежде чем решить, что все дочерние процессы успешны? Это не так сильно, как положительное сообщение от детей, что я здесь. Но мы не знаем, можно ли модифицировать дочерние процессы, чтобы они могли отправлять положительное сообщение. Мы также не знаем, какие каналы настроены — есть ли канал от родителя к ребенку, или от ребенка к родителю, или пара каналов, по одному в каждом направлении. Это немного сложно. - person Jonathan Leffler; 29.09.2016
comment
Я попытался сделать что-то подобное, написав в дочерний файл stdin_fileno и используя read(), чтобы получить значение на другом конце канала, но ничего не смог получить. - person Tran Van Boi; 30.09.2016
comment
@Jon: это не так сложно. Вызов read() вернет EOF, когда все дочерние элементы выполнились или завершились. Полностью детерминистский, если вы делаете это таким образом. - person Dietrich Epp; 30.09.2016
comment
О Конечно! Я упустил очевидное. Если вы получаете какие-либо данные, по крайней мере, один из детей не прошел; если вы не получили данных, все они успешно выполнены. - person Jonathan Leffler; 30.09.2016

Единственный надежный (но довольно экзотический, возможно, не очень переносимый) способ достижения вашей цели - использовать ptrace. Родитель устанавливает опцию PTRACE_TRACEEXEC, разветвляет все дочерние элементы и wait() в цикле. ребенок делает

    } else if () {
        ptrace(PTRACE_TRACEME, ...);
        execl(...);
    }

Родитель должен продолжать цикл wait до тех пор, пока все дочерние элементы не сообщат либо PTRACE_EVENT_EXEC, либо выход. В первом случае exec был успешным, и дочерний процесс остановлен. Когда вы знаете, что все дети сообщили об этом, и вы удовлетворены их состоянием, выполните ptrace(PTRACE_DETACH, ...) для всех из них и действуйте по своему усмотрению.

person user58697    schedule 29.09.2016