1. 程式人生 > >Linux訊號 二 訊號處理函式註冊

Linux訊號 二 訊號處理函式註冊

每一個訊號都有一個訊號處理函式,可以是SIG_IGN, SIG_DFL或者是使用者自定義的處理函式。使用使用者自定義的處理函式需要註冊,註冊介面有如下兩種。

第一種是signal呼叫

#include <signal.h>

/**
 *  sighandler_t是GNU的擴充套件,如果在glibc下面使用的話,編譯的時候需要加上-D_GNU_SOURCE
 *  或者手動定義
 */
typedef void (*sighandler_t)(int);

/**
 *  為訊號signum註冊訊號處理函式handler
 *  成功返回該訊號之前的處理函式,失敗返回SIG_ERR並將失敗原因填寫到errno中
 */
sighandler_t signal(int signum, sighandler_t handler);

使用signal呼叫會有相容性問題,尤其是移植到其它UNIX系統上,所以推薦使用第二種訊號註冊函式sigaction,該函式功能相對signal而言,能夠提供更多功能。

#include <signal.h>
/**
 *  註冊訊號處理函式,成功返回0,失敗返回-1並置errno
 *  引數act儲存待註冊的訊號處理函式結構體
 *  如果oldact非空的話,舊的訊號處理函式會儲存到該結構體中
 */
int sigaction(int signum, const struct sigaction *act,
                     struct sigaction *oldact);

struct sigaction {
    void     (*sa_handler)(int);
    void     (*sa_sigaction)(int, siginfo_t *, void *);
    sigset_t   sa_mask;
    int        sa_flags;
    void     (*sa_restorer)(void);
};

該結構在註冊訊號處理函式sigaction中使用
1. sa_handler是一個引數為訊號值的處理函式
2. sa_sigaction也是一個訊號處理函式,不過它有三個引數,能夠獲取到處訊號值以外更多
   資訊,當sa_flags中包含SA_SIGINFO標誌位的時候需要用到該函式。
3. sa_mask是訊號處理函式執行期間的遮蔽訊號集。就是說在訊號處理函式執行期間,遮蔽某
   些訊號。但是不是所有訊號都能夠被遮蔽,SIGKILL和SIGSTOP這兩個訊號就無法屏
   蔽,因為作業系統自身要能夠控制住程序。
4. sa_flags可以是下面這些值的集合:
   1. SA_NOCLDSTOP,
      這個標誌位只用於SIGCHLD訊號。父程序可以檢測子程序三個事件,子程序終止、
      子程序停止、子程序回覆。SA_NOCLDSTOP標誌位用於控制後兩個事件。即一旦父程序
      為SIGCHLD訊號設定了這個標誌位,那麼子程序停止和子程序恢復這兩件事情,就無需
      向父程序傳送SIGCHLD訊號

   2. SA_NOCLDWAIT
      這個標誌只用於SIGCHLD訊號,它可控制子程序終止時候的行為,如果父程序
      為SIGCHLD設定了SA_NOCLDWAIT標誌位,那麼子程序終止退出時,就不會進入殭屍
      狀態,而是直接自行了斷。但是對Linux而言,子程序仍然會發送SIGCHLD訊號,這
      點和上面的SA_NOCLDSTOP略有不同。

   3. SA_ONESHOT和SA_RESETHAND
      這兩個標誌位本質是一樣的,表示訊號處理函式是一次性的,訊號遞送出去以後,訊號
      處理函式便恢復成預設值SIG_DFL.

   4. SA_NODEFER和SA_NOMASK
      這兩個標誌位的作用是一樣的,訊號處理函式執行期間,不阻塞當前訊號。

   5. SA_RESTART
      這個標誌位表示,如果系統呼叫被訊號中斷,則不返回錯誤,而是自動重啟系統呼叫。

   6. SA_SIGINFO
      這個標誌位表示訊號傳送者會提供額外的資訊。這種情況下,訊號處理函式應該為
      三引數的函式。

 當sa_flags含有SA_SIGINFO的時候 ,需要使用帶三個引數的處理函式:

void
handler(int sig, siginfo_t *info, void *ucontext)
{
               ...
}

第一個引數 sig 為訊號值
第三個引數 ucontext,該結構體提供了程序上下文資訊,通常都不會使用到該引數,具體細節
可參考man     sigreturn

第二個引數 info 是一個siginfo_t型別的指標,包含了訊號更多的資訊。該結構體如下:

siginfo_t {
    int      si_signo;     /* 訊號值 */
    int      si_errno;     /* An errno value */

    int      si_code;      /* 訊號來源,可以通過該值來判斷訊號來源
                            * 可選值及含義
                            * SI_USER : 呼叫kill 或 raise的使用者程序
                            * SI_TKILL :呼叫tkill或tgkill的使用者程序
                            * SI_QUEUE : 呼叫sigqueue的使用者程序
                            * SI_MESGQ : 訊息到達POSIX訊息佇列
                            * SI_KERNEL : 核心產生的訊號
                            * SI_ASYNCIO : 非同步I/O操作完成
                            * SI_TIMER: POSIX定時器到期
                            */
    int      si_trapno;    /* Trap number that caused
                              hardware-generated signal
                              (unused on most architectures) */
    pid_t    si_pid;       /* 訊號傳送程序ID */
    uid_t    si_uid;       /* 訊號傳送程序這是使用者ID */
    int      si_status;    /* Exit value or signal */
    clock_t  si_utime;     /* User time consumed */
    clock_t  si_stime;     /* System time consumed */
    sigval_t si_value;     /* 使用sigqueue函式傳送訊號時攜帶的伴隨資料 */
    int      si_int;       /* POSIX.1b signal */
    void    *si_ptr;       /* POSIX.1b signal */
    int      si_overrun;   /* Timer overrun count;
                              POSIX.1b timers */
    int      si_timerid;   /* Timer ID; POSIX.1b timers */
    void    *si_addr;      /* Memory location which caused fault */
    long     si_band;      /* Band event (was int in
                              glibc 2.3.2 and earlier) */
    int      si_fd;        /* File descriptor */
    short    si_addr_lsb;  /* Least significant bit of address
                              (since Linux 2.6.32) */
    void    *si_lower;     /* Lower bound when address violation
                              occurred (since Linux 3.19) */
    void    *si_upper;     /* Upper bound when address violation
                              occurred (since Linux 3.19) */
    int      si_pkey;      /* Protection key on PTE that caused
                              fault (since Linux 4.6) */
    void    *si_call_addr; /* Address of system call instruction
                              (since Linux 3.5) */
    int      si_syscall;   /* Number of attempted system call
                              (since Linux 3.5) */
    unsigned int si_arch;  /* Architecture of attempted system call
                              (since Linux 3.5) */
}
上面的sigval_t結構體定義如下:
union sigval {
    int sival_int;
    void *sival_ptr;
}
通過指定sigqueue函式的第三個引數,可以傳遞給一個int值或者指標值個目標程序。考慮
到不同的程序有各自獨立的地址空間,傳遞指標到另一個程序幾乎沒有意義。