
一、可靠性与不可靠性:
1. 不可靠信号
主要由以下两个问题导致不可靠问题的发生:
a. 进程每次处理信号后, 就会对信号的响应设置为默认动作;如果用户不希望这样 *** 作,就要在信号处理函数结尾再调用一次signal,进行重装。
b. 信号会丢失。
Linux支持不可靠信号,信号值小于SIGRTMIN的都是不可靠的, 但是做了改进,在调用后,不用重新安装函数,信号会自动重装。Linux下的不可靠信号主要指信号丢失。
2. 可靠信号:
信号值位于SIGRTMIN和SIGRTMAX之间的信号都是可靠信号,可靠信号克服了信号可能丢失的问题。Linux在支持新版本的信号安装函数sigaTIon()以及信号发送函数sigqueue()的同时,仍然支持早期的signal()信号安装函数,支持信号发送函数kill()。
注:不要有这样的误解:由sigqueue()发送、sigacTIon安装的信号就是可靠的。事实上,可靠信号是指后来添加的新信号(信号值位于SIGRTMIN及SIGRTMAX之间);不可靠信号是信号值小于SIGRTMIN的信号。信号的可靠与不可靠只与信号值有关,与信号的发送及安装函数无关。目前linux中的signal()是通过sigaTIon()函数实现的,因此,即使通过signal()安装的信号,在信号处理函数的结尾也不必再调用一次信号安装函数。同时,由signal()安装的实时信号支持排队,同样不会丢失。
二、实时信号与非实时信号
SIGRTMIN(31)往后的都是实时信号,前32号信号已经有了预定义值,有了特定的用途和含义。 后32个信号表示实时信号。
非实时信号都不支持排队,都是不可靠信号;实时信号都支持排队,都是可靠信号。
三、响应:
三种方式:1、忽略,不做任何处理。2、捕捉,并调用处理函数。 3、执行缺省动作。
四、发送函数:
常用的有kill(), raise(), sigqueue(), alarm(), seTItimer(),abort()。详见man手册...
五、信号的安装:
主要使用signal() 和 sigaction()函数。
1、signal()不支持信号传递信息,主要是用于前32种非实时信号的安装,而sigaction()是较新的函数(由两个系统调用实现:sys_signal以及sys_rt_sigaction),有三个参数,支持信号传递信息,主要用来与sigqueue()系统调用配合使用,当然,sigaction()支持非实时信号的安装,sigaction()优于signal()主要体现在支持信号带有参数。
2、两者都支持装填这样的处理函数:typedef void (*sighandler_t)(int);sigaction()还支持装填带有参数的处理函数:
void (*_sa_sigaction)(int,struct siginfo *, void *);下面是sigaction的结构体:
struct sigaction { union{ __sighandler_t _sa_handler; void (*_sa_sigaction)(int,struct siginfo *, void *); }_u sigset_t sa_mask; unsigned long sa_flags; void (*sa_restorer)(void); }很巧妙地使用union来支持两种不同的信号处理函数。带有参数的处理函数主要为实时信号准备,当然也支持旧的信号(0~31)。
再看看那个保存参数的结构体struct siginfo:
siginfo_t { int si_signo; /* 信号值,对所有信号有意义*/ int si_errno; /* errno值,对所有信号有意义*/ int si_code; /* 信号产生的原因,对所有信号有意义*/ union{ /* 联合数据结构,不同成员适应不同信号 */ //确保分配足够大的存储空间 int _pad[SI_PAD_SIZE]; //对SIGKILL有意义的结构 struct{ ... }... ... ... ... ... //对SIGILL, SIGFPE, SIGSEGV, SIGBUS有意义的结构 struct{ ... }... ... ... } }其中使用了一个_pad成员,为所有信号处理的结构体分配足够的空间,union会以成员中最大的那个来分配空间,这样处理我认为有这样的考虑:1、可以设定足够的大小,使得整个siginfo结构体对齐到cache line,提高cache命中。2、参数拷贝和传递时更加方便了,不管目前存放的参数是什么,有多大,要拷贝的话,直接使用_pad标签,一次性就可以全部拷贝过去。
那个sa_mask,是个很好用的东西,用于指定在信号处理程序执行的过程中,哪些喜好应当被阻塞。缺省情况下,当前信号本身被阻塞,防止信号的嵌套发送,除非指定SA_NODEFER或者SA_NOMASK标志位。这个标记位,在一定程度上解决了信号丢失的问题,被阻塞的信号并不是被丢失掉,而是在当前信号处理过之后,继续处理。只不过不打断当前的处理过程而已。
sa_flags中包含了许多标志位,包括刚刚提到的SA_NODEFER及SA_NOMASK标志位。另一个比较重要的标志位是SA_SIGINFO,当设定了该标志位时,表示信号附带的参数可以被传递到信号处理函数中,因此,应该为sigaction结构中的sa_sigaction指定处理函数,而不应该为sa_handler指定信号处理函数,否则,设置该标志变得毫无意义。即使为sa_sigaction指定了信号处理函数,如果不设置SA_SIGINFO,信号处理函数同样不能得到信号传递过来的数据,在信号处理函数中对这些信息的访问都将导致段错误(Segmentation fault)
信号作为异步通知,安全保障比较少, 在开发应用程序的时候,尽量不要使用信号来传递参数,
六、进一步讨论sigaction:信号集及信号 *** 作函数:
信号集其实是一个位图:
typedef struct {unsigned long sig[_NSIG_WORDS];} sigset_t;
linux所支持的所有信号可以全部或部分的出现在信号集中,主要与信号阻塞相关函数配合使用,支持以下的 *** 作函数来 *** 作信号集合:
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);sigemptyset(sigset_t *set)初始化由set指定的信号集,信号集里面的所有信号被清空;sigfillset(sigset_t *set)调用该函数后,set指向的信号集中将包含linux支持的64种信号;sigaddset(sigset_t *set, int signum)在set指向的信号集中加入signum信号;sigdelset(sigset_t *set, int signum)在set指向的信号集中删除signum信号;sigismember(const sigset_t *set, int signum)判定信号signum是否在set指向的信号集中。
七、信号阻塞与信号未决:
使用函数sigprocmask()阻塞信号的传递,只是延迟信号的到达。信号会在解除阻塞后继续传递。这种情况往往需要在信号程序和其它程序共享全局变量时,如果全局变量的类型不是sig_atomic_t类型,当一部分程序恰好读、写到变量过程中,产生某个信号,而信号程序里会改变该变量,那么就会产生混乱。为了避免这种混乱,提供程序的可靠性,必须在 *** 作这类变量前阻塞信号, *** 作完成后恢复信号的传递。
每个进程都有一个用来描述哪些信号递送到进程时将被阻塞的信号集,该信号集中的所有信号在递送到进程后都将被阻塞。我们称正在阻塞的信号的集合为信号掩码(signal mask)。每个进程都有自己的信号掩码,创建子进程时子进程将继承父进程的信号掩码。我们可以通过修改当前的信号掩码来改变信号的阻塞情况。
int sigprocmask(int how, const sigset_t *set,sigset_t *oldset),该函数用来检查和改变调用进程的信号掩码,其中的how参数指出信号掩码改变的方式,必须是下面的值之一:
SIG_BLOCK,阻塞set中包含的信号。意思是说把set中的信号加到当前的信号掩码中去,新的信号掩码是set和旧信号掩码的并集。
SIG_UNBLOCK,解除set中信号的阻塞,从当前信号掩码中去除set中的信号。
SIG_SETMASK,设置信号掩码,既按照set中的信号重新设置信号掩码。
最后一个参数是进程原来的信号集。如果你只需要改变信号的阻塞情况而不需要关心原来的值,可以传递NULL指针给函数。如果你希望什么也不改变,只是想获得当前信号掩码的信息,那么把set设置成NULL,old中返回当前的设置。
使用sigpending(sigset_t *set)可以获得当前已经送到进程,但是被阻塞的所有信号,在set指向的信号集中返回结果。是一个用于查询的函数,暂时没有想到好的用处。
sigsuspend(const sigset_t *mask);用于在接收到某个信号之前, 临时用mask替换进程的信号掩码, 并暂停进程执行,直到收到信号为止。sigsuspend 返回后将恢复调用之前的信号掩码。信号处理函数完成后,进程将继续执行。该系统调用始终返回-1,并将errno设置为EINTR。
signsuspend的 *** 作是原子的(要么都执行,要么一点都不执行),主要做了一下工作:
(1) 设置新的mask阻塞当前进程;
(2) 收到信号,调用该进程设置的信号处理函数;
(3) 待信号处理函数返回后,恢复原先mask;
(4) sigsuspend返回。
八、信号的生命周期:
1、信号"诞生"。信号的诞生指的是触发信号的事件发生(如检测到硬件异常、定时器超时以及调用信号发送函数kill()或sigqueue()等)。
2、信号在目标进程中"注册";进程的task_struct结构中有关于本进程中未决信号的数据成员:
[cpp] view plain copy
欢迎分享,转载请注明来源:内存溢出
微信扫一扫
支付宝扫一扫
评论列表(0条)