Пишу свою собственную оболочку ... застрял на трубах? - PullRequest
16 голосов
/ 22 сентября 2009

В последние несколько дней я пытался написать свою собственную реализацию оболочки, но, похоже, я застрял на том, чтобы заставить каналы работать должным образом. Я могу разобрать строку и разветвить команды между каналами (например, ls | sort) по отдельности, но не могу заставить их передавать данные из одного канала в другой.

Мне кажется, я просто не понимаю, как правильно использовать dup2 () и pipe.

Я включил свой код, который все еще не работает ... :( Так застрял ...

void forkAndExecute( char* arrayOfWords[] , vector<pid_t> *vectorOfPIDs , bool hasNextCmd , bool hasPrevCmd) {

int fd[ 2 ];
pid_t pid;

if( hasNextCmd ){
    pipe(fd);
}

pid = fork();

//error if PID < 0
if( pid < 0 ) {
    cerr << ">>> fork failed >>>" << endl;
    exit(-1);
}
//child process if PID == 0
else if( pid == 0 ) {
    if ( hasPrevCmd ){
        dup2(fd[0] , 0);
        close(fd[0]);
        close(fd[1]);

    }
    if ( hasNextCmd ){
        dup2(fd[1],1);
        close(fd[0]);
        close(fd[1]);
    }
    execvp( arrayOfWords[0] , arrayOfWords );
    cout << ">>> command not found >>>" << endl;
    //if logic reaches here, exec failed
    exit(0);
} 
//parent process
else{
    close(fd[0]);
    close(fd[1]);
    //if( ! isLastCmd ){

    //}
    vectorOfPIDs->push_back(pid);
}

}

Ответы [ 9 ]

10 голосов
/ 24 сентября 2009

Первое предложение: символические константы лучше, чем магические числа.

const int PIPE_READ = 0;
const int PIPE_WRITE = 1;
int fd[2];
pipe(fd);
// Now you can refer to fd[PIPE_READ] and fd[PIPE_WRITE].

Второе предложение: сделайте шаг назад и подумайте, чего вы пытаетесь достичь.

Вы хотите порождать два процесса, когда стандартный вывод первого процесса подключен к стандартному второму процессу. Правильно?

Итак, в C это означает, что вам нужно принять вызов pipe, передать fd[PIPE_WRITE] первому дочернему процессу, который dup2 переходит в 1, и передать fd[PIPE_READ] второму дочернему процессу, который будет dup2 до 0.

Простой просмотр прототипа forkAndExecute' показывает, что он не может этого сделать:

void forkAndExecute( char* arrayOfWords[] , vector *vectorOfPIDs , 
    bool hasNextCmd , bool hasPrevCmd);

Он обрабатывает только одну команду, и, просматривая этот список аргументов, если он не прибегает к злым глобальным переменным, у него нет возможности получить файловый дескриптор из своего PrevCmd или получить файловый дескриптор из своего NextCmd.

Подумайте о том, как управлять дескрипторами файлов, которые вам нужны, и перепроектируйте forkAndExecute, чтобы иметь возможность их использовать.

6 голосов
/ 26 сентября 2009

хорошо, это работает для меня. Надеюсь, это поможет вам:

/************************
function: void pipeCommand(char** cmd1, char** cmd2)
comment: This pipes the output of cmd1 into cmd2.
**************************/
void pipeCommand(char** cmd1, char** cmd2) {
  int fds[2]; // file descriptors
  pipe(fds);
  // child process #1
  if (fork() == 0) {
    // Reassign stdin to fds[0] end of pipe.
    dup2(fds[0], STDIN_FILENO);
    close(fds[1]);
    close(fds[0]);
    // Execute the second command.
    // child process #2
    if (fork() == 0) {
        // Reassign stdout to fds[1] end of pipe.
        dup2(fds[1], STDOUT_FILENO);
        close(fds[0]);
        close(fds[1]);
        // Execute the first command.
        execvp(cmd1[0], cmd1);
    }
    wait(NULL);
    execvp(cmd2[0], cmd2);
    }
    close(fds[1]);
    close(fds[0]);
    wait(NULL);
}
6 голосов
/ 23 сентября 2009

Общий процесс добавил бы обработку ошибок к этому базовому процессу (псевдокод):

pipe(fds)
if (fork() is child) {
  dup2(fds[1], 1)
  close(fds[0])
  close(fds[1])
  exec("ls")
}
if (fork() is child) {
  dup2(fds[0], 0)
  close(fds[0])
  close(fds[1])
  exec("sort")
}
close(fds[0])
close(fds[1])
wait()

Сначала создайте трубу. Затем разветвите дочерние процессы, чтобы они унаследовали это. Переопределите дескрипторы файлов на 0 (стандартный вывод) и 1 (стандартный вывод), чтобы процессы считывали и записывали соответствующие места. Закройте все оставшиеся файловые дескрипторы, которые вы не хотите, чтобы дочерние процессы видели или блокировали по завершении работы. Выполните фактические дочерние процессы. Подождите, пока они закончат, и все готово!

5 голосов
/ 22 сентября 2009

Вот учебник по каналам UNIX, в частности, о том, как создавать трубопроводы в подобной оболочке архитектуре:

http://www.cse.ohio -state.edu / ~ mamrak / CIS762 / pipes_lab_notes.html

Не так много полностью написанного кода, но он довольно хорошо описывает концепции.

Вы также можете загрузить исходный код практически для любой оболочки, например bash , tcsh , zsh и т. Д.

2 голосов
/ 22 сентября 2009

Когда мне понадобилось несколько лет назад создать подобную оболочку, я использовал книгу Практическое программирование в Unix .

Это действительно полезно для примеров по многим темам IPC. У меня все еще есть копия на моем столе, на которую я время от времени ссылаюсь. Для 2 - 9 долларов, это очень хорошее соотношение цены и качества.

Для чего это стоит, просто подумал, что упомяну это.

2 голосов
/ 22 сентября 2009

Попробуйте прочитать исходный код Bash , чтобы увидеть, как они это сделали.

1 голос
/ 22 сентября 2009

Здесь - примечания к трубе из класса системного программирования, который я взял в прошлом семестре.

0 голосов
/ 26 сентября 2009

ну, у меня нет ответа, но я работаю над той же проблемой, атм. Я поделюсь тем, что у меня есть. Это работает для двух команд, но после того, как это выполнено, ввод / вывод прерывается. странным образом я еще не смог понять. позвони сантехнику!

void pipeCommand(char** cmd1, char** cmd2) {
  int fds[2]; // file descriptors
  pipe(fds);
  int oldIn, oldOut;
  // child process #1
  if (fork() == 0) {
    // Reassign stdin to fds[0] end of pipe.
    oldIn = dup(STDIN_FILENO);
    dup2(fds[0], STDIN_FILENO);
    close(fds[1]);
    close(fds[0]);
    // Execute the second command.
    execvp(cmd2[0], cmd2);
  // child process #2
  } else if ((fork()) == 0) {
    oldOut = dup(STDOUT_FILENO);
    // Reassign stdout to fds[1] end of pipe.
    dup2(fds[1], STDOUT_FILENO);
    close(fds[0]);
    close(fds[1]);
    // Execute the first command.
    execvp(cmd1[0], cmd1);
  // parent process
  } else
    wait(NULL);
    dup2(oldIn, STDIN_FILENO);
    dup2(oldOut, STDOUT_FILENO);
    close(oldOut);
    close(oldIn);
}

У меня такое ощущение, что это связано с тем, что я делаю или не делаю после ожидания ()

0 голосов
/ 24 сентября 2009

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

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

...