异步IO框架实现之实时信号(Real-Time Signal)

烽驿2009开源实时通信平台 源码获取:svn checkout http://fy2009.googlecode.com/svn/trunk/ fy2009-read-only

 

Linux下可伸缩(Scalable)的异步IO机制是什么?熟悉Linux下网络编程的人可能会不加思索地回答:当然

是EPOLL。没错,EPOLL是个优秀的异步IO机制(笔者将会在专门的博文中讨论它),但那是Linux kernel

2.5.44之后的事(正式被大量使用更是在2.6之后),在此之前呢?Poll,Select之流当然都不行(讨论它

们不是本篇的目的,恕不再展开),那时的Linux世界其实有个同样非常优秀的异步IO设施:实时信号

(Real-Time Singal),其性能与EPOLL基本相当,只是编程模型不如EPOLL简单--EPOLL既简单又高效,因此,在2.6之后成为主流也就不奇怪了。为了支持2.6之前的Linux版本,本项目的异步

IO框架(http://blog.csdn.net/DreamFreeLancer/archive/2009/07/26/4381316.aspx)仍然对实时信号提供了支持。本篇简要介绍实时信号的原理和在本异步IO框架实现中的注意事项。
如你所知,在Linux下,你可以通过Shell命令(如Kill)和程序方式(如signal函数)向一个进程发信号

,Linux进程收到该信号将以一种类似于中断的方式响应。通常的Linux信号不带参数,不能重复投递,就是说

如果有一个信号正在被处理,后续相同的信号将被丢失。但Linux还有一类被称为实时信号(Real-time

Signal)的信号(信号取值在SIGRTMIN和SIGRTMAX之间)却具有不同的行为模式,即它们可以带参数,也

可以重复投递,就是说连续发多个相同的实时信号,即使前面的信号尚未处理完成,后面的信号也不会丢失(

前提条件是系统的信号队列没有满,否则,仍然会丢失,这在很大程度上导致实时信号编程比EPOLL复杂)


在将实时信号用作异步IO设施时,需在程序实始化时将你打算用作IO状态报告的实时信号值(例如:

SIGRTMIN+16,较小或较大的实时信号有可能已经被系统用掉,且可能因Linux版本而异,这也是实时信号

机制让人烦恼的地方)block掉(使其进入信号队列,等待进程自己去拿,而还是象通常那样Call信号函数

):
sigset_t sigset;
sigaddset(&sigset, MY_RT_SIGNAL);
另外还得block掉SIGIO,当系统信号队列满时,进程会收到该信号
sigaddset(&sigset, SIGIO);

--上述操作估Linux2.6之前有效,但2.6上SIGIO无法被Block,需通过信号函数去捕捉。
另外必需ignore SIGPIPE,否则,Socket连接断裂时,进程会收到该信号,并导致进程Crash。
2.6.6以前的版本,整台机器共享同一信号队列(注意不是每个进程一个信号队列)--这是怪异的开始,

且整个队列的缺省长度只有1024--不幸的继续,对于大型通信Server,这通常都是不够大的,应用程序

初始化时一般需通过以下调用将其扩大,例如:
system ("echo 49152 > /proc/sys/kernel/rtsig-max");
但2.6.6以后的版本已经弃用上述伪文件接口,改用ulimit参数设置信号队列长度:
struct rlimit rlim;
rlim.rlim_cur=49152;
int setrlimit(RLIMIT_SIGPENDING, &rlim);
更重要的是信号队列改成了每个进程一个,这很重要。
--如果采用EPOLL,上面这些都不用做,由此可见,EPOLL的流行不是偶然。
然后在bool aio_provider_t::register_fd(aio_sap_it *dest_sap, int32 fd, sp_aioeh_t& eh)函数实现中,为指定Socket注册异步IO服务时,需进行如下操作:
::fcntl(fd, F_SETFL, O_NONBLOCK|O_ASYNC); //将Socket设成异步非阻塞方式
::fcntl(fd, F_SETSIG, MY_RT_SIGNAL); //告诉操作系统在fd上有IO事件时,发指定的实时信号到信号队


::fcntl(fd, F_SETOWN, thread_id); //将上述实时信号发往指定的线程。
在int8 aio_provider_t::heart_beat()实现中通过如下调用从系统信号队列中检索实时信号:
siginfo_t sig_info;
struct timespec sigtimedwait_timeout;
int rts_num = ::sigtimedwait(&sigset, &sig_info, &sigtimedwait_timeout);
上述调用从系统队列中等待实时信号,如果有信号,立即返回;否则,Block等待线程至超时。
值得注意的是,上述调用可能返回SIGIO(Linux2.6之前的版本,其后,SIGIO无法被放进队列),这时意味着系统信号队列已溢出,这时应用程序必需启动“IO状态报告的恢复机制”,首先是清空已满的信号队列:
signal(MY_RT_SIGNAL, SIG_IGN);
signal(MY_RT_SIGNAL, SIG_DFL);
然后,应用程序通常通过调用大连接数时效率较低的poll函数主动检查每个Socket的IO状态,从而使实时信

号机制随后恢复工作。该事件应被跟踪,如果经常发生,意味着当前信号队列偏小,或Server负载过重,

会导致系统性能下降。

 

你可能感兴趣的:(框架,linux,IO,socket,SAP,Signal)