كيفية استخدام معالجات الإشارة في لغة سي؟

How Use Signal Handlers C Language



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

الإشارة

الإشارة هي حدث يتم إنشاؤه لإخطار عملية أو سلسلة محادثات بوصول بعض المواقف المهمة. عندما تتلقى عملية أو مؤشر ترابط إشارة ، ستتوقف العملية أو الخيط عن ما تفعله وتتخذ بعض الإجراءات. قد تكون الإشارة مفيدة للتواصل بين العمليات.







الإشارات القياسية

يتم تعريف الإشارات في ملف الرأس إشارة كثابت ماكرو. بدأ اسم الإشارة بـ SIG متبوعًا بوصف قصير للإشارة. لذلك ، كل إشارة لها قيمة رقمية فريدة. يجب أن يستخدم برنامجك دائمًا اسم الإشارات وليس رقم الإشارات. السبب هو أن رقم الإشارة يمكن أن يختلف وفقًا للنظام ولكن معنى الأسماء سيكون قياسيًا.



الماكرو NSIG هو العدد الإجمالي للإشارة المحددة. قيمة ال NSIG أكبر بواحد من العدد الإجمالي للإشارة المحددة (يتم توزيع جميع أرقام الإشارات على التوالي).



فيما يلي الإشارات القياسية:





اسم الإشارة وصف
تنفس الصعداء حتى قم بإنهاء العملية. تُستخدم إشارة SIGHUP للإبلاغ عن انقطاع اتصال الجهاز الطرفي للمستخدم ، ربما بسبب فقد الاتصال عن بُعد أو توقفه.
توقع اقطع العملية. عندما يكتب المستخدم حرف INTR (عادةً Ctrl + C) يتم إرسال إشارة SIGINT.
سيجكويت قم بإنهاء العملية. عندما يكتب المستخدم حرف QUIT (عادةً Ctrl + ) يتم إرسال إشارة SIGQUIT.
عجل البحر تعليمات غير قانونية. عند إجراء محاولة لتنفيذ تعليمات غير صحيحة أو تعليمات مميزة ، يتم إنشاء إشارة SIGILL. أيضًا ، يمكن إنشاء SIGILL عندما يفيض المكدس ، أو عندما يواجه النظام مشكلة في تشغيل معالج إشارة.
سيغراب تتبع فخ. ستؤدي تعليمات نقطة التوقف وتعليمات المصيدة الأخرى إلى إنشاء إشارة SIGTRAP. يستخدم المصحح هذه الإشارة.
SIGABRT إحباط. يتم إنشاء إشارة SIGABRT عندما يتم استدعاء وظيفة abort (). تشير هذه الإشارة إلى خطأ تم اكتشافه بواسطة البرنامج نفسه وتم الإبلاغ عنه بواسطة استدعاء الوظيفة abort ().
سيجفي استثناء النقطة العائمة. عند حدوث خطأ حسابي فادح ، يتم إنشاء إشارة SIGFPE.
SIGUSR1 و SIGUSR2 يمكن استخدام الإشارات SIGUSR1 و SIGUSR2 كما يحلو لك. من المفيد كتابة معالج إشارة لهم في البرنامج الذي يستقبل الإشارة للاتصال البسيط بين العمليات.

الإجراء الافتراضي للإشارات

لكل إشارة إجراء افتراضي ، أحد الإجراءات التالية:

شرط: ستنتهي العملية.
جوهر: ستنتهي العملية وستنتج ملف تفريغ أساسي.
إشعال: ستتجاهل العملية الإشارة.
قف: ستتوقف العملية.
حساب: ستستمر العملية من التوقف.



يمكن تغيير الإجراء الافتراضي باستخدام وظيفة المعالج. لا يمكن تغيير بعض الإجراءات الافتراضية للإشارة. سيكيل و SIGABRT لا يمكن تغيير الإجراء الافتراضي للإشارة أو تجاهله.

معالجة الإشارة

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

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

يمكننا التعامل مع الإشارة باستخدام الإشارة أو التحريف وظيفة. هنا نرى كيف أبسط الإشارة() تستخدم الوظيفة للتعامل مع الإشارات.

intالإشارة() (intلافتةو فارغ (*وظيفة)(int))

ال الإشارة() سوف يستدعي وظيفة تعمل إذا كانت العملية تتلقى إشارة لافتة . ال الإشارة() إرجاع مؤشر للعمل وظيفة إذا نجحت أو تقوم بإرجاع خطأ إلى errno و -1 بخلاف ذلك.

ال وظيفة يمكن أن يحتوي المؤشر على ثلاث قيم:

  1. SIG_DFL : هو مؤشر لوظيفة النظام الافتراضية SIG_DFL () ، أعلن في ح الملف الاساسي. يتم استخدامه لاتخاذ الإجراء الافتراضي للإشارة.
  2. SIG_IGN : هو مؤشر لوظيفة تجاهل النظام SIG_IGN () ، أعلن في ح الملف الاساسي.
  3. مؤشر وظيفة معالج المعرفة من قبل المستخدم : نوع وظيفة معالج المعرفة من قبل المستخدم هو باطل (*) (كثافة العمليات) ، يعني أن نوع الإرجاع باطل ووسيطة واحدة من النوع int.

مثال معالج الإشارة الأساسي

#يشمل
#يشمل
#يشمل
فارغمعالج sig(intلافتة){

// يجب أن يكون نوع الإرجاع لوظيفة المعالج باطلاً
printf ('نوظيفة المعالج الداخلين')؛
}

intالأساسية(){
الإشارة(توقعومعالج sig)؛ // تسجيل معالج الإشارة
ل(intأنا=1؛؛أنا++){ //حلقة لا نهائية
printf ('٪ d: الوظيفة الرئيسية الداخليةن'وأنا)؛
نايم(1)؛ // تأخير لمدة 1 ثانية
}
إرجاع 0؛
}

في لقطة شاشة إخراج Example1.c ، يمكننا أن نرى أنه في الوظيفة الرئيسية يتم تنفيذ حلقة لانهائية. عندما يكتب المستخدم Ctrl + C ، يتوقف تنفيذ الوظيفة الرئيسية ويتم استدعاء وظيفة معالج الإشارة. بعد الانتهاء من وظيفة المعالج ، تم استئناف تنفيذ الوظيفة الرئيسية. عندما يكتب المستخدم Ctrl + ، يتم إنهاء العملية.

تجاهل الإشارات مثال

#يشمل
#يشمل
#يشمل
intالأساسية(){
الإشارة(توقعوSIG_IGN)؛ // سجل معالج الإشارة لتجاهل الإشارة

ل(intأنا=1؛؛أنا++){ //حلقة لا نهائية
printf ('٪ d: الوظيفة الرئيسية الداخليةن'وأنا)؛
نايم(1)؛ // تأخير لمدة 1 ثانية
}
إرجاع 0؛
}

هنا يتم تسجيل وظيفة المعالج SIG_IGN () وظيفة لتجاهل عمل الإشارة. لذلك ، عندما كتب المستخدم Ctrl + C ، توقع يتم إنشاء إشارة ولكن تم تجاهل الإجراء.

إعادة تسجيل مثال معالج الإشارة

#يشمل
#يشمل
#يشمل

فارغمعالج sig(intلافتة){
printf ('نوظيفة المعالج الداخلين')؛
الإشارة(توقعوSIG_DFL)؛ // إعادة تسجيل معالج الإشارة للإجراء الافتراضي
}

intالأساسية(){
الإشارة(توقعومعالج sig)؛ // تسجيل معالج الإشارة
ل(intأنا=1؛؛أنا++){ //حلقة لا نهائية
printf ('٪ d: الوظيفة الرئيسية الداخليةن'وأنا)؛
نايم(1)؛ // تأخير لمدة 1 ثانية
}
إرجاع 0؛
}

في لقطة شاشة إخراج Example3.c ، يمكننا أن نرى أنه عندما كتب المستخدم Ctrl + C لأول مرة ، تم استدعاء وظيفة المعالج. في وظيفة المعالج ، يقوم معالج الإشارة بإعادة التسجيل SIG_DFL للعمل الافتراضي للإشارة. عندما يكتب المستخدم Ctrl + C للمرة الثانية ، يتم إنهاء العملية وهو الإجراء الافتراضي لـ توقع الإشارة.

إرسال الإشارات:

يمكن للعملية أيضًا أن ترسل إشارات صريحة إلى نفسها أو إلى عملية أخرى. يمكن استخدام وظيفة رفع () و kill () لإرسال الإشارات. يتم التصريح عن كلتا الوظيفتين في ملف رأس signal.h.

int رفع (intلافتة)

وظيفة رفع () المستخدمة لإرسال إشارة لافتة لعملية الاستدعاء (نفسها). تقوم بإرجاع صفر إذا نجحت وقيمة غير صفرية إذا فشلت.

intقتل(pid_t pidو intلافتة)

وظيفة القتل المستخدمة لإرسال إشارة لافتة إلى عملية أو مجموعة عملية محددة بواسطة pid .

مثال على معالج إشارة SIGUSR1

#يشمل
#يشمل

فارغمعالج sig(intلافتة){
printf ('وظيفة المعالج الداخلين')؛
}

intالأساسية(){
الإشارة(سيجسر 1ومعالج sig)؛ // تسجيل معالج الإشارة
printf ('داخل الوظيفة الرئيسيةن')؛
رفع (سيجسر 1)؛
printf ('داخل الوظيفة الرئيسيةن')؛
إرجاع 0؛
}

هنا ، ترسل العملية إشارة SIGUSR1 إلى نفسها باستخدام وظيفة () (lift).

برنامج رفع مع اقتل المثال

#يشمل
#يشمل
#يشمل
فارغمعالج sig(intلافتة){
printf ('وظيفة المعالج الداخلين')؛
}

intالأساسية(){
pid_t pid؛
الإشارة(سيجسر 1ومعالج sig)؛ // تسجيل معالج الإشارة
printf ('داخل الوظيفة الرئيسيةن')؛
pid=getpid()؛ // معرف العملية في حد ذاته
قتل(pidوسيجسر 1)؛ // أرسل SIGUSR1 إلى نفسه
printf ('داخل الوظيفة الرئيسيةن')؛
إرجاع 0؛
}

هنا ، يتم إرسال العملية سيجسر 1 إشارة إلى نفسها باستخدام قتل() وظيفة. getpid () يستخدم للحصول على معرف العملية نفسه.

في المثال التالي سنرى كيف تتواصل عمليات الوالدين والطفل (Inter Process Communication) باستخدام قتل() ووظيفة الإشارة.

التواصل بين الوالدين والطفل مع الإشارات

#يشمل
#يشمل
#يشمل
#يشمل
فارغsig_handler_parent(intلافتة){
printf (الوالد: تلقى إشارة استجابة من الطفلن')؛
}

فارغsig_handler_child(intلافتة){
printf (الطفل: تلقى إشارة من أحد الوالدينن')؛
نايم(1)؛
قتل(هراء()وسيجسر 1)؛
}

intالأساسية(){
pid_t pid؛
لو((pid=شوكة())<0){
printf (فشلت الشوكةن')؛
خروج (1)؛
}
/ * عملية الطفل * /
آخر لو(pid==0){
الإشارة(سيجسر 1وsig_handler_child)؛ // تسجيل معالج الإشارة
printf (الطفل: انتظار الإشارةن')؛
وقفة()؛
}
/ * إجراءات الوالدين * /
آخر{
الإشارة(سيجسر 1وsig_handler_parent)؛ // تسجيل معالج الإشارة
نايم(1)؛
printf (الوالد: إرسال إشارة إلى الطفلن')؛
قتل(pidوسيجسر 1)؛
printf (الوالد: في انتظار الردن')؛
وقفة()؛
}
إرجاع 0؛
}

هنا، شوكة() تقوم الوظيفة بإنشاء عملية فرعية وإرجاع صفر إلى العملية الفرعية ومعرف العملية الفرعية إلى العملية الرئيسية. لذلك ، تم فحص pid لتحديد عملية الوالدين والطفل. في عملية الوالدين ، يتم النوم لمدة ثانية واحدة بحيث يمكن لعملية الطفل تسجيل وظيفة معالج الإشارة وانتظار الإشارة من الوالدين. بعد ثانية واحدة من عملية الوالد ترسل سيجسر 1 إشارة لعملية الطفل وانتظر إشارة الاستجابة من الطفل. في العملية الفرعية ، ينتظر أولاً إشارة من الوالدين وعندما يتم استقبال الإشارة ، يتم استدعاء وظيفة المعالج. من وظيفة المعالج ، ترسل العملية الفرعية أخرى سيجسر 1 إشارة إلى الوالدين. هنا getppid () يتم استخدام الوظيفة للحصول على معرف العملية الأصل.

استنتاج

Signal في Linux موضوع كبير. لقد رأينا في هذه المقالة كيفية التعامل مع الإشارة من الأساسيات جدًا ، وكذلك معرفة كيفية توليد الإشارة ، وكيف يمكن لعملية ما أن ترسل إشارة إلى نفسها وعملية أخرى ، وكيف يمكن استخدام الإشارة للاتصال بين العمليات.