Учитывая, что вы не предоставили MCVE ( Минимальный, Полный, Проверяемый пример ) (или MRE или любое другое имя, которое SO сейчас использует) или SSCCE ( Short, Self-Contained, Correct Пример ), мне пришлось создать функцию main()
, чтобы запустить адаптацию вашего кода. Это также делает минимальным использование кода сообщения об ошибках, доступного в моем SOQ (вопросы о переполнении стека) на GitHub в виде файлов stderr.c
и stderr.h
в подпапке src / libsoq . directory.
#define _GNU_SOURCE
#include "stderr.h"
#include <assert.h>
#include <errno.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
#include <unistd.h>
#define EXEC_ERROR(cmd) fprintf(stderr, "failed to execute %s (%d: %s)\n", cmd, errno, strerror(errno))
#define PRINT_ERROR(func) fprintf(stderr, "function %s() failed (%d: %s)\n", func, errno, strerror(errno))
static int wait_confirm = 1;
static int pipe_confirm = 0;
static int pipe_index;
static_assert(sizeof(pid_t) == sizeof(int), "sizeof(pid_t) != sizeof(int) - redo format strings");
static void print_command(int cmdnum, char **argv)
{
fprintf(stderr, "%d: command %d:", getpid(), cmdnum + 1);
while (*argv != NULL)
fprintf(stderr, " %s", *argv++);
putc('\n', stderr);
}
static void do_pipe(char **arglist, int pipe_index)
{
int fd[2];
pid_t writer_pid, reader_pid;
arglist[pipe_index] = NULL;
if (pipe(fd) < 0)
{
PRINT_ERROR("pipe");
exit(1);
}
if ((writer_pid = fork()) < 0)
{
PRINT_ERROR("fork - writer");
exit(1);
}
else if (writer_pid == 0)
{
dup2(fd[1], STDOUT_FILENO);
close(fd[0]);
close(fd[1]);
execvp(arglist[0], arglist);
EXEC_ERROR(arglist[0]);
exit(1);
}
else if ((reader_pid = fork()) < 0)
{
PRINT_ERROR("fork - reader");
exit(1);
}
else if (reader_pid == 0)
{
dup2(fd[0], STDIN_FILENO);
close(fd[1]);
close(fd[0]);
arglist += pipe_index + 1;
execvp(arglist[0], arglist);
EXEC_ERROR(arglist[0]);
exit(1);
}
else
{
close(fd[0]);
close(fd[1]);
fprintf(stderr, "%d: writer %d, reader %d\n", getpid(), writer_pid, reader_pid);
int status = 0xFFFF;
int corpse = waitpid(reader_pid, &status, 0);
fprintf(stderr, "%d: reader %d exited with status 0x%.4X\n", getpid(), corpse, status);
}
}
static void dec_str(char *target, int value, int width)
{
char *pos = target + width;
*--pos = value % 10 + '0';
value /= 10;
while (pos > target && value > 0)
{
*--pos = value % 10 + '0';
value /= 10;
}
while (pos > target)
*--pos = ' ';
}
static void hex_str(char *target, int value, int width)
{
char *pos = target + width;
while (pos > target)
{
*--pos = "0123456789ABCDEF"[value % 16];
value /= 16;
}
}
static void prevent_zombies(int signum)
{
int status = 0xFFFF;
int corpse = wait(&status);
char message[] = "XXXXX: PID XXXXX exited with status 0xXXXX (signal XX)\n";
dec_str(&message[ 0], getpid(), 5);
dec_str(&message[11], corpse, 5);
hex_str(&message[38], status, 4);
dec_str(&message[51], signum, 2);
write(2, message, strlen(message));
signal(signum, SIG_DFL);
}
static void process_arglist(char **arglist)
{
wait_confirm = 1;
pipe_confirm = 0;
for (int i = 0; arglist[i] != NULL; i++)
{
if (strcmp(arglist[i], "&") == 0)
{
wait_confirm = 0;
arglist[i] = NULL;
}
else if (strcmp(arglist[i], "|") == 0)
{
pipe_confirm = 1;
pipe_index = i;
}
}
int pid;
if (pipe_confirm)
{
do_pipe(arglist, pipe_index);
}
else if ((pid = fork()) < 0)
{
PRINT_ERROR("fork");
}
else if (pid == 0)
{
execvp(arglist[0], arglist);
EXEC_ERROR(arglist[0]);
exit(1);
}
else if (wait_confirm)
{
fprintf(stderr, "%d: child %d (%s) launched\n", getpid(), pid, arglist[0]);
int status = 0xFFFF;
int corpse = waitpid(pid, &status, 0);
fprintf(stderr, "%d: child %d (%s) exited with status 0x%.4X\n", getpid(), corpse, arglist[0], status);
}
else
{
fprintf(stderr, "%d: child %d running in background\n", getpid(), pid);
signal(SIGCHLD, prevent_zombies);
}
}
static void collect_zombies(void)
{
int status;
int corpse;
while ((corpse = waitpid(-1, &status, WNOHANG)) > 0)
fprintf(stderr, "%d: zombie %d exited with status 0x%.4X\n", getpid(), corpse, status);
}
int main(int argc, char **argv)
{
err_setarg0(argv[0]);
int no_zombies = 0;
/* Can't reuse commands because process_arglist() modifies them */
char *tty = ttyname(0);
size_t srclen = strlen(err_getarg0()) + sizeof(".c");
char *source = malloc(srclen);
if (source == NULL)
err_syserr("failed to allocate %zu bytes of memory: ", srclen);
strcpy(source, err_getarg0());
strcat(source, ".c");
char *cmd1[] = { "ls", "-l", source, "|", "cat", NULL };
char *cmd2[] = { "ps", "-ft", tty, NULL };
char *cmd3[] = { "ls", "-l", source, "|", "cat", NULL };
char *cmd4[] = { "ps", "-ft", tty, "&", NULL };
char *cmd5[] = { "sleep", "5", NULL };
char *cmd6[] = { "ls", "-l", source, "|", "cat", NULL };
char *cmd7[] = { "ps", "-ft", tty, NULL };
char **cmds[] = { cmd1, cmd2, cmd3, cmd4, cmd5, cmd6, cmd7, NULL };
if (argc != 1)
no_zombies = 1;
fprintf(stderr, "%d: %s collecting zombies\n", getpid(), (no_zombies ? "is" : "is not"));
for (int i = 0; cmds[i] != NULL; i++)
{
print_command(i, cmds[i]);
process_arglist(cmds[i]);
if (no_zombies)
collect_zombies();
}
return 0;
}
Я улучшил объем печатной информации - при отладке оболочек или многопроцессорных процессов обильно используйте печать и убедитесь, что текущий PID включен в большинство строк вывода. Я разделил использование PRINT_ERROR
и предпочитаю (и теперь нашел более удобное использование) функционально-подобные макросы, а не объектно-подобные макросы, которые действительно работают.
Функция print_command
печатает последовательность аргументов - чтобы убедиться, что я вижу правильные команды.
Хорошо, что вы удостоверились, что закрыли достаточно файловых дескрипторов.
Подробности функции do_pipe()
в основном без изменений, за исключением сообщения о том, какие процессы были созданы, и состояния второго процесса.
Функции dec_str()
и hex_str()
безопасны для сигнала и используются из обработчика сигнала (prevent_zombies()
) для отчет о том, что процесс завершился без нарушения правил. POSIX определяет, какие функции можно вызывать внутри обработчиков сигналов. Функция dec_str()
направляет и оправдывает число; функция hex_str()
вправо оправдывает, но ноль дополняет число. Функция prevent_zombies()
сообщает о пойманном сигнале и о погибшем ребенке и его статусе. Он также сбрасывает обработку сигнала для SIGCHLD до значения по умолчанию. Это работает хорошо здесь. Вы должны экспериментировать без этого; он меняет поведение.
Одна из ключевых особенностей process_arglist()
заключается в том, что он сбрасывает глобальные переменные wait_confirm
и pipe_confirm
на значения по умолчанию. Эти переменные на самом деле не должны быть глобальными (я сделал их статическими; здесь есть только один исходный файл, поэтому ничего кроме main()
не должно быть доступно вне его). Код анализа изменяется на for
l oop для локализации i
.
Функцию collect_zombies()
можно вызывать для сбора любых процессов zomb ie; он использует опцию WNOHANG
, так что он не ждет, пока процесс d ie - он возвращается, если процесс еще не завершен.
Функция main()
выполняет некоторую работу по настройке. Функция err_setarg0()
записывает имя процесса - оно будет использоваться err_syserr()
, вызываемым после обнаружения сбоя malloc()
. Код предполагает, что программа, которую вы запускаете, является производной от имени исходного файла. Мои версии были в sh47.c
и sh97.c
, поэтому err_getarg0()
сообщает sh47
или sh97
; это используется для создания sh47.c
или sh97.c
.
Поскольку process_arglist()
изменяет указатели в переданном им списке аргументов, команды не могут быть повторно использованы. Если аргументы были выделены отдельно, process_arglist()
также будет утечка памяти. Кроме того, поскольку в windows много процессов, я уверен, что команда ps
сообщает о них только в текущем окне, используя ttyname()
для получения имени терминала. Точно так же я только перечисляю исходный код программы, а не 200+ других файлов в каталоге.
Затем программа выполняет итерацию по списку команд, распечатывая и выполняя каждую команду. В зависимости от того, были ли какие-либо аргументы командной строки, он может собирать зомби. Это очень примитивная обработка аргументов; «настоящая» оболочка будет использовать getopt()
или аналогичную функцию для управления обработкой аргументов.
Пример выполнения - не собирать зомби
21013: is not collecting zombies
21013: command 1: ls -l sh97.c | cat
21013: writer 21014, reader 21015
-rw-r--r-- 1 jonathanleffler staff 5494 Apr 12 12:54 sh97.c
21013: reader 21015 exited with status 0x0000
21013: command 2: ps -ft /dev/ttys000
21013: child 21016 (ps) launched
UID PID PPID C STIME TTY TIME CMD
0 820 765 0 24Mar20 ttys000 0:00.03 login -pf jonathanleffler
501 822 820 0 24Mar20 ttys000 0:00.61 -bash
501 21013 822 0 1:14PM ttys000 0:00.01 sh97
501 21014 21013 0 1:14PM ttys000 0:00.00 (ls)
0 21016 21013 0 1:14PM ttys000 0:00.00 ps -ft /dev/ttys000
21013: child 21016 (ps) exited with status 0x0000
21013: command 3: ls -l sh97.c | cat
21013: writer 21017, reader 21018
-rw-r--r-- 1 jonathanleffler staff 5494 Apr 12 12:54 sh97.c
21013: reader 21018 exited with status 0x0000
21013: command 4: ps -ft /dev/ttys000 &
21013: child 21019 running in background
21013: command 5: sleep 5
21013: child 21020 (sleep) launched
UID PID PPID C STIME TTY TIME CMD
0 820 765 0 24Mar20 ttys000 0:00.03 login -pf jonathanleffler
501 822 820 0 24Mar20 ttys000 0:00.61 -bash
501 21013 822 0 1:14PM ttys000 0:00.01 sh97
501 21014 21013 0 1:14PM ttys000 0:00.00 (ls)
501 21017 21013 0 1:14PM ttys000 0:00.00 (ls)
0 21019 21013 0 1:14PM ttys000 0:00.00 ps -ft /dev/ttys000
501 21020 21013 0 1:14PM ttys000 0:00.00 sleep 5
21013: PID 21019 exited with status 0x0000 (signal 20)
21013: child 21020 (sleep) exited with status 0x0000
21013: command 6: ls -l sh97.c | cat
21013: writer 21021, reader 21022
-rw-r--r-- 1 jonathanleffler staff 5494 Apr 12 12:54 sh97.c
21013: reader 21022 exited with status 0x0000
21013: command 7: ps -ft /dev/ttys000
21013: child 21023 (ps) launched
UID PID PPID C STIME TTY TIME CMD
0 820 765 0 24Mar20 ttys000 0:00.03 login -pf jonathanleffler
501 822 820 0 24Mar20 ttys000 0:00.61 -bash
501 21013 822 0 1:14PM ttys000 0:00.01 sh97
501 21014 21013 0 1:14PM ttys000 0:00.00 (ls)
501 21017 21013 0 1:14PM ttys000 0:00.00 (ls)
501 21021 21013 0 1:14PM ttys000 0:00.00 (ls)
0 21023 21013 0 1:14PM ttys000 0:00.00 ps -ft /dev/ttys000
21013: child 21023 (ps) exited with status 0x0000
Пример выполнения - собирать зомби
21033: is collecting zombies
21033: command 1: ls -l sh97.c | cat
21033: writer 21034, reader 21035
-rw-r--r-- 1 jonathanleffler staff 5494 Apr 12 12:54 sh97.c
21033: reader 21035 exited with status 0x0000
21033: zombie 21034 exited with status 0x0000
21033: command 2: ps -ft /dev/ttys000
21033: child 21036 (ps) launched
UID PID PPID C STIME TTY TIME CMD
0 820 765 0 24Mar20 ttys000 0:00.03 login -pf jonathanleffler
501 822 820 0 24Mar20 ttys000 0:00.61 -bash
501 21033 822 0 1:17PM ttys000 0:00.01 sh97 1
0 21036 21033 0 1:17PM ttys000 0:00.00 ps -ft /dev/ttys000
21033: child 21036 (ps) exited with status 0x0000
21033: command 3: ls -l sh97.c | cat
21033: writer 21037, reader 21038
-rw-r--r-- 1 jonathanleffler staff 5494 Apr 12 12:54 sh97.c
21033: reader 21038 exited with status 0x0000
21033: zombie 21037 exited with status 0x0000
21033: command 4: ps -ft /dev/ttys000 &
21033: child 21039 running in background
21033: command 5: sleep 5
21033: child 21040 (sleep) launched
UID PID PPID C STIME TTY TIME CMD
0 820 765 0 24Mar20 ttys000 0:00.03 login -pf jonathanleffler
501 822 820 0 24Mar20 ttys000 0:00.61 -bash
501 21033 822 0 1:17PM ttys000 0:00.01 sh97 1
0 21039 21033 0 1:17PM ttys000 0:00.00 ps -ft /dev/ttys000
501 21040 21033 0 1:17PM ttys000 0:00.00 sleep 5
21033: PID 21039 exited with status 0x0000 (signal 20)
21033: child 21040 (sleep) exited with status 0x0000
21033: command 6: ls -l sh97.c | cat
21033: writer 21041, reader 21042
-rw-r--r-- 1 jonathanleffler staff 5494 Apr 12 12:54 sh97.c
21033: reader 21042 exited with status 0x0000
21033: zombie 21041 exited with status 0x0000
21033: command 7: ps -ft /dev/ttys000
21033: child 21043 (ps) launched
UID PID PPID C STIME TTY TIME CMD
0 820 765 0 24Mar20 ttys000 0:00.03 login -pf jonathanleffler
501 822 820 0 24Mar20 ttys000 0:00.61 -bash
501 21033 822 0 1:17PM ttys000 0:00.01 sh97 1
0 21043 21033 0 1:17PM ttys000 0:00.00 ps -ft /dev/ttys000
21033: child 21043 (ps) exited with status 0x0000
Сравнение
В первом примере вы можете увидеть зомби (ls)
; во втором таких процессов нет. Есть команда sleep 5
, чтобы выход фона ps
не смешивался с последующими ls
и ps
- попробуйте удалить sleep
и посмотрите, что произойдет.
Как видите, здесь нет проблем с запуском последовательности команд и нет признаков потери вывода. Я не уверен, что пошло не так в вашей версии, но большая часть вашего кода не была предоставлена, поэтому вполне возможно, что проблема была не в том, что вы показали. Есть много возможностей. С учетом сказанного, я думаю, что одним из факторов в вашей проблеме были неустановленные глобальные переменные. Другой может быть, что вы не сбрасывали обработку после завершения фоновых процессов. Обратите внимание, что если фоновые процессы не заканчиваются sh аккуратно в последовательности, в которой вы их запускаете (например, серия sleep
команд различной длительности), тогда фоновая обработка не будет работать должным образом.
Действительно, стоит внимательно следить за вашим кодом, по крайней мере, до тех пор, пока вы не будете достаточно уверены, что он работает (и стоит сохранить код мониторинга доступным, чтобы вы могли активировать его, если у вас возникнут проблемы после изменения). Вы можете посмотреть макрос #define
для отладочной печати в C, чтобы узнать, как сделать код отладки настраиваемым во время компиляции и во время выполнения. Если вы сделаете это, вам может потребоваться некоторая правильная обработка аргументов для управления отладкой (например, sh -x
на стероидах).