קבוצות תהליכים והפצת אותות¶
נניח שמפקח מפעיל shell שמריץ שני כלי עזר. אם נשלח אות רק ל־PID של ה־shell, ייתכן שהילדים ימשיכו לעבוד ולשמור קבצים וצינורות פתוחים. ביטול של משימה לוגית דורש הגדרה של הקבוצה שאליה היא שייכת, ולא הנחה שתהליך האב מעביר כל אות לכל צאצאיו.
קבוצת תהליכים מאפשרת להפנות אות לקבוצה שלמה, ו־session מארגן בין השאר יחסים של קבוצות ושל מסוף שולט. העץ ההורי וקבוצת התהליכים הם מבנים שונים. תהליך יכול ליצור קבוצה חדשה, ולכן שליחת אות לקבוצה אינה הבטחה לכל צאצא אפשרי אם התוכנית רשאית לצאת ממנה.
ניצור קבוצה נפרדת לכלי הניסוי. הקוד הבא דורש libc בפרויקט Cargo, ומריץ פקודת shell קבועה שאנחנו מגדירים לצורך התרגיל בלבד.
use std::{io, process::Command};
use std::os::unix::process::CommandExt;
fn main() -> io::Result<()> {
let mut child = Command::new("/bin/sh")
.arg("-c").arg("sleep 30 & wait")
.process_group(0)
.spawn()?;
let group = child.id() as libc::pid_t;
let result = unsafe { libc::kill(-group, libc::SIGTERM) };
if result == -1 {
let error = io::Error::last_os_error();
let _ = child.wait();
return Err(error);
}
let status = child.wait()?;
println!("leader status={status}");
Ok(())
}
הערך 0 ב־process_group מבקש שהילד יתחיל קבוצה שהמזהה שלה הוא ה־PID שלו. הערך השלילי ב־kill מפנה את האות לקבוצה הזאת. אנחנו לא משתמשים ב־kill(0, ...), שהיה מתייחס לקבוצה של המפקח ועלול לכלול את המסוף שממנו הריץ התלמיד את התוכנית.
הדוגמה שולחת את האות מיד ולכן חלק מההרצות עשויות לעצור את ה־shell לפני שיצר את sleep. כדי לחקור את המקרה שבו כבר קיים ילד, הוסיפו ערוץ מוכנות מפורש או עצירה ידנית לפני האות, ובדקו באמצעות ps את PID, PPID, PGID ו־SID. השהיה שרירותית יכולה לעזור בהדגמה, אבל אינה פרוטוקול מוכנות אמין.
הקוד אוסף רק את הילד הישיר שלו. הוא אינו מקבל אוטומטית את סטטוס כל הנכדים. כאשר הורה מת, ילדים יתומים עוברים להורה מתאים לפי כללי המערכת, ובסביבת supervisor אפשר להגדיר subreaper כדי לאמץ צאצאים מתייתמים. אחרי האימוץ עדיין צריך לבצע wait ולהחליט איך לקשר כל תוצאה למשימה המקורית.
שליחת אות היא בקשה אסינכרונית. הצלחת kill אינה אומרת שהיעד כבר סיים. התהליך יכול לטפל ב־SIGTERM, לחסום אותו או להמשיך זמן מה לפני שהוא מגיע לנקודת סיום. מפקח צריך להמתין, למדוד deadline, ולבחור אם להסלים לאות שאינו ניתן לטיפול כאשר המדיניות דורשת זאת. גם אחרי הסלמה נדרש איסוף תוצאה.
לכל תהליכון יש mask של אותות חסומים, בעוד שפעולת הטיפול המוגדרת לאות היא בדרך כלל תכונה של התהליך. אות שמופנה לתהליך יכול להימסר לתהליכון מתאים שאינו חוסם אותו, ואות שמופנה לתהליכון מסוים אינו בקשה אוטומטית להעבירו לאחר. האותות הרגילים גם אינם תור בלתי מוגבל של כל האירועים: כמה מופעים יכולים להתאחד למצב pending אחד.
ההבחנה הזאת משנה את תכנון המערכת. אם אנחנו צריכים לספור כל פעולה, אות רגיל אינו תחליף לתור הודעות. אם אנחנו רק צריכים לבקש מעבר למצב עצירה, מצב pending יכול להספיק. בפרק הבא נעביר קבלת אות להקשר רגיל שבו מותר להקצות, לנעול ולכתוב דוח.
זהות קבוצה היא עדיין מספר עם מחזור חיים. שמירת PGID אחרי שהקבוצה הסתיימה ואז שימוש בו מאוחר יותר יכולה לפגוע בקבוצה חדשה. בדוגמה אנחנו שומרים את הילד הישיר בלי לאסוף אותו לפני האות, אבל מפקח ממושך צריך פרוטוקול מפורש שסוגר את חלון השימוש בזהות. קבוצות אינן גבול בידוד חזק מול תוכנית שמנסה לצאת מהן; בהמשך נשתמש גם ב־cgroups לניהול משאבים ומשימות.
המודל של אותות וקבוצות מתואר בעמוד signal ובעמוד setpgid. בכל ניסוי ציינו בנפרד מי קיבל אות, מי סיים ומי נאסף, משום שאלה שלושה אירועים שונים.