תהליכים

waitpid#

המתנה לתהליך־ילד ספציפי שיסתיים ואיסופו.

waitpid הוא המקבילה הממוקדת של wait. היכן ש־wait אוסף כל ילד וחוסם עד שאחד יוצא, waitpid מאפשר לנקב איזה ילד (או איזו קבוצת ילדים) להמתין לו, ומאפשר לבדוק במקום לחסום על־ידי העברת WNOHANG ב־FLAGS. סטטוס היציאה של הילד הנאסף נוחת ב־$?, זהה ל־wait.

תקציר#

use POSIX ":sys_wait_h";

waitpid $pid, 0;                   # block until $pid exits
waitpid $pid, WNOHANG;             # poll: is $pid done yet?
waitpid -1,  WNOHANG;              # reap any ready zombie, don't block

מה מקבלים בחזרה#

  • ה־PID של הילד הנאסף בהצלחה.

  • 0 כאשר WNOHANG מוגדר ולפחות ילד תואם אחד קיים אך אף אחד מהם עדיין לא הסתיים - כלומר ״עדיין רץ, לנסות שוב מאוחר יותר״.

  • -1 כאשר אף תהליך־ילד אינו תואם את PID (מעולם לא היה קיים, כבר נאסף, או - במערכות שמבצעות איסוף אוטומטי - נקטף על־ידי הקרנל בלי עזרת Perl).

סטטוס יציאת הילד נכתב ל־$? וסטטוס הפלטפורמה הגולמי ל־${^CHILD_ERROR_NATIVE} בכל איסוף מוצלח. יש לפענח את $? באותו אופן כמו לאחר wait או קריאת backtick:

my $reaped = waitpid $pid, 0;
if ($reaped > 0) {
    my $exit   = $? >> 8;
    my $signal = $? & 127;
    my $core   = $? & 128;
}

מה משמעות PID#

PID בוחר אילו ילדים waitpid מוכן לאסוף:

  • PID חיובי - להמתין לאותו ילד ספציפי. -1 בחזרה משמעו אין ילד כזה (כבר נאסף, או שמעולם לא היה שלך).

  • 0 - להמתין לכל ילד בקבוצת התהליכים הנוכחית. שימושי כאשר התוכנית הצטרפה לקבוצת תהליכים עם תהליכים משתפי־פעולה אחרים ורוצים לאסוף רק את שלך.

  • -1 - להמתין לכל ילד, באותו תחום כמו wait.

  • פחות מ־-1 - להמתין לכל ילד שמזהה קבוצת התהליכים שלו שווה לערך המוחלט של PID. waitpid -$pgid, 0 ממתין לכל חבר בקבוצת תהליכים $pgid.

FLAGS הוא מסיכת ביטים. הדגל הנייד הוא WNOHANG, ההופך את הקריאה ללא־חוסמת: אם אף ילד תואם לא יצא, waitpid מחזיר 0 מיד במקום לישון. FLAGS של 0 משמעו ״לחסום עד שמשהו ניתן לאיסוף״, וממומש בכל פלטפורמה ש־Perl רץ עליה - גם באלה ללא קריאת מערכת מקומית waitpid(2), שבהן Perl מחקה אותו על־ידי זכירת סטטוסי היציאה של ילדים שכבר ראה.

מצב גלובלי שהוא נוגע בו#

  • $? - מוגדר לסטטוס היציאה של הילד הנאסף (מוזז: בית גבוה = קוד exit, 7 ביטים נמוכים = אות, ביט 0x80 = דגל core-dumped).

  • ${^CHILD_ERROR_NATIVE} - מוגדר למילת סטטוס הפלטפורמה הגולמית, לפני ש־Perl מנרמלת אותה ל־$?.

שני המשתנים אינם נוגעים כאשר waitpid מחזיר 0 (WNOHANG ושום דבר מוכן) או -1 (אין ילד תואם).

דוגמאות#

לחסום עד שילד ידוע יסתיים, ואז לבחון את קוד היציאה שלו:

my $pid = fork // die "fork: $!";
if ($pid == 0) {
    exec "/usr/bin/gzip", "big.log" or die "exec: $!";
}
waitpid $pid, 0;
die "gzip failed with status ", $? >> 8 if $?;

לבדוק בלי לחסום - שימושי בתוך לולאת אירועים או מטפל SIGCHLD:

use POSIX ":sys_wait_h";

sub harvest_children {
    while ((my $kid = waitpid -1, WNOHANG) > 0) {
        warn "child $kid exited with ", $? >> 8, "\n";
    }
}

האידיום הקלאסי ״לאסוף כל zombie ממתין, לא לחסום״:

use POSIX ":sys_wait_h";
1 while waitpid(-1, WNOHANG) > 0;

להמתין רק לילדים בקבוצת תהליכים ספציפית. setpgid תחילה, ואז למקד בקבוצה על־ידי שלילת המזהה שלה:

my $leader = fork // die "fork: $!";
if ($leader == 0) {
    setpgid 0, 0;        # become process-group leader
    exec @worker_cmd or die "exec: $!";
}
setpgid $leader, $leader;  # parent matches, races with child

# ... later, reap any member of that group:
while ((my $kid = waitpid -$leader, WNOHANG) > 0) {
    log_exit($kid, $?);
}

הבחנה בין שלושת ערכי ההחזרה במקום אחד:

my $r = waitpid $pid, WNOHANG;
if    ($r == $pid) { finalize_child($pid, $?) }
elsif ($r == 0)    { still_running($pid) }
elsif ($r == -1)   { warn "no such child $pid (already reaped?)" }

מקרי קצה#

  • WNOHANG דורש use POSIX. הקבוע אינו בנוי־ב־Perl; הוא מגיע מהמודול POSIX, בדרך כלל דרך use POSIX ":sys_wait_h";. כתיבת מספר שלם חשוף (waitpid $pid, 1) עובדת ב־Linux אך אינה ניידת ומסתירה כוונה.

  • $SIG{CHLD} = 'IGNORE' מתחרה עם waitpid. כאשר אומרים לקרנל לאסוף ילדים אוטומטית, לא נשאר ל־waitpid מה למצוא - הוא מחזיר -1 עם $! מוגדר ל־ECHILD. יש לבחור באסטרטגיית איסוף אחת לתוכנית, לא בשתיהן.

  • PIDs שכבר נאספו מחזירים -1. PID של ילד יחיד ניתן לאסוף רק פעם אחת; לאחר מכן, הוא נשכח. אם מעבירים את אותו PID ל־waitpid פעמיים, הקריאה השנייה מחזירה -1.

  • PID שלילי הוא קבוצת תהליכים, לא ״כל אחד חוץ מזה״. waitpid -1234, 0 ממתין לחברי קבוצת תהליכים 1234, לא ל־״כל ילד חוץ מ־PID 1234״. קוראים שאינם מכירים את מוסכמת POSIX לעיתים קוראים את זה לא נכון.

  • WNOHANG עם PID = -1 היא הצורה הסטנדרטית ״לרוקן את תור ה־zombies״. בשילוב עם לולאת while היא אוספת כל ילד שכבר יצא ועוצרת ברגע שאין לקרנל יותר מה להחזיר.

  • ערך החזרה -1 יכול גם להיות איסוף אוטומטי. במערכות מוגדרות לאסוף ילדים אוטומטית (למשל מטפל $SIG{CHLD} = 'IGNORE' המותקן בעת ההפעלה), waitpid אינו רואה ילד ומחזיר -1 גם עבור PID ש־fork זה עתה. ראו perlipc להערות הפלטפורמה.

  • המתנה חוסמת תמיד זמינה. גם בפלטפורמות ללא קריאת מערכת מקומית waitpid(2) או wait4(2), waitpid PID, 0 עובד - Perl מחקה אותו על־ידי שמירת סטטוסי הילדים שכבר אסף.

הבדלים מ־upstream#

תואם מלא ל־upstream Perl 5.42.

ראו גם#

  • wait - לחסום עבור כל ילד; שווה־ערך ל־waitpid -1, 0 במערכות התומכות בו

  • fork - ליצור את הילדים שתאספו מאוחר יותר עם waitpid

  • kill - לשלוח אות לאותם PIDs וקבוצות תהליכים ש־waitpid מקבל

  • $? - סטטוס יציאה של הילד הנאסף; יש לפענח כ־$? >> 8 עבור קוד היציאה, $? & 127 עבור האות

  • ${^CHILD_ERROR_NATIVE} - מילת סטטוס פלטפורמה גולמית עבור אותו ילד

  • POSIX - מקור של WNOHANG ושאר :sys_wait_h