לדלג לתוכן

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

פתרונות - צינורות - pipes


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

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

#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) {
        // תהליך הבן - קורא מהצינור
        close(pipefd[1]);  // סוגרים את צד הכתיבה

        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 {
        // תהליך האב - כותב לצינור
        close(pipefd[0]);  // סוגרים את צד הקריאה

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

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

    return 0;
}

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

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

#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) {
        // תהליך הבן - כותב את הPID שלו לצינור
        close(pipefd[0]);  // סוגרים את צד הקריאה

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

        close(pipefd[1]);
        _exit(0);
    } else {
        // תהליך האב - קורא מהצינור
        close(pipefd[1]);  // סוגרים את צד הכתיבה

        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);

    // תהליך בן ראשון - מריץ ls
    pid_t pid1 = fork();
    if (pid1 == 0) {
        dup2(pipefd[1], 1);  // stdout -> צד הכתיבה של הצינור
        close(pipefd[0]);
        close(pipefd[1]);

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

    // תהליך בן שני - מריץ wc -l
    pid_t pid2 = fork();
    if (pid2 == 0) {
        dup2(pipefd[0], 0);  // stdin -> צד הקריאה של הצינור
        close(pipefd[0]);
        close(pipefd[1]);

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

    // האב סוגר את הצינור ומחכה
    close(pipefd[0]);
    close(pipefd[1]);
    wait(NULL);
    wait(NULL);

    return 0;
}

שימו לב שהאב חייב לסגור את שני צדדי הצינור! אם האב לא יסגור את 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() {
    // יוצרים FIFO (לא נורא אם כבר קיים)
    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");  // מוחקים את ה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 - שרשרת של שלושה תהליכים

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

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

int main() {
    int pipe1[2];  // צינור בין תהליך 1 לתהליך 2
    int pipe2[2];  // צינור בין תהליך 2 לתהליך 3
    pipe(pipe1);
    pipe(pipe2);

    // תהליך 1 - מייצר מספרים 1-20
    pid_t pid1 = fork();
    if (pid1 == 0) {
        close(pipe1[0]);  // לא קורא מצינור 1
        close(pipe2[0]);  // לא קשור לצינור 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);
    }

    // תהליך 2 - מסנן מספרים זוגיים
    pid_t pid2 = fork();
    if (pid2 == 0) {
        close(pipe1[1]);  // לא כותב לצינור 1
        close(pipe2[0]);  // לא קורא מצינור 2

        // ממירים fd ל-FILE* לנוחות קריאת שורות
        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);
    }

    // תהליך 3 - מדפיס למסך
    pid_t pid3 = fork();
    if (pid3 == 0) {
        close(pipe1[0]);  // לא קשור לצינור 1
        close(pipe1[1]);
        close(pipe2[1]);  // לא כותב לצינור 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);
    }

    // האב סוגר הכל ומחכה
    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

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