Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1398300
  • 博文数量: 277
  • 博客积分: 2551
  • 博客等级: 少校
  • 技术积分: 3918
  • 用 户 组: 普通用户
  • 注册时间: 2011-02-21 22:46
文章分类

全部博文(277)

文章存档

2017年(3)

2016年(9)

2015年(65)

2014年(27)

2013年(85)

2012年(61)

2011年(27)

分类: LINUX

2013-03-08 09:58:09

    在Linux下,设备都被抽象为文件,一系列的设备文件就有自己独立的虚拟文件系统,所以,设备在系统调用参数中的表示就是file description。
    通过fd访问进程拥有的file对象,通过file对象可以访问其op结构,这里面要关心的一个file operation就是poll。因为系统调用poll和select,就是依靠这个文件操作poll实现的。
    poll文件操作有两个参数,一个是文件本身,一个可以看做是当设备尚未就绪时调用的回调函数,这个函数是把设备自己特有的等待队列传给内核,让内核把当前的进程挂载到其中(因为当设备就绪时,设备就应该去唤醒在自己特有等待队列中的所有节点,这样当前进程就获取了完成的信号了)。
    poll文件操作返回的必须是一组标准的掩码,其中的各个位指示当前的不同的就绪状态。

    poll和select的共同点就是,对全部指定设备做一次poll(当然这往往都是还没有就绪的),此时就会通过回调函数把当前进程注册到设备的等待队列,如果所有设备返回的掩码都没有显示任何的事件触发,就去掉回调函数的函数指针,进入有限时的睡眠状态,再恢复和不断做轮询,再作有限时的睡眠,直到其中一个设备有事件触发为止。只要有事件触发,系统调用返回,回到用户态。

    此时还不是所有的设备都就绪的,那就得不断地poll或者select了,而做一次这样的系统调用都得轮询所有的设备,次数是设备数*(睡眠次数-1),也就是时间复杂度是O(n),一般情况下,linux服务器程序都是用循环的方式调用select/poll,相当于是O(n2)的复杂度,当有上千个并发连接时,能否做到数据的实时处理都成问题。
     另外,数千个设备fd在每次调用时,都需要将其从用户空间复制到内核空间,这里的开销不可忽略。
    
     poll和select放在一起,因为其机制一致,而参数和数据结构就略有不同。select一次性传入三组作用于不同信道的设备fd,分别是输入,输出和错误异常。各组的fd期待各组所特有的,由代码指定的一组事件,如输入信道期待输入就绪,输入挂起和错误等事件。 然后,select就挑选调用者关心的fd做poll文件操作,检测返回的掩码,看看是否有fd所属信道感兴趣的事件,比如看看这个属于输出信道的fd有没有输出就绪等一系列的事件发生,如果有一个fd发生感兴趣事件就返回调用了。
     select,为了同时处理三组使用不同的事件判断规则的fd,采用了位图的方式表示,一组一个位图,位长度是当中最大的fd值,上限是1024,而且这还只是传入的位图,还有一样大小的传出的位图。当fd数越来越多时,所需的存储开销比较大。
       
     select改进后的poll,各个fd独立处理,不分组。
     poll()系统调用是System V的I/O多路复用的解决方案。它有三个参数,第一个是pollfd结构的数组指针,也就是指向一组fd及其相关信息的指针,因为这个结构包含的除了fd,还有期待的事件掩码和返回的事件掩码,实质上就是将select的中的fd,传入和传出参数归到一个结构之下,也不再把fd分为三组,也不再硬性规定fd感兴趣的事件,这由调用者自己设定。
     不使用位图来组织数据,也就不需要位图的全部遍历了。按照一般队列地遍历,每个fd做poll文件操作,检查返回的掩码是否有期待的事件,以及做是否有挂起和错误的必要性检查,如果有事件触发,就可以返回调用了。
        

      poll和select在面对高并发多连接的场景时,仍然有瓶颈,除了上述的关于每次调用都需要做一次从用户空间到内核空间的拷贝,还有这样的问题,poll和select会不得不多次操作,并且每次操作都很有可能需要多次进入睡眠状态,也就是多次全部轮询fd,出现重复而无意义的操作。

      重复而无意义的操作包括:
             首先  从用户到内核空间拷贝,既然长期监视这几个fd,甚至连期待的事件也不会改变,那拷贝无疑就是重复而无意义的,
                       可以让内核长期保存所 有需要监视的fd甚至期待事件,或者可以再需要时对部分期待事件进行修改;
             其次 将当前线程轮流加入到每个fd对应设备的等待队列,这样做无非是哪一个设备就绪时能够通知进程退出调用,
                       可以找个“代理”的回调函数,代替当前进程加入fd的等待队列好了(Linux的等待队列,实质上是回调函数队列,
                       也可以使用宏来将当前进程“加入”等待队列,其实就是将唤醒当前进程的回调函数加入队列)。

       poll改进后,就是epoll
             像poll系统调用一样,做轮询文件操作发现尚未就绪时,它就调用传入的一个回调函数,这是epoll指定的回调函数,
            它不再像以前的poll系统调用时指定的回调函数那样,而是就将那个“代理”的回调函数加入设备的等待队列,
            这个代理的回调函数就等待设备就绪时将它唤醒,然后它就把这个设备fd放到一个指定的地方,
            同时唤醒可能在等待的进程,到这个指定的地方取fd。
            



    

阅读(779) | 评论(0) | 转发(0) |
0

上一篇:centos下运行cgi模块

下一篇:erlang节点互通

给主人留下些什么吧!~~