Вы рядом, но есть ряд проблем, многие из них указали в комментариях к вопросу:
- Вы повторно выполняете
argv[0]
— программу — что не предвещает ничего плохого.
- Обычно вы должны выполнять дочернее действие в цикле, который разветвляет дочерние элементы.
- Если вы хотите, чтобы дочерние элементы выполнялись одновременно, вам нужен вызов
wait()
вне цикла, который их разветвляет.
- Вы читаете команду из стандартного ввода, хотя в спецификации указано, что нужно выполнять аргументы командной строки.
- Вы используете данные, считанные из стандартного ввода, в качестве имени команды для выполняемого процесса, а не в качестве аргумента командной строки.
- У вас есть локальная переменная
argv
, скрывающая (скрывающая) аргумент argv
для main()
. Таким образом, код в execvp()
обращается за пределами массива argv
после того, как counter
достигает 2, и вы используете нулевой указатель, когда counter
равен 1.
- Вы не должны сообщать об успехе (
exit(0);
) после того, как дочерний элемент не смог выполниться.
- Я думаю, вы должны сообщать о том, какой процесс не удалось выполнить — конечно, по стандартной ошибке — когда дочерний процесс не выполняется.
Между прочим, все ваши примеры командных строк используют полный путь к команде, что делает ненужным использование execvp()
. Вы вполне можете ввести только имя команды, как в командной строке, а затем execvp()
найдет команду и запустит ее (см. мой пример выполнения).
Асинхронное выполнение
Исправление их и добавление нескольких незначительных настроек, а также асинхронный запуск аргументов командной строки (возможно, одновременное выполнение многих команд) дает такой код:
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
for (int counter = 1; counter < argc; counter++)
{
pid_t pid = fork();
if (pid < 0)
{
perror("forking child process failed\n");
exit(EXIT_FAILURE);
}
else if (pid == 0) // child
{
char *args[] = { argv[counter], NULL };
execvp(args[0], args);
fprintf(stderr, "%s: failed to execute %s: (%d) %s\n",
argv[0], argv[counter], errno, strerror(errno));
exit(EXIT_FAILURE);
}
}
int corpse;
int status;
int failed = 0;
while ((corpse = waitpid(0, &status, 0)) > 0)
{
if (WIFEXITED(status))
{
printf("Process %d exited with status %d (0x%.4X)\n",
corpse, WEXITSTATUS(status), status);
if (WEXITSTATUS(status) != 0)
failed++;
}
else if (WIFSIGNALED(status))
{
printf("Process %d was signalled %d (0x%.4X)\n",
corpse, WTERMSIG(status), status);
failed++;
}
}
return((failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE);
}
Переменная failed
отслеживает, не выполнилась ли какая-либо из команд, и процесс сообщает об успехе (завершается с 0 или EXIT_SUCCESS), только если каждая из команд выполнена успешно; в противном случае он завершается с индикацией ошибки (выходит с EXIT_FAILURE, что обычно равно 1). При запуске на моем Mac (как args41
, созданном из args41.c
) он произвел:
$ args41 uname date ls
Sun Sep 15 07:26:58 MDT 2019
Darwin
Process 1907 exited with status 0 (0x0000)
Process 1908 exited with status 0 (0x0000)
20000-leagues-under-the-sea.txt bin maxargs.sh
LICENSE.md conn11 overlap.data
Metriseis_2012.dat conn11.c overlap47.c
README.md conn11.dSYM overlap61.c
Safe conn13 overlap73
Untracked conn13.c overlap73.c
acr.list conn13.dSYM overlap73.dSYM
acronym29.c conn17 packages
acronym43 conn17.c pseudo-json.md
acronym43.c conn17.dSYM question.md
acronym43.dSYM conn29 sll43.c
acronym47 conn29.c so-0167-2112
acronym47.c conn29.dSYM so-0167-2112.c
acronym47.dSYM doc so-0167-2112.dSYM
acronym53 dr41 so-1043-1305
acronym53.c dr41.c so-4921-8019
acronym53.dSYM dr41.dSYM so-4970-8730
acronym59 dw41 so-4971-1989
acronym59.c dw41.c so-4985-0919
acronym59.dSYM dw41.dSYM so-5102-0102
argc37 etc so-5134-1743
argc37.c gccw67 so-5225-1783
argc37.dSYM gccw67.c so-5279-4924
args41 gccw67.dSYM so-5358-5962
args41.c gccw67.o so-5394-5215
args41.dSYM get.jl.activity so-5416-6308
argv89 inc so-5424-4465.md
argv89.c lib src
argv89.dSYM makefile
Process 1909 exited with status 0 (0x0000)
$ args41 many things to do
args41: failed to execute many: (2) No such file or directory
args41: failed to execute things: (2) No such file or directory
args41: failed to execute to: (2) No such file or directory
args41: failed to execute do: (2) No such file or directory
Process 1939 exited with status 1 (0x0100)
Process 1941 exited with status 1 (0x0100)
Process 1940 exited with status 1 (0x0100)
Process 1942 exited with status 1 (0x0100)
$
Очевидно, что если вы хотите, чтобы процессы выполнялись синхронно, вы должны поместить цикл waitpid()
внутри цикла for
. Вы по-прежнему должны использовать цикл, потому что процесс может наследовать потомков, которых он не разветвлял, при неясных обстоятельствах. В этом случае вы можете предпочесть использовать waitpid(pid, &status, 0)
; тогда вам не нужен цикл вокруг waitpid()
.
Если вы хотите отслеживать имена процессов и при этом запускать процессы асинхронно, тогда родительский процесс должен будет вести запись PID каждого дочернего элемента в массиве по мере их разветвления, а цикл отчетности будет выполнять поиск по списку известных детей сообщить, кто из них умер.
Синхронное выполнение
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
int failed = 0;
for (int counter = 1; counter < argc; counter++)
{
pid_t pid = fork();
if (pid < 0)
{
perror("forking child process failed\n");
exit(EXIT_FAILURE);
}
else if (pid == 0) // child
{
char *args[] = { argv[counter], NULL };
execvp(args[0], args);
fprintf(stderr, "%s: failed to execute %s: (%d) %s\n",
argv[0], argv[counter], errno, strerror(errno));
exit(EXIT_FAILURE);
}
else
{
int status;
if (waitpid(pid, &status, 0) < 0)
{
fprintf(stderr, "failed to wait for %s process %d: (%d) %s\n",
argv[counter], (int)pid, errno, strerror(errno));
failed++;
}
else if (WIFEXITED(status))
{
printf("Process %s exited with status %d (0x%.4X)\n",
argv[counter], WEXITSTATUS(status), status);
if (WEXITSTATUS(status) != 0)
failed++;
}
else if (WIFSIGNALED(status))
{
printf("Process %s was signalled %d (0x%.4X)\n",
argv[counter], WTERMSIG(status), status);
failed++;
}
else
{
printf("Process %s died unexpectedly (0x%.4X)\n",
argv[counter], status);
failed++;
}
}
}
return((failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE);
}
Пример вывода (args89
создан из args89.c
):
$ args89 date uname ls
Sun Sep 15 08:34:00 MDT 2019
Process date exited with status 0 (0x0000)
Darwin
Process uname exited with status 0 (0x0000)
20000-leagues-under-the-sea.txt argv89.c makefile
LICENSE.md argv89.dSYM maxargs.sh
Metriseis_2012.dat bin overlap.data
README.md conn11 overlap47.c
Safe conn11.c overlap61.c
Untracked conn11.dSYM overlap73
acr.list conn13 overlap73.c
acronym29.c conn13.c overlap73.dSYM
acronym43 conn13.dSYM packages
acronym43.c conn17 pseudo-json.md
acronym43.dSYM conn17.c question.md
acronym47 conn17.dSYM sll43.c
acronym47.c conn29 so-0167-2112
acronym47.dSYM conn29.c so-0167-2112.c
acronym53 conn29.dSYM so-0167-2112.dSYM
acronym53.c doc so-1043-1305
acronym53.dSYM dr41 so-4921-8019
acronym59 dr41.c so-4970-8730
acronym59.c dr41.dSYM so-4971-1989
acronym59.dSYM dw41 so-4985-0919
argc37 dw41.c so-5102-0102
argc37.c dw41.dSYM so-5134-1743
argc37.dSYM etc so-5225-1783
args41 gccw67 so-5279-4924
args41.c gccw67.c so-5358-5962
args41.dSYM gccw67.dSYM so-5394-5215
args89 gccw67.o so-5416-6308
args89.c get.jl.activity so-5424-4465.md
args89.dSYM inc src
argv89 lib
Process ls exited with status 0 (0x0000)
$
Асинхронное выполнение с отслеживанием имени процесса
Этот вариант запускает команды асинхронно, но также отслеживает, какой процесс принадлежит каждому PID. Обратите внимание, что он использует функцию (шок, ужас) для определения аргумента, соответствующего только что умершему PID. Массив настроен так, чтобы было удобно отслеживать числа — элемент 0 массива PID не используется.
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
#include <unistd.h>
static int find_pid(int pid, int num_pids, pid_t pids[num_pids])
{
for (int i = 1; i < num_pids; i++)
{
if (pids[i] == pid)
return i;
}
return -1;
}
int main(int argc, char *argv[])
{
pid_t pids[argc];
for (int counter = 1; counter < argc; counter++)
{
pid_t pid = fork();
if (pid < 0)
{
perror("forking child process failed\n");
exit(EXIT_FAILURE);
}
else if (pid == 0) // child
{
char *args[] = { argv[counter], NULL };
execvp(args[0], args);
fprintf(stderr, "%s: failed to execute %s: (%d) %s\n",
argv[0], argv[counter], errno, strerror(errno));
exit(EXIT_FAILURE);
}
else
pids[counter] = pid;
}
int corpse;
int status;
int failed = 0;
while ((corpse = waitpid(0, &status, 0)) > 0)
{
int index = find_pid(corpse, argc, pids);
if (index < 0)
{
fprintf(stderr, "Unrecognized PID %d exited with status 0x%.4X\n",
corpse, status);
failed++;
}
else if (WIFEXITED(status))
{
printf("Process %s (PID %d) exited with status %d (0x%.4X)\n",
argv[index], corpse, WEXITSTATUS(status), status);
if (WEXITSTATUS(status) != 0)
failed++;
}
else if (WIFSIGNALED(status))
{
printf("Process %s (PID %d) was signalled %d (0x%.4X)\n",
argv[index], corpse, WTERMSIG(status), status);
failed++;
}
else
{
printf("Process %s (PID %d) died from indeterminate causes (0x%.4X)\n",
argv[index], corpse, status);
failed++;
}
}
return((failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE);
}
Пример запуска (args79
создан из args79.c
):
$ args79 uname date pwd
/Users/jleffler/soq
Process pwd (PID 2105) exited with status 0 (0x0000)
Darwin
Sun Sep 15 09:04:37 MDT 2019
Process uname (PID 2103) exited with status 0 (0x0000)
Process date (PID 2104) exited with status 0 (0x0000)
$
person
Jonathan Leffler
schedule
15.09.2019
fork()
s (потенциально) несколько потомков, ноwait()
s только один раз. Если вы хотите, чтобы он собирал несколько дочерних элементов, он долженwait()
для каждого. - person John Bollinger   schedule 15.09.2019execvp(argv[0])
, но вы, вероятно, не хотите этого делать. Обычно он запускает новый экземпляр родительского процесса, создавая таким образом облегченную форк-бомбу. - person John Bollinger   schedule 15.09.2019execvp()
), а затем подождать в циклеwaitpid()
, пока все дети не умрут. - person Jonathan Leffler   schedule 15.09.2019wait()
s находится в том же цикле, в котором онfork()
s, то вы будете собирать каждый дочерний элемент, прежде чем разветвлять следующий. Это нормально, если это то, чего ты хочешь. Однако если вы хотите предоставить дочерним элементам возможность одновременного запуска, вместо этого вы должны использовать второй, отдельный цикл для сбора дочерних элементов. - person John Bollinger   schedule 15.09.2019argv
новой переменной и используетеargc
, зная, что втораяargv
содержит только элемент1
помимоNULL
, ноcounter
может быть больше, потому что она принимает значениеargc
. Это может вызвать неопределенное поведение. - person XBlueCode   schedule 15.09.2019