не удалось использовать scanf для чтения из канала - PullRequest
0 голосов
/ 09 декабря 2018

Работа над IPC Меня попросили написать программу на C, которая работает как конвейер между двумя другими исполняемыми файлами C:

Первый исполняемый файл с именем 'sln1.out' получает шесть аргументов и печатает три числа.

Второй исполняемый файл с именем 'sln2.out' получает три аргумента и печатает одно число.

Я разделил следующий код на две части - первая - это запись в канал и насколько я знаюработает.Проблема начинается со второй части: я закрыл stdin, поэтому теперь, когда я использую dup(fd[0]), новый дубликат файлового дескриптора должен быть размещен там, где был stdin, и я полагаю, что я могу использовать scanf для чтения изтруба в этих условиях - но по какой-то причине это не сработало

#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>

int main(int argc, char* argv[])
{
    // check number of arguments.
    if(argc != 7)
    {
            printf("Wrong parameters");
            exit(1);
    }

    // creating the pipe.
    int fd[2];
    pipe(fd);

/* PART ONE: forking a child for 'sln1.out' that writes to fd[1] */

    // I  want to fork this process, and change the image of the child process to the 'sln1.out' process.
    pid_t pid_sln1 = fork();
    int sln1_status;
    if (pid_sln1 < 0)
    {
            perror("fork error, sln1");
    }
    else if(pid_sln1 == 0)
    {
            char* const parmListSln1[] = {"./sln1.out",argv[1],argv[2],argv[3],
                                            argv[4],argv[5],argv[6],NULL};
            // i closed the stdout, and used 'dup' that return the file descriptor
            //  of stdout as duplicate of fd[1]!
            close(STDOUT_FILENO);
            dup(fd[1]);

            execv("./sln1.out",parmListSln1);
            printf("Return not expected, exacv error.\n");
            exit(1);
    }

    // wait untill the child process terminated.
    wait(&sln1_status);
    if(sln1_status == 0)
    {
            printf("child process terminated successfully\n");
            // if we want to read from fd[0] we must close the write to fd[1]

            close(fd[1]);
    }
    else
    {
            printf("child process failed\n");
            exit(1);
    }


/* PART TWO: forking a child for 'sln2.out' that reads from fd[0] */

    // The same idea - forking a child to change its image to the 'sln2.out' process.
    pid_t pid_sln2 = fork();
    int sln2_status;

    if(pid_sln2 < 0)
    {
            printf("fork error, sln2.\n");
            exit(1);
    }
    else if(pid_sln2 == 0)
    {
            // closing 'stdin' and the use fo 'dup' create a duplicate to the readable
            // side of the pipe where the standard input should be
            close(STDIN_FILENO);
            dup(fd[0]);

            // reading the input from the pipe - with the same method used to 'stdin'!
            char* in[3];
            scanf("%s %s %s",in[0],in[1],in[2]);
            // build the parameters list for 'sln2.out'
            char* const paramListSln2[] = { "./sln2.out", in[0], in[1], in[2], NULL };

            // execute 'sln2.out'
            execv("./sln2.out",paramListSln2);
            printf("Return not expexted, execv error");
            exit(1);

    }

    // wait untill the child process terminated and determine success.
    wait(&sln2_status);
    if (sln2_status == 0)
    {
            printf("2nd child process terminated successfully!\n");
            exit(0);
    }
    else
    {
            printf("error with 'sln2.out' child process.\n");
            exit(1);
    }

    exit(0);
}

Вывод, который я получаю, может дать более подробную информацию:

child process terminated successfully
error with 'sln2.out' child process.

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

1 Ответ

0 голосов
/ 10 декабря 2018

Основная проблема - неинициализированные указатели

Когда я компилировал код в вопросе (исходный файл, ctrl61.c) с помощью командной строки:

gcc -O3 -g -std=c11 -Wall -Wextra -Werror -Wmissing-prototypes -Wstrict-prototypes ctrl61.c -o ctrl61

(с GCC 8.2.0работающий на Mac с macOS 10.14.2 Mojave), я получил такие предупреждения, как:

ctrl61.c:78:13: error: ‘in[0]’ may be used uninitialized in this function [-Werror=maybe-uninitialized]

для каждого из in[0], in[1] и in[2], и указанная строка была вызовом scanf().Неинициализированные указатели являются источником сбоев, и, действительно, проверка кода показывает, что указатели не инициализированы.Вам нужно выделить место для указателей, на которые нужно указать.Самое простое изменение - использовать:

char in[3][50];

(хотя вы должны затем использовать% 49 каждый раз в строке формата scanf().) Или вы можете использовать модификатор m для %s и другиепоследовательные изменения, так что scanf() выделяет память для вас.Обратите внимание, что некоторые системы (например, macOS) не поддерживают обязательный для POSIX модификатор sscanf().

Вы не закрываете достаточно файловых дескрипторов в дочернем (или, действительно, вparent).

Правило большого пальца : Если вы dup2() один конец канала для стандартного ввода или стандартного вывода, закройте оба возвращенных дескриптора исходного файлана pipe() как можно скорее.В частности, вы должны закрыть их перед использованием любого из семейства функций exec*().

Правило также применяется, если вы дублируете дескрипторы с dup() или fcntl() с `F_DUPFD.

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

Сообщение об ошибке

В комментариях вы упомянули об использовании perror() для сообщения о проблемах.Лично мне не нравится perror() за сообщения об ошибках;его форматирование недостаточно мощное.Однако это лучше, чем некоторые альтернативы.

Я обычно использую некоторый код, который доступен в моем репозитории SOQ (вопросы о переполнении стека) на GitHub в виде файлов stderr.c и stderr.h вподкаталог src / libsoq .Он имеет широкий контроль над форматированием.

Существует концептуально подобный пакет, err(3), доступный в Linux и BSD (включая macOS).Я предпочитаю мой, просто потому, что он мой (и потому что он имеет более мощные элементы управления, чем пакет err(3)).

Контрольный код ctrl61.c

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

int main(int argc, char *argv[])
{
    if (argc != 7)
    {
        fprintf(stderr, "Usage: %s arg1 arg2 arg3 arg4 arg5 arg6\n", argv[0]);
        exit(1);
    }

    int fd[2];
    pipe(fd);

    pid_t pid_sln1 = fork();
    int sln1_status;
    if (pid_sln1 < 0)
    {
        perror("fork error, sln1");
    }
    else if (pid_sln1 == 0)
    {
        char *paramListSln1[] =
        {
            "./sln1.out", argv[1], argv[2], argv[3],
            argv[4], argv[5], argv[6], NULL
        };

        close(STDOUT_FILENO);
        dup(fd[1]);
        close(fd[0]);
        close(fd[1]);

        execv(paramListSln1[0], paramListSln1);
        fprintf(stderr, "%s: failed to exec %s\n", argv[0], paramListSln1[0]);
        exit(1);
    }

    pid_t pid_sln2 = fork();
    int sln2_status;

    if (pid_sln2 < 0)
    {
        printf("fork error, sln2.\n");
        exit(1);
    }
    else if (pid_sln2 == 0)
    {
        close(STDIN_FILENO);
        dup(fd[0]);
        close(fd[0]);
        close(fd[1]);

        char in[3][50];
        scanf("%49s %49s %49s", in[0], in[1], in[2]);

        char *const paramListSln2[] = { "./sln2.out", in[0], in[1], in[2], NULL };

        execv(paramListSln2[0], paramListSln2);
        fprintf(stderr, "%s: failed to exec %s\n", argv[0], paramListSln2[0]);
        exit(1);
    }

    close(fd[0]);
    close(fd[1]);

    int pid1 = wait(&sln1_status);
    if (sln1_status == 0)
    {
        fprintf(stderr, "child process %d terminated successfully\n", pid1);
        close(fd[1]);
    }
    else
    {
        fprintf(stderr, "child process %d failed 0x%.4X\n", pid1, sln1_status);
        exit(1);
    }

    int pid2 = wait(&sln2_status);
    if (sln2_status == 0)
    {
        fprintf(stderr, "child process %d terminated successfully\n", pid2);
        close(fd[1]);
    }
    else
    {
        fprintf(stderr, "child process %d failed 0x%.4X\n", pid2, sln2_status);
        exit(1);
    }

    return(0);
}

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

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

Вспомогательная программа sln1.out.c

Это близкооснованный на коде, предложенном в комментарии, но исправляющем ошибку, при которой комментарий использовал argv[1], но должен был использовать argv[0].

#include <stdio.h>

static inline void dump_args(int argc, char **argv)
{
    int argnum = 0;
    fprintf(stderr, "%s: %d arguments\n", argv[0], argc);
    while (*argv != 0)
        fprintf(stderr, "%d: [%s]\n", argnum++, *argv++);
}

int main(int argc, char **argv)
{
    dump_args(argc, argv);
    if (argc != 7)
    {
        fprintf(stderr, "%s: incorrect argument count %d\n", argv[0], argc);
        return(1);
    }
    printf("1 2 3\n");
    return(0);
}

Программа sln2.out.c отличается требованием 3 аргументов и печатью 321 вместо 1 2 3.

Пример выполнения

$ ./ctrl61 abc zoo def pqr tuv 999
./sln1.out: 7 arguments
0: [./sln1.out]
1: [abc]
2: [zoo]
3: [def]
4: [pqr]
5: [tuv]
6: [999]
child process 15443 terminated successfully
./sln2.out: 4 arguments
0: [./sln2.out]
1: [1]
2: [2]
3: [3]
321
child process 15444 terminated successfully
$

Это показывает, что sln2.out было передано три аргумента, считанные из стандартного вывода sln1.out.

...