SysV IPC

semop#

מבצע פעולה אחת או יותר על סמפורי System V באופן אטומי.

semop הוא סוס העבודה של השימוש בסמפורי SysV: הוא מקבל מזהה של מערכת סמפורים (מ־semget) ורשימה ארוזה של פעולות, ואז מבקש מהקרנל להחיל את כולן או אף אחת. כל פעולה מציינת סמפור בודד בתוך המערכת, כמות להוסיף לו (בדרך כלל -1 להמתנה, +1 לסיגנל), ומילת דגלים. הקרנל חוסם את הקורא עד שכל פעולה שבוקשה יכולה להתקדם, ואז מבצע אותן כיחידה אחת. זה מה שהופך את semop לשמיש כפרימיטיב הדרה הדדית או משאב־ספירה בין תהליכים בלתי־קשורים.

תקציר#

semop $semid, $opstring

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

אמת בהצלחה, שקר בשגיאה כאשר $! נקבע. ערך האמת הוא התוצאה היחידה - semop מתקשר את התוצאה דרך תופעת הלוואי על מערכת הסמפורים, לא דרך ההחזרה שלו. בדקו אותו בכל קריאה:

semop($semid, $ops)
    or die "semop failed: $!";

החזרת שקר משמעה ששום פעולה ב־OPSTRING לא הוחלה; סמנטיקת SysV מבטיחה הכל־או־כלום.

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

קובע את $! בכשל, עם ערכי ה־errno הרגילים של semop(2): EAGAIN כאשר IPC_NOWAIT נקבע והפעולה היתה חוסמת, EIDRM כאשר מערכת הסמפורים הוסרה בעת ההמתנה, EINTR כאשר אות הפסיק את ההמתנה, EINVAL עבור מספר או מזהה סמפור שגויים, EACCES עבור אי־התאמת הרשאות מול mode של המערכת.

מבנה הפעולה#

OPSTRING הוא שרשור של רשומות ברוחב קבוע. כל רשומה היא שלושה מספרים שלמים מסוג short ברוחב מקומי ומיוצרת באמצעות pack:

pack("s!3", $semnum, $semop, $semflag)
  • $semnum - האינדקס של סמפור המטרה בתוך המערכת (0 עד nsems-1, כאשר nsems הוא הגודל שהועבר ל־semget).

  • $semop - הכמות להוסיף לערך הסמפור. ערכים שליליים ממתינים עד שהסמפור הוא לפחות |$semop| לפני החיסור; ערכים חיוביים מוסיפים ללא תנאי ומעירים ממתינים; אפס ממתין עד שהסמפור מגיע לאפס.

  • $semflag - OR ביטי של IPC_NOWAIT (להיכשל עם EAGAIN במקום לחסום) ושל SEM_UNDO (לגרום לקרנל להפוך את הפעולה אם התהליך יוצא מבלי לבטלה - חיוני לשימושים דמויי־מנעול שבהם קריסה אסור שתשאיר את הסמפור מוחזק).

האורך של OPSTRING מרמז על מספר הפעולות: הקרנל מעבד length($opstring) / sizeof(struct sembuf) רשומות. העברת שתי רשומות הארוזות סוף־אל־סוף מבקשת שתי פעולות שיוחלו אטומית.

ה־s! בתבנית הוא ה־short המקומי, התואם את ה־layout של sembuf בלינוקס. s רגיל הוא 16־ביט חתום בסדר־רשת ויארוז שגויות בשקט בכל פלטפורמה שבה sizeof(short) != 2 או שבה ה־endianness שונה מסדר־רשת.

דוגמאות#

המתנה (החסרה ב־1) על סמפור 0 של המערכת ב־$semid:

my $op = pack("s!3", 0, -1, 0);
semop($semid, $op)
    or die "wait on semaphore failed: $!";

סיגנל (הגדלה ב־1) של אותו סמפור:

my $op = pack("s!3", 0, 1, 0);
semop($semid, $op)
    or die "signal failed: $!";

נסו לרכוש ללא חסימה - היכשלו מהר אם מישהו אחר מחזיק אותו:

use POSIX qw(EAGAIN);
my $op = pack("s!3", 0, -1, IPC_NOWAIT);
unless (semop($semid, $op)) {
    die "semop: $!" unless $! == EAGAIN;
    # contended; caller decides what to do
}

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

my $op = pack("s!3", 0, -1, SEM_UNDO);
semop($semid, $op) or die "lock: $!";
# critical section
my $release = pack("s!3", 0, 1, SEM_UNDO);
semop($semid, $release) or die "unlock: $!";

שתי פעולות המוחלות אטומית - המתנה לסמפור 0 ולסמפור 1 ביחד, ללא שחרור אף אחד מהם אם אחד היה חוסם:

my $ops = pack("s!3", 0, -1, 0)
        . pack("s!3", 1, -1, 0);
semop($semid, $ops) or die "paired wait: $!";

המתינו לסמפור 0 להגיע לאפס - צורת ה־״barrier״, שבה $semop הוא אפס במקום שלילי:

my $op = pack("s!3", 0, 0, 0);
semop($semid, $op) or die "barrier: $!";

מקרי קצה#

  • OPSTRING קצר: האורך חייב להיות כפולה מדויקת של גודל רשומת sembuf. מחרוזת קטומה או מרופדת נותנת EINVAL. תמיד בנו את המחרוזת מקריאות pack, אף פעם לא ביד.

  • רוחב מקומי חשוב: s!3 ולא s3. סקריפט שנכתב עם s רגיל עובד על מארח עם short של 16־ביט במקרה ונשבר במקומות אחרים.

  • OPSTRING באורך אפס: מבקש אפס פעולות. הקרנל מקבל זאת ומחזיר הצלחה ללא תופעות לוואי - לעיתים נדירות מה שאתם רוצים. הגנו על הקריאה עם return unless length $ops.

  • הפסקת אות: semop חסום שחוזר עם $! שנקבע ל־EINTR אינו באג - נסו שוב אלא אם התקנתם מטפל שרוצה להפסיק. הטיפול ברירת המחדל של $SIG{*} ב־Perl אינו מפעיל מחדש את ה־syscall אוטומטית.

  • SEM_UNDO עם זוגות תואמים: הקרנל עוקב אחר ההתאמה המצטברת לכל סמפור לכל תהליך. רכישה פעמיים עם SEM_UNDO ואז שחרור פעם אחת מותירים -1 תלוי ועומד שייעקר ביציאה. צמדו כל -1 עם +1 תואם בזרימה רגילה; SEM_UNDO הוא ביטוח נגד קריסות, לא תחליף לשחרור.

  • מערכת שהוסרה תוך כדי המתנה: אם תהליך אחר קורא ל־semctl עם IPC_RMID, כל semop חסום מחזיר שקר כאשר $! נקבע ל־EIDRM. מזהה הסמפור אינו תקף מנקודה זו והלאה.

  • הרשאות: semop בודק הרשאת שינוי על המערכת. תהליך שיכול לבצע semget על handle לקריאה־בלבד עדיין לא יכול לבצע semop עליו.

  • ספירות פעולות גדולות: הקרנל מגביל את מספר הפעולות לכל קריאה ל־SEMOPM (בדרך כלל 32). בניית OPSTRING עם יותר רשומות נכשלת עם E2BIG.

הבדלים מ־upstream#

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

ראו גם#

  • semget - משיג את המזהה שאתם מעבירים כארגומנט הראשון; השניים תמיד בשימוש יחד

  • semctl - קובע או מתשאל ערכי סמפור, ומסיר את המערכת כשסיימתם איתה

  • pack - בונה כל רשומת sembuf; s!3 היא התבנית שאתם רוצים

  • msgsnd - המקבילה של תור ההודעות כאשר צריך להעביר נתונים ולא רק לאותת על זמינות

  • shmget - המקבילה של זיכרון משותף; סמפורים משמשים בדרך כלל לשמירה על גישה למקטע זיכרון משותף של SysV

  • IPC::SysV - מייצא את הקבועים IPC_NOWAIT, SEM_UNDO, ו־IPC_RMID המשמשים ב־$semflag ובפקודות semctl

  • IPC::Semaphore - עטיפת עצם מעל semget / semop / semctl כאשר ממשק פרוצדורלי מרגיש חשוף מדי