Сложность перенаправления вывода в коде dup2 и pipe в Unix.

Я новичок в юникс. В следующем коде я передаю три аргумента из командной строки «~$ foo last sort more», чтобы воспроизвести «~$ last | sort | more». Я пытаюсь создать программу, которая будет принимать три аргумента (по крайней мере, 3). Родитель разветвит три процесса. Первый процесс будет писать в канал. Второй процесс будет читать и писать в канал и из канала, а третий процесс будет читать из канала и записывать в стандартный вывод (терминал). Первый процесс будет выполняться «последним», второй процесс будет выполняться «сортировать», а третий процесс будет выполняться «больше», и процессы будут спать в течение 1, 2 и 3 секунд для синхронизации. Я почти уверен, что у меня проблемы с созданием канала и перенаправлением ввода и вывода. Я не получаю никакого вывода на терминал, но вижу, что процессы были созданы. Буду признателен за помощь.

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <dirent.h>
#include <unistd.h>
#include <signal.h>
#include <fcntl.h>
#include <errno.h>

#define FOUND       1
#define NOT_FOUND   0
#define FIRST_CHILD 1
#define LAST_CHILD  numargc
#define PATH_1 "/usr/bin/"
#define PATH_2 "/bin/"

#define DUP_READ()                             \
if (dup2(fdes[READ], fileno(stdin)) == -1)     \
            {                              \
                perror("dup error");       \
                exit(4);                   \
            }                              

#define DUP_WRITE()                            \
if (dup2(fdes[WRITE], fileno(stdout)) == -1)   \
                {                              \
                    perror("dup error");       \
                    exit(4);                   \
                }                              

#define CLOSE_FDES_READ()   \
close(fdes[READ]);

#define CLOSE_FDES_WRITE()   \
close(fdes[WRITE]);

#define EXEC(x, y)                                          \
if (execl(arraycmds[x], argv[y], (char*)NULL) == -1)        \
                {                                           \
                    perror("EXEC ERROR");                   \
                    exit(5);                                \
                }
#define PRINT                         \
printf("FD IN:%d\n", fileno(stdin));    \
printf("FD OUT:%d\n", fileno(stdout));

enum 
{
    READ, /* 0 */
    WRITE,
    MAX
};

int cmdfinder( char* cmd, char* path); /* 1 -> found, 0 -> not found */
int main (int argc, char* argv[])
{

    int numargc=argc-1;
    char arraycmds[numargc][150];
    int i=1, m=0, sleeptimes=5, numfork;
    int rc=NOT_FOUND;
    pid_t pid;
    int fdes[2];

    if(pipe(fdes) == -1)
    {
        perror("PIPE ERROR");
        exit(4);
    }

    while(i <= numargc)
    {
        memset(arraycmds[m], 0, 150);
        rc=cmdfinder(argv[i], arraycmds[m]);
        if (rc)
        {
            printf("Command found:%s\n", arraycmds[m]);
        } 
        i++;
        m++;
    }

    i=0; //array index
    numfork=1; //fork number

    while(numfork <= numargc)
    {
        if ((pid=fork()) == -1)
        {
            perror("FORK ERROR");
            exit(3);
        }
        else if (pid == 0)
        {
            /* Child */

            sleep(sleeptimes);

            if (numfork == FIRST_CHILD)
            {
                DUP_WRITE();
                EXEC(i, numfork);
            }
            else if (numfork == LAST_CHILD)
            {

                DUP_READ();
                CLOSE_FDES_WRITE();
                EXEC(i, numfork);
            }
            else 
            {

                DUP_READ();
                DUP_WRITE();
                CLOSE_FDES_READ();
                CLOSE_FDES_WRITE();

                EXEC(i, numfork);
            }
        }
        else 
        {
            /* Parent */
            printf("pid:%d\n", pid);    
            i++;
            numfork++;
            sleeptimes++;
        }
    }

    PRINT;
    printf("i:%d\n", i);
    printf("numfork:%d\n", numfork);
    printf("DONE\n");       
    return 0;
}


int cmdfinder(char* cmd, char* path)
{
    DIR* dir;
    struct dirent *direntry; 
    char *pathdir;
    int searchtimes=2; 

    while (searchtimes)
    {
        pathdir = (char*)malloc(250);
        memset(pathdir, 0, 250);

        if (searchtimes==2)
        {
            pathdir=PATH_1;
        }
        else
        {
            pathdir=PATH_2;
        }

        if ((dir  = opendir(pathdir)) == NULL)
        {
            perror("Directory not found");
            exit (1);
        }
        else
        {
            while (direntry = readdir(dir))
            {
                if (strncmp( direntry->d_name, cmd, strlen(cmd)) == 0)
                {
                    strcat(path, pathdir);
                    strcat(path, cmd);
                    //searchtimes--;
                    return FOUND;
                }
            }
        }
        closedir(dir);
        searchtimes--;
    }
    printf("%s: Not Found\n", cmd);
    return NOT_FOUND;
}

person user2632477    schedule 30.07.2013    source источник


Ответы (1)


Все ваши макросы делают это труднее для чтения, чем если бы вы просто написали это прямо. Особенно когда они ссылаются на локальные переменные. Чтобы узнать, что происходит с EXEC, мне приходится прыгать вверх от того места, где он используется, к тому, где он определен, выяснять, какие локальные массивы он использует, а затем прыгать вниз, чтобы увидеть, как этот доступ вписывается в поток main. Это лабиринт макросов.

И вау, cmdfinder? Ваш собственный $PATH поиск, только он жестко запрограммирован /usr/bin:/bin? И двойное вау, readdir, просто чтобы узнать, существует ли файл, имя которого уже определено? Просто stat это! Или ничего не делайте, просто запустите его и обработайте ENOENT, попробовав следующий. Или используйте execlp вот для чего он нужен!

Переходим к главному... у вас недостаточно каналов, и вы не закрываете все неиспользуемые дескрипторы.

last | sort | more — это конвейер из 3 команд, соединенных 2 каналами. Одной трубой не сделаешь. Первая команда должна писать в первый канал, средняя команда должна читать первый канал и записывать во второй канал, а последняя команда должна читать второй канал.

Вы можете сначала создать оба канала, а затем сделать все ответвления, что упрощает выполнение, но требует много close в каждом дочернем процессе, поскольку все они наследуют все fd канала. Или вы можете использовать более сложный цикл, создавая каждый канал непосредственно перед разветвлением первого процесса, который будет его использовать, и закрывая каждый дескриптор в родительском процессе, как только будет создан соответствующий дочерний процесс. Я бы не хотел видеть, сколько макросов вы использовали бы для этого.

За каждым успешным дублированием должно следовать закрытие скопированного дескриптора. dup — это сокращение от «дублировать», а не «перемещать». После того, как это будет сделано, у вас останется лишний дескриптор, так что не просто dup2(fdes[1], fileno(stdout), но и close(fdes[1]) потом. (Чтобы быть полностью надежным, вы должны проверить, уже ли fdes[1]==fileno(stdout), и в этом случае пропустить dup2 и close.)

ПОСЛЕДУЮЩИЕ ВОПРОСЫ

Вы не можете использовать один канал для 3 процессов, потому что не было бы способа различить, какие данные должны идти к какому месту назначения. Когда первый процесс записывает в канал, а оба других процесса пытаются из него читать, один из них получит данные, но вы не сможете предсказать, какой именно. Вам нужно, чтобы средний процесс читал то, что пишет первый процесс, а последний процесс читал то, что пишет средний процесс.

Вы наполовину правы в том, что файловые дескрипторы становятся общими после разветвления. Фактический объект трубы является общим. Это то, что заставляет всю систему работать. Но файловые дескрипторы - конечные точки, обозначенные небольшими целыми числами, например 1 для стандартного вывода, 0 для стандартного ввода и т. д., - не связаны так, как вы предлагаете. Один и тот же объект канала может быть связан с одним и тем же номером дескриптора файла в двух процессах, ассоциации независимы. Закрытие fd 1 в одном процессе не приводит к закрытию fd 1 в любом другом процессе, даже если они связаны между собой.

Совместное использование таблицы fd, так что закрытие в одной задаче влияет на другую задачу, является частью набора функций «pthread», а не набора функций «fork».

person Community    schedule 30.07.2013
comment
Большое Вам спасибо. Я очень ценю, что вы нашли время, чтобы посмотреть на мой код. У меня есть несколько вопросов: 1) Почему я не могу использовать один канал для трех процессов? 2) Я понимаю, что обычно после форка дескрипторы открытых файлов распределяются между родителем и дочерним элементом. Если дочерний процесс закрывает файловый дескриптор, то в родительском файле файловый дескриптор остается открытым, и наоборот. Правильно ли я понимаю? 3) Как перенаправить STDOUT одного процесса в файл, не влияя на STDOUT других процессов. Разве весь процесс не использует один и тот же fd для STDOUT, который равен 1. - person user2632477; 31.07.2013