Linux Signal 示例

信號是系統響應某些條件而產生的一個事件,接收到該信的進程作出相應的處理。一般信是由錯誤產生的,如段錯誤(SIGSEGV)。 但信還能夠做爲進程間通訊的一種方式,由一個進程發送給另外一個進程。html

信號定義在 signal.h 文件中,以 SIG 做爲開頭,可用 kill -l 命令查看,詳細信息參見 man 7 signallinux

信號處理

信號能夠經過 signalsigaction 函數來註冊處理, signal 函數是 struct sigactionsa_handler 的一種便捷實現。git

signal 函數

原型:github

void (*signal(int sig, void (*func)(int)))(int);

其中 sig 是須要捕獲的 signal number, 後一個是捕獲到信號後的處理函數指針,因此處理函數的原型必須是 void func(int) ,簡單的代碼示例以下:api

#include <stdio.h>
#include <string.h>
#include <signal.h>

static void
handler(int sig)
{
        printf("Recieved signal: %d\n", sig);
}

int
main(int argc, char *argv[])
{
        signal(SIGINT, handler);

        printf("Caught SIGINT, input 'quit' to exit...\n");
        // wait signal caught
        char buf[1024] = {0};
        while (1) {
                printf("Please input: ");
                scanf("%s", buf);
                if (strcmp(buf, "quit") == 0) {
                        break;
                }
        }
        printf("Exit...\n");
        return 0;
}

另外 api 中也提供了下面 2 個特殊的 handler:app

  • SIG_IGN

    忽略此信號socket

  • SIG_DFL

    恢復此信號的默認行爲函數

sigaction 函數

原型:ui

int sigaction(int sig, const struct sigaction *restrict act,
           struct sigaction *restrict oact);

其中 sigsignal number, act 指定信號的處理行爲, oact 若是不爲 NULL 則返回信號以前的處理行爲。線程

struct sigaction 的主要成員以下:

類型 名稱 描述
void(*) (int) sa_handler 處理函數指針,同 signal 函數中的 func 參數
sigset_t sa_mask 信號屏蔽字,是指當前被阻塞的一組信號,不能被當前進程收到
int sa_flags 處理行爲修改器,指明哪一種處理函數生效,詳見下文
void(*) (int, siginfo_t *, void *) sa_sigaction 處理函數指針,僅 sa_flags == SA_SIGINFO 時有效

其中 sa_flags 主要能夠設置爲如下值:

  • SA_NOCLDSTOP

    子進程中止時不產生 SIGCHLD 信號

  • SA_RESETHAND

    將信號的處理函數在處理函數的入口重置爲 SIG_DFL

  • SA_RESTART

    重啓可中斷的函數而不是給出 EINTR 錯誤

  • SA_SIGINFO

    使用 sa_sigaction 作爲信號的處理函數

  • SA_NODEFER

    捕獲到信號時不將它添加到信號屏蔽字中

簡單的代碼示例以下:

#include <stdio.h>
#include <string.h>
#include <signal.h>

#define SIG SIGINT

static void
sig_handler(int sig, siginfo_t *si, void *data)
{
        printf("Caught signal: %d\n", sig);
        printf("Sender pid: %d\n", si->si_pid);
        printf("Sender uid: %d\n", si->si_uid);
}

static int
sig_caught(int sig)
{
        printf("Start caught signal: %d\n", sig);
        struct sigaction sa;
        sa.sa_flags = SA_SIGINFO;
        sa.sa_sigaction = sig_handler;
        sigemptyset(&sa.sa_mask);
        int ret = sigaction(sig, &sa, NULL);
        if (ret == -1) {
                printf("Failed to caught signal: %d\n", sig);
                return -1;
        }

        return 0;
}

int
main(int argc, char *argv[])
{
        if (sig_caught(SIG) == -1) {
                return -1;
        }

        printf("Caught signal(%d), input 'quit' to exit...\n", SIG);
        char buf[1024] = {0};
        while(1) {
                printf("Please input: ");
                scanf("%s", buf);
                if (strcmp(buf, "quit") == 0) {
                        break;
                }
        }
        printf("Exit...\n");
        return 0;
}

信號屏蔽字

考慮一下這種狀況:在 signal()/sigaction() 返回以前進程就已經收到了須要處理的信號,此時進程會以默認行爲來處理,這顯然不符合咱們的指望。 這時就須要用到信號屏蔽字了,在進程啓動時就將須要處理的信號加入的屏蔽字中,等 signal()/sigaction() 返回後再解除屏蔽,解除屏蔽後至少會將收到的待處理信號發送一個給進程。

屏蔽字用到一下函數:

int sigemptyset(sigset_t *set);
int sigaddset(sigset_t *set, int signo);
int sigprocmask(int how, const sigset_t *restrict set,
           sigset_t *restrict oset);

sigprocmaskset 爲須要設置的屏蔽字集, oset 爲以前的屏蔽字集, how 控制着 set 如何生效,可設置爲如下值:

  • SIG_BLOCK

    該進程的屏蔽字集將爲當期屏蔽字集與 set 的並集, set 中包含了須要屏蔽的信號集

  • SIG_UNBLOCK

    該進程的屏蔽字集將爲當期屏蔽字集與 set 的補集的交集, set 中包含了須要解除屏蔽的信號集

  • SIG_SETMASK

    該進程的屏蔽字集將設置爲 set 的值

簡單的設置流程以下:

int
sig_block(int sig, int how)
{
        sigset_t mask;
        sigemptyset(&mask)
        sigaddset(&mask, sig);
        sigprocmask(how, &mask, NULL);
}

信號發送

信號能夠經過 kill 函數發送給指定進程,也能夠經過 raise 或者 alarm 函數發送給當前執行的線程或進程,下面來分別說說這幾個函數。

kill

原型:

int kill(pid_t pid, int sig);

kill 函數向指定進程發送指定的信號,若是信號爲 0 將執行錯誤檢查,信號並不會發送,能夠用來檢查 pid 的有效性。

pid 大於 0 時信號將發送給此進程, pid 小於等於 0 時,以下:

  • 等於 0

    信號將發送給發送者所在組裏的全部進程

  • 等於 -1

    信號將發送給全部進程

  • 小於 -1

    信號將發送給進程組爲 pid 絕對值的全部組內進程

alarm

原型:

unsigned alarm(unsigned seconds);

alarm 函數將在指定的 seconds 以後發送一個 SIGALRM 信號,若是 seconds 爲 0, 則取消以前的定時器請求。若是不爲 0 則取消以前的請求,從新設置爲 seconds 。 若是在等待結束以前有其餘的事件產生,那定時器請求也將被取消。

簡單的代碼示例以下:

#include <stdio.h>
#include <unistd.h>
#include <signal.h>

static void
handler(int sig)
{
        printf("alarm arrived: %d\n", sig);
}

int
main(int argc, char *argv[])
{
        signal(SIGALRM, handler);

        alarm(2);

        sleep(2);
        printf("alarm 5s over\n");

        alarm(10);
        sleep(1);

        unsigned int remaining = alarm(3);
        printf("alarm 10s remain: %u, reset to 3\n", remaining);
        sleep(3);
        printf("alarm 3s over\n");

        alarm(20);
        sleep(3);

        remaining = alarm(0);
        printf("cancel alarm 20s, remian: %u, exit...\n", remaining);
}

raise

原型:

int raise(int sig);

raise 函數將給當前執行的線程或進程發送信號,若是信號處理函數已經被調用, raise 函數將等待信號處理函數調用結束才返回。

結語

信號處理函數是會被重複調用的,因此必要保存其是可重入的,注意處理邏輯。

另外本文中的代碼都在 signal 中,這個 repo 也有其它的示例,有興趣的能夠看看。

附錄

信號表

/* ISO C99 signals.  */
#define    SIGINT        2    /* Interactive attention signal.  */
#define    SIGILL        4    /* Illegal instruction.  */
#define    SIGABRT        6    /* Abnormal termination.  */
#define    SIGFPE        8    /* Erroneous arithmetic operation.  */
#define    SIGSEGV        11    /* Invalid access to storage.  */
#define    SIGTERM        15    /* Termination request.  */

/* Historical signals specified by POSIX. */
#define    SIGHUP        1    /* Hangup.  */
#define    SIGQUIT        3    /* Quit.  */
#define    SIGTRAP        5    /* Trace/breakpoint trap.  */
#define    SIGKILL        9    /* Killed.  */
#define SIGBUS        10    /* Bus error.  */
#define    SIGSYS        12    /* Bad system call.  */
#define    SIGPIPE        13    /* Broken pipe.  */
#define    SIGALRM        14    /* Alarm clock.  */

/* New(er) POSIX signals (1003.1-2008, 1003.1-2013).  */
#define    SIGURG        16    /* Urgent data is available at a socket.  */
#define    SIGSTOP        17    /* Stop, unblockable.  */
#define    SIGTSTP        18    /* Keyboard stop.  */
#define    SIGCONT        19    /* Continue.  */
#define    SIGCHLD        20    /* Child terminated or stopped.  */
#define    SIGTTIN        21    /* Background read from control terminal.  */
#define    SIGTTOU        22    /* Background write to control terminal.  */
#define    SIGPOLL        23    /* Pollable event occurred (System V).  */
#define    SIGXCPU        24    /* CPU time limit exceeded.  */
#define    SIGXFSZ        25    /* File size limit exceeded.  */
#define    SIGVTALRM    26    /* Virtual timer expired.  */
#define    SIGPROF        27    /* Profiling timer expired.  */
#define    SIGUSR1        30    /* User-defined signal 1.  */
#define    SIGUSR2        31    /* User-defined signal 2.  */

/* Nonstandard signals found in all modern POSIX systems
   (including both BSD and Linux).  */
#define    SIGWINCH    28    /* Window size change (4.3 BSD, Sun).  */

/* Archaic names for compatibility.  */
#define    SIGIO        SIGPOLL    /* I/O now possible (4.2 BSD).  */
#define    SIGIOT        SIGABRT    /* IOT instruction, abort() on a PDP-11.  */
#define    SIGCLD        SIGCHLD    /* Old System V name */

/* Not all systems support real-time signals.  bits/signum.h indicates
   that they are supported by overriding __SIGRTMAX to a value greater
   than __SIGRTMIN.  These constants give the kernel-level hard limits,
   but some real-time signals may be used internally by glibc.  Do not
   use these constants in application code; use SIGRTMIN and SIGRTMAX
   (defined in signal.h) instead.  */
#define __SIGRTMIN    32
#define __SIGRTMAX    __SIGRTMIN

/* Biggest signal number + 1 (including real-time signals).  */
#define _NSIG        (__SIGRTMAX + 1)
相關文章
相關標籤/搜索