Làm thế nào để sử dụng bộ xử lý tín hiệu trong ngôn ngữ C?

How Use Signal Handlers C Language



Trong bài viết này, chúng tôi sẽ hướng dẫn bạn cách sử dụng bộ xử lý tín hiệu trong Linux bằng ngôn ngữ C. Nhưng trước tiên chúng ta sẽ thảo luận về tín hiệu là gì, cách nó sẽ tạo ra một số tín hiệu phổ biến mà bạn có thể sử dụng trong chương trình của mình và sau đó chúng ta sẽ xem xét cách một chương trình có thể xử lý các tín hiệu khác nhau trong khi chương trình thực thi. Vì vậy, hãy bắt đầu.

Dấu hiệu

Tín hiệu là một sự kiện được tạo ra để thông báo cho một quá trình hoặc chuỗi rằng một số tình huống quan trọng đã đến. Khi một quy trình hoặc tiểu trình đã nhận được tín hiệu, quy trình hoặc tiểu trình sẽ dừng những gì nó đang làm và thực hiện một số hành động. Tín hiệu có thể hữu ích cho giao tiếp giữa các quá trình.







Tín hiệu tiêu chuẩn

Các tín hiệu được xác định trong tệp tiêu đề signal.h như một hằng số vĩ mô. Tên tín hiệu bắt đầu bằng SIG và theo sau là mô tả ngắn gọn về tín hiệu. Vì vậy, mọi tín hiệu đều có một giá trị số duy nhất. Chương trình của bạn phải luôn sử dụng tên của tín hiệu, không phải số tín hiệu. Lý do là số hiệu có thể khác nhau tùy theo hệ thống nhưng ý nghĩa của tên sẽ là tiêu chuẩn.



Macro NSIG là tổng số tín hiệu được xác định. Giá trị của NSIG là một lớn hơn tổng số tín hiệu được xác định (Tất cả các số tín hiệu được cấp phát liên tiếp).



Sau đây là các tín hiệu tiêu chuẩn:





Tên tín hiệu Sự miêu tả
ĐĂNG KÍ Kết thúc quá trình. Tín hiệu SIGHUP được sử dụng để báo cáo việc thiết bị đầu cuối của người dùng bị ngắt kết nối, có thể do kết nối từ xa bị mất hoặc bị treo.
SIGINT Làm gián đoạn quá trình. Khi người dùng nhập ký tự INTR (thường là Ctrl + C), tín hiệu SIGINT sẽ được gửi.
SIGQUIT Thoát quy trình. Khi người dùng nhập ký tự QUIT (thường là Ctrl + ), tín hiệu SIGQUIT sẽ được gửi.
NIÊM PHONG Hướng dẫn bất hợp pháp. Khi một nỗ lực được thực hiện để thực hiện lệnh rác hoặc đặc quyền, tín hiệu SIGILL sẽ được tạo ra. Ngoài ra, SIGILL có thể được tạo khi ngăn xếp bị tràn hoặc khi hệ thống gặp sự cố khi chạy trình xử lý tín hiệu.
SIGTRAP Bẫy dấu vết. Một lệnh breakpoint và lệnh bẫy khác sẽ tạo ra tín hiệu SIGTRAP. Trình gỡ lỗi sử dụng tín hiệu này.
SIGABRT Huỷ bỏ. Tín hiệu SIGABRT được tạo ra khi hàm abort () được gọi. Tín hiệu này chỉ ra một lỗi được phát hiện bởi chính chương trình và được báo cáo bởi lệnh gọi hàm abort ().
SIGFPE Ngoại lệ dấu phẩy động. Khi xảy ra lỗi số học nghiêm trọng, tín hiệu SIGFPE được tạo ra.
SIGUSR1 và SIGUSR2 Các tín hiệu SIGUSR1 và SIGUSR2 có thể được sử dụng như bạn muốn. Sẽ rất hữu ích khi viết một trình xử lý tín hiệu cho chúng trong chương trình nhận tín hiệu để giao tiếp giữa các quá trình đơn giản.

Hành động mặc định của tín hiệu

Mỗi tín hiệu có một hành động mặc định, một trong những hành động sau:

Thuật ngữ: Quá trình sẽ kết thúc.
Cốt lõi: Quá trình sẽ kết thúc và tạo ra một tệp kết xuất lõi.
Ign: Quá trình sẽ bỏ qua tín hiệu.
Ngừng lại: Quá trình sẽ dừng lại.
Tài khoản: Quá trình sẽ tiếp tục không bị dừng.



Hành động mặc định có thể được thay đổi bằng cách sử dụng chức năng xử lý. Không thể thay đổi hành động mặc định của một số tín hiệu. SIGKILLSIGABRT Không thể thay đổi hoặc bỏ qua hành động mặc định của tín hiệu.

Xử lý tín hiệu

Nếu một quá trình nhận được một tín hiệu, thì quá trình đó có lựa chọn hành động cho loại tín hiệu đó. Quá trình có thể bỏ qua tín hiệu, có thể chỉ định một hàm xử lý hoặc chấp nhận hành động mặc định cho loại tín hiệu đó.

  • Nếu hành động được chỉ định cho tín hiệu bị bỏ qua, thì tín hiệu sẽ bị loại bỏ ngay lập tức.
  • Chương trình có thể đăng ký một chức năng xử lý bằng cách sử dụng chức năng như dấu hiệu hoặc cử chỉ . Đây được gọi là bộ xử lý bắt tín hiệu.
  • Nếu tín hiệu không được xử lý hoặc bị bỏ qua, hành động mặc định của nó sẽ diễn ra.

Chúng tôi có thể xử lý tín hiệu bằng cách sử dụng dấu hiệu hoặc cử chỉ hàm số. Ở đây chúng tôi thấy cách đơn giản nhất dấu hiệu() chức năng được sử dụng để xử lý các tín hiệu.

NSdấu hiệu() (NSký tên, vô hiệu (*hàm số)(NS))

Các dấu hiệu() sẽ gọi hàm số hoạt động nếu quá trình nhận được tín hiệu ký tên . Các dấu hiệu() trả về một con trỏ đến hàm hàm số nếu thành công hoặc nó trả về lỗi thành errno và -1 nếu ngược lại.

Các hàm số con trỏ có thể có ba giá trị:

  1. SIG_DFL : Nó là một con trỏ đến chức năng mặc định của hệ thống SIG_DFL () , được khai báo trong NS tập tin tiêu đề. Nó được sử dụng để thực hiện hành động mặc định của tín hiệu.
  2. SIG_IGN : Nó là một con trỏ đến hàm bỏ qua hệ thống SIG_IGN () , được khai báo trong NS tập tin tiêu đề.
  3. Con trỏ hàm xử lý do người dùng xác định : Loại hàm xử lý do người dùng xác định là void (*) (int) , nghĩa là kiểu trả về là void và một đối số kiểu int.

Ví dụ về trình xử lý tín hiệu cơ bản

#bao gồm
#bao gồm
#bao gồm
vô hiệusig_handler(NSký tên){

// Kiểu trả về của hàm xử lý phải là void
printf (' Chức năng xử lý bên trong ');
}

NSchủ chốt(){
dấu hiệu(SIGINT,sig_handler); // Đăng ký trình xử lý tín hiệu
(NStôi=1;;tôi++){ // Vòng lặp vô hạn
printf ('% d: Bên trong chức năng chính ',tôi);
ngủ(1); // Trì hoãn 1 giây
}
trở lại 0;
}

Trong ảnh chụp màn hình đầu ra của Example1.c, chúng ta có thể thấy rằng vòng lặp vô hạn trong hàm chính đang được thực thi. Khi người dùng nhập Ctrl + C, chức năng chính dừng thực thi và chức năng xử lý của tín hiệu được gọi. Sau khi hoàn thành hàm xử lý, việc thực thi hàm chính lại tiếp tục. Khi người dùng gõ Ctrl + , quá trình này sẽ bị thoát.

Ví dụ về bỏ qua tín hiệu

#bao gồm
#bao gồm
#bao gồm
NSchủ chốt(){
dấu hiệu(SIGINT,SIG_IGN); // Đăng ký trình xử lý tín hiệu để bỏ qua tín hiệu

(NStôi=1;;tôi++){ // Vòng lặp vô hạn
printf ('% d: Bên trong chức năng chính ',tôi);
ngủ(1); // Trì hoãn 1 giây
}
trở lại 0;
}

Ở đây hàm xử lý là đăng ký để SIG_IGN () chức năng bỏ qua hành động tín hiệu. Vì vậy, khi người dùng nhập Ctrl + C, SIGINT tín hiệu đang được tạo nhưng hành động bị bỏ qua.

Ví dụ về trình xử lý tín hiệu đăng ký lại

#bao gồm
#bao gồm
#bao gồm

vô hiệusig_handler(NSký tên){
printf (' Chức năng xử lý bên trong ');
dấu hiệu(SIGINT,SIG_DFL); // Đăng ký lại trình xử lý tín hiệu cho hành động mặc định
}

NSchủ chốt(){
dấu hiệu(SIGINT,sig_handler); // Đăng ký trình xử lý tín hiệu
(NStôi=1;;tôi++){ // Vòng lặp vô hạn
printf ('% d: Bên trong chức năng chính ',tôi);
ngủ(1); // Trì hoãn 1 giây
}
trở lại 0;
}

Trong ảnh chụp màn hình đầu ra của Example3.c, chúng ta có thể thấy rằng khi người dùng lần đầu tiên gõ Ctrl + C, hàm xử lý sẽ được gọi. Trong chức năng xử lý, trình xử lý tín hiệu đăng ký lại SIG_DFL cho hành động mặc định của tín hiệu. Khi người dùng gõ Ctrl + C lần thứ hai, quá trình sẽ bị kết thúc, đây là hành động mặc định của SIGINT dấu hiệu.

Gửi tín hiệu:

Một quá trình cũng có thể gửi tín hiệu một cách rõ ràng cho chính nó hoặc cho một quá trình khác. Hàm raise () và kill () có thể được sử dụng để gửi tín hiệu. Cả hai hàm đều được khai báo trong tệp tiêu đề signal.h.

NS nâng cao (NSký tên)

Hàm raise () được sử dụng để gửi tín hiệu ký tên đến quá trình gọi (chính nó). Nó trả về 0 nếu thành công và giá trị khác 0 nếu không thành công.

NSgiết chết(pid_t pid, NSký tên)

Chức năng tiêu diệt được sử dụng để gửi tín hiệu ký tên đến một quy trình hoặc nhóm quy trình được chỉ định bởi pid .

Ví dụ về trình xử lý tín hiệu SIGUSR1

#bao gồm
#bao gồm

vô hiệusig_handler(NSký tên){
printf ('Chức năng xử lý bên trong ');
}

NSchủ chốt(){
dấu hiệu(SIGUSR1,sig_handler); // Đăng ký trình xử lý tín hiệu
printf ('Bên trong chức năng chính ');
nâng cao (SIGUSR1);
printf ('Bên trong chức năng chính ');
trở lại 0;
}

Ở đây, quá trình gửi tín hiệu SIGUSR1 đến chính nó bằng cách sử dụng hàm raise ().

Nâng cao với Chương trình Ví dụ về Kill

#bao gồm
#bao gồm
#bao gồm
vô hiệusig_handler(NSký tên){
printf ('Chức năng xử lý bên trong ');
}

NSchủ chốt(){
pid_t pid;
dấu hiệu(SIGUSR1,sig_handler); // Đăng ký trình xử lý tín hiệu
printf ('Bên trong chức năng chính ');
pid=người lém lỉnh(); // ID tiến trình của chính nó
giết chết(pid,SIGUSR1); // Gửi SIGUSR1 cho chính nó
printf ('Bên trong chức năng chính ');
trở lại 0;
}

Tại đây, quá trình gửi SIGUSR1 tín hiệu cho chính nó bằng cách sử dụng giết chết() hàm số. getpid () được sử dụng để lấy ID quy trình của chính nó.

Trong ví dụ tiếp theo, chúng ta sẽ xem cách các quá trình cha và con giao tiếp với nhau (Inter Process Communication) bằng cách sử dụng giết chết() và chức năng tín hiệu.

Giao tiếp với con cái của cha mẹ bằng các tín hiệu

#bao gồm
#bao gồm
#bao gồm
#bao gồm
vô hiệusig_handler_parent(NSký tên){
printf ('Cha mẹ: Đã nhận được tín hiệu phản hồi từ con ');
}

vô hiệusig_handler_child(NSký tên){
printf ('Con: Đã nhận được tín hiệu từ cha mẹ ');
ngủ(1);
giết chết(có được(),SIGUSR1);
}

NSchủ chốt(){
pid_t pid;
nếu như((pid=cái nĩa())<0){
printf ('Ngã ba không thành công ');
lối ra (1);
}
/ * Quy trình con * /
khác nếu như(pid==0){
dấu hiệu(SIGUSR1,sig_handler_child); // Đăng ký trình xử lý tín hiệu
printf ('Con: chờ tín hiệu ');
tạm ngừng();
}
/ * Quy trình dành cho cha mẹ * /
khác{
dấu hiệu(SIGUSR1,sig_handler_parent); // Đăng ký trình xử lý tín hiệu
ngủ(1);
printf ('Parent: gửi tín hiệu đến Child ');
giết chết(pid,SIGUSR1);
printf ('Cha mẹ: đang chờ phản hồi ');
tạm ngừng();
}
trở lại 0;
}

Ở đây, cái nĩa() hàm tạo quy trình con và trả về 0 cho quy trình con và ID quy trình con cho quy trình mẹ. Vì vậy, pid đã được kiểm tra để quyết định quy trình cha mẹ và con. Trong tiến trình cha, nó được ngủ trong 1 giây để tiến trình con có thể đăng ký chức năng xử lý tín hiệu và chờ tín hiệu từ cha. Sau 1 giây quy trình dành cho cha mẹ gửi SIGUSR1 báo hiệu cho tiến trình con và đợi tín hiệu phản hồi từ con. Trong tiến trình con, đầu tiên nó chờ tín hiệu từ cha mẹ và khi nhận được tín hiệu, hàm xử lý được gọi. Từ hàm xử lý, tiến trình con sẽ gửi một SIGUSR1 báo hiệu cho phụ huynh. Ở đây getppid () hàm được sử dụng để lấy ID quy trình mẹ.

Phần kết luận

Tín hiệu trong Linux là một chủ đề lớn. Trong bài viết này, chúng ta đã xem cách xử lý tín hiệu từ rất cơ bản và cũng có được kiến ​​thức về cách tín hiệu tạo ra, cách một quá trình có thể gửi tín hiệu đến chính nó và các quá trình khác, cách tín hiệu có thể được sử dụng để giao tiếp giữa các quá trình.