有些乱。。。凑合看把
0. 可靠信号机制原理:
0.0 当一个信号的出现时, 我们说信号"触发"了.
0.1 我们可以为一个进程指定某个信号相应的 handler.
有三种特殊的handler : SIG_DFL SIG_IGN SIG_ERR 分别代表: 默认, 忽略, 报错 这三种handler
0.2 当进程收到信号并执行完相应 handler 之后意味着信号已经"发送完毕".
0.3 信号处于"触发"后到"发送完毕"之前的时期, 叫做pending .
0.4 对于某个信号,我们可以进行 block 和 unblock 操作, 注意这跟忽略该信号是不一样的.
0.5 在进程 block 某个信号之后, 该进程的这个信号可以由进程自身或其他进程"触发"一次或多次, 在实时信号被触发多次的情况下, 这些实时信号在内核维护下形成一个多级优先队列按先后顺序转发至接受进程:
0.5.1 按照信号的 signal number 大小排序:
小的优先级高排前面, 大的优先级低排后面.
0.5.2 其次按照信号产生的时间先后对具有相同 signal number 的多个不同信号排序:
先产生的在前面, 后产生的在后面.
0.5.3 但同一时间某一进程既有传统信号又有实时信号时, 他们由内核发送的顺序是未定的.
0.6 一个信号一旦产生, 除非目标进程终止或退出之前一直被block, 否则迟早被接受.不存在传统 UNIX 信号系统的不可靠的缺点.
0.7 可以通过调用 sigqueue() 触发信号, 同时还可以在首发进程之间传递一个整型数或一个指针
0.8 注意信号 handler 可以中断系统调用和库函数调用, 而且可以在是内核态中断.
1. 数据结构:
1.1 信号集合: bit vector, 每一个位对应一个信号是否 enable
# define _SIGSET_NWORDS (1024 / (8 * sizeof (unsigned long int)))
typedef struct
{
unsigned long int __val[_SIGSET_NWORDS];
} __sigset_t;
typedef __sigset_t sigset_t;
1.2 信号集合操作:
#include <signal.h>
sigset_t set;
int sigemptyset(sigset_t *set);
int sigfillset(sigset_t *set);
int sigaddset(sigset_t *set, int signum);
int sigdelset(sigset_t *set, int signum);
int sigismember(const sigset_t *set, int signum);
返回值:
除sigismember外:
成功: 0
出错: -1
sigismember:
是 : 1
否 : 0
出错: -1
1.2.1 sigset_t 类型应该在初次使用之前调用sigemptyset() 或 sigfillset() 先初始化.
1.3 操作进程当前使用信号集合 ( 进程忽略在 mask 集合中的信号 )
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
how: {SIG_BLOCK, SIG_UNBLOCK, SIG_SETMASK}
返回值:
成功: 0
出错: -1
1.4 为特定信号绑定 handler
int sigaction(int signum, const struct sigaction *act,
struct sigaction *oldact);
返回值:
成功: 0
出错: -1
1.4.1 数据结构:
struct sigaction {
void (*sa_handler)(int);
void (*sa_sigaction)(int , siginfo_t *, void *);
sigset_t sa_mask;
int sa_flags;
void (*sa_restorer)(void);
};
注意:
1.4.1.0 signum 为除SIGKILL和SIGSTOP外的所有信号.
1.4.1.1 不可同时指定元素sa_handler 和sa_sigaction(某些架构机器上, 这俩者属于一个union ).
1.4.1.2 sa_handler 可为宏 SIG_DFL:绑定该信号的系统默认handler, SIG_IGN: 忽落该信号.
1.4.1.3 sa_restorer 现已过时, 不应再使用. POSIX 中并未定义此元素.
1.4.1.4 可通过第2个参数为NULL来查询当前使用的 handler.
1.4.1.5 可通过指定第2和第3个参数为NULL来校验指定的信号在该机器上是否可以使用.
1.4.1.6 sa_mask 指定在handler处理信号期间,需要block的信号集合.此外触发该handler 的
信号将在handler处理信号期间被屏蔽, 除非在sa_flags中使用了SA_NODEFER 标志.
注意不可屏蔽
1.4.1.7 当在元素sa_flags中使用了 SA_SIGINFO 时, 注册 sa_sigaction 为信号处理 handler,
而不是 sa_handler
sa_sigaction 使用三个参数:
第1个参数,整型, 该信号的整数值;
第2个参数, siginfo_t 指针类型, ;
第3个参数,ucontext_t(转换为void 指针)类型.
注意: siginfo_t 包含许多可能的属性,需要针对不同的信号选取对其有意义的属性.
属性如下:
siginfo_t {
/* 对于所有类型信号: */
int si_signo; /* Signal number */
int si_errno; /* An errno value ( Linux 中通常未使用此属性 )*/
int si_code; /* Signal 的整型值, 而非 bit mask , 表示发生信号的原因 */
/*见 1.4.1.8 描述可能的取值, 以及相应的原因 */
union /* 针对不同的信号产生方式, 传递不同信息给 sa_sigaction */
{
int _pad[__SI_PAD_SIZE];
/* 进程调用 kill() 发送. */
struct
{
__pid_t si_pid; /* Sending process ID. */
__uid_t si_uid; /* Real user ID of sending process. */
} _kill;
/*计时器 timers (POSIX.1b) . */
struct
{
int si_tid; /* Timer ID. */
int si_overrun; /* Timer Overrun count. */
sigval_t si_sigval; /* Signal value. */
} _timer;
/*实时信号 (POSIX.1b). */
struct
{
__pid_t si_pid; /* Sending process ID. */
__uid_t si_uid; /* Real user ID of sending process. */
sigval_t si_sigval; /* 发送信号的进程传来的数据. 参考 sigqueue(3p) */
/* 子类型: */
/* typedef union sigval */
/* { */
/* int sival_int; // */
/* void *sival_ptr; // */
/* } sigval_t; */
} _rt;
/* SIGCHLD(通常由子进程退出引起) */
struct
{
__pid_t si_pid; /* Which child. */
__uid_t si_uid; /* Real user ID of sending process. */
int si_status; /* Exit value or signal. */
__clock_t si_utime; /* User time consumed */
__clock_t si_stime; /* System time consumed */
} _sigchld;
/* SIGILL, SIGFPE, SIGSEGV, SIGBUS. */
struct
{
void *si_addr; /* Memory location which caused fault */
} _sigfault;
/* SIGPOLL. */
struct
{
long int si_band; /* Band event for SIGPOLL. */
int si_fd; /* File descriptor */
} _sigpoll;
} _sifields;
}
1.4.1.8 关于siginfo_t 类型中 si_code 可取的值, 以及原因.
下面是对于一个任何类型的信号, si_code 可取的值及产生该信号的原因:
SI_USER kill(2) or raise(3)
SI_KERNEL Sent by the kernel.
SI_QUEUE sigqueue(2)
SI_TIMER POSIX timer expired
SI_MESGQ POSIX 消息队列状态改变 (since Linux 2.6.6); see mq_notify(3)
SI_ASYNCIO AIO 完成
SI_SIGIO queued SIGIO
SI_TKILL tkill(2) or tgkill(2) (since Linux 2.4.19)
下面是对于一个 SIGCHLD 信号, si_code 中可取的值和原因:
CLD_EXITED child has exited
CLD_KILLED child was killed
CLD_DUMPED child terminated abnormally
CLD_TRAPPED traced child has trapped
CLD_STOPPED child has stopped
CLD_CONTINUED stopped child has continued (since Linux 2.6.9)
下面是对于一个 SIGPOLL 信号, si_code 中可取的值和原因:
POLL_IN data input available
POLL_OUT output buffers available
POLL_MSG input message available
POLL_ERR i/o error
POLL_PRI high priority input available
POLL_HUP device disconnected
下面是对于一个 SIGILL 信号, si_code 中可取的值和原因:
ILL_ILLOPC illegal opcode
ILL_ILLOPN illegal operand
ILL_ILLADR illegal addressing mode
ILL_ILLTRP illegal trap
ILL_PRVOPC privileged opcode
ILL_PRVREG privileged register
ILL_COPROC coprocessor error
ILL_BADSTK internal stack error
下面是对于一个 SIGFPE 信号, si_code 中可取的值和原因:
FPE_INTDIV integer divide by zero
FPE_INTOVF integer overflow/
FPE_FLTDIV floating-point divide by zero
FPE_FLTOVF floating-point overflow
FPE_FLTUND floating-point underflow
FPE_FLTRES floating-point inexact result
FPE_FLTINV floating-point invalid operation
FPE_FLTSUB subscript out of range
下面是对于一个 SIGSEGV 信号, si_code 中可取的值和原因:
SEGV_MAPERR address not mapped to object
SEGV_ACCERR invalid permissions for mapped object
下面是对于一个 SIGBUS 信号, si_code 中可取的值和原因:
BUS_ADRALN invalid address alignment
BUS_ADRERR nonexistent physical address
BUS_OBJERR object-specific hardware error
下面是对于一个 SIGTRAP 信号, si_code 中可取的值和原因:
TRAP_BRKPT process breakpoint
TRAP_TRACE process trace trap
1.4.2 注意事项:
根据POSIX标准, 进程在忽略不是由 kill(2) 或raise(3) 产生的 SIGFPE, SIGILL, 或 SIGSEGV
信号时, 其行为是未定义的 . 整数除零将产生未定义结果. 在某些架构机器上将产生一个
SIGFPE 信号 (同样大多负整型数除 -1 也可能产生 SIGFPE.)忽略该信号可能导致死循环.
1.5 发送信号:
int sigqueue(pid_t pid, int signo, const union sigval value);
成功: 0
出错: -1
1.5.1 sigqueue 函数会立即返回.
1.5.1.1如果 pid 进程调用sigaction函数为 signo 指定的信号绑定handler 时使用了
SA_SIGINFO 标志,并且系统有相应的资源, 则该信号将加入系统的信号队列中, 并可
以被pid 号进程访问.
1.5.1.2若未使用 SA_SIGINFO 标志, 则至少向 pid 号进程发送一次 signo 信号. 而且此时对
于是否会向 pid 进程发送 value 参数是未定的.
1.5.1.3 注意:
在多线程环境中, ?一个线程调用 sigqueue 向所属进程发送信号,
并且该线程中并未block该信号,
还有同一进程内也没有其他的线程要处理这个信号(其它线程block掉了该信号, 或虽
然没有block该信号,但也没有使用 sigwait()来等待该信号),
则:
在本次 sigqueue 调用返回前, 就会向调用线程发送该信号.
1.6 检查是否有 pending 的信号
int sigpending(sigset_t *set);
成功: 返回0 , 并将 pending 信号集合的 mask 写入地址 set 中.
出错: -1
1.6.1 因为 set 为 mask ,而不是直接的集合.
所以, 在判断一个具体的信号是否有pending 的信号时应该如下使用,以SIGSTOP 为例:
sigset_t set;
Hsigemptyset(&set);
Hsigpending(&set);
if( !sigismember( &set, signum) ) printf("There is pending SIGSTOP signals" );
1.7 等待信号, 挂起线程:
int sigwait(const sigset_t *set, int *sig);
返回值:
成功: 0 .
出错:大于零的一个错误值.
1.7.1 挂起调用线程并等待, 直到收到一个在指定集合 set 中的信号时返回, 同时从pending 的
信号?列表中移除本次收到的这个信号, 并返回该信号数值到第二个参数 sig 中.
1.7.2 sigwait() 和 sigwaitinfo() 的区别在于:
sigwait()只根据 signal number 来识别需要等待的信号并返回本次信号的signal number.
sigwaitinfo() 则可以根据 siginfo_t 来识别需要等待的信号并返回本次信号的siginfo_t.
就是说 sigwaitinfo() 可以精确的指定需要等待的信号类型, 但也更加重量级
1.8 等待信号, 挂起调用进程:
int sigwaitinfo(const sigset_t *set, siginfo_t *info);
int sigtimedwait(const sigset_t *set, siginfo_t *info,
const struct timespec *timeout);
返回值:
成功: 本次信号的 signal number (大于零). 并从pending 信号列表中移除本次信号.
出错: -1.
而且当sigtimedwait() 在设定时间内未接受到信号会设置 errono 为 EAGAIN .
1.8.1 sigtimedwait() 除了多使用了一个超时参数 timeout 外, 其他都和sigwaitinfo 一样.
struct timespec {
long tv_sec; /* seconds */
long tv_nsec; /* nanoseconds */
}
当参数timeout 中两个参数都指定为 0 时,进行 poll 操作, 即 sigtimedwait()立即返回,并且:
在检查信号queue之前, 其中已有要等待的信号, 立即返回该信号的 siginto_t 信息.
queue 中一个也没有所要等待的信号时, 立即返回error.
1.9 等待信号, 挂起调用进程,:
int sigsuspend(const sigset_t *mask);
返回值:
成功: -1, 通常 errno 还被置为 EINTR .
若该进程挂起后, 接受的是终止类型的信号则该进程被终止.
接受的是非终止类型的信号, 则该进程执行相应的handler.
所以这个函数返回值很怪异.
出错: errno 被置为 EFAULT ( 参考 sigsuspend(3p) );
1.8.1 注意:
通常 sigsuspend 会和sigprocmask 一起使用. 这样可以预防在程序的关键代码段执行期
间受某些信号影响:
1.8.1.1 首先进程在执行关键代码之前调用sigprocmask 来blocks 某些信号. 并且将
执行block 操作之前的信号集合保存到 sigprocmask 的 oldset 中.
1.8.1.2 其次在关键代码执行完后, 该进程调用 sigsuspend 并传递之前调用
sigprocmask时保存在它的第三个参数 oldset 中的信号集合给 sigsuspend.