מתארי קבצים בלינוקס מוסברים - המספר הקטן שמאחוריו כל קלט ופלט¶
בלינוקס יש עיקרון עיצוב שחוזר על עצמו בכל מקום - "הכל הוא קובץ". קובץ רגיל על הדיסק הוא קובץ, אבל גם התקן, גם צינור בין תהליכים, וגם חיבור רשת מיוצגים באותה צורה בסיסית. המפתח שמחבר את כל אלה הוא מתאר קובץ - file descriptor, מספר שלם קטן שנראה תמים לגמרי, אבל מסתתר מאחוריו כמעט כל פעולת קלט ופלט שתוכנית עושה.
אז מה זה בעצם מתאר קובץ¶
כשתוכנית פותחת קובץ, מתחברת לרשת, או יוצרת צינור, הקרנל לא נותן לה גישה ישירה למשאב עצמו. במקום זה, הוא מחזיר לה מספר שלם קטן - מתאר קובץ, שמייצג "כרטיס כניסה" למשאב הזה. כל קריאה עתידית לפעולה על אותו משאב, כמו קריאה - read, כתיבה - write, או סגירה - close, מתבצעת דרך המספר הזה, לא דרך שם הקובץ המקורי.
לכל תהליך יש טבלת מתארים משלו, שמתחילה ריקה יחסית, אבל שלושה מתארים תמיד שמורים ופתוחים כברירת מחדל, עוד לפני שהתוכנית עשתה כלום:
- 0 - קלט תקני, standard input. ברירת המחדל היא קלט מהמקלדת.
- 1 - פלט תקני, standard output. ברירת המחדל היא הדפסה למסך.
- 2 - פלט שגיאות תקני, standard error. גם הוא מוצג במסך כברירת מחדל, אבל בערוץ נפרד מהפלט הרגיל.
בכל פעם שקוראים ל-printf בשפת C, מתחת למכסה זה בסופו של דבר כתיבה למתאר קובץ מספר 1.
איך הפניית קלט ופלט עובדת בפועל¶
ההפרדה בין מתאר לבין המשאב עצמו היא בדיוק מה שמאפשר הפניית קלט ופלט - redirection, שכל מי שעבד בטרמינל לינוקס מכיר. כשכותבים program > output.txt, המעטפת פשוט משנה למה מצביע מתאר מספר 1 של התהליך, לפני שהיא מריצה את התוכנית. התוכנית עצמה ממשיכה לכתוב "לפלט תקני" בדיוק כמו תמיד, בלי לדעת שהפעם זה קובץ ולא המסך.
הקריאה למערכת שמאפשרת את זה נקראת dup2 - היא לוקחת מתאר קיים ומשכפלת אותו לתוך מספר מתאר אחר, כך שהמספר 1 מפנה עכשיו למקום חדש. זו בדיוק הטכניקה שגם צינורות בין תהליכים משתמשים בה, כדי לחבר את הפלט של תוכנית אחת לקלט של תוכנית שנייה.
למה "הכל הוא קובץ" זה כל כך שימושי¶
כשקבצים, התקנים, שקעי רשת, וצינורות בין תהליכים כולם מיוצגים דרך אותה ממשק בסיסי - מספר מתאר קטן, עם אותן פעולות read ו-write שעובדות עליהם - קוד שנכתב פעם אחת יכול לעבוד על סוגי משאבים שונים לגמרי בלי שינוי. תוכנית שכותבת ל-stdout יכולה, בלי לשנות שורת קוד אחת, לכתוב בפועל לקובץ, לצינור, או לחיבור רשת, פשוט כי בשלב ההפעלה מתאר מספר 1 שלה חובר למקום אחר.
מה קורה כשמשאירים מתאר פתוח¶
מספר המתארים הפתוחים לכל תהליך מוגבל, ופתיחת קובץ בלי לסגור אותו בסופו של דבר, נקראת דליפת מתארים - file descriptor leak. תוכנית שפותחת קבצים או חיבורי רשת שוב ושוב בלי לסגור אותם, בסופו של דבר תתקל במגבלה ותתחיל לקבל שגיאות בכל ניסיון לפתוח משהו נוסף - בעיה מוכרת מאוד בשרתים שרצים זמן רב בלי הפסקה, ואחת הסיבות הנפוצות ביותר לבאגים שקשה לשחזר אותם, כי הם מצטברים לאט ומופיעים רק אחרי זמן.
איפה זה נכנס להבנה הרחבה יותר¶
מתארי קבצים הם נקודת המפגש בין תוכנית לבין כל המשאבים החיצוניים שלה - קבצים, רשת, ותקשורת בין תהליכים. הבנה שלהם היא הבסיס להבנת דברים כמו צינורות, שקעי רשת, וגם חלק גדול מהאבטחה סביב הרשאות גישה למשאבים.
זה בדיוק סוג ההבנה שאנחנו בונים בקורס ליבת המחשב - איך תהליך מדבר בפועל עם הקרנל כדי לגשת למשאבים, ולא רק מה קורה כשקוראים לפונקציה בספרייה.
ואם דליפת מתארים גרמה לכם כאב ראש בעבר, יש קהילה שכבר התמודדה עם זה.
לסיכום¶
מתאר קובץ הוא רק מספר שלם קטן, אבל הוא ההפשטה שמאחדת כל צורה של קלט ופלט בלינוקס תחת ממשק אחיד - read, write, close. שלושת המתארים הראשונים תמיד שמורים לקלט, פלט ושגיאות, והבנה של איך הם מופנים ומשוכפלים היא בדיוק מה שעומד מאחורי הפניית קלט ופלט וצינורות בין תהליכים שכל אחד מכיר מהטרמינל.