Nikita Shirikov Ed blog

Pipes

Более полная версия лекции от Альберта @vtmntscoat

Продолжаем учиться связывать процессы между собой

Пока что мы умеем передавать вывод одного процесса другому через неименнованный канал:

echo "hello" | wc -l

Ядро считает, сколько процессов записывают в канал и сколько читают. Когда пишущие процессы заканчиваются, то чтение из канала возвращает EOF. Если же не остается больше читающих процессов, то пишущие убиваются.

Теперь опробуем все в коде:

int main() {
    pid_t child;

    int fds[2];
    pipe(fds);

    if (child = fork()) {
        close(fds[0]); // надо закрывать, чтоб процесс мог убиться
        for (int i = 0; i < 10000; ++i) {
            write(fds[1], "hello", 5);
        }
        close(fds[1]);
        wait(NULL);
    } else {
        close(fds[1]); // аналогично
        for (int i = 0; i < 3; ++i) {
            char buf[10] = {0};
            if (read(fds[0], buf, sizeof(buf)) <= 0) {
                return 0;
            }
            puts(buf);
        }
    }
}

Пример 2:

int main() {
    int fds[2];
    pipe(fds);

    if (!fork()) {
        // child proc 1
        close(fds[0]);
        dup2(fds[1], STDOUT_FILENO);
        close(fds[1]);
        execlp("yes", "yes", NULL);
        perror("yes");
        exit(EXIT_FAILURE);
    } else if (!fork()) {
        // child proc 2
        close(fds[1]);
        dup2(fds[0], STDIN_FILENO);
        close(fds[0]);
        execlp("head", "head", NULL);
        perror("head");
        exit(EXIT_FAILURE);
    }

    close(fds[0]); close(fds[1]);
    while (waitpid(-1, NULL, 0) != -1);
}

Пример 3: раздаем команды через pipe

void handler(int sig) {
    write(STDOUT_FILENO, "killing zombies\n", strlen("killing zombies\n"))
    wait(NULL);
}

int main() {
    int fds[2];
    pipe(fds);

    signal(SIGCHLD, handler); // обработчик, чтоб избавляться от процессов зомби
    // или так signal(SIGCHLD, SIG_IGN);

    for (int i = 0; i < 4; ++i) {
        if (!fork()) {
            close(fds[1]);
            int task;
            while(read(fds[0], &task, sizeof(task)) > 0) {
                printf("pid %d: sleeping for %d seconds\n", getpid(), task);
                sleep(task);
            }
            return EXIT_SUCCESS;
        }
    }

    for (int i = 1; i < 10; ++i) {
        int task = i % 3 + 1;
        write(fds[1], &task, sizeof(task));
    }

    close(fds[1]);
    while (waitpid(-1, NULL, 0) != -1);
}

Posix дает гарантию: когда мы пишем в pipe данные размера меньше PIPE_BUF, то данные записываются атомарно.

SIGCHLD - специальный сигнал, который приходит, когда завершается какой-то процесс.

self pipe trick - в обработчике, просто записываем 1 байт в отдельный канал, чтоб не было асинхронности.

#Os