การสื่อสารระหว่างกระบวนการ - สัญญาณ

signalเป็นการแจ้งเตือนถึงกระบวนการที่ระบุการเกิดเหตุการณ์ สัญญาณเรียกอีกอย่างว่าsoftware interrupt และไม่สามารถคาดเดาได้ว่าจะเกิดขึ้นจึงเรียกอีกอย่างว่า asynchronous event.

สัญญาณสามารถระบุได้ด้วยตัวเลขหรือชื่อโดยปกติชื่อสัญญาณจะขึ้นต้นด้วย SIG สัญญาณที่มีสามารถตรวจสอบได้ด้วยคำสั่ง kill –l (l สำหรับ Listing signal names) ซึ่งมีดังต่อไปนี้ -

เมื่อใดก็ตามที่สัญญาณเพิ่มขึ้น (สัญญาณที่สร้างโดยโปรแกรมหรือระบบ) การดำเนินการเริ่มต้นจะดำเนินการ จะเกิดอะไรขึ้นถ้าคุณไม่ต้องการดำเนินการตามค่าเริ่มต้น แต่ต้องการดำเนินการของคุณเองในการรับสัญญาณ? เป็นไปได้สำหรับสัญญาณทั้งหมดหรือไม่? ใช่มันเป็นไปได้ที่จะจัดการสัญญาณ แต่ไม่ใช่สำหรับสัญญาณทั้งหมด ถ้าคุณต้องการละเว้นสัญญาณเป็นไปได้หรือไม่? ใช่มันเป็นไปได้ที่จะเพิกเฉยต่อสัญญาณ การเพิกเฉยต่อสัญญาณหมายถึงการไม่ดำเนินการตามค่าเริ่มต้นหรือการจัดการสัญญาณ เป็นไปได้ที่จะเพิกเฉยหรือจัดการกับสัญญาณเกือบทั้งหมด สัญญาณที่ไม่สามารถเพิกเฉยหรือจัดการ / จับได้คือ SIGSTOP และ SIGKILL

โดยสรุปการดำเนินการสำหรับสัญญาณมีดังนี้ -

  • การดำเนินการเริ่มต้น
  • จัดการกับสัญญาณ
  • ไม่สนใจสัญญาณ

ตามที่กล่าวไว้สัญญาณสามารถจัดการได้โดยเปลี่ยนแปลงการดำเนินการตามค่าเริ่มต้น การจัดการสัญญาณสามารถทำได้สองวิธีคือผ่านการเรียกระบบสัญญาณ () และซิกแอคชั่น ()

#include <signal.h>

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

สัญญาณเรียกระบบ () จะเรียกตัวจัดการที่ลงทะเบียนเมื่อสร้างสัญญาณตามที่ระบุไว้ในสัญญาณ ตัวจัดการสามารถเป็นหนึ่งใน SIG_IGN (ละเว้นสัญญาณ), SIG_DFL (การตั้งค่าสัญญาณกลับไปที่กลไกเริ่มต้น) หรือตัวจัดการสัญญาณที่ผู้ใช้กำหนดเองหรือที่อยู่ของฟังก์ชัน

การเรียกใช้ระบบนี้จะส่งคืนที่อยู่ของฟังก์ชันที่รับอาร์กิวเมนต์จำนวนเต็มและไม่มีค่าส่งคืน สายนี้ส่งคืน SIG_ERR ในกรณีที่เกิดข้อผิดพลาด

แม้ว่าจะมีสัญญาณ () ตัวจัดการสัญญาณตามลำดับตามที่ผู้ใช้ลงทะเบียนไว้ แต่สามารถเรียกใช้การปรับแต่งอย่างละเอียดเช่นการปิดบังสัญญาณที่ควรจะปิดกั้นการปรับเปลี่ยนพฤติกรรมของสัญญาณและฟังก์ชันอื่น ๆ จะไม่สามารถทำได้ สิ่งนี้เป็นไปได้โดยใช้การเรียกระบบ sigaction ()

#include <signal.h>

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

การเรียกระบบนี้ใช้เพื่อตรวจสอบหรือเปลี่ยนแปลงการทำงานของสัญญาณ ถ้าการกระทำไม่เป็นโมฆะการกระทำใหม่สำหรับสัญญาณสัญญาณจะถูกติดตั้งจากการกระทำ ถ้า oldact ไม่เป็นโมฆะการกระทำก่อนหน้านี้จะถูกบันทึกไว้ใน oldact

โครงสร้าง sigaction ประกอบด้วยฟิลด์ต่อไปนี้ -

Field 1 - Handler กล่าวถึงทั้งใน 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 ประกอบด้วยข้อมูลสัญญาณเช่นหมายเลขสัญญาณที่จะส่ง, ค่าสัญญาณ, รหัสกระบวนการ, รหัสผู้ใช้จริงของกระบวนการส่งเป็นต้น

Field 2 - ชุดสัญญาณที่จะปิดกั้น

int sa_mask;

ตัวแปรนี้ระบุหน้ากากของสัญญาณที่ควรถูกบล็อกระหว่างการทำงานของตัวจัดการสัญญาณ

Field 3 - ธงพิเศษ

int sa_flags;

ฟิลด์นี้ระบุชุดของแฟล็กที่ปรับเปลี่ยนพฤติกรรมของสัญญาณ

Field 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

ตามที่กล่าวไว้ระบบจะสร้างสัญญาณ (เมื่อดำเนินการบางอย่างเช่นหารด้วยศูนย์เป็นต้น) หรือผู้ใช้ยังสามารถสร้างสัญญาณโดยใช้โปรแกรม หากคุณต้องการสร้างสัญญาณโดยทางโปรแกรมให้ใช้ฟังก์ชันไลบรารี Raise ()

ตอนนี้ให้เราใช้โปรแกรมอื่นเพื่อสาธิตการจัดการและเพิกเฉยต่อสัญญาณ

สมมติว่าเราขึ้นสัญญาณโดยใช้ Raise () แล้วจะเกิดอะไรขึ้น? หลังจากเพิ่มสัญญาณการดำเนินการของกระบวนการปัจจุบันจะหยุดลง แล้วจะเกิดอะไรขึ้นกับกระบวนการหยุด? อาจมีสองสถานการณ์ - ขั้นแรกดำเนินการต่อเมื่อจำเป็น ประการที่สองยุติ (ด้วยคำสั่ง kill) กระบวนการ

หากต้องการดำเนินการตามกระบวนการที่หยุดทำงานต่อไปให้ส่ง SIGCONT ไปยังกระบวนการนั้น ๆ คุณยังสามารถออกคำสั่ง fg (พื้นหน้า) หรือ bg (พื้นหลัง) เพื่อดำเนินการต่อได้ ที่นี่คำสั่งจะเริ่มต้นการดำเนินการของกระบวนการสุดท้ายใหม่เท่านั้น หากมีการหยุดมากกว่าหนึ่งกระบวนการกระบวนการสุดท้ายเท่านั้นที่จะกลับมาทำงานต่อ หากคุณต้องการกลับมาดำเนินการตามกระบวนการที่หยุดไว้ก่อนหน้านี้ให้ดำเนินการต่อ (โดยใช้ fg / bg) พร้อมกับหมายเลขงาน

โปรแกรมต่อไปนี้ใช้เพื่อเพิ่มสัญญาณ SIGSTOP โดยใช้ฟังก์ชัน Raise () สัญญาณ 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

จนถึงตอนนี้เราได้เห็นโปรแกรมที่จัดการสัญญาณที่สร้างโดยระบบ ตอนนี้ให้เราดูสัญญาณที่สร้างขึ้นผ่านโปรแกรม (โดยใช้ฟังก์ชัน Raise () หรือผ่านคำสั่ง kill) โปรแกรมนี้สร้างสัญญาณ 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

จนถึงขณะนี้เราสังเกตเห็นว่าเรามีตัวจัดการสัญญาณหนึ่งตัวเพื่อจัดการสัญญาณหนึ่งตัว เรามีตัวจัดการตัวเดียวเพื่อจัดการสัญญาณหลายตัวได้หรือไม่? คำตอบคือใช่ ให้เราพิจารณาสิ่งนี้ด้วยโปรแกรม

โปรแกรมต่อไปนี้ทำสิ่งต่อไปนี้ -

Step 1 - ลงทะเบียนตัวจัดการ (handleSignals) เพื่อจับหรือจัดการกับสัญญาณ SIGINT (CTRL + C) หรือ SIGQUIT (CTRL + \)

Step 2 - หากผู้ใช้สร้างสัญญาณ SIGQUIT (ไม่ว่าจะผ่านคำสั่ง kill หรือการควบคุมแป้นพิมพ์ด้วย CTRL + \) ตัวจัดการจะพิมพ์ข้อความเป็นส่งคืน

Step 3 - หากผู้ใช้สร้างสัญญาณ SIGINT (ผ่านคำสั่ง kill หรือการควบคุมแป้นพิมพ์ด้วย CTRL + C) เป็นครั้งแรกผู้ใช้จะแก้ไขสัญญาณเพื่อดำเนินการเริ่มต้น (ด้วย SIG_DFL) ในครั้งต่อไป

Step 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

เรารู้ว่าในการจัดการกับสัญญาณเรามีการเรียกระบบสองแบบคือ signal () หรือ sigaction () จนถึงตอนนี้เราได้เห็นการเรียกระบบสัญญาณ () แล้วตอนนี้ถึงเวลาสำหรับการเรียกระบบ 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