Укладка зомби и Shell не будет печатать после команды pipe в реализации оболочки - PullRequest
0 голосов
/ 12 апреля 2020

Я прохожу курс по ОС и совершенно новый для linux. Я должен реализовать программу оболочки, которая поддерживает: запуск переднего плана / фоновых процессов, конвейер только для 2 команд.

Предполагается, что есть основная функция, которая выполняется в бесконечном l oop и анализирует входные команды для получения {char ** arglist} правильно - я должен реализовать функцию process_arglist для выполнения команды.

У меня есть несколько проблем, с которыми я не могу обратиться к источнику.

Во-первых, после выполнения одной команды конвейера, например, ls -l | less, любая дальнейшая команда не будет печататься.

Во-вторых, когда я запускаю несколько процессов в фоновом режиме и начинаю играть с ps, kill командами, чтобы увидеть zomb ie поведение процесса - я вижу, что у меня есть процессы зомба ie, сложенные в списке процессов. Я sh, чтобы предотвратить появление зомби как можно скорее.

Что я делаю не так?

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <signal.h>

#define PRINT_ERROR fprintf(stderr, "%s\n", strerror(errno)) /*prints error message to stderr according to current errno*/

int wait_confirm = 1; /* should parent wait for child or not */
int pipe_confirm = 0; /* does the command contain pipe symbol */
int pipe_index; /* which index in arglist is the pipe symbol */




void do_pipe(char** arglist, int pipe_index){
    int fd[2];
    int exit_code;
    pid_t writer_pid, reader_pid;
    arglist[pipe_index] = NULL;
    if(pipe(fd)<0){
        PRINT_ERROR;
        exit(1);
    }
    if ((writer_pid = fork()) < 0){
        PRINT_ERROR;
        exit(1);
    }
    else if (writer_pid == 0){ /* Lefthand-Side of pipe enters here */
        dup2(fd[1], STDOUT_FILENO);
        close(fd[0]);
        close(fd[1]);
        execvp(arglist[0], arglist);
        PRINT_ERROR; /* we get here if an error occured on execvp */
        exit(1);
    }
    else { /* parent enters here */
        if ((reader_pid = fork()) < 0){
            PRINT_ERROR;
            exit(1);
        }
        else if (reader_pid == 0){ /*Righthand-side of pipe enters here */
            dup2(fd[0], STDIN_FILENO);
            close(fd[1]);
            close(fd[0]);
            arglist += pipe_index+1; /* arglist points to the command on the right of '|' */
            execvp(arglist[0], arglist);
            PRINT_ERROR;
            exit(1);
        }
        else{ /*parent enters here*/
            close(fd[0]);
            close(fd[1]);
            waitpid(reader_pid, &exit_code, 0); /* wait for second command to complete */
        }
    }
}


void prevent_zombies(int signum){
    wait(NULL);
}

// arglist - a list of char* arguments (words) provided by the user
// it contains count+1 items, where the last item (arglist[count]) and *only* the last is NULL
// RETURNS - 1 if should continue, 0 otherwise
int process_arglist(int count, char** arglist){
    int i=0;
    int exit_code;
    /* Set arglist appropriately so we can pass it to execvp without '&', if exists. */
    while(arglist[i]!=NULL){
        if (strcmp(arglist[i], "&")==0){
            wait_confirm = 0;
            arglist[i] = NULL;
        }
        else if (strcmp(arglist[i], "|") == 0){
            pipe_confirm = 1;
            pipe_index = i;
        }
        i++;
    }
    if(pipe_confirm){ /* command is indeed a pipe command */
        do_pipe(arglist, pipe_index);
    }
    else {
        int pid = fork();
        if (pid==0){ /* child enters here */
            execvp(arglist[0], arglist);
            PRINT_ERROR;
        }
        else{ /*parent enters here*/
            if(wait_confirm){
                wait(&exit_code);
            }
            else{
                signal(SIGCHLD, prevent_zombies);
            }
        }
    }
    return 1;
}

1 Ответ

0 голосов
/ 12 апреля 2020

Учитывая, что вы не предоставили 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 на стероидах).

...