اتصال interprocess ( الاتصال بين العمليات (IPC)) عبارة عن مجموعة من الطرق لتبادل البيانات بين سلاسل العمليات. يمكن إطلاق العمليات على نفس الكمبيوتر وعلى أجهزة مختلفة متصلة عبر الشبكة. تأتي IPCs في عدة أنواع: "الإشارة"، "المقبس"، "الإشارة"، "الملف"، "الرسالة" ...
في هذه المقالة أريد أن ألقي نظرة على 3 أنواع فقط من IPC:
دعونا نلقي نظرة على تمرير الرسائل عبر الأنابيب المسماة. يبدو النقل بشكل تخطيطي كما يلي:
لإنشاء أنابيب مسماة سنستخدم الدالة، مكفيفو ():
#يشمل
تقوم الوظيفة بإنشاء ملف FIFO خاص باسم اسم المسار، والمعلمة وضعيحدد أذونات الملف.
إذا تم إنشاء ملف FIFO بنجاح، مكفيفو ()ترجع 0 (صفر). في حالة وجود أي أخطاء، تقوم الدالة بإرجاع -1 وتعيين رمز الخطأ إلى متغير خطأ.
الأخطاء النموذجية التي قد تحدث أثناء إنشاء القناة:
نفتح الملف للقراءة فقط ( O_RDONLY). ويمكن استخدامها O_NONBLOCKمعدّل مصمم خصيصًا لملفات FIFO، حتى لا تنتظر فتح الملف للكتابة على الجانب الآخر. ولكن في الكود المعطى هذه الطريقة غير ملائمة.
نقوم بتجميع البرنامج ثم تشغيله:
$ gcc -o mkfifo mkfifo.c $ ./mkfifo
في النافذة الطرفية التالية نقوم بتنفيذ:
$ echo "مرحبًا، أنبوبي المُسمى!" > /tmp/my_named_pipe
ونتيجة لذلك سنرى المخرجات التالية من البرنامج:
$ ./mkfifo /tmp/my_named_pipe تم إنشاؤه /tmp/my_named_pipe مفتوح الرسالة الواردة (22): مرحبًا، أنبوبي المسمى! إقرأ : النجاح
يتم الحفاظ على سلامة كائن الذاكرة، بما في ذلك جميع البيانات المرتبطة به، حتى يتم فصل/حذف الكائن ( shm_unlink()). هذا يعني أن أي عملية يمكنها الوصول إلى كائن الذاكرة الخاص بنا (إذا كانت تعرف اسمه) حتى نستدعيه بشكل صريح shm_unlink().
عامل oflagهو bitwise OR من العلامات التالية:
بعد إنشاء كائن ذاكرة مشتركة، نقوم بتعيين حجم الذاكرة المشتركة عن طريق الاتصال فترونكات (). إدخال الوظيفة هو واصف الملف الخاص بالكائن والحجم الذي نحتاجه.
بعد إنشاء كائن الذاكرة، قمنا بتعيين حجم الذاكرة المشتركة التي نحتاجها عن طريق الاتصال فترونكات (). ثم وصلنا إلى الذاكرة المشتركة باستخدام مماب (). (بشكل عام، حتى مع المكالمة نفسها مماب ()يمكنك إنشاء ذاكرة مشتركة. ولكن الفرق هو الدعوة shm_open()هو أن الذاكرة ستبقى مخصصة حتى يتم حذفها أو إعادة تشغيل الكمبيوتر.)
هذه المرة تحتاج إلى تجميع التعليمات البرمجية مع الخيار -lrt:
$ دول مجلس التعاون الخليجي -o shm_open -lrt shm_open.c
دعونا نرى ما حدث:
$ ./shm_open قم بإنشاء "مرحبًا، ذاكرتي المشتركة!" الذاكرة المشتركة مملوءة. يمكنك تشغيل "./shm_open print" لمعرفة القيمة. $ ./shm_open print حصلت عليها من الذاكرة المشتركة: مرحبًا، ذاكرتي المشتركة! $ ./shm_open قم بإنشاء "مرحبًا!" الذاكرة المشتركة مملوءة. يمكنك تشغيل "./shm_open print" لمعرفة القيمة. $ ./shm_open print تم الحصول عليها من الذاكرة المشتركة: مرحبًا! $ ./shm_open أغلق $ ./shm_open طباعة shm_open: لا يوجد مثل هذا الملف أو الدليل
في برنامجنا، نستخدم الوسيطة "إنشاء" لإنشاء ذاكرة مشتركة ولتغيير محتوياتها.
بمعرفة اسم كائن الذاكرة، يمكننا تغيير محتويات الذاكرة المشتركة. ولكن يجب أن ندعو shm_unlink()كيف تتوقف الذاكرة عن الوصول إلينا و shm_open()بدون معلمة O_CREATEإرجاع الخطأ "لا يوجد مثل هذا الملف أو الدليل".
هناك نوعان من الإشارات:
لذلك، لتنفيذ الإشارات سوف نستخدم وظيفة POSIX sem_open():
#يشمل
إلى وظيفة إنشاء الإشارة، نقوم بتمرير اسم الإشارة، التي تم إنشاؤها وفقًا لقواعد معينة، وأعلام التحكم. بهذه الطريقة نحصل على إشارة مسماة.
يتم إنشاء اسم الإشارة على النحو التالي: في البداية يوجد حرف "/" (شرطة مائلة)، متبوعًا بأحرف لاتينية. لا ينبغي استخدام رمز الشرطة المائلة بعد الآن. يمكن أن يصل طول اسم الإشارة إلى 251 حرفًا.
إذا كنا بحاجة إلى إنشاء إشارة، فسيتم تمرير إشارة التحكم O_CREATE. للبدء في استخدام إشارة موجودة بالفعل، إذن oflagيساوي الصفر. إذا جنبا إلى جنب مع العلم O_CREATEتمرير العلم O_EXCL، ثم الدالة sem_open()سيُرجع خطأ إذا كانت الإشارة بالاسم المحدد موجودة بالفعل.
معامل وضعيضبط حقوق الوصول بنفس الطريقة الموضحة في الفصول السابقة. متغير قيمةتتم تهيئة القيمة الأولية للإشارة. كلا الخيارين وضعو قيمةيتم تجاهلها في حالة وجود إشارة بالاسم المحدد بالفعل، و sem_open()دعا جنبا إلى جنب مع العلم O_CREATE.
لفتح إشارة موجودة بسرعة، نستخدم البناء التالي:
#يشمل
في وحدة تحكم واحدة نقوم بتشغيل:
$ ./sem_open تم أخذ الإشارة. في انتظار أن يتم إسقاطها.<-- здесь процесс в ожидании другого процесса
sem_wait: Success
sem_close: Success
في وحدة التحكم التالية نقوم بتشغيل:
$ ./sem_open 1 إسقاط الإشارة... sem_post: إسقاط إشارة النجاح.
كائن المزامنة (mutex) هو في الأساس نفس الشيء مثل الإشارة الثنائية (أي إشارة ذات حالتين: "مشغول" و"غير مشغول"). لكن المصطلح "mutex" يستخدم غالبًا لوصف مخطط يمنع عمليتين من مشاركة البيانات/المتغيرات في نفس الوقت. بينما يُستخدم مصطلح "الإشارة الثنائية" في أغلب الأحيان لوصف البنية التي تقيد الوصول إلى مورد واحد. وهذا يعني أنه يتم استخدام إشارة ثنائية حيث "تحتل" عملية واحدة إشارة، ثم "تحررها" عملية أخرى. بينما يتم تحرير كائن المزامنة (mutex) بنفس العملية/الخيط الذي يشغله.
لا يمكنك الاستغناء عن كائن المزامنة (mutex) عند كتابة قاعدة بيانات، على سبيل المثال، يمكن للعديد من العملاء الوصول إليها.
لاستخدام كائن المزامنة (mutex)، تحتاج إلى استدعاء الدالة pthread_mutex_init():
#يشمل
تقوم الوظيفة بتهيئة كائن المزامنة (variable كائن المزامنة) يصف mutexattr. لو mutexattrيساوي باطل، ثم تتم تهيئة كائن المزامنة (mutex) إلى القيمة الافتراضية. إذا تم تنفيذ الوظيفة بنجاح (رمز الإرجاع 0)، فسيتم اعتبار كائن المزامنة (mutex) مهيأ و"مجانيًا".
الأخطاء النموذجية التي قد تحدث:
رموز العودة ل pthread_mutex_lock():
يوضح هذا المثال خيطين يتشاركان في متغير مشترك. مؤشر ترابط واحد (الخيط الأول) في الوضع التلقائي يزيد المتغير باستمرار عدادبمقدار واحد، بينما يشغل هذا المتغير لمدة ثانية كاملة. يمنح هذا الخيط الأول الخيط الثاني إمكانية الوصول إلى المتغير عددلمدة 10 ميلي ثانية فقط، ثم يتم رفعه مرة أخرى لمدة ثانية. يطالبك الخيط الثاني بإدخال قيمة جديدة للمتغير من المحطة.
إذا لم نستخدم تقنية كائن المزامنة (mutex)، فلن نعرف القيمة التي ستكون في المتغير العام مع الوصول المتزامن بواسطة خيطين. أيضا أثناء بدء التشغيل الفرق بين pthread_mutex_lock()و pthread_mutex_trylock().
تحتاج إلى ترجمة التعليمات البرمجية باستخدام معلمة إضافية -lpthread:
$ دول مجلس التعاون الخليجي -o mutex -lpthread mutex.c
نبدأ ونغير قيمة المتغير ببساطة عن طريق إدخال قيمة جديدة في النافذة الطرفية:
$ ./mutex أدخل الرقم واضغط على "Enter" لتهيئة العداد بقيمة جديدة في أي وقت. 1 2 3 30
محدث:تم تحديث الفصل الثالث حول الإشارات. تمت إضافة فصل فرعي حول كائن المزامنة (mutex).
العلامات: إضافة العلامات
يلعب نظام التشغيل، الذي يتمتع بإمكانية الوصول إلى جميع مناطق الذاكرة، دور الوسيط في تبادل المعلومات لخيوط التطبيق. إذا كانت هناك حاجة لتبادل البيانات، فسيقدم الخيط طلبًا إلى نظام التشغيل. يقوم نظام التشغيل، باستخدام امتيازاته، بإنشاء اتصالات نظام مختلفة. تؤدي العديد من أدوات تبادل البيانات بين العمليات أيضًا وظائف المزامنة: في حالة عدم وجود بيانات لعملية المستلم، يتم وضع الأخيرة في حالة انتظار بواسطة نظام التشغيل، وعندما تصل البيانات من عملية الإرسال، تكون عملية المستلم مفعل.
يمكن إجراء النقل بعدة طرق:
- ذكريات مشتركه؛
- القناة (الأنبوب، الناقل) - ملف زائف تكتب فيه عملية واحدة وتقرأه أخرى؛
- المقابس هي آلية تدعمها النواة وتخفي ميزات البيئة وتسمح للعمليات بالتفاعل بشكل موحد، سواء على نفس الكمبيوتر أو على الشبكة؛
— صناديق البريد (Windows فقط)، أحادية الاتجاه، وإمكانية البث؛
- استدعاء الإجراء عن بعد، عملية أيمكن استدعاء إجراء قيد التقدم في، واسترجاع البيانات.
الناقلون (القنوات)
توجد طريقتان لإنشاء اتصال ثنائي الاتجاه باستخدام توجيهات الإخراج: توجيهات الإخراج غير المسماة وتوجيهات الإخراج المسماة. القناة عبارة عن مخزن مؤقت في ذاكرة الوصول العشوائي (RAM) يدعم قائمة انتظار البايت باستخدام خوارزمية FIFO. بالنسبة للمبرمج، يبدو هذا المخزن المؤقت كملف غير مسمى يمكن الكتابة إليه وقراءته، وبالتالي تبادل البيانات. يمكن للعمليات ذات الصلة فقط تبادل البيانات، أو بشكل أكثر دقة، العمليات التي لها سلف مشترك أنشأ خط الأنابيب هذا. ويرجع ذلك إلى حقيقة أن خط الأنابيب ليس له اسم؛ ويتم الوصول إليه باستخدام واصف له قيمة محلية لكل عملية.
تسمح الأنابيب غير المسماة (أو المجهولة) للعمليات ذات الصلة بتمرير المعلومات إلى بعضها البعض. عادةً، يتم استخدام توجيهات الإخراج غير المسماة لإعادة توجيه الإدخال/الإخراج القياسي لعملية فرعية بحيث يمكنها الاتصال بالعملية الأصل. لتبادل البيانات في كلا الاتجاهين، تحتاج إلى إنشاء قناتين غير مسماتين. تكتب العملية الأصلية البيانات إلى القناة الأولى باستخدام مقبض الكتابة الخاص بها، بينما تقرأ العملية الفرعية البيانات من القناة باستخدام مقبض القراءة الخاص بها. وبالمثل، تكتب العملية الفرعية البيانات إلى القناة الثانية وتقرأ العملية الأصلية البيانات منها. لا يمكن استخدام توجيهات الإخراج غير المسماة لنقل البيانات عبر شبكة أو للاتصال بين العمليات غير ذات الصلة.
خطوط الأنابيب المسماة. تحتوي خطوط الأنابيب هذه على اسم، وهو إدخال في دليل نظام ملفات نظام التشغيل، وبالتالي فهي مناسبة لتبادل البيانات بين عمليتين عشوائيتين أو سلاسل عمليات من هذه العمليات. خط الأنابيب المسمى هو ملف FIFO خاص ولا يحتوي على منطقة بيانات على القرص. يتم إنشاء خط أنابيب مسمى باستخدام نفس استدعاء النظام المستخدم لإنشاء أي نوع ملف، ولكن فقط باستخدام معلمة FIFO المحددة كنوع الملف. يقوم استدعاء النظام بإنشاء إدخال دليل لملف FIFO بالاسم المحدد، وبعد ذلك يمكن لأي عملية فتح هذا الملف وتمرير البيانات إلى عملية أخرى لديها أيضًا الملف بهذا الاسم مفتوحًا.
يتم استخدام الأنابيب المسماة لنقل البيانات بين العمليات المستقلة أو بين العمليات التي يتم تشغيلها على أجهزة كمبيوتر مختلفة. عادةً ما تقوم عملية خادم توجيه الإخراج المسمى بإنشاء توجيه إخراج مسمى باسم معروف أو اسم سيتم تمريره إلى العملاء. تقوم عملية عميل الأنبوب المسمى، بمعرفة اسم الأنبوب الذي تم إنشاؤه، بفتحه من جانبها، مع مراعاة القيود التي تحددها عملية الخادم. بعد ذلك يتم إنشاء اتصال بين الخادم والعميل، ويمكن من خلاله تبادل البيانات في كلا الاتجاهين. في المستقبل، ستكون الأنابيب المسماة ذات أهمية أكبر بالنسبة لنا.
أرز. 2.22 مخطط تنفيذ القناة
قوائم انتظار الرسائل
يشبه وضع الرسائل في قائمة انتظار خطوط الأنابيب إلا أنه يسمح للعمليات وسلاسل الرسائل بتبادل الرسائل المنظمة. تعد قوائم انتظار الرسائل وسيلة اتصال عالمية لعمليات نظام التشغيل، نظرًا لأن كل قائمة انتظار داخل نظام التشغيل لها اسم فريد.
ذكريات مشتركه
الذاكرة المشتركة هي جزء من الذاكرة الفعلية المعينة في مساحة العنوان الافتراضية لعمليتين أو أكثر.
أرز. 2.23 أ) ملف معين للذاكرة؛ ب) الذاكرة المشتركة
إحدى مزايا الملفات المعينة للذاكرة هي سهولة مشاركتها. تتيح تسمية كائن تعيين الملف إمكانية مشاركة الملف بين عمليات متعددة. في هذه الحالة، يتم تعيين محتوياته إلى الذاكرة الفعلية المشتركة.
صناديق البريد
توفر صناديق البريد اتصالات أحادية الاتجاه فقط. كل عملية تقوم بإنشاء صندوق بريد هي "خادم صندوق البريد". عمليات أخرى، تسمى "عملاء صندوق البريد"، ترسل رسائل إلى الخادم، وتكتبها في صندوق البريد. تتم دائمًا إضافة الرسائل الواردة إلى صندوق البريد وتخزينها حتى يقرأها الخادم. يمكن أن تكون كل عملية خادم صندوق بريد وعميل صندوق بريد في نفس الوقت، وبالتالي إنشاء اتصال ثنائي الاتجاه بين العمليات.
كيفية استخدام قوائم انتظار الرسائل والإشارات والذاكرة المشتركة لتوصيل التطبيقات
تعمل العمليات التي تديرها نواة UNIX بشكل مستقل، مما يؤدي إلى نظام أكثر استقرارًا. ومع ذلك، يجد كل مطور نفسه في النهاية في موقف حيث يجب على مجموعة من العمليات التواصل مع مجموعة أخرى، على سبيل المثال، لتبادل البيانات أو تمرير الأوامر. تسمى طريقة المراسلة هذه اتصال interprocess (IPC). تحدد مواصفات نظام UNIX System V (SysV) ثلاث آليات لـ IPC، يشار إليها عادةً باسم SysV IPC:
بالإضافة إلى ذلك، يمكن للعمليات أن تتفاعل بطرق أخرى، مثل:
غالبًا ما يشار إلى المجموعة الأخيرة باسم IPC. تركز هذه المقالة على أساليب SysV IPC نظرًا لبساطتها وكفاءتها.
أساليب SysV IPC الثلاثة لها بناء جملة مماثل، على الرغم من اختلاف أغراضها. عادة يجب عليك القيام بما يلي:
يتم تمثيل كل مثيل IPC بواسطة معرف يساعد على تمييزه عن مثيلات IPC الأخرى الموجودة في نظام معين. على سبيل المثال، يجب أن يستخدم كل من التطبيقين المختلفين كتلًا من الذاكرة المشتركة لمعرف IPC على مستوى النظام للتمييز بين المثيلين. قد لا يكون الأمر واضحًا، لكن التحدي الرئيسي هو فهم كيفية توزيع المعلومات، مع الأخذ في الاعتبار كيفية ربط العملية بمثيل IPC عادي، وعدم وضع آلية IPC في المقام الأول.
يستخدم استدعاء مكتبة ftok معلومات inode من ملف معين ومعرف فريد لإنشاء مفتاح لا يتغير طالما أن الملف موجود ويظل المعرف ثابتًا. بهذه الطريقة، يمكن لعمليتين استخدام ملفات التكوين الخاصة بهما وثوابت وقت الترجمة لإنشاء نفس مفتاح IPC. يسمح وجود الثوابت لنفس التطبيق بإنشاء مثيلات متعددة لآلية IPC عن طريق تغييرها.
بمجرد أن تكون لدى العمليات المتعددة مفاتيح IPC جاهزة بشكل مستقل، يجب أن تحصل على معرف خاص مرتبط بمثيل IPC محدد عبر استدعاء النظام get. يتطلب الحصول على المكالمات مفتاح IPC ومجموعة من العلامات، بالإضافة إلى بعض معلومات الحجم للإشارات والذاكرة المشتركة. نظرًا لأن UNIX هو نظام متعدد المستخدمين، فإن مربعات الاختيار تتضمن إذنًا للوصول إلى الملف بالتنسيق الثماني العادي (على سبيل المثال، 666 يعني أنه يمكن لأي شخص قراءة الملف وكتابته). إذا تم أيضًا تعيين علامة IPC_CREAT، فسيتم إنشاء مثيل IPC في حالة عدم وجوده. إذا لم يتم تعيين علامة IPC_CREAT ولم يتم إنشاء مثيل IPC، فسيقوم استدعاء get بإنشاء رسالة خطأ.
هناك طريقة أسهل لإنشاء معرف مثيل IPC لتلك التطبيقات التي يمكنها القيام بذلك بنفسها. إذا كنت تستخدم مفتاح IPC_PRIVATE عند الاتصال بـ get لإنشاء IPC، فسيكون معرف المثيل فريدًا. لا تحتاج العمليات الأخرى التي تحتاج إلى الاتصال بـ IPC إلى الاتصال بها لأنها تحتوي بالفعل على معرف.
التطبيقات مجانية لاستخدام مثيلات IPC طالما أنها تحتوي على المعرف. تختلف كل طريقة IPC عن الأخرى ولا يمكن الوصول إليها إلا في القسم الخاص بها.
توفر قوائم انتظار الرسائل آلية تسمح لعملية واحدة بنشر رسالة يمكن لعملية أخرى استلامها. عند تلقي رسالة، تتم إزالتها من قائمة الانتظار. تعتبر قوائم انتظار الرسائل فريدة من نوعها حيث أنه ليس من الضروري تشغيل كلتا العمليتين في نفس الوقت - يمكن لعملية واحدة نشر رسالة والخروج منها، ويمكن استلام الرسالة بواسطة عملية أخرى بعد عدة أيام.
يجب أن تتكون الرسائل من عدد صحيح طويل متبوعًا بمحتوى الرسالة. يُظهر بنية مشابهة في لغة C باستخدام رسالة بحجم 100 بايت.
يستخدم مستلم الرسالة نوع الرسالة. عندما تتلقى رسالة من قائمة الانتظار، يمكنك تحديد أول رسالة متاحة أو يمكنك البحث عن نوع رسالة محدد. أنواع الرسائل التي سيتم استخدامها خاصة بالتطبيق لأنها تنشئ قوائم انتظار، والتي تختلف عن الأشكال الأخرى من IPC حيث تتعرف النواة على عمليات نقل بيانات التطبيق من خلال قراءة حقل النوع.
يعرض مثالاً لوضع رسالة في قائمة الانتظار.
يستورد الكود الموجود ملفات الرأس الضرورية ثم يحدد المتغيرات للاستخدام داخل الوظيفة الرئيسية. الخطوة الأولى هي تحديد مفتاح IPC باستخدام /tmp/foo كملف مشترك والرقم 42 كمعرف. لعرض هذا الرقم، يتم استخدام printf(3c) لعرض هذا الرقم. ثم يتم إنشاء قائمة انتظار الرسائل باستخدام msgget. المعلمة الأولى لـ msgget هي مفتاح IPC، والثانية هي مجموعة من العلامات. في المثال، تتمتع العلامات بأذونات ثمانية، مما يسمح لأي شخص لديه مفتاح IPC باستخدام IPC بالكامل، بالإضافة إلى علامة IPC_CREAT، التي تستدعي msgget لإنشاء قائمة الانتظار. مرة أخرى، يتم عرض النتيجة على الشاشة.
إرسال رسالة إلى قائمة الانتظار أمر بسيط. بعد إعادة تعيين مساحة الذاكرة في الرسالة، يتم نسخ السلسلة العادية إلى جزء النص من المخزن المؤقت. يتم تحديد نوع الرسالة على أنه 1، ثم يتم استدعاء msgsnd. يتوقع msgsnd أن يتم تمرير معرف قائمة الانتظار ومؤشر للبيانات وحجم البيانات وعلامة تشير إلى ما إذا كان يجب حظر المكالمة أم لا. إذا تم تعيين علامة IPC_NOWAIT، فسيتم إرجاع المكالمة حتى إذا كانت قائمة الانتظار ممتلئة. إذا تم تعيين العلامة على 0، فسيتم حظر المكالمة حتى تكون هناك مساحة خالية في قائمة الانتظار، أو يتم حذف قائمة الانتظار، أو يتلقى التطبيق إشارة.
يعمل جانب العميل من هذه المعادلة بطريقة مماثلة. يوجد أدناه مثال للتعليمات البرمجية التي تسترد رسالة مرسلة من الخادم.
يشبه إجراء الحصول على مفتاح IPC ومعرف قائمة انتظار الرسائل رمز الخادم. لا يحدد استدعاء msgget أي إشارات لأن الخادم قد قام بالفعل بإنشاء قائمة الانتظار. إذا تم تصميم التطبيقات بحيث يمكن بدء تشغيل العميل قبل الخادم، فسيتعين على كل من العميل والخادم تحديد أذونات الوصول وعلامة IPC_CREAT بحيث يقوم أي تطبيق يبدأ قبل الآخرين بإنشاء قائمة انتظار.
ثم يقوم mq_client.c باستدعاء msgrcv للحصول على الرسالة من قائمة الانتظار. تحدد المتغيرات الثلاثة الأولى معرف قائمة انتظار الرسائل ومؤشر إلى مقدار الذاكرة للرسالة وحجم المخزن المؤقت. المعلمة الرابعة هي معلمة النوع، والتي تسمح لك باختيار الرسائل التي تريد تلقيها:
المعلمة الخامسة لـ msgrcv هي مرة أخرى علامة حظر. يوضح كيفية عمل العميل والخادم.
تظهر النتائج من العميل والخادم أنهما حصلا على نفس مفتاح IPC لأنهما كانا يشيران إلى نفس الملف والمعرف. أنشأ الخادم مثيل IPC الذي حددت النواة له القيمة 2، وكان تطبيق العميل على علم بذلك. لذلك، ليس من المستغرب أن يتلقى العميل "مرحبا بالعالم!" من قائمة انتظار الرسائل.
هذا المثال يوضح أبسط المواقف. يمكن أن تكون قائمة انتظار الرسائل مفيدة للعمليات قصيرة العمر، مثل معاملة الويب التي تحدد العمل لتطبيق خلفي ثقيل، مثل تنفيذ مجموعة من المهام. يمكن أن يكون العميل أيضًا خادمًا، ويمكن للعديد من التطبيقات نشر رسائل في قائمة الانتظار. يسمح حقل نوع الرسالة للتطبيقات بإرسال رسائل إلى مستلمين محددين.
لا يجب أن يتضمن الاتصال بين العمليات إرسال الكثير من البيانات. في الواقع، قد يكون بت واحد كافيًا للإشارة إلى أن العملية تستخدم موردًا معينًا. خذ بعين الاعتبار عمليتين تحتاجان إلى الوصول إلى بعض الأجهزة، ولكن يمكن لواحدة منهما فقط استخدامها في وقت معين. يمكنهم التناوب باستخدام العداد. إذا قرأت إحدى العمليات العداد ورأيت الرقم 1، فمن الواضح أن عملية أخرى تستخدم الأجهزة. إذا كانت قيمة العداد هي 0، فيمكن للعملية استخدام الجهاز طالما تم تعيين قيمة العداد على 1 أثناء العملية ويتم إعادة تعيين القيمة إلى 0 عند اكتمال العملية.
هناك مشكلتان في هذه الحالة. الأول هو تركيب العداد العام وتحديد مكانه وهو أكبر الإزعاج. المشكلة الثانية هي أن عمليات الاستعلام والتخصيص المطلوبة لقفل موارد الأجهزة ليست ذرية. إذا كانت إحدى العمليات تقرأ قيمة العداد على أنها 0، ولكن تم تجاوزها بواسطة عملية أخرى قبل أن تتمكن العملية الأولى من تعيين قيمة العداد على أنها 1، فيمكن للعملية الثانية قراءة قيمة العداد وتعيينها. ستعتبر كلتا العمليتين أنه بإمكانهما استخدام الأجهزة. لا توجد طريقة لمعرفة ما إذا كانت عملية (عمليات) أخرى قد قامت بتعيين قيمة للعداد. تسمى سباق الحظ. تعمل الإشارات على حل هاتين المشكلتين من خلال توفير واجهة مشتركة للتطبيقات واستخدام الاختبار الذري أو عملية التعيين.
يعد تطبيق SysV للإشارات أكثر عمومية من تلك الموضحة أعلاه. أولاً، ليس من الضروري أن تكون قيمة الإشارة 0 أو 1؛ يمكن أن يكون 0 أو أي رقم موجب. ثانيًا، من الممكن إجراء عدد من العمليات باستخدام الإشارات بطريقة مشابهة لمعلمة النوع المستخدمة مع msgrcv . يتم تقديم هذه العمليات كمجموعة من الأدوات للنواة، إما أن يتم تشغيلها معًا أو لا يتم تشغيلها على الإطلاق. تتطلب النواة أن يتم إصدار هذه الأوامر في بنية تسمى sembuf، والتي تتضمن المكونات (بالترتيب):
يحتوي sem_op على عدد كبير من التكوينات:
يشرح المثال ج استخدام الإشارات عن طريق اختبار برنامج يمكن تشغيله عدة مرات في وقت واحد، ولكنه يضمن وجود عملية واحدة فقط في القسم الحرج في كل مرة. يتم استخدام نسخة بسيطة من الإشارات. يكون المورد مجانيًا إذا كانت قيمة الإشارة 0.
يُظهر الطريقة بنفس طريقة مثال قائمة انتظار الرسائل. مثلما يحدد msgget حجم قائمة انتظار الرسائل في المعلمة الثانية، يحدد semget حجم مجموعة الإشارة. مجموعة الإشارة هي مجموعة من الإشارات التي تشترك في مثيل IPC شائع. لا يمكن تغيير عدد الإشارات في المجموعة. إذا تم إنشاء مجموعة إشارة، فسيتم تجاهل المعلمة الثانية لـ semget. إذا قام semget بإرجاع عدد صحيح سالب يشير إلى الفشل، فسيتم طباعة السبب ويخرج البرنامج.
مباشرة قبل حلقة while الرئيسية، تتم تهيئة sem_num وsem_flg، حيث أنهما يظلان ثابتين خلال هذا المثال. يحدد SEM_UNDO أنه إذا خرج مالك الإشارة قبل تحرير الإشارة، فلن يتم حظر كافة التطبيقات الأخرى.
في هذه الحلقة، تتم طباعة رسالة حالة للإشارة إلى أن التطبيق قد بدأ في انتظار الإشارة. يتم تزويد هذه الرسالة بوسيطة سطر الأوامر الأولى لتمييزها عن الحالات الأخرى. قبل الدخول إلى القسم الحرج، يقوم التطبيق بتأمين الإشارة. تتم كتابة تعليماتين للإشارة. الأول هو 0، مما يعني أن التطبيق ينتظر حتى يتم إعادة تعيين قيمة الإشارة إلى 0. والثاني هو 1، مما يعني أنه بعد إعادة تعيين قيمة الإشارة، سيتم إضافة 1 إليها، ويستدعي التطبيق semop لتشغيل الأوامر، وتمريرها معرف الإشارة، بالإضافة إلى عنوان بنية البيانات وعدد أوامر sembuf التي سيتم استخدامها.
عندما يعود semop، يعرف التطبيق أنه قام بحظر الإشارة ويطبع رسالة لإظهار ذلك. ثم يبدأ القسم الحرج، والذي يتوقف في هذه الحالة مؤقتًا لعدد عشوائي من الثواني. يتم تحرير الإشارة أخيرًا عن طريق تشغيل أمر sembuf واحد بقيمة semop تبلغ -1، والذي له تأثير طرح 1 من قيمة الإشارة وإعادة تعيينها إلى الصفر. تتم طباعة المزيد من مخرجات التصحيح، ويتوقف التطبيق لفترة معينة من الوقت، ويستمر التنفيذ. يظهر إخراج حالتين من هذا التطبيق.
يظهر حالتين قيد التشغيل، بالاسم a وb على التوالي. أولاً يحصل "أ" على الإشارة وفي نفس الوقت يحاول "ب" الحصول على القفل. بمجرد أن يقوم a بتحرير الإشارة، يتم منح b القفل. وينقلب الوضع عندما ينتظر (أ) أن يكمل (ب) عمله. ينتهي A بإعادة تعيين الإشارة بعد قطع الاتصال بها، نظرًا لأن b ليس في حالة الانتظار.
آخر شيء جدير بالذكر حول الإشارات هو أنها معروفة باسم الأقفال الموصى بها. وهذا يعني أن الإشارات في حد ذاتها لا تمنع عمليتين من استخدام نفس المورد في نفس الوقت؛ بدلاً من ذلك، فهي مطلوبة للإشارة إلى أن الموارد قيد الاستخدام بالفعل.
ربما تكون الذاكرة المشتركة هي أقوى طريقة IPC في SysV وأسهلها أداءً. كما يوحي الاسم، تتم مشاركة كتلة من الذاكرة بين عمليات متعددة. يعرض برنامجًا يستدعي fork(2) للتقسيم إلى عمليات رئيسية وأخرى فرعية تتواصل باستخدام قطعة ذاكرة مشتركة.
يجب أن تكون معلمات shmget معروفة الآن: المفتاح والحجم والأعلام. حجم منطقة الذاكرة المشتركة في هذا المثال هو عدد صحيح واحد. يختلف عن الأمثلة السابقة في استخدام IPC_PRIVATE لمفتاح IPC. عند استخدام IPC_PRIVATE، يتم ضمان معرف IPC فريد ومن المتوقع أن يوفر التطبيق المعرف نفسه. في المثال، shmid هي العملية الأم والطفلة لأنهما نسختان من بعضهما البعض. ينتج عن استدعاء نظام الشوكة نسخة ثانية من العملية الحالية، تسمى العملية الفرعية، والتي تكون مطابقة فعليًا للعملية الأصلية. يتم استئناف كلتا العمليتين بعد الشوكة. يتم استخدام نتيجة الشوكة لتحديد ما إذا كانت العملية الحالية هي أحد الوالدين أم الطفل.
تبدو كل من العملية الأصلية والعملية الفرعية متماثلة. أولاً، يتم استخدام استدعاء نظام shmat للتأكد من وجود المؤشر في مقطع الذاكرة المشتركة. يتطلب shmat معرف ذاكرة مشترك ومؤشر وبعض العلامات. يتم استخدام المؤشر لطلب عنوان ذاكرة محدد. بتمرير 0، يمكن للنواة اختيار أي شيء تريده. تكون العلامات في الغالب خاصة بالموردين، إلا أن SHM_RDONLY هي علامة شائعة تشير إلى أن المقطع غير قابل للكتابة. كما هو موضح في، غالبًا ما يتم استخدام shmat للسماح للنواة بمعالجة كل شيء.
يقوم shmat بإرجاع مؤشر إلى المقطع المشترك، والذي تتم طباعته على الشاشة لتصحيح الأخطاء. تقوم كل عملية بعد ذلك بتعديل مقطع الذاكرة المشتركة وطباعة القيمة. أخيرًا، تقوم العملية الأصلية بحذف مقطع الذاكرة المشتركة باستخدام shmctl(2) . يظهر نتيجة تنفيذ هذا البرنامج.
بناءً على المخرجات، يمكنك رؤية كيفية مشاركة نفس الجزء من الذاكرة المشتركة. أولاً، القيمة التي قامت العملية الفرعية بتعيينها وقراءتها بواسطة العملية الأصلية في الذاكرة المشتركة هي 1. ثم قامت العملية الأصلية بتعيين القيمة على 42، والتي تقرأها العملية الفرعية. لاحظ أن العمليات الأصلية والتابعة لها مؤشرات مختلفة لعناوين مقطع الذاكرة المشتركة، على الرغم من وصولها إلى نفس الذاكرة الفعلية. يؤدي هذا إلى إنشاء مشكلة لبعض بنيات البيانات مثل القوائم المرتبطة عند استخدام عنوان فعلي، لذلك قد ترغب في استخدام العنوان المقابل إذا كنت تقوم بإنشاء بنيات معقدة في الذاكرة المشتركة.
يعتمد هذا المثال على عملية واحدة متوقفة مؤقتًا بينما تقوم عملية أخرى بالكتابة إلى مقطع ذاكرة مشترك. في تطبيق حقيقي، قد يكون هذا غير عملي، لذلك إذا كان التطبيق الخاص بك يحتوي على عمليات متعددة للكتابة على نفس منطقة الذاكرة، ففكر في قفل تلك المنطقة باستخدام إشارة.
يوفر UNIX بعض الأساليب لـ IPC. أساليب IPC الخاصة بـ SysV هي قوائم انتظار الرسائل والإشارات والذاكرة المشتركة. تسمح قوائم انتظار الرسائل لأحد التطبيقات بإرسال رسالة يمكن للتطبيقات الأخرى استلامها لاحقًا، حتى بعد إنهاء التطبيق. تضمن الإشارات أن التطبيقات المختلفة يمكنها قفل الموارد وتجنب سباقات الحالة. تتيح الذاكرة المشتركة للتطبيقات المختلفة مشاركة مقطع ذاكرة مشترك، مما يوفر طريقة سريعة للتواصل فيما بينها ونقل كميات كبيرة من البيانات. يمكن استخدام هذه الطرق معًا. على سبيل المثال، يمكنك استخدام الإشارة للتحكم في الوصول إلى مقطع الذاكرة المشتركة.
تعتبر أساليب IPC مفيدة لمطوري التطبيقات لأنها توفر طريقة قياسية للتواصل بين التطبيقات وتتوفر عبر إصدارات مختلفة من UNIX. في المرة القادمة التي تحتاج فيها إلى تأمين الموارد أو نقل البيانات بين العمليات، جرب آليات IPC الخاصة بـ SysV.
اتصال interprocess ( الاتصال بين العمليات (IPC)) عبارة عن مجموعة من الطرق لتبادل البيانات بين سلاسل العمليات. يمكن إطلاق العمليات على نفس الكمبيوتر وعلى أجهزة مختلفة متصلة عبر الشبكة. تأتي IPCs في عدة أنواع: "الإشارة"، "المقبس"، "الإشارة"، "الملف"، "الرسالة" ...
في هذه المقالة أريد أن ألقي نظرة على 3 أنواع فقط من IPC:
دعونا نلقي نظرة على تمرير الرسائل عبر الأنابيب المسماة. يبدو النقل بشكل تخطيطي كما يلي:
لإنشاء أنابيب مسماة سنستخدم الدالة، مكفيفو ():
#يشمل
تقوم الوظيفة بإنشاء ملف FIFO خاص باسم اسم المسار، والمعلمة وضعيحدد أذونات الملف.
إذا تم إنشاء ملف FIFO بنجاح، مكفيفو ()ترجع 0 (صفر). في حالة وجود أي أخطاء، تقوم الدالة بإرجاع -1 وتعيين رمز الخطأ إلى متغير خطأ.
الأخطاء النموذجية التي قد تحدث أثناء إنشاء القناة:
نفتح الملف للقراءة فقط ( O_RDONLY). ويمكن استخدامها O_NONBLOCKمعدّل مصمم خصيصًا لملفات FIFO، حتى لا تنتظر فتح الملف للكتابة على الجانب الآخر. ولكن في الكود المعطى هذه الطريقة غير ملائمة.
نقوم بتجميع البرنامج ثم تشغيله:
$ gcc -o mkfifo mkfifo.c $ ./mkfifo
في النافذة الطرفية التالية نقوم بتنفيذ:
$ echo "مرحبًا، أنبوبي المُسمى!" > /tmp/my_named_pipe
ونتيجة لذلك سنرى المخرجات التالية من البرنامج:
$ ./mkfifo /tmp/my_named_pipe تم إنشاؤه /tmp/my_named_pipe مفتوح الرسالة الواردة (22): مرحبًا، أنبوبي المسمى! إقرأ : النجاح
يتم الحفاظ على سلامة كائن الذاكرة، بما في ذلك جميع البيانات المرتبطة به، حتى يتم فصل/حذف الكائن ( shm_unlink()). هذا يعني أن أي عملية يمكنها الوصول إلى كائن الذاكرة الخاص بنا (إذا كانت تعرف اسمه) حتى نستدعيه بشكل صريح shm_unlink().
عامل oflagهو bitwise OR من العلامات التالية:
بعد إنشاء كائن ذاكرة مشتركة، نقوم بتعيين حجم الذاكرة المشتركة عن طريق الاتصال فترونكات (). إدخال الوظيفة هو واصف الملف الخاص بالكائن والحجم الذي نحتاجه.
بعد إنشاء كائن الذاكرة، قمنا بتعيين حجم الذاكرة المشتركة التي نحتاجها عن طريق الاتصال فترونكات (). ثم وصلنا إلى الذاكرة المشتركة باستخدام مماب (). (بشكل عام، حتى مع المكالمة نفسها مماب ()يمكنك إنشاء ذاكرة مشتركة. ولكن الفرق هو الدعوة shm_open()هو أن الذاكرة ستبقى مخصصة حتى يتم حذفها أو إعادة تشغيل الكمبيوتر.)
هذه المرة تحتاج إلى تجميع التعليمات البرمجية مع الخيار -lrt:
$ دول مجلس التعاون الخليجي -o shm_open -lrt shm_open.c
دعونا نرى ما حدث:
$ ./shm_open قم بإنشاء "مرحبًا، ذاكرتي المشتركة!" الذاكرة المشتركة مملوءة. يمكنك تشغيل "./shm_open print" لمعرفة القيمة. $ ./shm_open print حصلت عليها من الذاكرة المشتركة: مرحبًا، ذاكرتي المشتركة! $ ./shm_open قم بإنشاء "مرحبًا!" الذاكرة المشتركة مملوءة. يمكنك تشغيل "./shm_open print" لمعرفة القيمة. $ ./shm_open print تم الحصول عليها من الذاكرة المشتركة: مرحبًا! $ ./shm_open أغلق $ ./shm_open طباعة shm_open: لا يوجد مثل هذا الملف أو الدليل
في برنامجنا، نستخدم الوسيطة "إنشاء" لإنشاء ذاكرة مشتركة ولتغيير محتوياتها.
بمعرفة اسم كائن الذاكرة، يمكننا تغيير محتويات الذاكرة المشتركة. ولكن يجب أن ندعو shm_unlink()كيف تتوقف الذاكرة عن الوصول إلينا و shm_open()بدون معلمة O_CREATEإرجاع الخطأ "لا يوجد مثل هذا الملف أو الدليل".
هناك نوعان من الإشارات:
لذلك، لتنفيذ الإشارات سوف نستخدم وظيفة POSIX sem_open():
#يشمل
إلى وظيفة إنشاء الإشارة، نقوم بتمرير اسم الإشارة، التي تم إنشاؤها وفقًا لقواعد معينة، وأعلام التحكم. بهذه الطريقة نحصل على إشارة مسماة.
يتم إنشاء اسم الإشارة على النحو التالي: في البداية يوجد حرف "/" (شرطة مائلة)، متبوعًا بأحرف لاتينية. لا ينبغي استخدام رمز الشرطة المائلة بعد الآن. يمكن أن يصل طول اسم الإشارة إلى 251 حرفًا.
إذا كنا بحاجة إلى إنشاء إشارة، فسيتم تمرير إشارة التحكم O_CREATE. للبدء في استخدام إشارة موجودة بالفعل، إذن oflagيساوي الصفر. إذا جنبا إلى جنب مع العلم O_CREATEتمرير العلم O_EXCL، ثم الدالة sem_open()سيُرجع خطأ إذا كانت الإشارة بالاسم المحدد موجودة بالفعل.
معامل وضعيضبط حقوق الوصول بنفس الطريقة الموضحة في الفصول السابقة. متغير قيمةتتم تهيئة القيمة الأولية للإشارة. كلا الخيارين وضعو قيمةيتم تجاهلها في حالة وجود إشارة بالاسم المحدد بالفعل، و sem_open()دعا جنبا إلى جنب مع العلم O_CREATE.
لفتح إشارة موجودة بسرعة، نستخدم البناء التالي:
#يشمل
في وحدة تحكم واحدة نقوم بتشغيل:
$ ./sem_open تم أخذ الإشارة. في انتظار أن يتم إسقاطها.<-- здесь процесс в ожидании другого процесса
sem_wait: Success
sem_close: Success
في وحدة التحكم التالية نقوم بتشغيل:
$ ./sem_open 1 إسقاط الإشارة... sem_post: إسقاط إشارة النجاح.
كائن المزامنة (mutex) هو في الأساس نفس الشيء مثل الإشارة الثنائية (أي إشارة ذات حالتين: "مشغول" و"غير مشغول"). لكن المصطلح "mutex" يستخدم غالبًا لوصف مخطط يمنع عمليتين من مشاركة البيانات/المتغيرات في نفس الوقت. بينما يُستخدم مصطلح "الإشارة الثنائية" في أغلب الأحيان لوصف البنية التي تقيد الوصول إلى مورد واحد. وهذا يعني أنه يتم استخدام إشارة ثنائية حيث "تحتل" عملية واحدة إشارة، ثم "تحررها" عملية أخرى. بينما يتم تحرير كائن المزامنة (mutex) بنفس العملية/الخيط الذي يشغله.
لا يمكنك الاستغناء عن كائن المزامنة (mutex) عند كتابة قاعدة بيانات، على سبيل المثال، يمكن للعديد من العملاء الوصول إليها.
لاستخدام كائن المزامنة (mutex)، تحتاج إلى استدعاء الدالة pthread_mutex_init():
#يشمل
تقوم الوظيفة بتهيئة كائن المزامنة (variable كائن المزامنة) يصف mutexattr. لو mutexattrيساوي باطل، ثم تتم تهيئة كائن المزامنة (mutex) إلى القيمة الافتراضية. إذا تم تنفيذ الوظيفة بنجاح (رمز الإرجاع 0)، فسيتم اعتبار كائن المزامنة (mutex) مهيأ و"مجانيًا".
الأخطاء النموذجية التي قد تحدث:
رموز العودة ل pthread_mutex_lock():
يوضح هذا المثال خيطين يتشاركان في متغير مشترك. مؤشر ترابط واحد (الخيط الأول) في الوضع التلقائي يزيد المتغير باستمرار عدادبمقدار واحد، بينما يشغل هذا المتغير لمدة ثانية كاملة. يمنح هذا الخيط الأول الخيط الثاني إمكانية الوصول إلى المتغير عددلمدة 10 ميلي ثانية فقط، ثم يتم رفعه مرة أخرى لمدة ثانية. يطالبك الخيط الثاني بإدخال قيمة جديدة للمتغير من المحطة.
إذا لم نستخدم تقنية كائن المزامنة (mutex)، فلن نعرف القيمة التي ستكون في المتغير العام مع الوصول المتزامن بواسطة خيطين. أيضا أثناء بدء التشغيل الفرق بين pthread_mutex_lock()و pthread_mutex_trylock().
تحتاج إلى ترجمة التعليمات البرمجية باستخدام معلمة إضافية -lpthread:
$ دول مجلس التعاون الخليجي -o mutex -lpthread mutex.c
نبدأ ونغير قيمة المتغير ببساطة عن طريق إدخال قيمة جديدة في النافذة الطرفية:
$ ./mutex أدخل الرقم واضغط على "Enter" لتهيئة العداد بقيمة جديدة في أي وقت. 1 2 3 30
محدث:تم تحديث الفصل الثالث حول الإشارات. تمت إضافة فصل فرعي حول كائن المزامنة (mutex).
العلامات: