1. 程式人生 > >Linux訊號 四 非同步等待訊號與同步等待訊號介面

Linux訊號 四 非同步等待訊號與同步等待訊號介面

訊號的同步等待和非同步等待區別就是訊號處理函式的執行與否,非同步等待是訊號處理函式已經執行了,同步等待是訊號處理函式還沒有執行。

非同步等待介面:pause() 和 sigsuspend()

1. pause()

/**
 * 等待訊號
 * pause()函式將呼叫程序/執行緒掛起,使之進入可中斷的睡眠狀態,直到傳遞了一個訊號為止。
 * 這個訊號的動作或者是執行使用者定義的訊號處理函式,或者是終止程序。如果執行使用者定義的
 * 訊號處理函式,pause()會在訊號處理函式執行完畢後返回,如果是終止程序,pause()函式
 * 就不返回了,如果核心發出的訊號被忽略,那麼就不會被喚醒。
 * 
 * 成功返回-1,並置errno為EINTER.
 *
 * pause函式並不能區分是什麼訊號觸發中斷,所以不能使用pause函式來等待特定的訊號。
 */
#include <unistd.h>
int pause(void);

2. sigsuspend()

/**
 * 等待特定訊號
 *
 * sigsuspend暫時使用引數mask替換呼叫程序/執行緒的訊號掩碼,並且阻塞直到特定訊號
 * 發生。如果訊號終結了程序,sigsuspend函式不返回,如果訊號被捕獲,則sigsuspend
 * 會等到訊號處理函式執行完成後才返回,並且呼叫程序/執行緒的訊號掩碼會恢復到呼叫之前
 * 的樣子。
 * 訊號SIGKILL 和 SIGSTOP是無法阻塞的。
 * 返回值-1,錯誤碼可以是EINTR(訊號中斷)或者EFAULT(引數錯誤)
 *
 */
#include <signal.h>
int sigsuspend(const sigset_t *mask);

同步訊號接收介面有兩類:

1. sigwait() 、sigwaitinfo()、sigtimedwait(),這三個函式介面略有差異,做的事情類似。都是等待特定訊號,如果沒有訊號則掛起當前程序,有的話立即返回。

/**
 * 等待訊號發生
 * 
 * 成功返回0,並將訊號值儲存到引數set中,失敗返回一個正的錯誤碼。
 * 
 */
#include <signal.h>
int sigwait(const sigset_t *set, int *sig);

 

/**
 * 等待訊號返回
 * 
 * 當引數第二個siginfo_t不為空時,核心會將關於該訊號更詳細的資訊儲存到該指標指向地址,
 * 如果有多個訊號滿足條件,sigwaitinfo只會返回其中一個。
 * 成功返回訊號值,失敗返回-1並置errno
 *
*/
#include <signal.h>
int sigwaitinfo(const sigset_t *set, siginfo_t *info);

/**
 * 和sigwaitinfo功能一樣,只是多了一個時間引數,超時未等到訊號立即返回,
 * 如果時間引數timeout為NULL,則和sigwaitinfo一樣。
 * 如果timeout兩個引數為0,那麼sigtimedwait會立即返回。
 *
 */

struct timespec {
    long    tv_sec;         /* seconds */
    long    tv_nsec;        /* nanoseconds */
}

int sigtimedwait(const sigset_t *set, siginfo_t *info,
                        const struct timespec *timeout);

通常呼叫上述介面前都需要先呼叫sigprocmask介面將關注的訊號遮蔽,防止被訊號處理函式劫走。

2. Linux還提供了另外一種同步等待訊號介面 signalfd

/**
 *  建立一個用於接收訊號的檔案描述符。
 *
 *  引數fd = -1時,該函式會建立一個檔案描述符。
 *      fd != -1,表示修改操作,一般是修改mask的值,此時fd是之前signalfd的
 *      返回值
 *
 *  引數mask表示訊號集,關注的訊號集合。這些訊號的集合應該在呼叫signalfd函式
 *      之前,先呼叫sigprocmask函式阻塞這些訊號,防止被訊號處理函式劫走。
 *
 *  引數flags用來控制行為,目前支援的標誌位如下:
 *      SFD_CLOEXEC :和普通檔案的O_CLOEXEC一樣,呼叫exec函式時,檔案描述符
 *  會被關閉。
 *      SFD_NONBLOCK : 控制將來的讀取操作,如果執行read操作時,並沒有訊號到來,
 *  則立即返回失敗,並設定errno為EAGAIN。
 *
 *  成功返回檔案描述符fd,失敗返回-1並置errno
 *
 *  返回的描述符fd可用於poll,epoll,select等函式,用來檢測描述符fd上面的可讀
 *  事件。
 *  
 *  建立檔案描述符後,可以使用read函式來讀取到來的訊號。提供的緩衝區大小最少要
 *  放下一個signalfd_siginfo結構體,該結構體如下,如果有多個訊號返回,read會
 *  返回多個該結構體大小的位元組數。
 */
#include <sys/signalfd.h>
int signalfd(int fd, const sigset_t *mask, int flags);

struct signalfd_siginfo {
    uint32_t ssi_signo;    /* Signal number */
    int32_t  ssi_errno;    /* Error number (unused) */
    int32_t  ssi_code;     /* Signal code */
    uint32_t ssi_pid;      /* PID of sender */
    uint32_t ssi_uid;      /* Real UID of sender */
    int32_t  ssi_fd;       /* File descriptor (SIGIO) */
    uint32_t ssi_tid;      /* Kernel timer ID (POSIX timers)
    uint32_t ssi_band;     /* Band event (SIGIO) */
    uint32_t ssi_overrun;  /* POSIX timer overrun count */
    uint32_t ssi_trapno;   /* Trap number that caused signal */
    int32_t  ssi_status;   /* Exit status or signal (SIGCHLD) */
    int32_t  ssi_int;      /* Integer sent by sigqueue(3) */
    uint64_t ssi_ptr;      /* Pointer sent by sigqueue(3) */
    uint64_t ssi_utime;    /* User CPU time consumed (SIGCHLD) */
    uint64_t ssi_stime;    /* System CPU time consumed
                                         (SIGCHLD) */
    uint64_t ssi_addr;     /* Address that generated signal
                                         (for hardware-generated signals) */
    uint16_t ssi_addr_lsb; /* Least significant bit of address
                                         (SIGBUS; since Linux 2.6.37)
    uint8_t  pad[X];       /* Pad size to 128 bytes (allow for
                                         additional fields in the future) */
};

signalfd建立的檔案描述符使用完成之後要呼叫close函式來關閉該檔案描述符:

/* 關閉檔案描述符 */
close(fd);

參考資料:

1. 《Linux環境程式設計,從應用到核心》 高峰,李彬著

2. man signalfd : http://www.man7.org/linux/man-pages/man2/signalfd.2.html