sigblock(), sigwait(), sigprocmask(), signalfd

今天朋友問我說: 被 sigblock() 的 signal 能用 sigwait() 收到被 block 的 signal 嗎?
答案是: 可以的.

被 sigblock() 的 signal 會處於 pending 的狀態存在 queue 中, 如下例

/* sig-block-wait.c */
#include <signal.h>
#include <unistd.h>
#include <stdio.h>
#include <error.h>
#include <errno.h>

int main()
{
    sigblock(SIGINT);

    printf("press ctrl+c now!\n");
    usleep(3 * 1000 * 1000);

    sigset_t sigset;
    sigemptyset(&sigset);
    sigaddset(&sigset, SIGINT);

    int signum;
    if(sigwait(&sigset, &signum)) {
        perror("failed to sigwait: ");
    }

    printf("got signum: %d\n", signum);

    return 0;
}

首先, 我們 sigblock SIGINT, 也就是按下 ctrl+c 時會收到的 signal, 然後睡 3 秒~ 醒來後, 建立 signal mask, 告訴 sigwait() 要等待 SIGINT, 因為先前有一個 queue 著, 所以會立即返回, 印出 signum 後結束 process.

不過, sigblock() 不再被建議使用, 所以上面的程序可用 sigprocmask() 改寫如下

/* sig-mask-wait.c */
#include <signal.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <error.h>
#include <errno.h>

int main()
{
    sigset_t sigset;
    sigemptyset(&sigset);
    sigaddset(&sigset, SIGINT);
    if(sigprocmask(SIG_BLOCK, &sigset, NULL)) {
        perror("failed to block SIGINT: ");
        exit(1);
    }

    int signum;
    if(sigwait(&sigset, &signum)) {
        perror("failed to sigwait: ");
        exit(1);
    }

    printf("got signum: %d\n", signum);

    return 0;
}

以 select/poll/epoll 開發 non-blocking I/O 時, 如果使用如 sigwait(), sigpause() 等 function 時會讓 event loop 無法得到機會執行, 解決方式為改用 signalfd (usleep 則可以 timerfd 取代) 並加到 epoll wait list 中, 如下例

#include <signal.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <error.h>
#include <errno.h>
#include <sys/signalfd.h>
#include <sys/epoll.h>

int main()
{
    sigset_t sigset;
    sigemptyset(&sigset);
    sigaddset(&sigset, SIGINT);
    if(sigprocmask(SIG_BLOCK, &sigset, NULL)) {
        perror("failed to block SIGINT: ");
        exit(1);
    }

    int sigfd = signalfd(-1, &sigset, SFD_NONBLOCK);
    if(-1 == sigfd) {
        perror("failed to create signalfd: ");
        exit(1);
    }

    int epollfd = epoll_create1(0);
    if(-1 == epollfd) {
        perror("failed to create epollfd: ");
        exit(1);
    }

    struct epoll_event event = {
        EPOLLIN,
        { .fd = sigfd }
    };
    if(epoll_ctl(epollfd, EPOLL_CTL_ADD, sigfd, &event)) {
        perror("failed to add fd to epoll: ");
        exit(1);
    }

    int result = epoll_wait(epollfd, &event, 1, 5000);
    if(-1 == result) {
        perror("failed to wait on epoll: ");
        exit(1);
    }
    else if(0 == result) {
        printf("no ctrl+c trigered\n");
    }
    else {
        struct signalfd_siginfo siginfo;
        read(sigfd, &siginfo, sizeof(siginfo));
        printf("got signum: %d\n", siginfo.ssi_signo);
    }

    return 0;
}

以上 sample code 可在這裡下載
https://co-op.space:4443/derekdai/test-signal