לדלג לתוכן

5.5 מתארי קבצים פתרון

פתרונות - מתארי קבצים - file descriptors


פתרון 1 - כתיבה לקובץ עם write

כתבו תוכנית שפותחת קובץ בשם hello.txt לכתיבה, כותבת אליו את המחרוזת "hello from fd!\n" באמצעות הsyscall write, וסוגרת את הקובץ.

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

int main() {
    int fd = open("hello.txt", O_WRONLY | O_CREAT | O_TRUNC, 0644);
    if (fd < 0) {
        write(2, "open failed\n", 12);
        return 1;
    }

    const char *msg = "hello from fd!\n";
    write(fd, msg, strlen(msg));

    close(fd);
    return 0;
}

פתרון 2 - הפניית stdout לקובץ עם dup2

כתבו תוכנית שמפנה את stdout לקובץ בשם redirected.txt באמצעות dup2, ואז משתמשת ב-printf כדי לכתוב כמה שורות.

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

int main() {
    int fd = open("redirected.txt", O_WRONLY | O_CREAT | O_TRUNC, 0644);
    if (fd < 0) {
        perror("open");
        return 1;
    }

    // מפנים את stdout (fd 1) לקובץ
    dup2(fd, 1);
    close(fd);  // כבר לא צריכים את הfd המקורי

    // מעכשיו printf כותב לקובץ
    printf("this line goes to the file\n");
    printf("this line also goes to the file\n");
    printf("nothing appears on screen!\n");

    return 0;
}

אחרי ההרצה, אף שורה לא תודפס למסך. כל הפלט יהיה בתוך redirected.txt.


פתרון 3 - קריאת קובץ בית-אחר-בית

כתבו תוכנית שקוראת קובץ טקסט בית אחרי בית באמצעות הsyscall read, ומדפיסה כל תו למסך באמצעות write לfd 1.

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

int main() {
    int fd = open("hello.txt", O_RDONLY);
    if (fd < 0) {
        write(2, "open failed\n", 12);
        return 1;
    }

    char c;
    while (read(fd, &c, 1) > 0) {
        write(1, &c, 1);  // כותבים בית אחד ל-stdout
    }

    close(fd);
    return 0;
}

שימו לב: read מחזירה 0 כשמגיעים לסוף הקובץ (EOF), ולכן הלולאה עוצרת. בכל איטרציה אנחנו קוראים בדיוק בית אחד וכותבים אותו ל-stdout.


פתרון 4 - אב ובן כותבים לאותו קובץ

כתבו תוכנית שפותחת קובץ, קוראת ל-fork, ואז גם האב וגם הבן כותבים את הPID שלהם לקובץ.

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

int main() {
    int fd = open("pids.txt", O_WRONLY | O_CREAT | O_TRUNC, 0644);
    if (fd < 0) {
        perror("open");
        return 1;
    }

    pid_t pid = fork();

    if (pid == 0) {
        // תהליך הבן
        char buf[64];
        sprintf(buf, "child PID: %d\n", getpid());
        write(fd, buf, strlen(buf));
        close(fd);
        _exit(0);
    } else if (pid > 0) {
        // תהליך האב
        char buf[64];
        sprintf(buf, "parent PID: %d\n", getpid());
        write(fd, buf, strlen(buf));

        wait(NULL);  // מחכים שהבן יסיים
        close(fd);
    } else {
        perror("fork");
        close(fd);
        return 1;
    }

    return 0;
}

נקודות חשובות:
- הקובץ נפתח לפני הfork, ולכן שני התהליכים חולקים את אותו fd
- שני התהליכים כותבים לאותו קובץ - הסדר יכול להשתנות בין הרצות
- האב מחכה לבן עם wait כדי למנוע zombie


פתרון 5 - שאלה תיאורטית

מה קורה למתארי קבצים הפתוחים כאשר תהליך קורא ל-execve?

כברירת מחדל, מתארי קבצים פתוחים נשארים פתוחים לאחר קריאה ל-execve. כלומר, התוכנה החדשה שנטענת יורשת את כל הfd-ים שהיו פתוחים לפני הexecve.

זה בדיוק מה שמאפשר לshell לבצע הפניית פלט (redirection) עם תוכנות חיצוניות. כשהshell מריץ ls > output.txt, הוא:

  1. קורא fork כדי ליצור תהליך בן
  2. בתהליך הבן: פותח את output.txt ומבצע dup2 כדי להפנות את stdout (fd 1) לקובץ
  3. בתהליך הבן: קורא execve("/bin/ls", ...) כדי להריץ את ls
  4. התוכנה ls מתחילה לרוץ - היא כותבת ל-stdout (fd 1) שמצביע על הקובץ

אם execve היה סוגר את כל הfd-ים, ההפניה הייתה נשברת ו-ls היה כותב למסך במקום לקובץ.

הערה: ניתן לסמן fd ספציפי כ-"close-on-exec" עם הדגל O_CLOEXEC בopen, או עם הsyscall fcntl. fd שמסומן כך כן ייסגר אוטומטית בexecve. אבל ברירת המחדל היא שהfd נשאר פתוח.