الخميس، 1 أكتوبر 2020

Inter Process Communication - الإشارات

 

Inter Process Communication - الإشارات



السؤال الأول الذي يتبادر إلى الذهن هو ، لماذا نحتاج إلى إشارات؟ إجابة بسيطة ، لحماية المنطقة الحرجة / المشتركة المشتركة بين عمليات متعددة.

لنفترض أن العمليات المتعددة تستخدم نفس منطقة الكود وإذا أراد الجميع الوصول إلى المتوازيات ، فإن النتيجة متداخلة. لنفترض ، على سبيل المثال ، أن عدة مستخدمين يستخدمون طابعة واحدة فقط (قسم عام / حرج) ، على سبيل المثال 3 مستخدمين ، مع إعطاء 3 وظائف في نفس الوقت ، إذا بدأت جميع الوظائف بشكل متوازي ، فسيتم تداخل إخراج مستخدم واحد مع آخر. لذلك ، نحتاج إلى حماية ذلك باستخدام الإشارات ، أي قفل القسم الحرج عند تشغيل إحدى العمليات وإلغاء قفلها عند الانتهاء. سوف يتكرر هذا لكل مستخدم / عملية حتى لا تتداخل وظيفة واحدة مع وظيفة أخرى.

بشكل أساسي ، يتم تصنيف الإشارات إلى نوعين -

إشارات ثنائية - حالتان فقط 0 و 1 ، أي مؤمن / غير مؤمن أو متاح / غير متاح ، تنفيذ Mutex.

عد الإشارات - تسمى الإشارات التي تسمح بإحصاء تعسفي للموارد.

افترض أن لدينا 5 طابعات (لنفترض أن طابعة واحدة تقبل مهمة واحدة فقط) وأن لدينا 3 مهام للطباعة. الآن سيتم إعطاء 3 مهام لـ 3 طابعات (واحدة لكل منها). مرة أخرى جاءت 4 وظائف أثناء هذا التقدم. الآن ، من بين طابعتين متاحتين ، تمت جدولة وظيفتين وبقيت لدينا وظيفتان أخريان ، والتي لن تكتمل إلا بعد توفر واحدة من المورد / الطابعة. يمكن اعتبار هذا النوع من الجدولة حسب توفر الموارد على أنه إشارات عد.

لإجراء التزامن باستخدام الإشارات ، فيما يلي الخطوات -

الخطوة 1 - إنشاء إشارة أو الاتصال بإشارة موجودة بالفعل (semget ())

الخطوة 2 - تنفيذ العمليات على الإشارة ، أي تخصيص أو تحرير أو انتظار الموارد (semop ())

الخطوة 3 - إجراء عمليات التحكم في قائمة انتظار الرسائل (semctl ())

الآن ، دعنا نتحقق من ذلك باستخدام مكالمات النظام لدينا.

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>

int semget(key_t key, int nsems, int semflg)

يقوم استدعاء النظام هذا بإنشاء أو تخصيص مجموعة إشارات System V. يجب تمرير الحجج التالية -

  • الوسيطة الأولى ، المفتاح ، تتعرف على قائمة انتظار الرسائل. يمكن أن يكون المفتاح إما قيمة عشوائية أو قيمة يمكن اشتقاقها من وظيفة المكتبة ftok ().

  • الوسيطة الثانية ، nsems ، تحدد عدد الإشارات. إذا كان الثنائي هو 1 ، فهذا يعني الحاجة إلى مجموعة إشارة واحدة ، وإلا حسب العدد المطلوب لمجموعات السيمافور.

  • الوسيطة الثالثة semflg تحدد إشارة / إشارات الإشارة المطلوبة مثل IPC_CREAT (إنشاء إشارة إذا لم تكن موجودة) أو IPC_EXCL (تُستخدم مع IPC_CREAT لإنشاء إشارة وفشل الاستدعاء ، إذا كانت الإشارة موجودة بالفعل). تحتاج إلى تمرير الأذونات كذلك.

ملاحظة - راجع الأقسام السابقة للحصول على تفاصيل حول الأذونات.

ستعيد هذه المكالمة معرف إشارة صالح (يستخدم لمزيد من استدعاءات الإشارات) على النجاح و -1 في حالة الفشل. لمعرفة سبب الفشل ، تحقق من متغير errno أو وظيفة perror ().

العديد من الأخطاء المتعلقة بهذه المكالمة هي EACCESS (تم رفض الإذن) ، EEXIST (قائمة الانتظار موجودة بالفعل لا يمكن إنشاؤها) ، ENOENT (قائمة الانتظار غير موجودة) ، ENOMEM (لا توجد ذاكرة كافية لإنشاء قائمة الانتظار) ، ENOSPC (الحد الأقصى للمجموعات تم تجاوزه) ، إلخ.

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>

int semop(int semid, struct sembuf *semops, size_t nsemops)

يقوم استدعاء النظام هذا بإجراء العمليات على مجموعات إشارات النظام الخامس ، أي تخصيص الموارد أو انتظار الموارد أو تحرير الموارد. يجب تمرير الحجج التالية -

  • تشير الوسيطة الأولى ، semid ، إلى معرف مجموعة الإشارات التي تم إنشاؤها بواسطة semget ().

  • الوسيطة الثانية ، semops ، هي المؤشر إلى مجموعة من العمليات التي سيتم تنفيذها على مجموعة الإشارات. الهيكل على النحو التالي -

struct sembuf {
   unsigned short sem_num; /* Semaphore set num */
   short sem_op; /* Semaphore operation */
   short sem_flg; /* Operation flags, IPC_NOWAIT, SEM_UNDO */
};

يشير العنصر ، sem_op ، في الهيكل أعلاه ، إلى العملية التي يجب تنفيذها -

  • إذا كان sem_op هو –ve ، فخصص أو احصل على الموارد. يحظر عملية الاستدعاء حتى يتم تحرير موارد كافية بواسطة العمليات الأخرى ، بحيث يمكن تخصيص هذه العملية.

  • إذا كانت sem_op تساوي صفرًا ، فإن عملية الاستدعاء تنتظر أو تنام حتى تصل قيمة الإشارة إلى 0.

  • إذا كانت sem_op تساوي + ve ، فقم بتحرير الموارد.

على سبيل المثال -

Struct sembuf sem_lock = {0، -1، SEM_UNDO} ؛

Struct sembuf sem_unlock = {0، 1، SEM_UNDO} ؛

  • المتغير الثالث ، nsemops ، هو عدد العمليات في تلك المصفوفة.

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>

int semctl(int semid, int semnum, int cmd, …)

يقوم استدعاء النظام هذا بإجراء عملية تحكم لإشارة System V. يجب تمرير الحجج التالية -

  • الوسيطة الأولى ، semid ، هي معرف السيمافور. هذا المعرف هو معرف السيمافور ، وهو القيمة المعادة لاستدعاء نظام semget ().

  • الوسيطة الثانية ، semnum ، هي عدد السيمافور. يتم ترقيم الإشارات من 0.

  • الوسيطة الثالثة ، cmd ، هي الأمر لتنفيذ عملية التحكم المطلوبة على السيمافور.

  • الوسيطة الرابعة من النوع union semun تعتمد على cmd. في حالات قليلة ، لا تنطبق الوسيطة الرابعة.

دعونا نتحقق من اتحاد سيمون -

union semun {
   int val; /* val for SETVAL */
   struct semid_ds *buf; /* Buffer for IPC_STAT and IPC_SET */
   unsigned short *array; /* Buffer for GETALL and SETALL */
   struct seminfo *__buf; /* Buffer for IPC_INFO and SEM_INFO*/
};

هيكل البيانات semid_ds المحدد في sys / sem.h هو كما يلي -

struct semid_ds {
   struct ipc_perm sem_perm; /* Permissions */
   time_t sem_otime; /* Last semop time */
   time_t sem_ctime; /* Last change time */
   unsigned long sem_nsems; /* Number of semaphores in the set */
};

ملاحظة - يرجى الرجوع إلى صفحات الدليل لهياكل البيانات الأخرى.

اتحاد semun arg القيم الصالحة لـ cmd هي -

  • IPC_STAT - ينسخ معلومات القيم الحالية لكل عضو من semid_ds الهيكل إلى الهيكل الذي تم تمريره المشار إليه بواسطة arg.buf. يتطلب هذا الأمر إذن قراءة للإشارة.

  • IPC_SET - يضبط معرف المستخدم ومعرف المجموعة للمالك والأذونات وما إلى ذلك التي يشير إليها الهيكل semid_ds.

  • IPC_RMID - يزيل مجموعة الإشارات.

  • IPC_INFO - إرجاع المعلومات حول حدود ومعلمات السيمافور في البنية semid_ds المشار إليها بواسطة arg .__ buf.

  • SEM_INFO - إرجاع بنية seminfo تحتوي على معلومات حول موارد النظام المستهلكة بواسطة الإشارة.

سيعيد هذا الاستدعاء قيمة (قيمة غير سالبة) اعتمادًا على الأمر الذي تم تمريره. عند النجاح ، تُرجع IPC_INFO و SEM_INFO أو SEM_STAT الفهرس أو المعرف الخاص بأعلى إدخال مستخدم وفقًا لـ Semaphore أو قيمة semncnt لـ GETNCNT أو قيمة sempid لـ GETPID أو قيمة semval لـ GETVAL 0 لعمليات أخرى على النجاح و - 1 في حالة الفشل. لمعرفة سبب الفشل ، تحقق من متغير errno أو وظيفة perror ().

قبل النظر في الكود ، دعونا نفهم كيفية تنفيذه -

  • إنشاء عمليتين مثل الطفل والوالد

  • قم بإنشاء ذاكرة مشتركة مطلوبة بشكل أساسي لتخزين العداد والأعلام الأخرى للإشارة إلى انتهاء عملية القراءة / الكتابة في الذاكرة المشتركة.

  • يتم زيادة العداد عن طريق العد بواسطة كل من العمليات الأصل والتابعة. يتم تمرير العدد إما كوسيطة سطر أوامر أو يتم استخدامه كوسائط افتراضي (إذا لم يتم تمريره كوسيطة سطر أوامر أو كانت القيمة أقل من 10000). يتم استدعاؤها مع وقت نوم معين لضمان وصول كل من الوالدين والطفل إلى الذاكرة المشتركة في نفس الوقت ، أي بالتوازي.

  • نظرًا لأنه يتم زيادة العداد بخطوات 1 بواسطة كل من الوالد والطفل ، يجب أن تكون القيمة النهائية مضاعفة العداد. نظرًا لأن كلا من العمليات الأم والطفل تؤدي العمليات في نفس الوقت ، لا يتم زيادة العداد كما هو مطلوب. وبالتالي ، نحتاج إلى ضمان اكتمال عملية واحدة متبوعة بعملية أخرى.

  • يتم تنفيذ جميع التطبيقات المذكورة أعلاه في الملف shm_write_cntr.c

  • تحقق مما إذا تم تنفيذ قيمة العداد في ملف shm_read_cntr.c

  • لضمان الاكتمال ، يتم تنفيذ برنامج السيمافور في ملف shm_write_cntr_with_sem.c. قم بإزالة الإشارة بعد الانتهاء من العملية بأكملها (بعد أن تتم القراءة من برنامج آخر)

  • نظرًا لأن لدينا ملفات منفصلة لقراءة قيمة العداد في الذاكرة المشتركة وليس لها أي تأثير من الكتابة ، يظل برنامج القراءة كما هو (shm_read_cntr.c)

  • من الأفضل دائمًا تنفيذ برنامج الكتابة في محطة واحدة وبرنامج القراءة من محطة أخرى. نظرًا لأن البرنامج لا يكمل التنفيذ إلا بعد اكتمال عملية الكتابة والقراءة ، فلا بأس من تشغيل البرنامج بعد تنفيذ برنامج الكتابة بالكامل. سينتظر برنامج الكتابة حتى يتم تشغيل برنامج القراءة ولا ينتهي إلا بعد انتهائه.

برامج بدون إشارات.

/* Filename: shm_write_cntr.c */
#include<stdio.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#include<sys/types.h>
#include<string.h>
#include<errno.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>

#define SHM_KEY 0x12345
struct shmseg {
   int cntr;
   int write_complete;
   int read_complete;
};
void shared_memory_cntr_increment(int pid, struct shmseg *shmp, int total_count);

int main(int argc, char *argv[]) {
   int shmid;
   struct shmseg *shmp;
   char *bufptr;
   int total_count;
   int sleep_time;
   pid_t pid;
   if (argc != 2)
   total_count = 10000;
   else {
      total_count = atoi(argv[1]);
      if (total_count < 10000)
      total_count = 10000;
   }
   printf("Total Count is %d\n", total_count);
   shmid = shmget(SHM_KEY, sizeof(struct shmseg), 0644|IPC_CREAT);

   if (shmid == -1) {
      perror("Shared memory");
      return 1;
   }

   // Attach to the segment to get a pointer to it.
   shmp = shmat(shmid, NULL, 0);
   if (shmp == (void *) -1) {
      perror("Shared memory attach");
      return 1;
   }
   shmp->cntr = 0;
   pid = fork();

   /* Parent Process - Writing Once */
   if (pid > 0) {
      shared_memory_cntr_increment(pid, shmp, total_count);
   } else if (pid == 0) {
      shared_memory_cntr_increment(pid, shmp, total_count);
      return 0;
   } else {
      perror("Fork Failure\n");
      return 1;
   }
   while (shmp->r

التسميات: