关于 MsgDeliverEvent()

对于 QNX 的 MsgDeliverEvent() 这个内核调用,后台有不少疑问,分出来细讲一下吧。这个函数的基本用法是这样的:

如上所见,客户端是会需要阻塞等待事件发生的。但这个并不是绝对的,根据事件具体是什么而定。

MsgDeliverEvent()里的事件

MsgDeliverEvent()里的事件其实可以有好几种,具体使用哪种,取决于客户端与服务器端的约定。具体事件是在 sys/siginfo.h里定义的。

#define SIGEV_NONE               0       /* notify */
#define SIGEV_SIGNAL             1       /* notify, signo, value */
#define SIGEV_SIGNAL_CODE        2       /* notify, signo, value, code */
#define SIGEV_SIGNAL_THREAD      3       /* notify, signo, value, code */
#define SIGEV_PULSE              4       /* notify, coid, priority, code, value */
#define SIGEV_UNBLOCK            5       /* notify */
#define SIGEV_INTR               6       /* notify */
#define SIGEV_THREAD             7       /* notify, notify_function, notify_attributes */

具体各个事件类型的定义,可以去QNX用户手册里查;一般来说,比较常用的,就是“脉冲”(SIGEV_PULSE);“信号”(SIGEV_SIGNAL)和中断(SIGEV_INTR)。这里,脉冲需要客户端阻塞在一个事先准备好的频道上(MsgReceive() / MsgReceivePulse() );而中断,可以用InterruptWait()来等。但是对于“信号”,客户端可以选择阻塞在 sigwait() / sigwaitinfo() 上,专门等这个事件;但是,也可以跟传统UNIX的“信号处理”一样,对于指定的信号用 signal()函数挂一个回调处理。这样客户端不需要专门阻塞在某一点,只要异步处理信号就可以了。当然需要提醒的是,在多线程编程时代,异步处理也是有很多同步措施需要担心的;所以有时候于其担心自己的程序不知道什么时候会被打断,还不如开一个线程为阻塞点,这样程序也容易一点。

也许会有疑问,既然客户端终究是要阻塞的,那直接开个线程,阻塞到服务器上多好,何必这么复杂地绕来绕去呢?这是因为有些情况下没办法直接阻塞到服务器端,最明显的例子就是POSIX的 select() 函数。

Select()函数

Select()这个函数,在单线程UNIX程序的时候,应该算是一个非常常用到的操作了。大家有机会看看Unix常用程序的源码,在很多场合可以看到这个函数。QNX在sys/select.h里,定义了这个函数。

int select( int width,
            fd_set * readfds,
            fd_set * writefds,
            fd_set * exceptfds,
            struct timeval * timeout );

具体详细定义大家可以查函数说明,但基本上的意思就是给出“多个”fd,当这些 fd 可以读(readfds),可以写(writefds), 或者出错(exceptfds)时,函数返回。timeout可以是NULL,表示一直等下去,或者可以给出时间,这样可以在一定的时间后,自动退出select()。

一个用select()的程序通常是这样的。

switch ( n = select( 1 + max( console, serial ), &rfd, 0, 0, &tv ) ) {
      case -1:
        perror( "select" );
        return EXIT_FAILURE;
      case  0:
        puts( "select timed out" );
        break;
      default:
        printf( "%d descriptors ready ...\n", n );
        if( FD_ISSET( console, &rfd ) )
          puts( " -- console descriptor has data pending" );
        if( FD_ISSET( serial, &rfd ) )
          puts( " -- serial descriptor has data pending" );
    }

好,现在问题来了,大家想想,在QNX上要怎么实现这个函数?

在QNX上实现select()函数

别的文章里介绍过,在QNX上,每一个 fd 就是指向一个服务器的“连接号”。如果同时select() 10个fd的话,用“直接开线程去阻塞在服务器上”这种办法,那意味着就要开10个线程。fd_set的标准大小是1024,而一个select()可以涵盖3个fd_set,最坏的情况大家可以算算需要开多少线程才能满足要求?

所以这个时候就可以看到select() 是怎么用 MsgDeliverEvent() 来实现的了。大约是这样:

int select( int width,fd_set * readfds, fd_set * writefds,
            fd_set * exceptfds, struct timeval * timeout )
{
	Ionotify_t ion;
                 structure sigevent evt;

	msg = &ion.msgi ;
	msg->type = _IO_NOTIFY;
	msg->combine_len = sizeof msgi;
	msg->action = _NOTIFY_ACTION_POLLARM;

	/* for each fd, send a _IO_NOTIFY msg to their server */
	for fd in readfds 
	{
		SIGEV_SIGNAL_VALUE_INIT(&;msg->event, SIGSELECT, fd);
		msg->flags |= _NOTIFY_COND_INPUT;
		MsgSend(fd, msg, sizeof(*msg), &ion.o, sizeof ion.o)
	}

	for fd in writefds
	{
		…
	}

	For fd in exceptfds
	{
		…
	}

	/* all messages send to different servers already, waitfor the reply */
	sigemptyset(&set);
	sigaddset(&set, SIGSELECT);
	if (sigtimedwait(&set, &info, &timeout) == -1)
	  return -1;
	fd = info.si_value & _NOTIFY_DATA_MASK;
	if (info.si_value & _NOTIFY_COND_INPUT) 
		print(“fd %d is ready to input!\n”, fd);
	if (info.si_value & _NOTIFY_COND_OUTPUT) 
		print(“fd %d is ready to output\n”, fd);
	if (info.si_value & _NOTIFY_COND_OBAND)
		print(“fd %d is excepted\n”, fd);
	return 1;	
}

上面并不是真实的代码,但你可以看到大致是怎么工作的。对于每一个fd,我们会有一个 SIGEV_SIGNAL_VALUE 事件,fd 作为事件的值;然后整个事件,加上一些flag,通过struct _io_notify被发送到了服务器端。

当所有的服务器都被通知了以后,程序进入一个 sigtimedwait() 阻塞状态,等各个服务器反应。

服务器端,当条件满足后,会 MsgDeliverEvent()那个对应的 SIGEV_SIGNAL_VALUE。其实就是一个 SIGSELECT 发送给客户端。客户端的sigtimedwait() 就会捕捉到这个信号,然后根据带回来的值,来决定哪个fd发生了什么。

select()的性能

要说明的是,上述的这种方案,并不是实际上QNX实装的方案。可以看出来这个方案的性能其实是比较差的。每次调用select() 只有一个 fd 会返回,如果是httpd服务器这种有大量socket连接(大量fd),但其实服务器是同一个的时候,在传统的操作系统上一次 select() 可以有多个 fd 被唤醒。所以在实装上QNX实现了 poll()/epoll(),而select(),则在内部被转化为调用 poll() 实现了。

poll()的具体实现就不在这里展开了,但基本还是跟上面逻辑一样,先传递一个事件,然后等MsgDelieverEvent() 把事件发回来的方案相似,只是对于指向同一服务器的 fd 做了合并优化,这样就有可能同时得到多个fd被唤醒,大大提高了性能。

 

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值