System V IPC#

System V IPC נותן לשני תהליכים בלתי־קשורים משאב משותף שהגרעין מחזיק בבעלותו: מערכת סמפורים לתיאום, או מקטע זיכרון משותף להעברת בתים ללא העתקה דרך pipe. כל משאב מזוהה על־ידי מפתח שלם ושורד עד שתהליך מסיר אותו במפורש.

מודולי הנוחות IPC::SysV, IPC::Semaphore ו־IPC::SharedMem אינם זמינים ב־PetaPerl, ולכן המתכונים כאן משתמשים במשפחת הפונקציות המובנות הגולמית semget / shmget עם ערכי הדגלים הכתובים כמספרים פשוטים. פונקציות מובנות אלה הן השכבה שהמודולים עוטפים; שימוש בהן ישירות עולה רק כמה קבועים בעלי שם שאתם מספקים בעצמכם.

קבועי הדגלים שאתם צריכים#

מספר מצומצם של שלמים קטנים שולט באופן שבו קריאות אלה מתנהגות. אלה הם ערכי ה־Linux הסטנדרטיים:

use constant {
    IPC_PRIVATE => 0,        # "give me a fresh, private key"
    IPC_CREAT   => 0o1000,   # create the resource if absent
    IPC_EXCL    => 0o2000,   # fail if it already exists
    IPC_RMID    => 0,        # control command: remove the resource
};

תשעת הביטים הנמוכים של מילת הדגל הם ביטי הרשאה רגילים, אותם 0600 / 0644 שהייתם מעבירים ל־chmod. IPC_CREAT | 0600 פירושו ”צור אותו, קריאה/כתיבה לבעלים“.

מסירה דרך זיכרון משותף#

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

use constant {
    IPC_PRIVATE => 0,
    IPC_CREAT   => 0o1000,
    IPC_RMID    => 0,
};

my $size = 1024;

my $id = shmget(IPC_PRIVATE, $size, IPC_CREAT | 0600);
defined $id or die "shmget: $!";

my $payload = "shared-payload";
shmwrite($id, $payload, 0, length $payload) or die "shmwrite: $!";

my $kid = fork() // die "fork: $!";
if ($kid == 0) {
    # Child reads the same segment the parent wrote.
    shmread($id, my $buf, 0, length $payload) or die "shmread: $!";
    print "child read: $buf\n";          # child read: shared-payload
    exit 0;
}

waitpid($kid, 0);

# The segment outlives the processes - remove it explicitly.
shmctl($id, IPC_RMID, 0) or warn "shmctl: $!";

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

  • shmread / shmwrite מקבלים אורך מפורש. הם אינם עוצרים ב־NUL או בתו שורה חדשה; אתם אומרים להם בדיוק כמה בתים להעביר, החל מהיסט. העבירו length $payload כך שהקורא והכותב יסכימו על הגודל.

  • IPC_RMID אינו אוטומטי. מקטע משותף נשאר בגרעין לאחר שכל תהליך שהיה מחובר אליו יצא. ניתן לראות מקטעים יתומים עם ipcs -m ב־shell. תמיד צמדו shmget עם shmctl IPC_RMID תואם.

סמפור לתיאום#

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

use constant {
    IPC_PRIVATE => 0,
    IPC_CREAT   => 0o1000,
    IPC_RMID    => 0,
    SETVAL      => 16,       # semctl command: set one value
    GETVAL      => 12,       # semctl command: read one value
};

# A set with one semaphore.
my $sem = semget(IPC_PRIVATE, 1, IPC_CREAT | 0600);
defined $sem or die "semget: $!";

# Initialise semaphore 0 to the value 1 (one token available).
semctl($sem, 0, SETVAL, 1) or die "semctl SETVAL: $!";

my $value = semctl($sem, 0, GETVAL, 0);
print "semaphore value: $value\n";       # semaphore value: 1

# Remove the set when finished.
semctl($sem, 0, IPC_RMID, 0) or warn "semctl IPC_RMID: $!";

פעולות ההמתנה/סיגנל עצמן עוברות דרך semop, שמקבל מחרוזת ארוזה של שלשות (sem_num, sem_op, sem_flg) שנבנו עם pack. הקטנה באחד ממתינה עד שאסימון פנוי; הגדלה באחד משחררת אותו. כמו בזיכרון משותף, מערכת סמפורים שהגרעין מחזיק בבעלותו חייבת להיות מוסרת עם IPC_RMID אחרת היא נשארת - גלויה תחת ipcs -s.

הבא#

זיכרון משותף וסמפורים הם הערוצים כבדי־המשקל. עבור הערוצים הקלים יותר, מוכווני־הזרם, ראו תהליכי משנה ותהליכים נלווים ו־סוקטי TCP.