如何正確使用 sigaction() 和 SA_RESTART?
我想開發一個 C 程式碼,它將同時等待鍵盤輸入和鍵盤生成的信號,並且能夠使其他 I/O(帶有管道和文件)處於活動狀態。
我有一個最小的信號處理程序,它增加一個標誌(並且目前提供調試):
void Handler (int signo) { char msg[80]; ss->nTstp++; sprintf (msg, "\n%s .. Called Handler %d sig %d ..\n", TS(), ss->nTstp, signo); write (STDERR_FILENO, msg, strlen (msg)); }
我這樣設置了處理程序:
sigemptyset (& ss->sa.sa_mask); sigaddset (& ss->sa.sa_mask, SIGTSTP); ss->sa.sa_flags = SA_RESTART | SA_NOCLDSTOP; ss->sa.sa_handler = Handler; rc = sigaction (SIGTSTP, & ss->sa, NULL);
我的困境是信號被直接確認,但我的終端輸入自動重啟。在我按下 Enter 之前,我不會對我的處理程序標誌採取行動。
如果我不設置 SA_RESTART,我會在終端輸入上獲得 EINTR,但我相信我還必須在每個其他文件描述符上期待 EINTR(並且必須編寫可重新啟動的傳輸)。
有沒有辦法在特定的文件描述符上禁用 SA_RESTART,或者保證其他 fds 不會得到 EINTR?
如果我了解您的需求,您希望能夠處理 (1) 來自鍵盤的輸入、(2) 信號和 (3) 潛在的其他事件源。如果這是正確的,那麼這可能是你所追求的開始:
#include <errno.h> #include <poll.h> #include <signal.h> #include <stdio.h> #include <string.h> #include <unistd.h> static int pipe_fds[2]; static void handler(const int signo) { #define msg "received SIGTSTP\n" write(pipe_fds[1], msg, strlen(msg)); #undef msg } int main(void) { if (pipe(pipe_fds) < 0) { perror("pipe"); } struct sigaction sa = { .sa_handler = handler, .sa_flags = SA_RESTART | SA_NOCLDSTOP, }; if (sigemptyset(&sa.sa_mask) < 0) { perror("sigemptyset"); return 1; } if (sigaction(SIGTSTP, &sa, NULL) < 0) { perror("sigaction"); return 1; } struct pollfd fds[] = { { .fd = STDIN_FILENO, .events = POLL_IN, }, { .fd = pipe_fds[0], .events = POLL_IN, }, }; const int num_fds = 2; char buffer[1024] = {}; for (;;) { const int ret = poll(fds, num_fds, -1); if (ret < 0) { if (errno == EINTR) { // Receiving SIGTSTP can cause poll() to return // -1 with errno = EINTR. Ignore that. continue; } perror("poll"); return 1; } for (int i = 0; i < num_fds; ++i) { if (fds[i].revents & POLL_IN) { const int count = read(fds[i].fd, buffer, sizeof(buffer) - 1); buffer[count - 1] = '\0'; printf("Read '%s' from file descriptor %d\n", buffer, fds[i].fd); } } } return 0; }
該
main()
功能從創建管道開始;該程序使用管道在信號處理函式(在信號上下文中)和主程序之間進行通信。接下來,它為信號設置信號處理程序SIGTSTP
,就像您上面描述的那樣。之後,它會創建一個
struct pollfd
名為的數組fds
。該數組中的每個條目對應於程序對監視活動感興趣的文件描述符。數組中的第一個條目是標準輸入的文件描述符。第二個是上述管道的讀端。如果您想擴展此範例以處理其他事件源 — 與文件描述符關聯的事件 — 那麼這將是執行此操作的地方,只需fds
使用適當的文件描述符將其他元素添加到數組中。然後它使用
poll
. 隨著-1
超時,poll
將阻塞直到 (1) 它在已註冊的文件描述符之一上獲得活動或 (2) 信號中斷它(例如接收SIGTSTP
)。因此,程序會檢查 的返回值,poll
如果它小於 0,則顯式檢查並忽略EINTR
(被系統呼叫中斷)錯誤。如果
poll()
因為活動而返回,則revents
關聯的欄位struct pollfd
將被相應地標記。然後它將從關聯的文件描述符中讀取並列印一條消息。這是一個範例執行:
$ ./a.out Hello! Read 'Hello!' from file descriptor 0 How are you? Read 'How are you?' from file descriptor 0 ^ZRead 'received SIGTSTP' from file descriptor 3 Good Read 'Good' from file descriptor 0 ^C $
在範例執行中,它
Hello!
隨後How are you?
從鍵盤讀取。在這兩種情況下,程序都會通過讀取我輸入的內容並列印響應來做出響應。接下來,我生成了SIGTSTP
信號,它received SIGTSTP
從管道中讀取,並列印了響應。接下來,它Good
從鍵盤讀取並列印響應。最後我中斷了程序,Ctrl-C
程序終止了。請注意,可以
read
返回更少的可用字節。為簡單起見,我沒有檢查這種情況。根據您要處理的事件源,您可能希望這樣做。