Fork и dup2 - Дочерний процесс не завершается - Проблемы с файловыми дескрипторами?

Я пишу свою собственную оболочку для домашнего задания и сталкиваюсь с проблемами.

Моя программа-оболочка получает входные данные cat scores | grep 100 из консоли и печатает выходные данные, как и ожидалось, но команда grep не завершается, и я вижу, как она работает бесконечно из команды ps.


EDIT. При закрытии fds произошла ошибка. Теперь команда grep не выполняется, а вывод консоли -

grep: (стандартный ввод): Неверный файловый дескриптор


Я читаю количество команд из консоли, создаю необходимые каналы и сохраняю их в двумерном int array fd[][] перед разветвлением первого процесса.

fd[0][0] будет содержать конец чтения 1-го канала, а fd[0][1] будет содержать конец записи 1-го канала. fd[1][0] будет содержать конец 2-го канала для чтения, а fd[1][1] будет содержать конец 2-го канала для записи и так далее.

Каждый новый процесс дублирует свой stdin с концом чтения своего канала с предыдущим процессом и дублирует свой stdout с концом записи своего канала со следующим процессом.

Ниже моя функция:

void run_cmds(char **args, int count,int pos)
{
    int pid,status;
    pid = fork();
    if ( pid == 0 )
    {
        if(pos != 0) dup2(fd[pos-1][0],0); // not changing stdin for 1st process
        if(pos != count) dup2(fd[pos][1],1); //not changing stdout for last process
        close_fds(pos);
        execvp(*args,args);
    }
    else
    {
        waitpid(pid,&status,0);
        count--;
        pos++;
        //getting next command and storing it in args
        if(count > 0)
            run_cmds(args,count,pos);
        }
    }
}
  • args будет содержать аргументы команды.
  • count — это количество команд, которые мне нужно создать.
  • pos — позиция команды во входных данных.

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

Чего мне не хватает в моем понимании/реализации dup2/fork и почему команда ждет бесконечно?

Любые материалы будут очень полезны. Поразил этим за последние пару дней!


РЕДАКТИРОВАТЬ: функция close_fds() выглядит следующим образом: для любого процесса я закрываю оба канала, связывающие процесс.

void close_fds(int pos)
{
 if ( pos != 0 )
        {
        close(fd[pos-1][0]);
        close(fd[pos-1][1]);
        }
 if ( pos != count) 
        {
        close(fd[pos][0]);  
        close(fd[pos][1]);
        }
}

person Mor Eru    schedule 10.10.2014    source источник
comment
Пожалуйста, проверьте возвращаемое значение каждого системного вызова, который вы вызываете (например, fork, waitpid, dup2, execvp). Если они возвращают код ошибки, используйте perror или strerror(errno), чтобы распечатать ошибку. Пожалуйста, обновите вопрос, указав, что ошибок не было. Пожалуйста, вызовите abort(); после execvp, чтобы убедиться, что дочерний процесс не продолжит работу.   -  person pts    schedule 11.10.2014
comment
Разместите всю свою программу (sscce.org) в одном файле .c.   -  person pts    schedule 11.10.2014


Ответы (2)


Первый диагноз

Ты говоришь:

Каждый новый процесс дублирует свой стандартный вывод с концом канала для чтения предыдущего процесса и дублирует свой стандартный вывод с концом канала для записи со следующим процессом.

Вы не упомянули волшебное слово close().

Вы должны убедиться, что вы закрываете как конец чтения, так и конец записи каждого канала, когда вы используете dup() или dup2() для подключения его к стандартному вводу. Это означает, что с двумя каналами у вас есть 4 вызова close().

Если вы не закроете каналы правильно, процесс, который читает, не получит EOF (потому что есть процесс, возможно, он сам, который может писать в канал). Крайне важно иметь достаточное количество (не слишком мало и не слишком много) вызовов close().


Я звоню close_fds() после звонков dup2. Функция пройдет через массив fd[][2] и выполнит вызов close() для каждого fd в массиве.

OK. Это важно. Это означает, что мой первичный диагноз, вероятно, был не точен.

Второй диагноз

Несколько других предметов:

  1. У вас должен быть код после execvp(), который сообщает об ошибке и завершает работу, если возвращается execvp() (что означает сбой).

  2. Не стоит сразу звонить waitpid(). Все процессы в конвейере должны работать одновременно. Вам нужно запустить все процессы, затем дождаться выхода последнего, очищая все остальные по мере их смерти (но не обязательно беспокоиться обо всем в конвейере, прежде чем продолжить).

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

  3. Вы уменьшаете count, а также увеличиваете pos перед рекурсией. Это может быть плохо. Я думаю, вам следует просто увеличить pos.

Третий диагноз

После обновления отображается функция close_fds().

Я вернулся к тому, что есть проблемы с закрытием каналов (хотя проблемы с ожиданием и сообщением об ошибках все еще являются проблемами). Если у вас есть 6 процессов в конвейере и все 5 соединительных каналов создаются до запуска каких-либо процессов, каждый процесс должен закрыть все 10 файловых дескрипторов каналов.

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

Пожалуйста, создайте MCVE (Как создать минимальный, полный и проверяемый пример?) или SSCCE (Короткий, самодостаточный, правильный пример) — два имени и ссылки для одной и той же основной идеи.

Вам следует создать программу, которая создает структуры данных, которые вы передаете коду, вызывающему run_cmds(). То есть вы должны создать любые структуры данных, которые создает ваш код синтаксического анализа, и показать код, который создает канал или каналы для команды 'cat score | grep 100'.

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

person Jonathan Leffler    schedule 11.10.2014
comment
+Jonathan - Я вызываю close_fds() после вызова dup2. Функция просматривает массив fd[][] и выполняет вызов close() для каждого fd в массиве. - person Mor Eru; 11.10.2014
comment
Джонатан... Я добавил код для close_fds(). Произошла ошибка, которую я исправил сейчас. Теперь команда grep не выполняется, а вывод консоли - grep: (стандартный ввод): Неверный файловый дескриптор. ОТРЕДАКТИРОВАННЫЙ ВОПРОС - person Mor Eru; 11.10.2014
comment
Как я уже просил вас, добавьте проверку ошибок и отчеты об ошибках в свой код, обновите свой код в вопросе и укажите, какие ошибки были напечатаны. - person pts; 11.10.2014
comment
@pts: этот комментарий предназначался мне или ОП? Если это для ОП, возможно, это относится к вопросу, а не к моему ответу. Однако я на 100% согласен с вашим мнением — невозможно решить проблему, не имея достаточно кода, чтобы увидеть, что происходит не так. Как создаются трубы? Как раздваиваются дети? И т.п. - person Jonathan Leffler; 11.10.2014

Наиболее вероятные причины, по которым grep не завершается:

  • Вы не вызываете waitpid с правильным PID (даже если в вашем коде есть такой вызов, он может по какой-то причине не выполняться), поэтому grep становится процессом-зомби. Возможно, ваш родительский процесс оболочки сначала ожидает другого процесса (бесконечно, потому что другой никогда не завершается), и он не вызывает waitpid с PID grep. Вы можете найти Z в выводе ps, если grep — зомби.

  • grep не получает EOF на свой стандартный ввод (fd 0), какой-то процесс держит открытым конец записи своего канала. Вы закрыли все файловые дескрипторы в массиве fd в родительском процессе оболочки? Если не закрыть везде, grep никогда не получит EOF и никогда не завершится, потому что он будет заблокирован (навсегда) в ожидании дополнительных данных на своем стандартном вводе.

person pts    schedule 10.10.2014
comment
+pts - 1. Я не вижу Z в выводе ps для процесса grep. Похоже, grep ждет. 2. Я закрыл все fds в родительском процессе, но второй процесс (в данном случае grep) ничего не выводит на консоль и продолжает ждать. - person Mor Eru; 11.10.2014
comment
Я не смогу вам больше помочь, пока вы не опубликуете всю свою программу как sscce.org. - person pts; 11.10.2014