关于信号

使用

#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t  signal(int  signum, sighandler_t handler);

参数1:我们要进行处理的信号。命令kill -l查看(共64个),这些信号都是系统定义的宏。
参数2:我们处理的方式(3种)。

第一种:

signal(SIGINT, SIG_ING);  
//SIG_ING表示忽略指定信号
//SIGINT信号通常是CTRL+C,或DELETE。

第二种:

signal(SIGINT, SIG_DFL);   //SIG_DFL执行默认系统动作

第三种:

signal(SIGINT, void(*handle)(int));

example:
void handler(int sig)
{
    std::cout << "捕捉到信号" << sig << std::endl;
}
int main()
{
     signal(SIGALRM,  handler);
     return 0;
 }       

常用信号:

SIGABRT 由调用abort函数产生,进程非正常退出

SIGALRM 用alarm函数设置的timer超时或setitimer函数设置的interval timer超时

SIGBUS 由内存访问引起的某种特定的硬件异常

SIGFPE 数学相关的异常,如被0除,浮点溢出,等等

信号机制

首先明确内核负责接收信号,然后将其放入对应进程的信号队列。此时信号在内核空间,进程在用户空间。然后内核向进程发送一个中断,使其切到内核态。
而后进程在两种情况下对信号进行检测:
* 进程处理完中断从内核返回用户态前。
* 进程在内核态,从睡眠到被唤醒时检测。
检测到信号后,将当前内核栈内容拷贝到用户栈,并让下一条栈上指令指向信号处理函数。然后切回用户态,处理函数。
信号处理函数执行完成后,还需要返回内核态,检查是否还有其它信号未处理。确定处理完,恢复中断前的运行位置,然后回到用户态继续执行。

信号使用

发送信号:

int raise(int sig);  //想当前进程发送sig信号

int kill(pid_t pid, int sig);  //向进程pid发送信号sig
//pid > 0 :向进程号为pid的进程发送信号
//pid = 0 :向当前进程所在的进程组发送信号
//pid = -1 :向所有进程(除PID=1外)发送信号(权限范围内)
//pid < -1 :向进程组号为-pid的所有进程发送信号

将进程睡眠直到接收信号:

int pause(void);  //将进程转入睡眠状态,直到接收到任意信号

int sigsuspend(const sigset_t* mask);  //将进程睡眠直到接收到特定信号

信号驱动IO

除了多线程,多路复用,用信号驱动IO也能实现文件描述符上IO事件的管理。

原理:
如果想关注某文件描述符上的IO操作,就请求内核为自己发送一个信号,之后进程就可以执行其他任务直到I/O就绪为止。此时内核会向进程发送信号。
使用步骤:
  • 设置文件描述符属主。即如果有事件通知给谁,一般是本进程。
  • 设置文件描述符非阻塞。
  • 通过打开O_ASYNC标志使信号驱动I/O。
  • 为内核发送的通知信号安装一个信号处理例程。

代码:

void setsocket(int fd)
{
    fcntl(fd, F_SETOWN, getpid());  //当发生IO时通知此进程
    int flag = fcntl(fd, F_GETFL);  //取得文件描述符状态旗标
    fcntl(fd, F_SETFL, flag | O_ASYNC | O_NONBLOCK); //O_ASYNC使信号驱动IO

}

void handler(int sig)
{
    printf("IO事件通知\n");
}

还有,信号驱动I/O只支持边缘触发通知,所以处理可读事件要循环read()直到返回错误码EAGAIN或EWOULDBLOCK。

那么IO信号什么情况会被发送?
  • 监听套接字接收到新的连接。
  • TCP主动端请求连接完成,进入ESTABLISHED状态。
  • 套接字有新的事件。(注意不只是可读事件,旧事件没读完不会二次触发)
  • 套接字对端使用shutdouwn()关闭写连接,或者close()。
  • 套接字上发送缓冲区有新的空间。
  • 套接字发生异步错误。

代码时发现一些细节:

shutdown(sockfd, SHUT_RD);  //不会触发IO信号
close(sockfd);  //触发

shutdown(sockfd, SHUT_WR);  //触发IO信号
close(sockfd);   //不触发

这个感觉跟epoll有点像了,也是记住文件描述符,就绪通知,而且自我感觉这个比epoll更棒,因为epoll还需要阻塞epoll_wait()去拿到那些套接字,同epoll一样的,程序性能不会因为检查的文件描述符个数而下降。

但现在有个问题:
默认的IO就绪通知信号SIGIO是个非实时信号。这就尴尬了,以程序的运行速度,很容易发生多个信号合成一个信号的情况(事实上我测试程序时它就无比眼疾手快的合了),测试时我用的sleep()来观察,实际中当然不行,所以必须改一些设置。还有既然要求实用性,刚刚只能接收一个参数的signal函数已经不能满足要求了。

  • 通过专属Linux的fcntl() F_SETSIG操作来指定一个实时信号,取代SIGIO。
  • 信号处理函数使用sigaction()来安装。

来看一下函数原型:

int sigaction(int sig, const struct sigaction *act, struct sigaction *oldact);   

他的参数比较复杂,但足够传入发生事件描述符,活跃事件类型等信息,到这里,基本已经能够完成服务器监听套接字并处理活跃事件的功能了。

还有个小细节:排队的实时信号可排队,但是队长有限,超出会被丢弃,这个一个可以通过系统设置来改,一个可以通过sigwaitinfo()函数获取,然后临时切换到select()等去处理,恩,听起来就难得不要不要。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值