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

猜你喜欢

转载自blog.csdn.net/fuyuande/article/details/83927202