Giao tiếp giữa các quá trình - Tín hiệu
A signallà một thông báo cho một quá trình chỉ ra sự xuất hiện của một sự kiện. Tín hiệu còn được gọi làsoftware interrupt và không thể đoán trước được sự xuất hiện của nó, do đó nó còn được gọi là asynchronous event.
Tín hiệu có thể được chỉ định bằng một số hoặc một tên, thông thường tên tín hiệu bắt đầu bằng SIG. Các tín hiệu có sẵn có thể được kiểm tra bằng lệnh kill –l (l cho tên tín hiệu Liệt kê), như sau:
Bất cứ khi nào tín hiệu được nâng lên (tín hiệu theo chương trình hoặc do hệ thống tạo), một hành động mặc định sẽ được thực hiện. Điều gì sẽ xảy ra nếu bạn không muốn thực hiện hành động mặc định nhưng muốn thực hiện hành động của riêng bạn khi nhận tín hiệu? Điều này có khả thi cho tất cả các tín hiệu không? Có, có thể xử lý tín hiệu nhưng không phải cho tất cả các tín hiệu. Điều gì xảy ra nếu bạn muốn bỏ qua các tín hiệu, điều này có khả thi không? Có, có thể bỏ qua tín hiệu. Bỏ qua tín hiệu có nghĩa là không thực hiện hành động mặc định hoặc xử lý tín hiệu. Có thể bỏ qua hoặc xử lý gần như tất cả các tín hiệu. Các tín hiệu không thể bỏ qua hoặc xử lý / bắt được là SIGSTOP và SIGKILL.
Tóm lại, các hành động được thực hiện đối với các tín hiệu như sau:
- Hành động mặc định
- Xử lý tín hiệu
- Bỏ qua tín hiệu
Như đã thảo luận, tín hiệu có thể được xử lý thay đổi việc thực thi hành động mặc định. Việc xử lý tín hiệu có thể được thực hiện theo một trong hai cách, tức là thông qua lệnh gọi hệ thống, signal () và sigaction ().
#include <signal.h>
typedef void (*sighandler_t) (int);
sighandler_t signal(int signum, sighandler_t handler);
Tín hiệu cuộc gọi hệ thống () sẽ gọi trình xử lý đã đăng ký khi tạo ra tín hiệu như đã đề cập trong signum. Bộ xử lý có thể là một trong các SIG_IGN (Bỏ qua tín hiệu), SIG_DFL (Đặt tín hiệu trở lại cơ chế mặc định) hoặc bộ xử lý tín hiệu do người dùng xác định hoặc địa chỉ chức năng.
Lệnh gọi thành công của hệ thống này trả về địa chỉ của một hàm nhận đối số là số nguyên và không có giá trị trả về. Cuộc gọi này trả về SIG_ERR trong trường hợp có lỗi.
Mặc dù với signal (), trình xử lý tín hiệu tương ứng được người dùng đăng ký có thể được gọi, nhưng không thể thực hiện việc tinh chỉnh như che dấu các tín hiệu cần bị chặn, sửa đổi hành vi của tín hiệu và các chức năng khác. Điều này có thể sử dụng lệnh gọi hệ thống sigaction ().
#include <signal.h>
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact)
Lệnh gọi hệ thống này được sử dụng để kiểm tra hoặc thay đổi hành động tín hiệu. Nếu hành động không rỗng, hành động mới cho dấu hiệu tín hiệu được cài đặt từ hành động. Nếu oldact không rỗng, hành động trước đó sẽ được lưu trong oldact.
Cấu trúc sigaction chứa các trường sau:
Field 1 - Xử lý được đề cập trong sa_handler hoặc sa_sigaction.
void (*sa_handler)(int);
void (*sa_sigaction)(int, siginfo_t *, void *);
Trình xử lý cho sa_handler chỉ định hành động sẽ được thực hiện dựa trên dấu hiệu và với SIG_DFL cho biết hành động mặc định hoặc SIG_IGN để bỏ qua tín hiệu hoặc con trỏ đến một chức năng xử lý tín hiệu.
Trình xử lý cho sa_sigaction chỉ định số tín hiệu làm đối số đầu tiên, con trỏ tới cấu trúc siginfo_t làm đối số thứ hai và con trỏ tới ngữ cảnh người dùng (kiểm tra getcontext () hoặc setcontext () để biết thêm chi tiết) làm đối số thứ ba.
Cấu trúc siginfo_t chứa thông tin tín hiệu như số tín hiệu được gửi, giá trị tín hiệu, id quy trình, id người dùng thực của quy trình gửi, v.v.
Field 2 - Bộ tín hiệu bị chặn.
int sa_mask;
Biến này chỉ định mặt nạ của các tín hiệu sẽ bị chặn trong quá trình thực thi trình xử lý tín hiệu.
Field 3 - Cờ đặc biệt.
int sa_flags;
Trường này chỉ định một tập hợp các cờ sửa đổi hành vi của tín hiệu.
Field 4 - Trình xử lý khôi phục.
void (*sa_restorer) (void);
Lệnh gọi hệ thống này trả về 0 khi thành công và -1 trong trường hợp thất bại.
Chúng ta hãy xem xét một vài chương trình mẫu.
Đầu tiên, chúng ta hãy bắt đầu với một chương trình mẫu, chương trình này tạo ra ngoại lệ. Trong chương trình này, chúng tôi đang cố gắng thực hiện phép toán chia cho 0, điều này làm cho hệ thống tạo ra một ngoại lệ.
/* 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;
}
Các bước biên dịch và thực hiện
Floating point exception (core dumped)
Do đó, khi chúng tôi đang cố gắng thực hiện một phép toán số học, hệ thống đã tạo ra một ngoại lệ dấu chấm động với kết xuất lõi, đây là hành động mặc định của tín hiệu.
Bây giờ, chúng ta hãy sửa đổi mã để xử lý tín hiệu cụ thể này bằng cách sử dụng lệnh gọi hệ thống signal ().
/* 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;
}
Các bước biên dịch và thực hiện
Received SIGFPE, Divide by Zero Exception
Như đã thảo luận, tín hiệu được tạo ra bởi hệ thống (khi thực hiện một số hoạt động nhất định như chia cho 0, v.v.) hoặc người dùng cũng có thể tạo tín hiệu theo chương trình. Nếu bạn muốn tạo tín hiệu theo chương trình, hãy sử dụng hàm thư viện raise ().
Bây giờ, chúng ta hãy lấy một chương trình khác để chứng minh việc xử lý và bỏ qua tín hiệu.
Giả sử rằng chúng ta đã tăng một tín hiệu bằng cách sử dụng raise (), điều gì sẽ xảy ra sau đó? Sau khi tăng tín hiệu, quá trình thực thi hiện tại bị dừng lại. Sau đó, điều gì xảy ra với quá trình bị dừng? Có thể có hai trường hợp - Đầu tiên, tiếp tục thực hiện bất cứ khi nào được yêu cầu. Thứ hai, chấm dứt (với lệnh kill) tiến trình.
Để tiếp tục thực hiện quy trình đã dừng, hãy gửi SIGCONT đến quy trình cụ thể đó. Bạn cũng có thể đưa ra lệnh fg (nền trước) hoặc bg (nền) để tiếp tục thực hiện. Ở đây, các lệnh sẽ chỉ bắt đầu lại quá trình cuối cùng. Nếu có nhiều quá trình bị dừng, thì chỉ quá trình cuối cùng được tiếp tục. Nếu bạn muốn tiếp tục quá trình đã dừng trước đó, hãy tiếp tục các công việc (sử dụng fg / bg) cùng với số công việc.
Chương trình sau đây được sử dụng để nâng tín hiệu SIGSTOP bằng cách sử dụng hàm raise (). Tín hiệu SIGSTOP cũng có thể được tạo ra bằng cách người dùng nhấn phím CTRL + Z (Control + Z). Sau khi phát tín hiệu này, chương trình sẽ ngừng thực hiện. Gửi tín hiệu (SIGCONT) để tiếp tục thực hiện.
Trong ví dụ sau, chúng tôi đang tiếp tục quá trình đã dừng bằng lệnh fg.
/* signal_raising.c */
#include<stdio.h>
#include<signal.h>
#include<stdlib.h>
int main() {
printf("Testing SIGSTOP\n");
raise(SIGSTOP);
return 0;
}
Các bước biên dịch và thực hiện
Testing SIGSTOP
[1]+ Stopped ./a.out
./a.out
Bây giờ, hãy nâng cấp chương trình trước đó để tiếp tục thực hiện quá trình đã dừng bằng cách phát hành SIGCONT từ một thiết bị đầu cuối khác.
/* 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;
}
Các bước biên dịch và thực hiện
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
Trong một thiết bị đầu cuối khác
kill -SIGCONT 30379
Cho đến nay, chúng ta đã thấy chương trình xử lý tín hiệu do hệ thống tạo ra. Bây giờ, chúng ta hãy xem tín hiệu được tạo ra thông qua chương trình (sử dụng hàm raise () hoặc thông qua lệnh kill). Chương trình này tạo ra tín hiệu SIGTSTP (dừng đầu cuối), có hành động mặc định là dừng thực thi. Tuy nhiên, vì chúng ta đang xử lý tín hiệu ngay bây giờ thay vì hành động mặc định, nó sẽ đến với trình xử lý đã xác định. Trong trường hợp này, chúng tôi chỉ in thông báo và thoát.
/* 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;
}
Các bước biên dịch và thực hiện
Testing SIGTSTP
Received SIGTSTP
Chúng tôi đã thấy các trường hợp thực hiện hành động mặc định hoặc xử lý tín hiệu. Bây giờ, đã đến lúc bỏ qua tín hiệu. Ở đây, trong chương trình mẫu này, chúng tôi đang đăng ký tín hiệu SIGTSTP để bỏ qua thông qua SIG_IGN và sau đó chúng tôi đang nâng tín hiệu SIGTSTP (dừng đầu cuối). Khi tín hiệu SIGTSTP đang được tạo ra sẽ bị bỏ qua.
/* 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;
}
Các bước biên dịch và thực hiện
Testing SIGTSTP
Signal SIGTSTP is ignored
Cho đến nay, chúng tôi đã quan sát thấy rằng chúng tôi có một bộ xử lý tín hiệu để xử lý một tín hiệu. Chúng ta có thể có một trình xử lý duy nhất để xử lý nhiều tín hiệu không? Câu trả lời là Có. Hãy để chúng tôi xem xét điều này với một chương trình.
Chương trình sau đây thực hiện những việc sau:
Step 1 - Đăng ký một trình xử lý (handleSignals) để bắt hoặc xử lý các tín hiệu SIGINT (CTRL + C) hoặc SIGQUIT (CTRL + \)
Step 2 - Nếu người dùng tạo tín hiệu SIGQUIT (thông qua lệnh kill hoặc điều khiển bàn phím bằng CTRL + \), trình xử lý chỉ cần in thông báo dưới dạng trả về.
Step 3 - Nếu người dùng tạo tín hiệu SIGINT (thông qua lệnh kill hoặc điều khiển bàn phím bằng CTRL + C) lần đầu tiên, thì nó sẽ sửa đổi tín hiệu để thực hiện hành động mặc định (với SIG_DFL) từ lần sau.
Step 4 - Nếu người dùng tạo tín hiệu SIGINT lần thứ hai, nó sẽ thực hiện một hành động mặc định, đó là kết thúc chương trình.
/* 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;
}
Các bước biên dịch và thực hiện
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
Nhà ga khác
kill 71
Phương pháp thứ hai
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
Chúng ta biết rằng để xử lý một tín hiệu, chúng ta có hai lệnh gọi hệ thống, tức là signal () hoặc sigaction (). Cho đến bây giờ chúng ta đã thấy với lệnh gọi hệ thống signal (), bây giờ là lúc cho lệnh gọi hệ thống sigaction (). Hãy để chúng tôi sửa đổi chương trình trên để thực hiện bằng cách sử dụng sigaction () như sau:
/* 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;
}
Hãy cho chúng tôi xem quá trình biên dịch và thực thi. Trong quá trình thực thi, hãy cho chúng tôi thấy sự cố CTRL + C hai lần, các cách kiểm tra / cách còn lại (như trên) bạn cũng có thể thử cho chương trình này.
Các bước biên dịch và thực hiện
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