การสื่อสารระหว่างกระบวนการ - สัญญาณ
ก 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