I / O зависает после рекурсивных вызовов pipe () - PullRequest
2 голосов
/ 26 сентября 2011

Это продолжение моего предыдущего вопроса .Я пишу оболочку Linux, и поэтому мне нужно иметь дело с возможностью ввода пользователями нескольких конвейерных команд.Он почти работает правильно, за исключением того, что после вызова execvp () для последней команды I / O зависает.Моя подсказка никогда не появляется снова, и я должен нажать Ctrl + C, чтобы покинуть оболочку.Я не думаю, что происходит бесконечный цикл, скорее я неправильно закрываю свои потоки.Я не могу найти правильный способ сделать это.

Пример - Если я вообще не использую канал, оболочка работает правильно:

ad@ubuntu:~/Documents$ gcc mash.c -o mash
ad@ubuntu:~/Documents$ ./mash
/home/ad/Documents> ls
a.out     bio1.odt  blah.cpp       controller.txt  mash.c
bio1.doc  blahblah.txt  Chapter1Notes.odt  mash
/home/ad/Documents> 

Однако, если я наберу:

/home/ad/Documents> ls -l | grep sh
-rwxr-xr-x 1 ad ad  13597 2011-09-26 00:03 mash
-rw-r--r-- 1 ad ad   3060 2011-09-25 23:58 mash.c

Приглашение не появляется снова.main () первоначально вызывает execute () со стандартным вводом и выводом.

Спасибо за ваше время!

Код:

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <stdlib.h>

int MAX_PATH_LENGTH = 1024; //Maximum path length to display.
int BUF_LENGTH = 1024; // Length of buffer to store user input
char * delims = " \n"; // Delimiters for tokenizing user input.
const int PIPE_READ = 0;
const int PIPE_WRITE = 1;

void execute(char **argArray, int read_fd, int write_fd){
    dup2(read_fd, 0);
    dup2(write_fd, 1);
    //Problem when entering only newline character
    char **pA = argArray;
    int i = 0;
    while(*pA != NULL) {
        if(strcmp(argArray[i],"<") == 0) { 
            int input = open(argArray[i+1], O_RDWR | O_CREAT);
            pid_t pid = fork();
            if(pid == 0) {
                dup2(input, 0);
                argArray[i] = 0;
                execvp(argArray[0], &argArray[0]);
                printf("Error redirecting input.\n");
                exit(1);
            }
            wait(pid);
        }
        else if(strcmp(argArray[i],">") == 0) { 
            int output = open(argArray[i+1], O_RDWR | O_CREAT);
            pid_t pid = fork();
            if(pid == 0){
                dup2(output,1);
                close(output);
                argArray[i] = 0;
                execvp(argArray[0], &argArray[0]);
                printf("Error redirecting output.\n");
                exit(1);
            }
            close(output);
            wait(NULL);

        }
        else if(strcmp(argArray[i],"|") == 0) {
            int fds[2];
            pipe(fds);
            pid_t pid = fork();
            if(pid == 0) {
                dup2(fds[PIPE_WRITE], 1);
                close(fds[PIPE_READ]);
                close(fds[PIPE_WRITE]);
                argArray[i] = 0;
                execvp(argArray[0], &argArray[0]);       
                printf("%s: command not found.\n", argArray[0]);     
                exit(1);
            } else {
                dup2(fds[PIPE_READ], 0);
                execute(&argArray[i+1], 0, 1);
                close(fds[PIPE_READ]);
                close(fds[PIPE_WRITE]);
                wait(pid);
                printf("herp\n");
            }
        }
        *pA++;
        i++;
    }
    pid_t pid = vfork();
    if(pid == 0){
        execvp(argArray[0], &argArray[0]);
        printf("%s: command not found.\n", argArray[0]);
        exit(1);
    }
    else {
        wait(NULL);
    }
}

int main () {

    char path[MAX_PATH_LENGTH];
    char buf[BUF_LENGTH];
    char* strArray[BUF_LENGTH];
    /**
     * "Welcome" message. When mash is executed, the current working directory
     * is displayed followed by >. For example, if user is in /usr/lib/, then
     * mash will display :
     *      /usr/lib/> 
     **/
    getcwd(path, MAX_PATH_LENGTH);
    printf("%s> ", path);
    fflush(stdout);

    /**
     * Loop infinitely while waiting for input from user.
     * Parse input and display "welcome" message again.
     **/ 
    while(1) {
        fgets(buf, BUF_LENGTH, stdin);
        char *tokenPtr = NULL;
        int i = 0;
        tokenPtr = strtok(buf, delims);

        if(strcmp(tokenPtr, "exit") == 0){

            exit(0);
        }
        else if(strcmp(tokenPtr, "cd") == 0){
            tokenPtr = strtok(NULL, delims);
            if(chdir(tokenPtr) != 0){
                printf("Path not found.\n");
            }
            getcwd(path, MAX_PATH_LENGTH);
        }
        else if(strcmp(tokenPtr, "pwd") == 0){
            printf("%s\n", path);
        }
        else {
            while(tokenPtr != NULL) {
                strArray[i++] = tokenPtr;
                tokenPtr = strtok(NULL, delims);
            }
            execute(strArray, 0, 1);
        }

    bzero(strArray, sizeof(strArray)); // clears array
    printf("%s> ", path);
    fflush(stdout);
    }

}

Ответы [ 2 ]

2 голосов
/ 26 сентября 2011

Эта строка - dup2(fds[PIPE_READ], 0); - перезаписывает текущий дескриптор файла стандартного ввода дескриптором, ссылающимся на канал.Как только команда pipe завершается, любая попытка чтения из stdin завершится неудачей.

Эта строка - fgets(buf, BUF_LENGTH, stdin); - не проверяет наличие ошибок.

Наконец - вы ждетевторой процесс в трубе, чтобы закончить, прежде чем вы начнете второй.Это то, что вызывает твой тупик;команда "grep" ожидает ввода, но вы еще не выполнили команду "ls".Вы ждете завершения команды grep, но она не может завершиться, потому что ожидает ввода.

В вашем последнем воплощении кода: когда вызывается функция execute (), она сканируетаргументы и находит трубу;затем он разветвляется и запускает первую команду ("ls"):

        pid_t pid = fork();
        if(pid == 0) {
            dup2(fds[PIPE_WRITE], 1);
            close(fds[PIPE_READ]);
            close(fds[PIPE_WRITE]);
            argArray[i] = 0;
            execvp(argArray[0], &argArray[0]);       
            printf("%s: command not found.\n", argArray[0]);     
            exit(1);

Затем он повторяется, снова вызывая execute ():

        } else {
            dup2(fds[PIPE_READ], 0);
            execute(&argArray[i+1], 0, 1);  // <--- HERE

... это, конечно, forkи запустить "grep", прежде чем вернуться.Обратите внимание, что это происходит с / обоими / дескрипторами файловых каналов / open /.Поэтому сам процесс grep будет держать оба конца трубы открытыми.execute () выполняет ожидание (NULL) перед возвратом;это, однако, на самом деле будет ждать завершения «ls» (поскольку этот процесс завершается первым).Затем он возвращается и продолжает:

            close(fds[PIPE_READ]);
            close(fds[PIPE_WRITE]);
            wait(pid);   // <--- WRONG, compile with -Wall to see why
            printf("herp\n");
        }

Я указал на одну ошибку.Попробуйте скомпилировать с помощью "-Wall" или прочитайте документацию по функции wait ()!Если вы измените его на ожидание (NULL), оно будет правильным, однако в этом случае оно заблокируется.Причина в том, что команда "grep" не завершила и все еще читает ввод.Причина, по которой он все еще читает ввод, состоит в том, что сам процесс grep имеет открытый конец записи канала !!Таким образом, grep никогда не видит «конец» ввода, поступающего из канала.Простое решение - закрыть рекурсивный вызов fd канала перед рекурсивным вызовом (однако с вашим кодом остаются другие проблемы, в том числе, как я уже указывал, что вы уничтожаете свой дескриптор stdin).

1 голос
/ 26 сентября 2011

Эти две строки имеют аргументы в неправильном порядке:

dup2(0, read_fd);
dup2(1, write_fd);

Вы должны написать:

dup2(read_fd, 0);
dup2(write_fd, 1);

Или:

dup2(read_fd,  STDIN_FILENO);
dup2(write_fd, STDOUT_FILENO);

Однако, будь то исправленный или оригинальный, вызов для выполнения:

 execute(strArray, 0, 1);

, что означает, что эти два dup2() вызова ничего не делают (дублируя от 0 до 0 и от 1 до 1).

...