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

الاتصال بين العمليات - الإشارات

 

الاتصال بين العمليات - الإشارات


Ad by Valueimpression

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

يمكن تحديد الإشارة برقم أو اسم ، وعادة ما تبدأ أسماء الإشارة بـ SIG. يمكن التحقق من الإشارات المتاحة باستخدام الأمر kill –l (l لإدراج أسماء الإشارات) ، وهو كالتالي -

الإشارة

عندما يتم رفع إشارة (إما بشكل برمجي أو بواسطة النظام) ، يتم تنفيذ إجراء افتراضي. ماذا لو كنت لا ترغب في تنفيذ الإجراء الافتراضي ولكنك ترغب في تنفيذ الإجراءات الخاصة بك عند استقبال الإشارة؟ هل هذا ممكن لجميع الإشارات؟ نعم ، من الممكن التعامل مع الإشارة ولكن ليس مع كل الإشارات. ماذا لو أردت تجاهل الإشارات ، هل هذا ممكن؟ نعم ، من الممكن تجاهل الإشارة. تجاهل الإشارة لا يعني عدم تنفيذ الإجراء الافتراضي أو التعامل مع الإشارة. من الممكن تجاهل أو التعامل مع جميع الإشارات تقريبًا. الإشارات التي لا يمكن تجاهلها أو التعامل معها / التقاطها هي SIGSTOP و SIGKILL.

باختصار ، الإجراءات التي تم تنفيذها للإشارات هي كما يلي -

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

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

#include <signal.h>

typedef void (*sighandler_t) (int);
sighandler_t signal(int signum, sighandler_t handler);

تقوم إشارة استدعاء النظام () باستدعاء المعالج المسجل عند توليد الإشارة كما هو مذكور في Signum. يمكن أن يكون المعالج إما واحدًا من SIG_IGN (تجاهل الإشارة) أو SIG_DFL (إعادة الإشارة إلى الآلية الافتراضية) أو معالج إشارة محدد من قبل المستخدم أو عنوان وظيفة.

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

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

#include <signal.h>

int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact)

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

يحتوي هيكل التوقيع على الحقول التالية -

الحقل 1 - المعالج مذكور إما في sa_handler أو sa_sigaction.

void (*sa_handler)(int);
void (*sa_sigaction)(int, siginfo_t *, void *);

يحدد معالج sa_handler الإجراء الذي سيتم تنفيذه بناءً على الإشارة ومع SIG_DFL يشير إلى الإجراء الافتراضي أو SIG_IGN لتجاهل الإشارة أو المؤشر إلى وظيفة معالجة الإشارة.

يحدد معالج sa_sigaction رقم الإشارة باعتباره الوسيطة الأولى ، والمؤشر إلى بنية siginfo_t كالوسيطة الثانية والمؤشر لسياق المستخدم (راجع getcontext () أو setcontext () لمزيد من التفاصيل) كوسيطة ثالثة.

تحتوي البنية siginfo_t على معلومات إشارة مثل رقم الإشارة التي سيتم تسليمها ، وقيمة الإشارة ، ومعرف العملية ، ومعرف المستخدم الحقيقي لعملية الإرسال ، وما إلى ذلك.

الحقل 2 - مجموعة الإشارات المطلوب حظرها.

int sa_mask;

يحدد هذا المتغير قناع الإشارات التي يجب حظرها أثناء تنفيذ معالج الإشارة.

حقل 3 - أعلام خاصة.

int sa_flags;

يحدد هذا الحقل مجموعة من الأعلام التي تعدل سلوك الإشارة.

الحقل 4 - استعادة المعالج.

void (*sa_restorer) (void);

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

دعونا نفكر في بعض نماذج البرامج.

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

/* signal_fpe.c */
#include<stdio.h>

int main() {
   int result;
   int v1, v2;
   v1 = 121;
   v2 = 0;
   result = v1/v2;
   printf("Result of Divide by Zero is %d\n", result);
   return 0;
}

خطوات التجميع والتنفيذ

Floating point exception (core dumped)

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

الآن ، دعونا نعدل الكود للتعامل مع هذه الإشارة المعينة باستخدام إشارة نظام () استدعاء.

/* signal_fpe_handler.c */
#include<stdio.h>
#include<signal.h>
#include<stdlib.h>

void handler_dividebyzero(int signum);

int main() {
   int result;
   int v1, v2;
   void (*sigHandlerReturn)(int);
   sigHandlerReturn = signal(SIGFPE, handler_dividebyzero);
   if (sigHandlerReturn == SIG_ERR) {
      perror("Signal Error: ");
      return 1;
   }
   v1 = 121;
   v2 = 0;
   result = v1/v2;
   printf("Result of Divide by Zero is %d\n", result);
   return 0;
}

void handler_dividebyzero(int signum) {
   if (signum == SIGFPE) {
      printf("Received SIGFPE, Divide by Zero Exception\n");
      exit (0);
   } 
   else
      printf("Received %d Signal\n", signum);
      return;
}

خطوات التجميع والتنفيذ

Received SIGFPE, Divide by Zero Exception

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

الآن ، دعونا نأخذ برنامجًا آخر لإثبات التعامل مع الإشارة وتجاهلها.

افترض أننا قمنا برفع الإشارة باستخدام الزيادة () ، ماذا يحدث بعد ذلك؟ بعد رفع الإشارة ، يتم إيقاف تنفيذ العملية الحالية. ثم ماذا يحدث للعملية المتوقفة؟ يمكن أن يكون هناك سيناريوهان - أولاً ، تابع التنفيذ كلما لزم الأمر. ثانيًا ، قم بإنهاء العملية (بأمر القتل).

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

يستخدم البرنامج التالي لرفع إشارة SIGSTOP باستخدام وظيفة lift (). يمكن أيضًا إنشاء Signal SIGSTOP بضغط المستخدم على مفتاح CTRL + Z (Control + Z). بعد إصدار هذه الإشارة ، سيتوقف البرنامج عن التنفيذ. أرسل الإشارة (SIGCONT) لمتابعة التنفيذ.

في المثال التالي ، نستأنف العملية المتوقفة باستخدام الأمر fg.

/* signal_raising.c */
#include<stdio.h>
#include<signal.h>
#include<stdlib.h>

int main() {
   printf("Testing SIGSTOP\n");
   raise(SIGSTOP);
   return 0;
}

خطوات التجميع والتنفيذ

Testing SIGSTOP
[1]+ Stopped ./a.out
./a.out

الآن ، قم بتحسين البرنامج السابق لمواصلة تنفيذ العملية المتوقفة عن طريق إصدار SIGCONT من محطة أخرى.

/* signal_stop_continue.c */
#include<stdio.h>
#include<signal.h>
#include <sys/types.h>
#include <unistd.h>

void handler_sigtstp(int signum);

int main() {
   pid_t pid;
   printf("Testing SIGSTOP\n");
   pid = getpid();
   printf("Open Another Terminal and issue following command\n");
   printf("kill -SIGCONT %d or kill -CONT %d or kill -18 %d\n", pid, pid, pid);
   raise(SIGSTOP);
   printf("Received signal SIGCONT\n");
   return 0;
}

خطوات التجميع والتنفيذ

Testing SIGSTOP
Open Another Terminal and issue following command
kill -SIGCONT 30379 or kill -CONT 30379 or kill -18 30379
[1]+ Stopped ./a.out

Received signal SIGCONT
[1]+ Done ./a.out

في محطة أخرى

kill -SIGCONT 30379

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

/* signal_raising_handling.c */
#include<stdio.h>
#include<signal.h>
#include<stdlib.h>

void handler_sigtstp(int signum);

int main() {
   void (*sigHandlerReturn)(int);
   sigHandlerReturn = signal(SIGTSTP, handler_sigtstp);
   if (sigHandlerReturn == SIG_ERR) {
      perror("Signal Error: ");
      return 1;
   }
   printf("Testing SIGTSTP\n");
   raise(SIGTSTP);
   return 0;
}

void handler_sigtstp(int signum) {
   if (signum == SIGTSTP) {
      printf("Received SIGTSTP\n");
      exit(0);
   }
   else
      printf("Received %d Signal\n", signum);
      return;
}

خطوات التجميع والتنفيذ

Testing SIGTSTP
Received SIGTSTP

لقد رأينا حالات أداء الإجراء الافتراضي أو التعامل مع الإشارة. الآن ، حان الوقت لتجاهل الإشارة. هنا ، في نموذج البرنامج هذا ، نقوم بتسجيل إشارة SIGTSTP لتجاهلها من خلال SIG_IGN ثم نقوم برفع إشارة SIGTSTP (توقف المحطة). عندما يتم إنشاء إشارة SIGTSTP التي سيتم تجاهلها.

/* signal_raising_ignoring.c */
#include<stdio.h>
#include<signal.h>
#include<stdlib.h>

void handler_sigtstp(int signum);

int main() {
   void (*sigHandlerReturn)(int);
   sigHandlerReturn = signal(SIGTSTP, SIG_IGN);
   if (sigHandlerReturn == SIG_ERR) {
      perror("Signal Error: ");
      return 1;
   }
   printf("Testing SIGTSTP\n");
   raise(SIGTSTP);
   printf("Signal SIGTSTP is ignored\n");
   return 0;
}

خطوات التجميع والتنفيذ

Testing SIGTSTP
Signal SIGTSTP is ignored

حتى الآن ، لاحظنا أن لدينا معالج إشارة واحد للتعامل مع إشارة واحدة. هل يمكننا الحصول على معالج واحد للتعامل مع إشارات متعددة؟ الجواب نعم. دعونا نفكر في هذا مع برنامج.

يقوم البرنامج التالي بما يلي -

الخطوة 1 - تسجيل معالج (إشارات المقابض) لالتقاط أو التعامل مع الإشارات SIGINT (CTRL + C) أو SIGQUIT (CTRL + \)

الخطوة 2 - إذا قام المستخدم بإنشاء إشارة SIGQUIT (إما من خلال أمر القتل أو التحكم في لوحة المفاتيح باستخدام CTRL + \) ، يقوم المعالج ببساطة بطباعة الرسالة كعودة.

الخطوة 3 - إذا قام المستخدم بإنشاء إشارة SIGINT (إما من خلال أمر القتل أو التحكم في لوحة المفاتيح باستخدام CTRL + C) لأول مرة ، فإنه يعدل الإشارة لأداء الإجراء الافتراضي (باستخدام SIG_DFL) من المرة القادمة.

الخطوة 4 - إذا قام المستخدم بتوليد إشارة SIGINT مرة ثانية ، فإنه يقوم بإجراء افتراضي ، وهو إنهاء البرنامج.

/* Filename: sigHandler.c */
#include<stdio.h>
#include<unistd.h>
#include<signal.h>

void handleSignals(int signum);

int main(void) {
   void (*sigHandlerInterrupt)(int);
   void (*sigHandlerQuit)(int);
   void (*sigHandlerReturn)(int);
   sigHandlerInterrupt = sigHandlerQuit = handleSignals;
   sigHandlerReturn = signal(SIGINT, sigHandlerInterrupt);
   if (sigHandlerReturn == SIG_ERR) {
      perror("signal error: ");
      return 1;
   }
   sigHandlerReturn = signal(SIGQUIT, sigHandlerQuit);
   
   if (sigHandlerReturn == SIG_ERR) {
      perror("signal error: ");
      return 1;
   }
   while (1) {
      printf("\nTo terminate this program, perform the following: \n");
      printf("1. Open another terminal\n");
      printf("2. Issue command: kill %d or issue CTRL+C 2 times (second time it terminates)\n", getpid());
      sleep(10);
   }
   return 0;
}

void handleSignals(int signum) {
   switch(signum) {
      case SIGINT:
      printf("\nYou pressed CTRL+C \n");
      printf("Now reverting SIGINT signal to default action\n");
      signal(SIGINT, SIG_DFL);
      break;
      case SIGQUIT:
      printf("\nYou pressed CTRL+\\ \n");
      break;
      default:
      printf("\nReceived signal number %d\n", signum);
      break;
   }
   return;
}

خطوات التجميع والتنفيذ

To terminate this program, perform the following:
1. Open another terminal
2. Issue command: kill 74 or issue CTRL+C 2 times (second time it terminates)
^C
You pressed CTRL+C
Now reverting SIGINT signal to default action
          
To terminate this program, perform the following:
1. Open another terminal
2. Issue command: kill 74 or issue CTRL+C 2 times (second time it terminates)
^\You pressed CTRL+\
To terminate this program, perform the following:
1. Open another terminal
2. Issue command: kill 120
Terminated

محطة أخرى

kill 71

الطريقة الثانية

To terminate this program, perform the following:
1. Open another terminal
2. Issue command: kill 71 or issue CTRL+C 2 times (second time it terminates)
^C
You pressed CTRL+C
Now reverting SIGINT signal to default action

To terminate this program, perform the following:
1. Open another terminal
2. Issue command: kill 71 or issue CTRL+C 2 times (second time it terminates)
^C

نحن نعلم أنه للتعامل مع الإشارة ، لدينا استدعائين للنظام ، أي إما إشارة () أو إشارة (). لقد رأينا حتى الآن مع استدعاء نظام الإشارة () ، حان الوقت الآن لاستدعاء نظام sigaction (). دعونا نعدل البرنامج أعلاه لأداء باستخدام sigaction () على النحو التالي -

/* Filename: sigHandlerSigAction.c */
#include<stdio.h>
#include<unistd.h>
#include<signal.h>

void handleSignals(int signum);

int main(void) {
   void (*sigHandlerReturn)(int);
   struct sigaction mysigaction;
   mysigaction.sa_handler = handleSignals;
   sigemptyset(&mysigaction.sa_mask);
   mysigaction.sa_flags = 0;
   sigaction(SIGINT, &mysigaction, NULL);
   
   if (mysigaction.sa_handler == SIG_ERR) {
      perror("signal error: ");
      return 1;
   }
   mysigaction.sa_handler = handleSignals;
   sigemptyset(&mysigaction.sa_mask);
   mysigaction.sa_flags = 0;
   sigaction(SIGQUIT, &mysigaction, NULL);
   
   if (mysigaction.sa_handler == SIG_ERR) {
      perror("signal error: ");
      return 1;
   }
   while (-1) {
      printf("\nTo terminate this program, perform either of the following: \n");
      printf("1. Open another terminal and issue command: kill %d\n", getpid());
      printf("2. Issue CTRL+C 2 times (second time it terminates)\n");
      sleep(10);
   }
   return 0;
}

void handleSignals(int signum) {
   switch(signum) {
      case SIGINT:
      printf("\nYou have entered CTRL+C \n");
      printf("Now reverting SIGINT signal to perform default action\n");
      signal(SIGINT, SIG_DFL);
      break;
      case SIGQUIT:
      printf("\nYou have entered CTRL+\\ \n");
      break;
      default:
      printf("\nReceived signal number %d\n", signum);
      break;
   }
   return;
}

دعونا نرى عملية التجميع والتنفيذ. في عملية التنفيذ ، دعنا نرى مشكلة CTRL + C مرتين ، الشيكات / الطرق المتبقية (على النحو الوارد أعلاه) يمكنك تجربتها لهذا البرنامج أيضًا.

خطوات التجميع والتنفيذ

To terminate this program, perform either of the following:
1. Open another terminal and issue command: kill 3199
2. Issue CTRL+C 2 times (second time it terminates)
^C
You have entered CTRL+C
Now reverting SIGINT signal to perform default action
To terminate this program, perform either of the following:
1. Open another terminal and issue command: kill 3199
2. Issue CTRL+C 2 times (second time it terminates)
^C

التسميات: