- 前言
- 一、信号概念
- 信号的响应过程
- 常用信号
- 信号术语
- 二、信号函数
- signal
- sigaction
- 信号集
- sigprocmask
- sigpending
- sigsuspend
- 三、实例分析——统一事件源
信号是软件中断(但不是中断,可以认为信号的响应依赖于中断)。它提供了一种处理异步事件的方法。Linux 信号可由如下条件产生:
- 命令行输入中断键或kill命令,例如ctrl+c中断前台进程
- 硬件异常,比如非法内存访问,除以0
- 通过函数设置产生信号(kill函数)
一、信号概念
信号都有一个名字都以SIG开头(kill -l查看),当某个信号出现时,进程可以告诉内核用下列三种方式来执行。
1、忽略信号。
2、捕捉信号。
3、执行默认动作,大多数信号的默认动作是终止该进程。
进程由于系统调用或者中断进入内核,完成相应任务返回用户空间之前,会检查有无信号到达,如果没有就返回用户态恢复上下文数据后继续执行程序;如果有信号,也会返回用户态,但这时会执行信号处理函数,执行完毕后,再次返回内核态,循环。
信号是在内核态回到用户态的路上被响应的。
SIGALRM 调用alarm函数设置的定时器超时时,产生此信号。
SIGCHLD 在子进程终止时,会产生此信号返回给父进程。
SIGINT 当用户按中断键时,终端将产生此信号发生给前台进程。
SIGPIPE 在管道读的进程已经终止时还往管道写将产生此信号。
SIGTERM 此信号由kill命令发出的,意义是系统默认终止。
捕捉信号:如果信号的处理动作是用户自定义的函数,在信号抵达时就调用这个函数,这个动作称为捕捉信号。
阻塞信号:不忽略该信号,在其发生时记住它,在进程做好准备后再通知它。
pending:在信号产生和递送之间,称之为pending(未决的)。如果有一个阻塞的信号,且内核对该信号不忽略,则该信号就是pending的,直到解除阻塞或者忽略该信号。调用sigpending函数能获得pending状态的信号集。
可重入函数:产生信号后,当前进程会立马调用信号处理函数handler,假设当前进程在malloc时信号到来,handler中也调用了malloc,这时候就会出错。所以规定handler中必须调用安全的函数,称为可重入函数,且这些函数会阻塞任何引起不一致的信号。
信号屏蔽字:规定了当前要阻塞递送到该进程的信号集。
二、信号函数 signal为一个信号设置处理函数
#includesigactiontypedef void (*sighandler_t)(int);//不懂的可以搜一下typedef函数指针 sighandler_t signal(int signum, sighandler_t handler); --signum 信号名 --handler 是常量SIG_IGN,就代表忽略该信号 是常量SIG_DFL,就代表执行默认动作 是函数,则在信号发生时调用该函数,也就是捕捉信号(信号处理函数) 成功返回一个函数指针,指向前一次调用signal传入的handler函数指针,失败返回SIG_ERR
signal 函数的使用方法简单,但并不属于 POSIX 标准,在各类 UNIX 平台上的实现不尽相同,因此其用途受到了一定的限制。而POSIX 标准定义的信号处理接口是 sigaction 函数,其接口头文件及原型如下:
#include信号集int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact); --signum 信号名 --act 指定新的处理方式 --oldact 若不为空,则输出信号之前的处理方式 成功返回0,失败返回-1并设置errno struct sigaction { void (*sa_handler)(int); //作用等同于signal函数中的handler void (*sa_sigaction)(int, siginfo_t *, void *); sigset_t sa_mask; int sa_flags; void (*sa_restorer)(void); }
信号集是一个能表示多个信号的数据类型,本质上是一个数组。数组的每个位代表了一个信号
#includesigprocmaskint sigemptyset(sigset_t *set); //初始化由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); //检测信号集中是否存在signum这个信号
检查或改变进程的信号掩码(信号屏蔽字)
#includesigpendingint sigprocmask(int how, const sigset_t *set, sigset_t *oldset); -- how SIG_BLOCK:设置新的(Set中的)信号掩码,与原先的信号掩码相并 SIG_UNBLOCK:为(Set中的)信号解除阻塞 SIG_SETMASK:为该进程设置新的(Set中的)信号掩码 -- set 新的信号屏蔽字由参数set(非空)指定 --oldset 如果oset不为空,则把当前进程的信号屏蔽字保存到oset中 成功返回0,失败返回-1并设置errno sigprocmask(SIG_UNBLOCK,&set,&saveset); //对set里的信号屏蔽字解阻塞,把之前信号屏蔽字的状态保存到saveset sigprocmask(SIG_BLOCK,&set,&oldset); //对set里的信号屏蔽字加阻塞,把之前的状态保存到oldset中 sigpromask(SIG_SETMASK,&oldset,NULL); //恢复了上一次的状态,也就是第一次调用sigpromask后的状态 sigpromask(SIG_SETMASK,&saveset,NULL); //恢复到最开始的状态,第一次调用sigpromask之前的状态
返回由pending信号构成的pengind集
#includesigsuspendint sigpending(sigset_t *set); set--用于保存返回的信号集 成功返回0,失败返回-1并设置errno
进程的信号屏蔽字替换为由参数mask指向的信号集。在捕捉到一个信号或发生一个会终止该进程的信号前,该进程会被阻塞。可以简单看作是解除信号屏蔽字的阻塞和pause的原子操作。
#include三、实例分析——统一事件源int sigsuspend(const sigset_t *mask);
信号是异步事件,信号处理函数handler和主函数是两条不一样的执行路线,为了不让主函数等待很久,所以加快执行的速度。
解决方法:把信号的主要处理逻辑放到程序的主循环中,当调用handler时,通过管道把信号值传递给主函数,主函数根据信号值执行目标信号对应的逻辑代码。
handler往管道的写端写信号值,主函数则从管道的读端读出该信号值。那么主函数怎么知道管道上何时有数据可读呢?这很简单,我们只需要使用I/O复用来监听管道的读端文件描述符上的可读事件。这样,信号事件就能和其他I/O事件一样被处理,称为统一事件源。
下面给出伪码
#include <头文件>
//主函数
int main(int argc,char *argv[])
{
1建立监听套接字
int listenfd=socket(PF_INET,SOCK_STREAM,0);
struct sockaddr_in serv_addr;
serv_addr.sin_family...
serv_addr.sin_port....
serv_addr.sin_addr...
bind(listenfd,(sockaddr*)serv_addr,sizeof(serv_addr));
listen(listenfd,listenfd_size);
2创建管道--用socketpair来创建全双工的通信方式
//使用socketpair函数能够创建一对套节字进行进程间通信
socketpair(PF_INET,SOCK_STREAM,0,pipefd);
//pipefd[0]、pipefd[1]都能进行双向读写操作
SetNonBlock(pipefd[1]);
3将listenfd和管道的读端fd加入监听
addfd(epfd,listenfd);
addfd(epfd,pipefd[0]);
4为信号设置处理方式
addsig(SIGINT);
while(1)
{
5监听
struct epoll_event events[MAX_EVENTS];
int nread=epoll_wait(epfd,events,MAX_EVENTS,-1);
for(int i=0;i
int fd=events[i];
if(fd==listenfd)
{
int clntsock=accept(...);
addfd(epfd,clntsock);
}
else if(fd==pipefd[0])//管道有信号传来,处理该信号
{
char signals[size]
int read_len=recv(pipefd[0],msg,sizeof(msg),0);//接收信号
//信号值占一个字节,通过for循环遍历,用switch case来处理每个信号
for(int i=0;i
switch(signals[i])
{
case SIGINT
...
break;
case ...
...
break;
}
}
}
}
}
close(listenfd);
close(pipefd[0]);
close(pipefd[1]);
exit(0);
}



