לדלג לתוכן

5.6 - צינורות - פתרון

פתרונות - pipes


פתרון 1 - אב שולח הודעה לבן

כתבו תוכנית שיוצרת pipe, קוראת ל-fork, האב שולח "hello child!" דרך הpipe, והבן קורא ומדפיס.

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/wait.h>

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

    pid_t pid = fork();

    if (pid == 0) {
        // child process - reads from the pipe
        close(pipefd[1]);  // close the write side

        char buf[128];
        int n = read(pipefd[0], buf, sizeof(buf) - 1);
        buf[n] = '\0';
        printf("child received: %s\n", buf);

        close(pipefd[0]);
        _exit(0);
    } else {
        // parent process - writes to the pipe
        close(pipefd[0]);  // close the read side

        const char *msg = "hello child!";
        write(pipefd[1], msg, strlen(msg));

        close(pipefd[1]);
        wait(NULL);
    }

    return 0;
}

פתרון 2 - בן שולח PID לאב

כתבו תוכנית שיוצרת pipe, קוראת ל-fork, הבן שולח את הPID שלו כמחרוזת דרך הpipe, והאב קורא ומדפיס.

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/wait.h>

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

    pid_t pid = fork();

    if (pid == 0) {
        // child process - writes its PID to the pipe
        close(pipefd[0]);  // close the read side

        char buf[64];
        sprintf(buf, "child PID is: %d", getpid());
        write(pipefd[1], buf, strlen(buf));

        close(pipefd[1]);
        _exit(0);
    } else {
        // parent process - reads from the pipe
        close(pipefd[1]);  // close the write side

        char buf[128];
        int n = read(pipefd[0], buf, sizeof(buf) - 1);
        buf[n] = '\0';
        printf("parent received: %s\n", buf);

        close(pipefd[0]);
        wait(NULL);
    }

    return 0;
}

פתרון 3 - מימוש ls | wc -l

ממשו תוכנית שמבצעת את מה ש-ls | wc -l עושה.

#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>

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

    // first child process - runs ls
    pid_t pid1 = fork();
    if (pid1 == 0) {
        dup2(pipefd[1], 1);  // stdout -> write side of the pipe
        close(pipefd[0]);
        close(pipefd[1]);

        char *args[] = {"/bin/ls", NULL};
        execve("/bin/ls", args, NULL);
        perror("execve ls");
        _exit(1);
    }

    // second child process - runs wc -l
    pid_t pid2 = fork();
    if (pid2 == 0) {
        dup2(pipefd[0], 0);  // stdin -> read side of the pipe
        close(pipefd[0]);
        close(pipefd[1]);

        char *args[] = {"/usr/bin/wc", "-l", NULL};
        execve("/usr/bin/wc", args, NULL);
        perror("execve wc");
        _exit(1);
    }

    // parent closes the pipe and waits
    close(pipefd[0]);
    close(pipefd[1]);
    wait(NULL);
    wait(NULL);

    return 0;
}

שימו לב שהאב חייב לסגור את שני צדדי הpipe! אם האב לא יסגור את pipefd[1], wc לעולם לא יקבל EOF ויתקע.


פתרון 4 - תקשורת דרך FIFO

כתבו שתי תוכנות נפרדות שמתקשרות דרך FIFO.

writer.c:

#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <sys/stat.h>
#include <stdio.h>

int main() {
    // create the FIFO (fine if it already exists)
    mkfifo("/tmp/my_fifo", 0644);

    printf("writer: waiting for reader to connect...\n");
    int fd = open("/tmp/my_fifo", O_WRONLY);
    printf("writer: connected!\n");

    const char *msg = "hello from writer!\n";
    write(fd, msg, strlen(msg));
    printf("writer: message sent\n");

    close(fd);
    unlink("/tmp/my_fifo");  // remove the FIFO
    return 0;
}

reader.c:

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

int main() {
    printf("reader: waiting for writer to connect...\n");
    int fd = open("/tmp/my_fifo", O_RDONLY);
    printf("reader: connected!\n");

    char buf[128];
    int n = read(fd, buf, sizeof(buf) - 1);
    buf[n] = '\0';
    printf("reader received: %s", buf);

    close(fd);
    return 0;
}

כדי לבדוק:
1. קמפלו את שתי התוכנות: gcc writer.c -o writer ו-gcc reader.c -o reader
2. בטרמינל ראשון הריצו: ./writer
3. בטרמינל שני הריצו: ./reader

הopen יחסום עד ששני הצדדים מחוברים, ואז הנתונים יזרמו.


פתרון 5 - שרשרת של שלושה processes

כתבו תוכנית שיוצרת שרשרת: process 1 כותב מספרים 1-20, process 2 מסנן זוגיים, process 3 מדפיס.

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/wait.h>

int main() {
    int pipe1[2];  // pipe between process 1 and process 2
    int pipe2[2];  // pipe between process 2 and process 3
    pipe(pipe1);
    pipe(pipe2);

    // process 1 - generates numbers 1-20
    pid_t pid1 = fork();
    if (pid1 == 0) {
        close(pipe1[0]);  // doesn't read from pipe 1
        close(pipe2[0]);  // not connected to pipe 2
        close(pipe2[1]);

        for (int i = 1; i <= 20; i++) {
            char buf[16];
            sprintf(buf, "%d\n", i);
            write(pipe1[1], buf, strlen(buf));
        }

        close(pipe1[1]);
        _exit(0);
    }

    // process 2 - filters even numbers
    pid_t pid2 = fork();
    if (pid2 == 0) {
        close(pipe1[1]);  // doesn't write to pipe 1
        close(pipe2[0]);  // doesn't read from pipe 2

        // convert fd to FILE* for convenient line reading
        FILE *in = fdopen(pipe1[0], "r");
        char line[64];
        while (fgets(line, sizeof(line), in)) {
            int num = atoi(line);
            if (num % 2 == 0) {
                write(pipe2[1], line, strlen(line));
            }
        }

        fclose(in);
        close(pipe2[1]);
        _exit(0);
    }

    // process 3 - prints to the screen
    pid_t pid3 = fork();
    if (pid3 == 0) {
        close(pipe1[0]);  // not connected to pipe 1
        close(pipe1[1]);
        close(pipe2[1]);  // doesn't write to pipe 2

        char buf[64];
        int n;
        while ((n = read(pipe2[0], buf, sizeof(buf) - 1)) > 0) {
            buf[n] = '\0';
            printf("%s", buf);
        }

        close(pipe2[0]);
        _exit(0);
    }

    // parent closes everything and waits
    close(pipe1[0]);
    close(pipe1[1]);
    close(pipe2[0]);
    close(pipe2[1]);
    wait(NULL);
    wait(NULL);
    wait(NULL);

    return 0;
}

הפלט הצפוי:

2
4
6
8
10
12
14
16
18
20

נקודות חשובות:
- יצרנו שני pipes כי יש שלושה processes בשרשרת
- כל process חייב לסגור את כל הfd-ים שהוא לא משתמש בהם
- בprocess 2 השתמשנו ב-fdopen כדי להמיר fd ל-FILE* - ככה יכולנו להשתמש ב-fgets שקוראת שורה שלמה. זה דוגמה טובה לשילוב בין fd-ים לFILE* כשזה נוח
- האב חייב לסגור את כל ארבעת הfd-ים של הpipes, אחרת הprocesses ייתקעו