linux高性能服务器编程学习笔记九:信号

1、信号是由系统、用户或进程发送给目标进程的信息,以通知目标信息某个状态的改变或系统异常。Linux信号可由如下条件产生:

1)对于前台进程,可以输入某些特殊的终端字符来给它发送信号。(Ctrl+C给前台进程发送中断信号)

2)诸如越界访问或者浮点异常等系统异常

3)诸如alarm定时器到期引起SIGSLARM信号的系统状态变化

4)运行Kill命令或调用kill函数

2、发送信号

Linux下,一个进程给其它进程发送信号的APIkill函数

int kill(pid_t pid,intsig)

1)该函数把信号发送给目标进程;目标进程由pid参数指定,当pid>0时,表示信号发送给PIDpid的进程。pid=0时,表示信号发送给调用kill()函数的进程所在的进程组的所有进程。pid=-1时,表示将信号发送给除了init进程外的所有进程,前提是发送者拥有对目标进程发送信号的权限。pid<-1时,表示发送给进程组PID-pid的进程组中的所有进程。

2sig参数为0,表示kill函数不发送任何信号,因为linux定义的信号值都大于0。当将sig设置为0可以检测目标进程或进程组是否存在,不过一方面由于这种检测不是原子操作,另一方面由于进程PID的回绕,这种检测方法不是原子操作。

3)调用成功返回0,失败则返回-1并设置errno。几种可能的errno如表:

linux高性能服务器编程学习笔记九:信号

3、信号处理函数

typedef void(*_singlehandler_) (int)

(1)信号处理函数只带有一个整型参数,该参数用来指示信号类型。该函数是可重入的,因此在函数中不能调用一些不安全的函数。

(2)除了这种用户自定义的信号处理函数之外,还有两种其它处理方式—SIG_IGN和SIG_DFL。分表表示忽略目标信号和使用目标信号的默认处理方式。默认处理方式有以下几种:结束进程(TERM)、忽略信号(Ign)、结束进程并产生核心转存文件(Core)、暂停进程(Stop)以及继续进程(Cont)。

4、linux信号

linux高性能服务器编程学习笔记九:信号

linux高性能服务器编程学习笔记九:信号

由上表可知,不可捕捉或忽略的信号有两种:SIGKILLSIGSTOP

5、信号函数

1signal系统调用

_sighandler_ signal(intsig,_sighandler_ _handler_)

sig参数指定要捕获的信号类型,_handler_参数是_sighandler_类型的函数指针,用于指定信号处理函数,亦可以是SIG_DFLSIG_IGNsignal函数成功时返回一个_sighandler_类型的函数指针。出错返回SIG_ERR,并设置errno

2sigaction系统调用

int sigaction(int sig,const struct sigacion* act, struct sigaction* oact)

sig参数指定要捕获的信号类型,act参数指定新的信号处理方式,oact参数则输出先前的处理方式(如果不为NULL)。actoact都是sigaction结构体类型的指针。此结构体定义:

structsigaction

{

    #ifdef _USE_POSIX199309

       union

{

           _sighadler_ sa_handler;

           void (*sa_sigaction) (int,siginfo_t*, void*);

}

_sigaction_handler;

    #define sa_handler   _sigaction_handler.sa_handler

    #define sa_sigaction _sigaction_handler.sa_sigation

    #else

       _sighandler_t sa_handler;

    #endif

       _sigset_t sa_mask;

       int sa_flags;

       void (*sa_restorer) (void);

};

sa_mask成员指定信号处理函数。

sa_mask成员是在进程原有的信号掩码的基础上增加信号掩码以指定哪些信号不能发送给本进程。其类型为sigset_t结构体。其定义为:

#define_SIGSET_NWORDS   {1024/(8*sizeof(unsignedlong int))}

typedefstruct

{

    unsigned long int _val[_SIGSET_NWORDS];

}_sigset_t;

由定义可知,实际上其是一个长整型数组,每个元素的每个位表示一个信号,这种定义方式和文件描述符集fd_set类似,因此也提供了一组函数来设置、修改、删除和查询信号集:

linux高性能服务器编程学习笔记九:信号

我们除了在sigaction结构体的sa_mask成员设置进程的信号掩码之外,还可以调用sigprocmask函数设置进程的信号掩码

int sigprocmask(int_how,const sigset_t* _set,sigset_t* _oset);

_set指定新的信号掩码,_oset输出旧的信号掩码。而_how参数指定设置进程信号掩码的方式:

linux高性能服务器编程学习笔记九:信号

如果_setNULL,就是不设置新的信号掩码,但是可以通过_oset查看当前的信号掩码。sigprocmask成功返回0,失败则返回-1并设置errno

sa_flags成员用于设置程序收到信号时的行为,其可选值如下表

linux高性能服务器编程学习笔记九:信号

sigaction成功是返回0,失败返回-1并设置errno

3)被挂起的信号

当设置了进程信号掩码后,被屏蔽的信号将被系统挂起。假如取消对被挂起信号的屏蔽,则它会立刻被进程接收到。sigpending函数可以可以获得进程当前被挂起的信号集:

int sigpending(sigset_*set);

set参数用于保存被挂起的信号集。即使进程多次接收到同一个被挂起的信号,此函数也只能反映一次,并且再次使用sigprocmask函数使能该挂起的信号时,该信号的处理函数也只会被调用一次。

该函数成功时返回0,失败时返回-1并设置errno

4)我们要始终清楚地知道进程在每个运行时刻的信号掩码,以及如何适当的捕捉到信号。在多进程、多线程环境中,我们应当以进程、线程为单位来处理信号和信号掩码。并且不能设想新创建的子进程、线程具有和父进程、主线程完全相同的信号特征。比如,fork调用生成的子进程将继承父进程的信号掩码,但挂起信号集会被设置为空。

6、统一事件源

信号处理函数需要尽可能的执行完毕以确保信号不会被屏蔽(为了避免一些静态条件,信号在处理期间,系统不会再次触发它)太久。典型的解决方法是:把信号的主要处理逻辑放到程序的主循环中,信号处理函数只是简单的通知主循环程序接收到信号并把信号值传递给主循环。主循环再根据接收到的信号值执行目标信号对应的逻辑代码。信号处理函数通常使用管道将信号传递给主循环,为了让主循环知道管道上何时有数据可读,需要用到I/O复用系统调用来监听管道的读端文件描述符的可读事件。如此一来,信号事件就和其它I/O事件一样被处理,这就是统一事件源。LibeventI/O框架库和超级服务xinetd都统一处理信号和I/O事件。

7、网络编程相关信号

SIGPIPE

1)往一个读端关闭的管道或socket连接中写数据将引发SIGPIPE信号。引起SIGPIPE信号的写操作将设置errnoEPIPE。程序接收到SIGPIPE信号的默认行为是终止进程,我们绝对不希望因为错误的写操作而导致程序退出。因此应当在代码中捕获并处理该信号。

2)也可以使用send函数的MSG_NOSIGNAL标志来禁止写操作触发SIGPIPE信号,这种情况就可以使用send函数反馈的errno值来判断管道或socket连接的读端是否已经关闭。

3)我们也可以利用I/O复用系统调用来检测管道或socket连接的读端是否已经关闭,以poll为例,管道读端关闭时,写端文件描述符上的POLLHUP事件将被触发;当socket连接被对方关闭时,socket上的POLLRDHUP事件将被触发。

SIGURG

内核通知应用程序带外数据到达主要两种方法:I/O复用技术,select等系统调用在接收到带外数据时将返回,并向应用程序报告socket上的异常事件;另一种方法就是使用SIGURG信号。但应用程序检测到带外数据到达后,还需要进一步判断带外数据在数据流中的具体位置,才能够准确无误地读取带外数据。可以使用sockatmark系统调用解决这个问题,它判断一个socket是否处于带外标记。