Làm cách nào để tránh sử dụng printf trong trình xử lý tín hiệu?


86

printfkhông được sử dụng lại, nó không được cho là an toàn khi sử dụng nó trong bộ xử lý tín hiệu. Nhưng tôi đã thấy rất nhiều mã ví dụ sử dụng printfcách này.

Vì vậy, câu hỏi của tôi là: khi nào chúng ta cần tránh sử dụng printftrong bộ xử lý tín hiệu và có đề xuất thay thế không?


12
Một câu trả lời đơn giản và không mấy hữu ích cho câu hỏi trong tiêu đề của bạn: Có thấy printfcuộc gọi đó trong trình phát tín hiệu đó không? Xóa đi.
Keith Thompson

6
Xin chào Yu Hao! Tôi nghĩ bạn sẽ thấy liên kết rất thú vị để đọc. "Sử dụng chức năng reentrant để xử lý tín hiệu an toàn hơn" Tôi đã đọc nó sau một thời gian dài, tôi muốn chia sẻ nhân tạo ở đây với bạn. Hy vọng bạn thích.
Grijesh Chauhan

Câu trả lời:


58

Bạn có thể sử dụng một số biến cờ, đặt cờ đó bên trong trình xử lý tín hiệu và dựa trên printf()hàm gọi cờ đó trong hàm main () hoặc phần khác của chương trình trong quá trình hoạt động bình thường.

Không an toàn khi gọi tất cả các chức năng, chẳng hạn như printftừ bên trong bộ xử lý tín hiệu. Một kỹ thuật hữu ích là sử dụng trình xử lý tín hiệu để đặt a flagvà sau đó kiểm tra điều đó flag từ chương trình chính và in thông báo nếu cần.

Lưu ý trong ví dụ bên dưới, bộ xử lý tín hiệu ding () đặt cờ alarm_firedthành 1 khi SIGALRM được bắt và trong alarm_firedgiá trị hàm chính được kiểm tra để gọi printf có điều kiện một cách chính xác.

static int alarm_fired = 0;
void ding(int sig) // can be called asynchronously
{
  alarm_fired = 1; // set flag
}
int main()
{
    pid_t pid;
    printf("alarm application starting\n");
    pid = fork();
    switch(pid) {
        case -1:
            /* Failure */
            perror("fork failed");
            exit(1);
        case 0:
            /* child */
            sleep(5);
            kill(getppid(), SIGALRM);
            exit(0);
    }
    /* if we get here we are the parent process */
    printf("waiting for alarm to go off\n");
    (void) signal(SIGALRM, ding);
    pause();
    if (alarm_fired)  // check flag to call printf
      printf("Ding!\n");
    printf("done\n");
    exit(0);
}

Tham khảo: Lập trình Linux cho Người mới bắt đầu, Tái bản lần thứ 4 , Trong cuốn sách này, chính xác mã của bạn được giải thích (những gì bạn muốn), Chương 11: Quy trình và Tín hiệu, trang 484

Ngoài ra, bạn cần đặc biệt chú ý khi viết các hàm xử lý vì chúng có thể được gọi là không đồng bộ. Đó là, một trình xử lý có thể được gọi vào bất kỳ thời điểm nào trong chương trình, không thể đoán trước được. Nếu hai tín hiệu đến trong một khoảng thời gian rất ngắn, một trình xử lý có thể chạy trong một trình xử lý khác. Và được coi là thực hành tốt hơn để khai báo volatile sigatomic_t, loại này luôn được truy cập nguyên tử, tránh sự không chắc chắn về việc ngắt truy cập vào một biến. (đọc: Truy cập dữ liệu nguyên tử và Xử lý tín hiệu để biết chi tiết hết hạn).

Đọc Định nghĩa bộ xử lý tín hiệu : để tìm hiểu cách viết một hàm xử lý tín hiệu có thể được thiết lập với signal()hoặc các sigaction()hàm.
Danh sách các chức năng được ủy quyền trong trang hướng dẫn sử dụng , gọi chức năng này bên trong bộ xử lý tín hiệu là an toàn.


18
Nó được coi là thực hành tốt hơn để khai báovolatile sigatomic_t alarm_fired;
Basile Starynkevitch


1
@GrijeshChauhan: nếu chúng tôi đang làm việc trong một mã sản phẩm, thì chúng tôi không thể gọi chức năng tạm dừng, luồng có thể ở bất kỳ đâu khi tín hiệu xảy ra, vì vậy trong trường hợp đó chúng tôi thực sự không biết nơi để giữ "if (alert_fired) printf (" Ding! \ n ");" trong mã.
Pankaj kushwaha

@pankajkushwaha có, bạn là chính xác, nó là đau khổ từ tình trạng chủng tộc
Grijesh Chauhan

@GrijeshChauhan, Có hai điều mà tôi không thể hiểu được. 1. Làm thế nào để bạn biết khi nào để kiểm tra cờ? Vì vậy, sẽ có nhiều điểm kiểm tra trong mã ở hầu hết mọi điểm để in. 2. Chắc chắn sẽ có các điều kiện đua trong đó tín hiệu có thể được gọi trước khi đăng ký tín hiệu hoặc tín hiệu có thể xảy ra sau điểm kiểm tra. Tôi nghĩ rằng điều này sẽ chỉ giúp ích cho bản in trong một số điều kiện nhưng không hoàn toàn giải quyết được vấn đề.
Darshan b

52

Vấn đề chính là nếu tín hiệu bị ngắt malloc()hoặc một số chức năng tương tự, trạng thái bên trong có thể tạm thời không nhất quán trong khi nó đang di chuyển các khối bộ nhớ giữa danh sách trống và danh sách đã sử dụng hoặc các hoạt động tương tự khác. Nếu mã trong trình xử lý tín hiệu gọi một hàm sau đó sẽ gọi malloc(), điều này có thể phá hỏng hoàn toàn việc quản lý bộ nhớ.

Tiêu chuẩn C có một quan điểm rất thận trọng về những gì bạn có thể làm trong trình xử lý tín hiệu:

ISO / IEC 9899: 2011 §7.14.1.1 signalChức năng

¶5 Nếu tín hiệu xảy ra không phải là kết quả của việc gọi hàm aborthoặc raise, hành vi không được xác định nếu trình xử lý tín hiệu đề cập đến bất kỳ đối tượng nào có thời lượng lưu trữ tĩnh hoặc luồng không phải là đối tượng nguyên tử không khóa ngoài việc gán giá trị đến một đối tượng được khai báo là volatile sig_atomic_thoặc trình xử lý tín hiệu gọi bất kỳ hàm nào trong thư viện chuẩn ngoài aborthàm, _Exithàm, quick_exithàm hoặc signalhàm có đối số đầu tiên bằng số tín hiệu tương ứng với tín hiệu gây ra lệnh gọi người xử lý. Hơn nữa, nếu một lệnh gọi signalhàm như vậy dẫn đến SIG_ERRtrả về, giá trị của errnolà không xác định. 252)

252) Nếu bất kỳ tín hiệu nào được tạo ra bởi trình xử lý tín hiệu không đồng bộ, hành vi đó là không xác định.

POSIX hào phóng hơn rất nhiều về những gì bạn có thể làm trong trình xử lý tín hiệu.

Khái niệm Tín hiệu trong ấn bản POSIX 2008 cho biết:

Nếu quá trình là đa luồng hoặc nếu quá trình là một luồng và một trình xử lý tín hiệu được thực thi khác với kết quả của:

  • Việc kêu gọi quá trình abort(), raise(), kill(), pthread_kill(), hoặc sigqueue()để tạo ra một tín hiệu mà không bị chặn

  • Một tín hiệu đang chờ xử lý đang được bỏ chặn và được gửi trước khi cuộc gọi đã bỏ chặn nó trở lại

hành vi không được xác định nếu bộ xử lý tín hiệu đề cập đến bất kỳ đối tượng nào khác errnovới thời lượng lưu trữ tĩnh ngoài việc gán giá trị cho một đối tượng được khai báo là volatile sig_atomic_t, hoặc nếu bộ xử lý tín hiệu gọi bất kỳ hàm nào được xác định trong tiêu chuẩn này ngoài một trong các hàm được liệt kê trong bảng sau.

Bảng sau đây xác định một tập hợp các chức năng sẽ không đồng bộ-tín hiệu-an toàn. Do đó, các ứng dụng có thể gọi chúng, không hạn chế, từ các chức năng bắt tín hiệu:

_Exit()             fexecve()           posix_trace_event() sigprocmask()
_exit()             fork()              pselect()           sigqueue()
…
fcntl()             pipe()              sigpause()          write()
fdatasync()         poll()              sigpending()

Tất cả các chức năng không có trong bảng trên được coi là không an toàn đối với tín hiệu. Khi có tín hiệu, tất cả các chức năng được xác định bởi tập này của POSIX.1-2008 sẽ hoạt động như được xác định khi được gọi từ hoặc bị ngắt bởi một chức năng bắt tín hiệu, với một ngoại lệ duy nhất: khi một tín hiệu ngắt một chức năng không an toàn và tín hiệu- hàm bắt gọi một hàm không an toàn, hành vi là không xác định.

Các hoạt động nhận giá trị errnovà các hoạt động gán giá trị errnosẽ là không đồng bộ-tín hiệu-an toàn.

Khi một tín hiệu được gửi tới một luồng, nếu hành động của tín hiệu đó chỉ định kết thúc, dừng hoặc tiếp tục, thì toàn bộ quá trình sẽ được kết thúc, dừng hoặc tiếp tục tương ứng.

Tuy nhiên, nhóm printf()hàm đáng chú ý không có trong danh sách đó và có thể không được gọi an toàn từ bộ xử lý tín hiệu.

Bản cập nhật POSIX 2016 mở rộng danh sách các chức năng an toàn để bao gồm, đặc biệt, một số lượng lớn các chức năng <string.h>, đây là một bổ sung đặc biệt có giá trị (hoặc là một sự giám sát đặc biệt khó chịu). Danh sách bây giờ là:

_Exit()              getppid()            sendmsg()            tcgetpgrp()
_exit()              getsockname()        sendto()             tcsendbreak()
abort()              getsockopt()         setgid()             tcsetattr()
accept()             getuid()             setpgid()            tcsetpgrp()
access()             htonl()              setsid()             time()
aio_error()          htons()              setsockopt()         timer_getoverrun()
aio_return()         kill()               setuid()             timer_gettime()
aio_suspend()        link()               shutdown()           timer_settime()
alarm()              linkat()             sigaction()          times()
bind()               listen()             sigaddset()          umask()
cfgetispeed()        longjmp()            sigdelset()          uname()
cfgetospeed()        lseek()              sigemptyset()        unlink()
cfsetispeed()        lstat()              sigfillset()         unlinkat()
cfsetospeed()        memccpy()            sigismember()        utime()
chdir()              memchr()             siglongjmp()         utimensat()
chmod()              memcmp()             signal()             utimes()
chown()              memcpy()             sigpause()           wait()
clock_gettime()      memmove()            sigpending()         waitpid()
close()              memset()             sigprocmask()        wcpcpy()
connect()            mkdir()              sigqueue()           wcpncpy()
creat()              mkdirat()            sigset()             wcscat()
dup()                mkfifo()             sigsuspend()         wcschr()
dup2()               mkfifoat()           sleep()              wcscmp()
execl()              mknod()              sockatmark()         wcscpy()
execle()             mknodat()            socket()             wcscspn()
execv()              ntohl()              socketpair()         wcslen()
execve()             ntohs()              stat()               wcsncat()
faccessat()          open()               stpcpy()             wcsncmp()
fchdir()             openat()             stpncpy()            wcsncpy()
fchmod()             pause()              strcat()             wcsnlen()
fchmodat()           pipe()               strchr()             wcspbrk()
fchown()             poll()               strcmp()             wcsrchr()
fchownat()           posix_trace_event()  strcpy()             wcsspn()
fcntl()              pselect()            strcspn()            wcsstr()
fdatasync()          pthread_kill()       strlen()             wcstok()
fexecve()            pthread_self()       strncat()            wmemchr()
ffs()                pthread_sigmask()    strncmp()            wmemcmp()
fork()               raise()              strncpy()            wmemcpy()
fstat()              read()               strnlen()            wmemmove()
fstatat()            readlink()           strpbrk()            wmemset()
fsync()              readlinkat()         strrchr()            write()
ftruncate()          recv()               strspn()
futimens()           recvfrom()           strstr()
getegid()            recvmsg()            strtok_r()
geteuid()            rename()             symlink()
getgid()             renameat()           symlinkat()
getgroups()          rmdir()              tcdrain()
getpeername()        select()             tcflow()
getpgrp()            sem_post()           tcflush()
getpid()             send()               tcgetattr()

Kết quả là bạn sẽ sử dụng write()mà không có sự hỗ trợ định dạng do printf()et al cung cấp hoặc bạn kết thúc việc thiết lập một cờ mà bạn kiểm tra (định kỳ) ở những vị trí thích hợp trong mã của bạn. Kỹ thuật này được thể hiện rất rõ trong câu trả lời của Grijesh Chauhan .


Chức năng tiêu chuẩn C và tín hiệu an toàn

chqrlie hỏi một câu hỏi thú vị mà tôi không có nhiều hơn một câu trả lời:

Làm thế nào mà hầu hết các hàm chuỗi từ <string.h>hoặc các hàm lớp ký tự <ctype.h>và nhiều hàm thư viện chuẩn C khác không có trong danh sách trên? Việc triển khai sẽ cần phải có mục đích xấu để làm strlen()cho cuộc gọi từ bộ xử lý tín hiệu không an toàn.

Đối với nhiều chức năng trong <string.h>, rất khó để hiểu tại sao họ không được tuyên bố an toàn async-tín hiệu, và tôi muốn đồng ý strlen()là một ví dụ tiêu biểu, cùng với strchr(), strstr()vv Mặt khác, các chức năng khác như strtok(), strcoll()strxfrm()khá phức tạp và không có khả năng là tín hiệu không đồng bộ an toàn. Bởi vì strtok()giữ lại trạng thái giữa các cuộc gọi, và bộ xử lý tín hiệu không thể dễ dàng biết liệu một số phần của mã đang sử dụng strtok()có bị rối hay không. Các hàm strcoll()strxfrm()hoạt động với dữ liệu nhạy cảm với ngôn ngữ và việc tải ngôn ngữ liên quan đến tất cả các loại cài đặt trạng thái.

Các hàm (macro) từ <ctype.h>tất cả đều nhạy cảm với ngôn ngữ và do đó có thể gặp phải các vấn đề tương tự như strcoll()strxfrm().

Tôi cảm thấy khó hiểu tại sao các hàm toán học từ <math.h>không phải là tín hiệu không đồng bộ an toàn, trừ khi đó là vì chúng có thể bị ảnh hưởng bởi SIGFPE (ngoại lệ dấu phẩy động), mặc dù khoảng thời gian duy nhất tôi thấy một trong những hàm đó là đối với số nguyên. chia cho số không. Sự không chắc chắn tương tự phát sinh từ <complex.h>, <fenv.h><tgmath.h>.

Một số chức năng trong <stdlib.h>có thể được miễn trừ - abs()ví dụ. Những người khác đặc biệt có vấn đề: malloc()và gia đình là những ví dụ điển hình.

Một đánh giá tương tự có thể được thực hiện đối với các tiêu đề khác trong Tiêu chuẩn C (2011) được sử dụng trong môi trường POSIX. (Tiêu chuẩn C rất hạn chế nên không quan tâm đến việc phân tích chúng trong môi trường Tiêu chuẩn C. thuần túy.) Những người được đánh dấu là 'phụ thuộc vào ngôn ngữ' là không an toàn vì thao tác với ngôn ngữ có thể yêu cầu cấp phát bộ nhớ, v.v.

  • <assert.h>- Có lẽ không an toàn
  • <complex.h>- Có thể an toàn
  • <ctype.h> - Không an toàn
  • <errno.h> - An toàn
  • <fenv.h>- Có lẽ không an toàn
  • <float.h> - Không có chức năng
  • <inttypes.h> - Chức năng nhạy cảm với ngôn ngữ (không an toàn)
  • <iso646.h> - Không có chức năng
  • <limits.h> - Không có chức năng
  • <locale.h> - Chức năng nhạy cảm với ngôn ngữ (không an toàn)
  • <math.h>- Có thể an toàn
  • <setjmp.h> - Không an toàn
  • <signal.h> - Được phép
  • <stdalign.h> - Không có chức năng
  • <stdarg.h> - Không có chức năng
  • <stdatomic.h>- Có thể an toàn, có thể không an toàn
  • <stdbool.h> - Không có chức năng
  • <stddef.h> - Không có chức năng
  • <stdint.h> - Không có chức năng
  • <stdio.h> - Không an toàn
  • <stdlib.h> - Không phải tất cả đều an toàn (một số được phép; số khác thì không)
  • <stdnoreturn.h> - Không có chức năng
  • <string.h> - Không phải tất cả đều an toàn
  • <tgmath.h>- Có thể an toàn
  • <threads.h>- Có lẽ không an toàn
  • <time.h>- Phụ thuộc vào ngôn ngữ (nhưng time()được phép rõ ràng)
  • <uchar.h> - Phụ thuộc vào ngôn ngữ
  • <wchar.h> - Phụ thuộc vào ngôn ngữ
  • <wctype.h> - Phụ thuộc vào ngôn ngữ

Việc phân tích các tiêu đề POSIX sẽ… khó hơn vì có rất nhiều trong số chúng và một số chức năng có thể an toàn nhưng nhiều chức năng sẽ không… nhưng cũng đơn giản hơn vì POSIX cho biết chức năng nào là an toàn tín hiệu không đồng bộ (không nhiều chức năng trong số chúng). Lưu ý rằng một tiêu đề như <pthread.h>có ba chức năng an toàn và nhiều chức năng không an toàn.

NB: Hầu hết việc đánh giá các hàm và tiêu đề C trong môi trường POSIX là phỏng đoán bán giáo dục. Không có nghĩa là một tuyên bố dứt khoát từ một cơ quan tiêu chuẩn.


Tại sao hầu hết các hàm chuỗi <string.h>hoặc các hàm lớp ký tự <ctype.h>và nhiều hàm thư viện chuẩn C khác không có trong danh sách trên? Việc triển khai sẽ cần phải có mục đích xấu để làm strlen()cho cuộc gọi từ bộ xử lý tín hiệu không an toàn.
chqrlie

@chqrlie: câu hỏi thú vị - hãy xem bản cập nhật (không có cách nào để đưa nhiều điều đó vào nhận xét một cách hợp lý).
Jonathan Leffler

Cảm ơn bạn đã phân tích sâu. Về <ctype.h>nội dung, đó là ngôn ngữ cụ thể và có thể gây ra sự cố nếu tín hiệu làm gián đoạn chức năng cài đặt ngôn ngữ, nhưng khi ngôn ngữ được tải, việc sử dụng chúng sẽ an toàn. Tôi đoán, trong một số tình huống phức tạp, việc tải dữ liệu ngôn ngữ có thể được thực hiện tăng dần, do đó làm cho các chức năng <ctype.h>không an toàn. Kết luận vẫn là: Khi nghi ngờ, hãy kiêng.
chqrlie

@chqrlie: Tôi đồng ý rằng đạo đức của câu chuyện nên là Khi nghi ngờ, hãy tiết chế . Đó là một bản tóm tắt hay.
Jonathan Leffler

13

Làm thế nào để tránh sử dụng printftrong bộ xử lý tín hiệu?

  1. Luôn luôn tránh nó, sẽ nói: Chỉ cần không sử dụng printf()trong bộ xử lý tín hiệu.

  2. Ít nhất trên các hệ thống tuân theo POSIX, bạn có thể sử dụng write(STDOUT_FILENO, ...)thay thế printf(). Tuy nhiên, việc định dạng có thể không dễ dàng: In int từ trình xử lý tín hiệu bằng cách sử dụng các chức năng ghi hoặc không an toàn


1
Alk Always avoid it.nghĩa là gì? Tránh né printf()?
Grijesh Chauhan

2
@GrijeshChauhan: Có, vì OP đã hỏi khi nào nên tránh sử dụng printf()trong bộ xử lý tín hiệu.
alk

Alk +1 cho 2điểm, kiểm tra OP yêu cầu Làm thế nào để tránh sử dụng printf()trong bộ xử lý tín hiệu?
Grijesh Chauhan

7

Với mục đích gỡ lỗi, tôi đã viết một công cụ xác minh rằng trên thực tế bạn chỉ đang gọi các hàm trong async-signal-safedanh sách và in thông báo cảnh báo cho mỗi hàm không an toàn được gọi trong ngữ cảnh tín hiệu. Mặc dù nó không giải quyết được vấn đề muốn gọi các hàm không an toàn từ ngữ cảnh tín hiệu, nhưng ít nhất nó cũng giúp bạn tìm ra các trường hợp mà bạn đã vô tình làm như vậy.

Mã nguồn trên GitHub . Nó hoạt động bằng cách quá tải signal/sigaction, sau đó tạm thời chiếm quyền điều khiển các PLTmục nhập của các chức năng không an toàn; điều này khiến các lệnh gọi đến các chức năng không an toàn được chuyển hướng đến một trình bao bọc.



1

Triển khai tín hiệu không snprintf("%dđồng bộ của riêng bạn và sử dụngwrite

Nó không tệ như tôi nghĩ, Làm thế nào để chuyển đổi một int thành chuỗi trong C? có một số cách triển khai.

Vì chỉ có hai loại dữ liệu thú vị mà bộ xử lý tín hiệu có thể truy cập:

  • sig_atomic_t quả cầu
  • int đối số tín hiệu

điều này về cơ bản bao gồm tất cả các trường hợp sử dụng thú vị.

Thực tế strcpylà tín hiệu an toàn cũng làm cho mọi thứ trở nên tốt hơn.

Chương trình POSIX bên dưới sẽ in ra để đếm số lần nó nhận được SIGINT cho đến nay, mà bạn có thể kích hoạt bằng Ctrl + Cvà ID tín hiệu.

Bạn có thể thoát khỏi chương trình với Ctrl + \(SIGQUIT).

C chính:

#define _XOPEN_SOURCE 700
#include <assert.h>
#include <limits.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <unistd.h>

/* Calculate the minimal buffer size for a given type.
 *
 * Here we overestimate and reserve 8 chars per byte.
 *
 * With this size we could even print a binary string.
 *
 * - +1 for NULL terminator
 * - +1 for '-' sign
 *
 * A tight limit for base 10 can be found at:
 * /programming/8257714/how-to-convert-an-int-to-string-in-c/32871108#32871108
 *
 * TODO: get tight limits for all bases, possibly by looking into
 * glibc's atoi: /programming/190229/where-is-the-itoa-function-in-linux/52127877#52127877
 */
#define ITOA_SAFE_STRLEN(type) sizeof(type) * CHAR_BIT + 2

/* async-signal-safe implementation of integer to string conversion.
 *
 * Null terminates the output string.
 *
 * The input buffer size must be large enough to contain the output,
 * the caller must calculate it properly.
 *
 * @param[out] value  Input integer value to convert.
 * @param[out] result Buffer to output to.
 * @param[in]  base   Base to convert to.
 * @return     Pointer to the end of the written string.
 */
char *itoa_safe(intmax_t value, char *result, int base) {
    intmax_t tmp_value;
    char *ptr, *ptr2, tmp_char;
    if (base < 2 || base > 36) {
        return NULL;
    }

    ptr = result;
    do {
        tmp_value = value;
        value /= base;
        *ptr++ = "ZYXWVUTSRQPONMLKJIHGFEDCBA9876543210123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"[35 + (tmp_value - value * base)];
    } while (value);
    if (tmp_value < 0)
        *ptr++ = '-';
    ptr2 = result;
    result = ptr;
    *ptr-- = '\0';
    while (ptr2 < ptr) {
        tmp_char = *ptr;
        *ptr--= *ptr2;
        *ptr2++ = tmp_char;
    }
    return result;
}

volatile sig_atomic_t global = 0;

void signal_handler(int sig) {
    char key_str[] = "count, sigid: ";
    /* This is exact:
     * - the null after the first int will contain the space
     * - the null after the second int will contain the newline
     */
    char buf[2 * ITOA_SAFE_STRLEN(sig_atomic_t) + sizeof(key_str)];
    enum { base = 10 };
    char *end;
    end = buf;
    strcpy(end, key_str);
    end += sizeof(key_str);
    end = itoa_safe(global, end, base);
    *end++ = ' ';
    end = itoa_safe(sig, end, base);
    *end++ = '\n';
    write(STDOUT_FILENO, buf, end - buf);
    global += 1;
    signal(sig, signal_handler);
}

int main(int argc, char **argv) {
    /* Unit test itoa_safe. */
    {
        typedef struct {
            intmax_t n;
            int base;
            char out[1024];
        } InOut;
        char result[1024];
        size_t i;
        InOut io;
        InOut ios[] = {
            /* Base 10. */
            {0, 10, "0"},
            {1, 10, "1"},
            {9, 10, "9"},
            {10, 10, "10"},
            {100, 10, "100"},
            {-1, 10, "-1"},
            {-9, 10, "-9"},
            {-10, 10, "-10"},
            {-100, 10, "-100"},

            /* Base 2. */
            {0, 2, "0"},
            {1, 2, "1"},
            {10, 2, "1010"},
            {100, 2, "1100100"},
            {-1, 2, "-1"},
            {-100, 2, "-1100100"},

            /* Base 35. */
            {0, 35, "0"},
            {1, 35, "1"},
            {34, 35, "Y"},
            {35, 35, "10"},
            {100, 35, "2U"},
            {-1, 35, "-1"},
            {-34, 35, "-Y"},
            {-35, 35, "-10"},
            {-100, 35, "-2U"},
        };
        for (i = 0; i < sizeof(ios)/sizeof(ios[0]); ++i) {
            io = ios[i];
            itoa_safe(io.n, result, io.base);
            if (strcmp(result, io.out)) {
                printf("%ju %d %s\n", io.n, io.base, io.out);
                assert(0);
            }
        }
    }

    /* Handle the signals. */
    if (argc > 1 && !strcmp(argv[1], "1")) {
        signal(SIGINT, signal_handler);
        while(1);
    }

    return EXIT_SUCCESS;
}

Biên dịch và chạy:

gcc -std=c99 -Wall -Wextra -o main main.c
./main 1

Sau khi nhấn Ctrl + C mười lăm lần, thiết bị đầu cuối hiển thị:

^Ccount, sigid: 0 2
^Ccount, sigid: 1 2
^Ccount, sigid: 2 2
^Ccount, sigid: 3 2
^Ccount, sigid: 4 2
^Ccount, sigid: 5 2
^Ccount, sigid: 6 2
^Ccount, sigid: 7 2
^Ccount, sigid: 8 2
^Ccount, sigid: 9 2
^Ccount, sigid: 10 2
^Ccount, sigid: 11 2
^Ccount, sigid: 12 2
^Ccount, sigid: 13 2
^Ccount, sigid: 14 2

nơi 2là số tín hiệu cho SIGINT.

Đã thử nghiệm trên Ubuntu 18.04. GitHub ngược dòng .


0

Một kỹ thuật đặc biệt hữu ích trong các chương trình có vòng lặp chọn là ghi một byte đơn xuống đường ống khi nhận tín hiệu và sau đó xử lý tín hiệu trong vòng lặp chọn. Điều gì đó dọc theo những dòng này (xử lý lỗi và các chi tiết khác bị bỏ qua cho ngắn gọn) :

static int sigPipe[2];

static void gotSig ( int num ) { write(sigPipe[1], "!", 1); }

int main ( void ) {
    pipe(sigPipe);
    /* use sigaction to point signal(s) at gotSig() */

    FD_SET(sigPipe[0], &readFDs);

    for (;;) {
        n = select(nFDs, &readFDs, ...);
        if (FD_ISSET(sigPipe[0], &readFDs)) {
            read(sigPipe[0], ch, 1);
            /* do something about the signal here */
        }
        /* ... the rest of your select loop */
    }
}

Nếu bạn quan tâm đó là tín hiệu nào, thì byte xuống đường ống có thể là số tín hiệu.


-1

Bạn có thể sử dụng printf trong bộ xử lý tín hiệu nếu bạn đang sử dụng thư viện pthread. unix / posix chỉ định rằng printf là nguyên tử cho các chủ đề cf Dave Butenhof trả lời tại đây: https://groups.google.com/forum/#!topic/comp.programming.threads/1-bU71nYgqw Lưu ý rằng để có hình ảnh rõ ràng hơn của đầu ra printf, bạn nên chạy ứng dụng của mình trong một bảng điều khiển (trên linux, hãy sử dụng ctl + alt + f1 để khởi động bảng điều khiển 1), thay vì một tty giả do GUI tạo ra.


3
Bộ xử lý tín hiệu không chạy trong một số luồng riêng biệt, chúng chạy trong ngữ cảnh của luồng đang chạy khi xảy ra ngắt tín hiệu. Câu trả lời này hoàn toàn sai.
itaych
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.