poll&ampamp;&ampamp;epoll之poll实现.doc

上传人:白大夫 文档编号:3271603 上传时间:2019-08-07 格式:DOC 页数:6 大小:28.50KB
返回 下载 相关 举报
poll&ampamp;&ampamp;epoll之poll实现.doc_第1页
第1页 / 共6页
亲,该文档总共6页,到这儿已超出免费预览范围,如果喜欢就下载吧!
资源描述

《poll&ampamp;&ampamp;epoll之poll实现.doc》由会员分享,可在线阅读,更多相关《poll&ampamp;&ampamp;epoll之poll实现.doc(6页珍藏版)》请在三一文库上搜索。

1、poll&&epoll之poll实现0.等待队列在Linux内核中等待队列有很多用途,可用于中断处理、进程同步及定时。我们在这里只说,进程经常必须等待某些事件的发生。等待队列实现了在事件上的条件等待:希望等待特定事件的进程把自己放进合适的等待队列,并放弃控制全。因此,等待队列表示一组睡眠的进程,当某一条件为真时,由内核唤醒它们。等待队列由循环链表实现,由等待队列头(wait_queue_head_t)和等待队列项(wait_queue)组成,其元素(等待队列项)包含指向进程描述符的指针。每个等待队列都有一个等待队列头(waitqueuehead),等待队列头是一个类型为wait

2、_queue_head_t的数据结构定义等待队列头(相关内容可以在linux/include/wait.h中找到)等待队列头结构体的定义:structwait_queue_headspinlock_tlock;/自旋锁变量,用于在对等待队列头structlist_headtask_list;/指向等待队列的list_head;typedefstruct_wait_queue_headwait_queue_head_t;使用等待队列时首先需要定义一个wait_queue_head,这可以通过DECLARE_WAIT_QUEUE_HEAD宏来完成,这是静态定义的方法。该宏会定义一个wait_que

3、ue_head,并且初始化结构中的锁以及等待队列。Linux中等待队列的实现思想如下图所示,当一个任务需要在某个wait_queue_head上睡眠时,将自己的进程控制块信息封装到wait_queue中,然后挂载到wait_queue的链表中,执行调度睡眠。当某些事件发生后,另一个任务(进程)会唤醒wait_queue_head上的某个或者所有任务,唤醒工作也就是将等待队列中的任务设置为可调度的状态,并且从队列中删除。(2)等待队列中存放的是在执行设备操作时不能获得资源而挂起的进程定义等待对列:structwait_queueunsignedintflags;/prepare_to_wait(

4、)里有对flags的操作,查看以得出其含义#defineWQ_FLAG_EXCLUSIVE0x01/一个常数,在prepare_to_wait()用于修改flags的值void*private/通常指向当前任务控制块wait_queue_func_tfunc;/唤醒阻塞任务的函数,决定了唤醒的方式structlist_headtask_list;/阻塞任务链表;typedefstruct_wait_queuewait_queue_t;poll实现分析1.select/poll缺点select/poll的缺点在于:1.每次调用时要重复地从用户态读入参数。2.每次调用时要重复地扫描文件描述符。3.

5、每次在调用开始时,要把当前进程放入各个文件描述符的等待队列。在调用结束后,又把进程从各个等待队列中删除。2.内核实现2.1主要数据结构:(1)structpoll_table_entrystructfilefilp;wait_queue_twait;/内部有一个指针指向一个进程wait_queue_head_twait_address;/等待队列头部(等待队列有多个wait_queue_t组成,通过双链表连接);(2)structpoll_table_pagestructpoll_table_pagenext;structpoll_table_entryentry;structpoll_tab

6、le_entryentries0;(3)structpoll_wqueuespoll_tablept;/一个函数指针,通常指向_pollwait或nullstructpoll_table_page*table;interror;(4)structpoll_liststructpoll_list*next;/按内存页连接,因为kmalloc有申请数据限制intlen;/用户空间传入fd的数量structpollfdentries0;/存放用户空间存入的数据;typedefvoid(*poll_queue_proc)(structfile*,wait_queue_head_t*,structpol

7、l_table_struct*);typedefstructpoll_tablestructpoll_queue_procqproc;poll_table;2.2poll系统调用函数关系总图intpoll(structpollfd*fds,nfds_tnfds,inttimeout);3.内核2.6.9poll实现代码分析fs/select.c-sys_pollasmlinkagelongsys_poll(structpollfd_user*ufds,unsignedintnfds,longTImeout)structpoll_wqueuestable;structpoll_list*head

8、;structpoll_list*walk;poll_initwait(while(i!=0)structpoll_list*pp;pp=kmalloc(sizeof(structpoll_list)+sizeof(structpollfd)*(iPOLLFD_PER_PAGE?POLLFD_PER_PAGE:i),GFP_KERNEL);if(head=NULL)head=pp;elsewalk-next=pp;walk=pp;if(copy_from_user(pp-entries,ufds+nfds-i,sizeof(structpollfd)*pp-len)err=-EFAULT;go

9、toout_fds;i-=pp-len;/*这一大堆代码就是建立一个链表,每个链表的节点是一个page大小(通常是4k),这链表节点由一个指向structpoll_list的指针掌控每个poll_list的entrys成员指向一个structpollfd。上面的循环就是把用户态的structpollfd拷进这些entries里。通常用户程序的poll调用就监控几个fd,所以上面这个链表通常也就只需要一个节点,即操作系统的一页。但是,当用户传入的fd很多时,由于poll系统调用每次都要把所有structpollfd拷进内核,所以参数传递和页分配此时就成了poll系统调用的性能瓶颈。*/fdcou

10、nt=do_poll(nfds,head,其中poll_initwait较为关键,从字面上看,应该是初始化变量table,注意此处table在整个执行poll的过程中是很关键的变量。而structpoll_table其实就只包含了一个函数指针。现在我们来看看poll_initwait到底在做些什么void_pollwait(structfile*filp,wait_queue_head_t*wait_address,poll_table*p);voidpoll_initwait(structpoll_wqueues*pwq)/*设置回调函数*/很明显,poll_initwait的主要动作就是把

11、table变量的成员poll_table对应的回调函数置为_pollwait。这个_pollwait不仅是poll系统调用需要,select系统调用也一样是用这个_pollwait,说白了,这是个操作系统的异步操作的“御用”回调函数。当然了,epoll没有用这个,它另外新增了一个回调函数,以达到其高效运转的目的,这是后话,暂且不表。最后一句do_poll,我们跟进去:staTIcintdo_poll(unsignedintnfds,structpoll_list*list,structpoll_wqueues*wait,longTImeout)intcount=0;poll_table*pt=

12、for(;)structpoll_list*walk;set_current_state(TASK_INTERRUPTIBLE);walk=list;while(walk!=NULL)do_pollfd(walk-len,walk-entries,walk=walk-next;pt=NULL;if(count|!timeout|signal_pending(current)break;count=wait-error;if(count)break;timeout=schedule_timeout(timeout);/*让current挂起,别的进程跑,timeout到了以后再回来运行curre

13、nt*/_set_current_state(TASK_RUNNING);returncount;注意set_current_state和signal_pending,它们两句保障了当用户程序在调用poll后挂起时,发信号可以让程序迅速推出poll调用,而通常的系统调用是不会被信号打断的。纵览do_poll函数,主要是在循环内等待,直到count大于0才跳出循环,而count主要是靠do_pollfd函数处理。注意标红的while循环,当用户传入的fd很多时(比如1000个),对do_pollfd就会调用很多次,poll效率瓶颈的另一原因就在这里。do_pollfd就是针对每个传进来的fd,调

14、用它们各自对应的poll函数,简化一下调用过程,如下:fs/select.c-sys_poll()-do_poll()staticvoiddo_pollfd(unsignedintnum,structpollfd*fdpage,poll_table*pwait,int*count)structfile*file=fget(fd);file-f_op-poll(file,如果fd对应的是某个socket,do_pollfd调用的就是网络设备驱动实现的poll;如果fd对应的是某个ext3文件系统上的一个打开文件,那do_pollfd调用的就是ext3文件系统驱动实现的poll。一句话,这个fil

15、e-f_op-poll是设备驱动程序实现的,那设备驱动程序的poll实现通常又是什么样子呢?其实,设备驱动程序的标准实现是:调用poll_wait,即以设备自己的等待队列为参数(通常设备都有自己的等待队列,不然一个不支持异步操作的设备会让人很郁闷)调用structpoll_table的回调函数。作为驱动程序的代表,我们看看socket在使用tcp时的代码:net/ipv4/tcp.c-tcp_pollunsignedinttcp_poll(structfile*file,structsocket*sock,poll_table*wait)poll_wait(file,sk-sk_sleep,w

16、ait);tcp_poll的核心实现就是poll_wait,而poll_wait就是调用structpoll_table对应的回调函数,那poll系统调用对应的回调函数就是_poll_wait,所以这里几乎就可以把tcp_poll理解为一个语句:_poll_wait(file,sk-sk_sleep,wait);由此也可以看出,每个socket自己都带有一个等待队列sk_sleep,所以上面我们所说的“设备的等待队列”,其实不止一个。这时候我们再看看_poll_wait的实现:fs/select.c-_poll_wait()void_pollwait(structfile*filp,wait_

17、queue_head_t*wait_address,poll_table*_p)_poll_wait的作用就是创建了上图所示的数据结构(一次_poll_wait即一次设备poll调用只创建一个poll_table_entry),并通过structpoll_table_entry的wait成员,把current挂在了设备的等待队列上,此处的等待队列是wait_address,对应tcp_poll里的sk-sk_sleep。现在我们可以回顾一下poll系统调用的原理了:先注册回调函数_poll_wait,再初始化table变量(类型为structpoll_wqueues),接着拷贝用户传入的structpollfd(其实主要是fd)(瓶颈1),然后轮流调用所有fd对应的poll(把current挂到各个fd对应的设备等待队列上)(瓶颈2)。在设备收到一条消息(网络设备)或填写完文件数据(磁盘设备)后,会唤醒设备等待队列上的进程,这时current便被唤醒了。current醒来后离开sys_poll的操作相对简单,这里就不逐行分析了。

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 其他


经营许可证编号:宁ICP备18001539号-1