Signals

如何正確使用 sigaction() 和 SA_RESTART?

  • July 26, 2020

我想開發一個 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返回更少的可用字節。為簡單起見,我沒有檢查這種情況。根據您要處理的事件源,您可能希望這樣做。

引用自:https://unix.stackexchange.com/questions/600348