1. 程式人生 > >Linux Signal 示例

Linux Signal 示例

訊號是系統響應某些條件而產生的一個事件,接收到該信的程序做出相應的處理。通常信是由錯誤產生的,如段錯誤(SIGSEGV)。 但信還可以作為程序間通訊的一種方式,由一個程序傳送給另一個程序。

訊號定義在 signal.h 檔案中,以 SIG 作為開頭,可用 kill -l 命令檢視,詳細資訊參見 man 7 signal

訊號處理

訊號可以通過 signalsigaction 函式來註冊處理, signal 函式是 struct sigactionsa_handler 的一種便捷實現。

signal 函式

原型:

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

其中 sig 是需要捕獲的 signal number, 後一個是捕獲到訊號後的處理函式指標,所以處理函式的原型必須是 void func(int) ,簡單的程式碼示例如下:

#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:

  • SIG_IGN

    忽略此訊號

  • SIG_DFL

    恢復此訊號的預設行為

sigaction 函式

原型:

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_tsa_mask訊號遮蔽字,是指當前被阻塞的一組訊號,不能被當前程序收到
intsa_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)