一、对于程序来讲,有三种处理信号的方式:忽略信号不作处理、系统默认方式处理信号、自定义处理信号。(缺省是系统默认方式处理信号)
我们可以通过signal函数来设置某信号的处理方式。
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
signum值代表某信号
handler是一个函数指针:1、当其值是SIG_IGN时表示忽略此信号;2、SIG_DFL表示以系统默认方式处理;3、当其值是处理函数的地址时,则代表自定义处理,信号来时调用该函数。二、信号之间的中断:
1、信号之间会被打断,例如A信号处理过程中会被其它B信号打断。
2、系统是靠标记位来标记是否收到信号,这意味着进程只知道有无某个信号,而不知道某信号被触发多少次;在某信号被处理前,多少个信号都只算一个。
3、当某信号调用其处理函数的时候,标记位即被清除,但处理函数不会被同一个信号打断,例如:A信号来了,进入了A信号的处理函数,A信号的标记位被清零,此时又有N个新的A信号来到(如上进程只知道有新的A信号),但正在处理的A信号函数是不会被打断的,直到返回才能处理刚来的其它A信号。
三、信号问题
函数重入问题:重入的意思是函数调用期间被信号中断,而信号函数再次进入该函数。而不可重入的函数内部涉及到全局变量,强制重入有可能会造成混乱。
例如:printf函数,会修改全局变量stdout,如果强制冲入会导致输出混乱(中断前输出一部分,中断处理时输出令一部分)。
四、阻塞信号
信号是异步通知的,我们并不知道信号什么时候会发生。很经常的有些重要操作被信号中断会造成可怕的后果,所以我们希望在某些重要函数中能不会被信号打断。使用阻塞信号可以很好地解决这个问题。
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
像select函数一样,sigprocmask第二个参数set是一个标记位集合,每一个位代表一个信号。根据标记位来设置某信息是否阻塞。
第一个参数可以是:SIG_BLOCK,SIG_UNBLOCK,SIG_SETMASK。
SIG_BLOCK作用是将参数二set里标记位为1对应的信号设为阻塞。(原理是用参数二set和进程原有的set运行“或“运算)。
SIG_UNBLOCK作用是将参数二set里标记位为1对应的信号设为非阻塞。(原理是用参数二set补集和进程原有的set运行“与“运算)。
SIG_SETMASK作用是将参数二set里标记位为1对应的信号设为阻塞,为0的标记为非阻塞。(原理是直接将参数二set赋值给进程原有的set)。
第三个参数oldset用以保存进程原有的set。
另外signal.h提供现成的函数来对sigset_t标记集合进行操作:
int sigemptyset(sigset_t *set); //将set所有位清零
int sigfillset(sigset_t * set); //将set所有位置1
int sigaddset(sigset_t *set, int signum); //将set中signum位'置1
int sigdelset(sigset_t *set, int signum); //将set中signum位'置0
int sigismember(const sigset_t *set, int signum); //查看set中signum位是否为1
注意:SIGKILL和SIGSTOP不能被阻塞。
于是我们可以这样调用:
sigset_t set,oldset;
sigemptyset(&set);
sigaddset(&set,SIGALRM); //将SIGALRM信号对应的位置1
sigprocmask(SIG_BLOCK,&set,&oldset); //设置按set信号阻塞
//此区域的SIGALRM信号是阻塞的,因此这里的语句不会被SIGALRM中断。
sigprocmask(SIG_SETMASK,&oldset,NULL); //恢复oldset信号阻塞
五、自动重启
自动重启的意思是某个系统函数正在阻塞时被信号中断并进入信号处理函数中,待信号处理完,自动重启被中断的系统函数。(linux默认I/O函数自动重启,但sleep不会)
而非自动重启的函数则是被信号中断以后,则被迫异常返回,并设置errno为SIGINT。
非自动重启的用处:
例如我们想实现阻塞函数超时返回,这是服务器中很常见功能——服务器等待接收客户端请求,超时则断开连接。sigaction可以帮我们实现这个功能。
int sigaction(int signum,const struct sigaction *act ,struct sigaction *oldact); //sigaction是signal函数的加强版
第一个参数是信号类型,例如SIGALRM。
第二个参数是一个结构体:
struct sigaction
{
void (*sa_handler) (int); //信号处理函数指针,或者SIG_IGN、SIG_DFL 。(作用同signal)
sigset_t sa_mask; //设置调用信号处理函数期间的阻塞哪些信号。(即设置不被哪些信号打断)信号处理函数返回时会自动复位。
int sa_flags; //设置标记,下面介绍。
void (*sa_sigaction) (int,siginfo_t,void *); //带参数的信号处理函数指针。除了参数,其它同sa_handler。通过sa_flags开关决定调用哪个信号处理函数。(只能调用其中一个,默认调用sa_handler)
}
这里先介绍sa_flags其中两个值
1、SA_INTERRUPT(被中断系统函数不自动重启)或者 SA_RESTART(被中断系统函数自动重启)
2、SA_SIGINFO 使用带参数的信号处理函数(既信号来时调用sa_sigaction而不调用sa_handle)
...
所以要实现超时中断,我们可以这样:
void sigfun(int)
{
}
//...
struct sigaction sa;
sa.sa_handle=sigfun;
sa.sa_flags=SA_INTERRUPT;
sigemptyset(&sa.sa_mask);
sigaction(SIGALRM,&sa,NULL);
alarm(10);
read(cusfd,buf,MAXLINE); //这里阻塞等待读取客户端数据,若客户端10s内还没发送,则会被SIGALRM中断
alarm(0);