Неопределенный трубопровод в C

Я не совсем уверен, почему waitpid() постоянно зависает. Я предполагаю, что вилки должны завершиться и вернуться к родительскому процессу после выполнения execvp(). Этого не происходит. Если я закомментирую функцию waitpid(), исходный вывод будет правильным, но программа начнет вести себя неожиданно.

edit
- execFirst() Обрабатывает выполнение первой команды из списка
- execMid() Обрабатывает выполнение любой команды в списке, которая не является первой или последней командой в списке
- execLast() Обрабатывает выполнение последней команды в списке

// Closes pipe
void closePipe(int *pPipe){
    close(pPipe[0]);
    close(pPipe[1]);

    return;
}

// Opens the read end of pPipe and closes the write end
// @Private
void readFromPipe(int *pPipe){
    dup2(pPipe[0],0);
    close(pPipe[1]);
    close(pPipe[0]);

    return;
}

// Opens the write end of pPipe and closes the read end
// @Private
void writeToPipe(int *pPipe){
    dup2(pPipe[1],1);
    close(pPipe[0]);
    close(pPipe[1]);

    return;
}
// @Private
void execLast(sSettings *pSettings_, sCommand *pCommand, int iPipe[]){
    readFromPipe(iPipe);
    _execvp(pSettings_, pCommand);
}

// @Private
void execMid(sSettings *pSettings_, sCommand *pCommand, int iPipe1[], int iPipe2[]){
    readFromPipe(iPipe1);
    writeToPipe(iPipe2);
    _execvp(pSettings_, pCommand);
}

// @Private
void execFirst(sSettings *pSettings_, sCommand *pCommand, int iPipe[]){
    writeToPipe(iPipe);
    _execvp(pSettings_, pCommand);
}

void execIndefDepthPipe(sSettings *pSettings_, sCommandList *pCommandList_){
    int iPipe1[2];
    int iPipe2[2];

    int iStatus, iProcessId, iNumCommands = pCommandList_->iSize;
    _pipe(pSettings_, iPipe1);
    _pipe(pSettings_, iPipe2);

    sCommand *pCommand;
    do{
        pCommand    = popHeadNode(pCommandList_);
        iProcessId  = _fork(pSettings_);

        if(iProcessId){ // If Parent
            // continue; // Do nothing
            waitpid(iProcessId, &iStatus, WUNTRACED); // It waits forever. I don't know why.
        }
        else { // Execute the commands as a child
            if(pCommand != NULL) {
                if(pCommandList_->iSize == (iNumCommands-1)) { // Exec First
                    //closePipe(iPipe2);
                    execFirst(pSettings_, pCommand, iPipe1);
                }
                else if(pCommandList_->iSize == 0) { // Exec Last
                    closePipe(iPipe1);
                    execLast(pSettings_, pCommand, iPipe2);
                }
                else { // Exec mid
                    execMid(pSettings_, pCommand, iPipe1, iPipe2);
                }
            }
            _exit(0);
        }
    } while(pCommand != NULL);

    return;
}

person evilmaniac    schedule 10.05.2015    source источник
comment
Не могли бы вы предоставить нам основную функцию с образцом ввода, чтобы мы могли немедленно начать тестирование? Не могли бы вы также опубликовать свою структуру sCommandList? Просто для ясности. Кроме того, не забудьте проверить все системные вызовы на наличие ошибок: pipe(), fork(), waitpid() и т. д. — в них может присутствовать ошибка.   -  person C Goldsworthy    schedule 10.05.2015


Ответы (1)


Я предполагаю, что вилки должны завершаться и возвращаться обратно к родительскому процессу после выполнения execvp().

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

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

Вместо этого вам нужно создать все процессы, прежде чем ждать любого из них. У вас уже есть одна из двух необходимых петель. Вместо того, чтобы вызывать waitpid там, где вы это делаете, сохраните идентификатор процесса в структуре данных и перейдите к следующей итерации. Затем в втором цикле после того, как вы использовали весь «список команд», вызовите waitpid в цикле без указания какого-либо конкретного идентификатора процесса для ожидания; по завершении каждого процесса удалите его идентификатор из структуры данных, в которой вы их сохранили; когда эта структура данных пуста, все готово.

Кроме того, вам необходимо проверить каждый системный вызов на наличие ошибок, кроме close. (Ошибка POSIX заключается в том, что close допускает сбой; разумные реализации Unix возвращают ошибку только в том случае, если передается номер файлового дескриптора, который не был открыт в первую очередь, и я еще не встречал программу, которая должна была заботиться об этом. Если вам нужно убедиться, что данные попали на диск, вам нужно вызвать fsync перед close; но делать fsync в конвейере бессмысленно.)

Пока я здесь, несколько замечаний о хорошем стиле программирования: (1) ваш код будет легче читать, если вы перестанете использовать венгерские префиксы. (1a) бессмысленные символы подчеркивания в конце идентификаторов по соглашению зарезервированы для переменных, определенных внутри макросов, C не поддерживает макрогигиену. (1b) имена функций, которые начинаются с подчеркивания, зарезервированы стандартом C для внутреннего использования библиотеки C; ваша оболочка вокруг fork должна называться forkWithSettings - было бы лучше что-то еще более описательное, но, не зная, что находится в этом объекте настроек, я не могу ничего предложить. (2) по большому счету не так уж важно, где вы расставите свои фигурные скобки, но, ради всего святого, выберите один стиль и последовательно используйте его во всем файле.

person zwol    schedule 10.05.2015
comment
Зарезервированы только те идентификаторы, которые начинаются с символа подчеркивания, за которым следует заглавная буква или другой символ подчеркивания. - person dreamlax; 10.05.2015
comment
@dreamlax Все идентификаторы, начинающиеся с подчеркивания, зарезервированы для использования в качестве внешних символов (например, функций). Вы правы, что только _[_A-Z].* зарезервированы безоговорочно. - person zwol; 10.05.2015