לדלג לתוכן

צינורות בלינוקס מוסברים - איך הסימן | מחבר בין שתי תוכניות זרות

כל מי שעבד בטרמינל לינוקס הריץ פקודה כמו cat file.txt | grep error בלי לחשוב פעמיים על מה שקורה מאחורי הסימן הקטן הזה. שתי תוכניות שלא יודעות כלום אחת על השנייה, פתאום מדברות ביניהן בזמן אמת, כאילו הפלט של אחת פשוט זורם ישר לקלט של השנייה. זה בדיוק מה שקורה, וזה בזכות מנגנון בקרנל שנקרא צינור - pipe.

אז מה זה בעצם צינור

צינור הוא ערוץ תקשורת חד כיווני בין שני תהליכים, שמנוהל כולו בתוך הקרנל. יש לו שני קצוות - קצה לכתיבה, וקצה לקריאה, וכל אחד מהם מיוצג בתור מתאר קובץ רגיל, בדיוק כמו שראינו קודם. תהליך אחד כותב לקצה הכתיבה עם write, ותהליך אחר קורא מקצה הקריאה עם read, בדיוק כאילו זה קובץ רגיל - למרות שאין כאן שום קובץ על הדיסק, רק בלוק זיכרון זמני שהקרנל מנהל.

כשקוראים לפונקציה pipe(), הקרנל יוצר את שני הקצוות האלה ומחזיר שני מתארי קבצים חדשים. תהליך שרוצה לתקשר עם תהליך בן, בדרך כלל יוצר את הצינור לפני שהוא קורא ל-fork, כדי ששני התהליכים, ההורה והבן, יירשו יחד גישה לאותם שני קצוות.

איך | בטרמינל בכלל עובד

כשכותבים cat file.txt | grep error, המעטפת מבצעת רצף פעולות מדויק. היא יוצרת צינור, ואז מריצה שתי פקודות בנפרד, כל אחת כתהליך משלה. לתהליך שמריץ cat, המעטפת מחברת את הפלט התקני שלו, מתאר מספר 1, לקצה הכתיבה של הצינור, באמצעות אותה dup2 שדיברנו עליה כשעסקנו במתארי קבצים. לתהליך שמריץ grep, היא מחברת את הקלט התקני שלו, מתאר מספר 0, לקצה הקריאה של אותו צינור.

התוצאה - cat כותב לפלט התקני שלו כרגיל, בלי לדעת שהוא בכלל מחובר לצינור, ו-grep קורא מהקלט התקני שלו כרגיל, בלי לדעת שהמידע מגיע מתוכנית אחרת ולא מהמקלדת. שתי התוכניות רצות בלי שום שינוי בקוד שלהן - כל הקסם קורה ברמת הפניית מתארי הקבצים, לפני שהן בכלל מתחילות לרוץ.

חסימה - מה קורה כשהצינור ריק או מלא

לצינור יש קיבולת מוגבלת של בייטים שהוא יכול להחזיק בזמן נתון. אם תהליך מנסה לקרוא מצינור ריק, הוא נחסם - blocked, ומושהה עד שמישהו כותב אליו משהו. ואם תהליך מנסה לכתוב לצינור שכבר מלא עד גדותיו, גם הוא נחסם, עד שהקורא בצד השני יוציא מספיק נתונים כדי לפנות מקום.

ההתנהגות הזאת היא בדיוק מה שמאפשר לפקודות מקושרות לרוץ במקביל ולעבד נתונים בזרם, בלי שהתוכנית הראשונה צריכה לסיים לגמרי לפני שהשנייה מתחילה. cat יכול לכתוב שורה, grep יעבד אותה מיד, ובינתיים cat כבר מכין את השורה הבאה - עיבוד זורם, במקום המתנה לכל הקובץ להיטען לזיכרון קודם.

צינור אנונימי מול צינור בעל שם

הצינור שראינו עד עכשיו, שנוצר עם pipe(), קיים רק כל עוד התהליכים שיצרו אותו רצים - הוא לא מופיע בשום מקום במערכת הקבצים, ואי אפשר לתהליך שלישי לגלות אותו ולהתחבר אליו. זה מתאים מצוין לתקשורת בין הורה לילד, אבל לא לתקשורת בין תהליכים שלא קשורים זה לזה מראש.

בשביל זה קיים צינור בעל שם - named pipe, או FIFO. הוא נראה בפועל כמו קובץ במערכת הקבצים, אפשר ליצור אותו עם הפקודה mkfifo, ותהליכים לגמרי לא קשורים יכולים לפתוח אותו לפי הנתיב שלו ולתקשר דרכו, בלי שהם צריכים להיות בני משפחה של אותו הורה.

למה זה חלק חשוב מהתמונה הגדולה

צינורות הם דוגמה מצוינת לאיך הקרנל מספק כלים בסיסיים, מתארי קבצים, fork, ו-dup2, ומהם המעטפת בונה יכולת חזקה בהרבה - שרשור פקודות שלמות. ברגע שמבינים את זה, גם צנרת מורכבת עם כמה סימני | ברצף הופכת להיות ברורה לגמרי - זו רק שרשרת של תהליכים, כל אחד מחובר לבא אחריו דרך צינור.

זה בדיוק סוג הבנייה מהיסודות שאנחנו עושים בקורס ליבת המחשב - איך כלים שנראים פשוטים מהטרמינל בנויים בפועל מפעולות קרנל בסיסיות.

ואם אתם מנסים לבנות שרשור תהליכים ומשהו נתקע, יש קהילה שכבר פתרה את זה.

הצטרפו לקהילה בדיסקורד

לסיכום

צינור הוא ערוץ תקשורת חד כיווני שהקרנל מנהל בזיכרון, עם קצה לכתיבה וקצה לקריאה שמיוצגים כמתארי קבצים רגילים. הסימן | שאתם מכירים מהטרמינל הוא בעצם המעטפת שבונה צינור כזה ומפנה אליו את הפלט והקלט התקניים של שתי תוכניות, בלי שהן בכלל יודעות שהן מחוברות. וכשצריך תקשורת בין תהליכים זרים לגמרי, יש גם גרסה בעלת שם שמופיעה במערכת הקבצים.