Bài 44.226 phút đọc
Quy tắc vàng viết handler
Sau bài này bạn sẽ làm được
- Kể các hàm async-signal-safe được phép gọi
- Giải thích vì sao printf trong handler nguy hiểm
- Áp dụng mẫu handler chỉ đặt cờ
- Chứng minh vấn đề bằng một chương trình chạy được
Bộ xử lý tín hiệu chạy giữa chừng mọi thứ, kể cả giữa một lệnh malloc hay printf. Nên nó chỉ được gọi một tập nhỏ các hàm an toàn. Vi phạm quy tắc này tạo ra những lỗi hiếm, không tái hiện được, và cực khó tìm.
#Vấn đề
/* Handler chay KHONG DONG BO: no ngat luong chinh o BAT KY diem
nao. Tuong tuong luong chinh dang o giua printf, dang giu khoa
noi bo cua stdio, thi tin hieu toi va handler cung goi printf.
printf thu lay khoa do -> khoa da bi luong chinh giu -> KHOA
CHET. Chuong trinh treo, va no chi treo khi tin hieu toi DUNG
luc printf dang chay, tuc rat hiem va khong tai hien duoc.
Do la ly do handler chi duoc goi ham ASYNC-SIGNAL-SAFE: ham
khong dung khoa hay trang thai toan cuc co the dang bi luong
chinh dung do. */Async-signal-safe
Một hàm an toàn để gọi từ bên trong bộ xử lý tín hiệu. Nó không dùng khoá dùng chung, không dùng trạng thái toàn cục có thể đang bị dở dang, nên gọi nó giữa chừng một hàm khác không gây hỏng.
#Hàm async-signal-safe
| Trong handler | Được phép | Vì sao |
|---|---|---|
| write | Có | Syscall trực tiếp, không đệm, không khoá |
| _exit | Có | Thoát ngay, không xả bộ đệm |
| read, close | Có | Syscall trực tiếp |
| signal, kill, raise | Có | Async-signal-safe |
| Gán sig_atomic_t | Có | Nguyên tử |
| printf, fprintf | KHÔNG | Dùng khoá và bộ đệm stdio |
| malloc, free | KHÔNG | Dùng khoá heap, có thể đang bị giữ |
| exit | KHÔNG | Chạy atexit, xả bộ đệm, không an toàn |
| strcpy, sprintf | KHÔNG, một số bản | Có thể dùng locale hoặc trạng thái |
#Vì sao printf nguy hiểm
sigbad.c, printf trong handler
#include <stdio.h>
#include <signal.h>
static void xu_ly(int s) {
(void)s;
printf("[trong handler] KHONG an toan!\n"); /* SAI: printf khong an toan */
}
int main(void) {
signal(SIGINT, xu_ly);
raise(SIGINT);
printf("[main] sau raise\n");
return 0;
}terminal
gcc -O2 -Wall -Wextra sigbad.c -o sigbad.exe && ./sigbad.exe
[trong handler] KHONG an toan! [main] sau raise
#Mẫu đúng
Handler chỉ đặt cờ, main xử lý
#include <signal.h>
#include <unistd.h>
static volatile sig_atomic_t co_tin_hieu = 0;
static void handler(int s) {
(void)s;
co_tin_hieu = 1; /* CHI dat co */
/* Neu that su can in trong handler, dung write (an toan): */
/* write(STDERR_FILENO, "nhan tin hieu\n", 14); */
}
int main(void) {
struct sigaction sa = {0};
sa.sa_handler = handler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_RESTART;
sigaction(SIGINT, &sa, NULL);
while (1) {
if (co_tin_hieu) { /* xu ly o vong lap chinh: AN TOAN */
don_dep();
break;
}
lam_viec();
}
return 0;
}Làm việc trong handler
static void handler(int s) {
(void)s;
printf("dang thoat...\n"); /* khong an toan */
dong_moi_ket_noi(); /* dung khoa, malloc, khong an toan */
luu_du_lieu(); /* ghi tep phuc tap, khong an toan */
exit(0); /* chay atexit, khong an toan */
}
Chỉ đặt cờ
static void handler(int s) {
(void)s;
co_tin_hieu = 1; /* DUY NHAT dieu an toan */
}
/* Don dep o vong lap chinh, noi moi ham deu an toan: */
if (co_tin_hieu) {
dong_moi_ket_noi();
luu_du_lieu();
break;
}
#Ống tự đánh thức
Tự làm thử
- Gọi
printftrong cả main và handler, gửi tín hiệu liên tục, và quan sát chương trình hỏng. - Viết lại theo mẫu handler chỉ đặt cờ và xác nhận nó không hỏng.
- In một thông điệp từ handler dùng
writethay vìprintf. - Bắt
SIGSEGV, in backtrace bằngbacktrace_symbols_fd, rồi_exit. - Đọc
man 7 signal-safetyvà liệt kê mười hàm an toàn. - Cài kỹ thuật self-pipe để tín hiệu đánh thức một vòng lặp
select.
Trình chấm điểm tự động sẽ được bổ sung ở giai đoạn sau. Hiện tại bạn tự chạy thử trên máy.
Tóm tắt
- Handler chạy giữa chừng mọi thứ, nên chỉ được gọi hàm async-signal-safe.
write,_exit,kill, gánsig_atomic_tlà an toàn;printf,malloc,exitthì không.printftrong handler chạy đúng phần lớn thời gian và chỉ hỏng khi trùng thời điểm, nên là lỗi cực khó tìm.- Mẫu an toàn: handler chỉ đặt cờ, việc thật làm ở vòng lặp chính; nếu phải in thì dùng
write. - Self-pipe hoặc
signalfdkết hợp tín hiệu với vòng lặp sự kiện để thoát sạch một server.