- 浏览: 26408 次
- 性别:
- 来自: 杭州
文章分类
最新评论
-
wode66:
仔细看过楼主的文章,文字解释都很清晰,不过,图片gif有些地方 ...
Java并发编程J.U.C之Condition -
xiaoZ5919:
好文章!thanks for sharing
Tomcat Wrapper组件 -
bjmike:
<p>好文章,拜读了!!</p>
Java并发编程之ConcurrentHashMap -
elam:
我觉得作者顺便讲讲volatile关键字会更清晰引用IbmDe ...
Java并发编程之ConcurrentHashMap -
wanbin021614:
文章写得很好提个小建议,应该补充一下ConcurrentHas ...
Java并发编程之ConcurrentHashMap
最近花些功夫在研究Java NIO的JDK源码,发现Selector的实现,除了在唤醒机制上做了手脚,主要依赖操作系统的实现,为了无负担的弄懂Selector,有必要研究一 下操作系统是如何实现选择的。本文主要参考linux-2.6.10内核epoll的实现(poll见上一篇 )。
本文可能会表现得很肤浅,高手们请直接略过,另外,本文所出现的“政府”字样,乃比喻性质的,或者就认为它是“清政府”好了,请相关人员不要曲解。
上回冒充大侠poll府上走了一遭,感觉还不过瘾,于是计划再到它表哥epoll家去闯闯,可是man了一下之后,我有点退却了,丫的,还以为它表哥是一个人,原来是仨儿:
#include <sys/epoll.h> int epoll_create(int size); int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event); int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout); typedef union epoll_data { void *ptr; int fd; __uint32_t u32; __uint64_t u64; } epoll_data_t; struct epoll_event { __uint32_t events; /* Epoll events */ epoll_data_t data; /* User data variable */ };
首先看看epoll_event是啥玩意儿,应该和pollfd类似吧?
还记得pollfd的定义吗?
struct pollfd { int fd; /* file descriptor */ short events; /* requested events */ short revents; /* returned events */ };
对比一下,发现区别不大,epoll_data_t是一个共用体,至少我们可以认为它可以是一个fd,所以较大的不同点就在于epoll_event没有 revents了,上次探索poll的时候不是发现,这是poll一个很关键的地方吗?最终事件是否发生就看它的值了。决心带着这个疑问去探一探。
这次,我不打算带大家一步一步的串门,因为要理解epoll,最关键的就是它的结构设计,所以这里先从epoll的结构出发,请看下面一副简化的结构及联系图:
先介绍一下图中涉及到的各种结构体:
- 先说明一下epitem结构体是什么,顾名思义,即为epollitem,epoll的基本单元,下面分别介绍一下几个主要的变量的含义:
- struct list_head rdllink,或者取名为ready_list_link你会更容易理解,当epitem对应的fd的存在已经ready的I/O事件,则 ep_poll_callback回调函数会将该结点链接到eventpoll中的rdllist循环链表中去,这样就将ready的epitem都串连 起来了
- struct epoll_filefd ffd,ffd中只包含一个fd及fd对应的file的指针
- struct eventpoll *ep,eventpoll的指针,每个epitem都有这样一个指针,它指向对应的eventpoll变量,其实它的作用很简单,我们只要拿到了epitem,就可以根据它拿到eventpoll
- struct event_poll event,还记得epoll_ctl的参数类型吗?其中就有一个event_poll指针,而该event即用来存放总用户空间拷贝的event_poll
- 然后说明一下eventpoll结构体中主要的变量的含义:
- struct list_head rdllist,ready_link_list,表示这是一个链表,事实上它就是一个循环链表,链表中的每个结点即为epitem中的rdllink,rdllist中链接的所有rdllink对应的epitem有事件ready
- struct rb_root rbr,红黑树的根结点,其实每一个epitem中的第一个变量即为struct rb_node rbn;即表示红黑树的一个结点,所以rbr即是这样一颗红黑树,它的结点都为epitem变量,即相当于一个Set,将所有epitem管理起来,通过 它可以很方便的增删改查epitem
- 再看eppoll_entry结构体,它主要有这样几个变量:
- void * base,base指向其对应的epitem
- wait_queue_t wait,等待队列的项,wait中有一个唤醒回调函数指针,且该指针被初始化为ep_poll_callback,wait会被挂在到设备的等待队列 中,等待设备的唤醒,当设备因状态改变,唤醒wait时,会执行ep_poll_callback,而ep_poll_callback会做这样一件 事:list_add_tail(&epi->rdllink,&ep->rdllist),其中epi即为epitem变 量,通过wait偏移拿到eppoll_entry,然后可以拿到base指针,即拿到了对应的epitem,而ep即为eventpoll变量,通过 epitem的ep指针即可拿到,list_add_tail将epi的rdllink链到ep的rdllist中
下面结合这幅图大致讲解一下epoll_create、epoll_ctl、epoll_wait都在做些什么:
- 首先,epoll_create会创建一个epoll的文件(epfd),同时创建并初始化一个struct eventpoll,其中file的private_data指针即指向了eventpoll变量,因此,知道epfd就可以拿到file,即拿到了 eventpoll变量,这就是epoll_create所做的工作
- epoll_ctl又做了什么事呢?首先大家看到了eventpoll中的rb_root红黑树吗?epoll_ctl其实就是在操作这颗红黑树,epoll_ctl有三种操作:
- EPOLL_CTL_ADD:往红黑树中创建并添加一个epitem,对应处理函数为ep_insert
在添加epitem时,也就是在ep_insert过程中,会创建一个eppoll_entry,并将wait_queue挂载到设备的等待队列上,其中 该wait_queue的唤醒回调函数为ep_poll_callback,当设备有事件ready而唤醒wait_queue时,就会执行 ep_poll_callback将当前epitem链接到eventpoll中的rdllist中去,另外,如果在挂载wait_queue时就发现设 备有事件ready了,同样会将epitem链接到rdllist中去 - EPOLL_CTL_MOD:修改对应的epitem,对应处理函数为ep_modify
在ep_modify过程中,处理会修改epitem对应的event值,同样会先查看一下对应设备的当前状态,如果有ready事件发生,则会将当前epitem链接到rdllist中去 - EPOLL_CTL_DEL:从红黑树中删除对应的epitem,对应处理函数为ep_remove
释放钩子、链接、资源空间等,如epitem所占的空间 - 其实epoll_ctl已经将绝大部分事情都做了,epoll_wait有只需要收集结果就行了,它的目标也很单一,就看rdllist中是否有 元素即可,当然,它还需要控制timeout,及结果转移,因为对于rdllist链接的epitem,只能说明其对应的fd有事件ready,但是哪些 事件是不知道的,因此epoll_ctl再收集结果时,会亲自查看一下对应file的ready状态,来写回events
在给大家大致讲解了epoll涉及到的结构及epoll三兄弟大概在做些什么之后,开始我们的探索之旅吧:
epoll_create
先看sys_epoll_create系统调用:
asmlinkage long sys_epoll_create(int size) { int error, fd; struct inode *inode; struct file *file; DNPRINTK(3, (KERN_INFO "[%p] eventpoll: sys_epoll_create(%d)\n", current, size)); /* Sanity check on the size parameter */ error = -EINVAL; if (size goto eexit_1; /* * Creates all the items needed to setup an eventpoll file. That is, * a file structure, and inode and a free file descriptor. */ error = ep_getfd(&fd, &inode, &file); if (error) goto eexit_1; /* Setup the file internal data structure ( "struct eventpoll" ) */ error = ep_file_init(file); if (error) goto eexit_2; DNPRINTK(3, (KERN_INFO "[%p] eventpoll: sys_epoll_create(%d) = %d\n", current, size, fd)); return fd; eexit_2: sys_close(fd); eexit_1: DNPRINTK(3, (KERN_INFO "[%p] eventpoll: sys_epoll_create(%d) = %d\n", current, size, error)); return error; }
我们只需要注意到两个函数:ep_getfd和ep_file_init
- ep_getfd其实就是在创建文件,我们这里不讲文件是如何创建的,大家只需要知道,调用了这个函数之后,除非出错,否则epoll文件就会被创建出来
- ep_file_init我们到可以讲一下:
顾名思义,它就是初始化刚才创建的文件的,下面看看它究竟初始化了哪些内容:
static int ep_file_init(struct file *file) { struct eventpoll *ep; if (!(ep = kmalloc(sizeof(struct eventpoll), GFP_KERNEL))) return -ENOMEM; memset(ep, 0, sizeof(*ep)); rwlock_init(&ep->lock); init_rwsem(&ep->sem); init_waitqueue_head(&ep->wq); init_waitqueue_head(&ep->poll_wait); INIT_LIST_HEAD(&ep->rdllist); ep->rbr = RB_ROOT; file->private_data = ep; DNPRINTK(3, (KERN_INFO "[%p] eventpoll: ep_file_init() ep=%p\n", current, ep)); return 0; }
从这几行代码可以看出,ep_file_init就做了两件事:
- 创建并初始化一个eventpoll结构体变量
- 指定file的private_data指针指向刚创建的eventpoll变量,这样,只要根据epoll文件描述符epfd就可以拿到file进而就拿到了eventpoll变量了,该eventpoll就是epoll_ctl和epoll_wait工作的场所
对外看来,epoll_create就做了一件事,那就是创建一个epoll文件,事实上,更关键的是,它创建了一个eventpoll结构体变量,该变量为epoll_ctl和epoll_wait的工作打下了基础。
epoll_ctl
展示一下epoll_ctl系统调用先:
asmlinkage long sys_epoll_ctl(int epfd, int op, int fd, struct epoll_event __user *event) { int error; struct file *file, *tfile; struct eventpoll *ep; struct epitem *epi; struct epoll_event epds; DNPRINTK(3, (KERN_INFO "[%p] eventpoll: sys_epoll_ctl(%d, %d, %d, %p)\n", current, epfd, op, fd, event)); error = -EFAULT; if (EP_OP_HASH_EVENT(op) && copy_from_user(&epds, event, sizeof(struct epoll_event))) goto eexit_1; /* Get the "struct file *" for the eventpoll file */ error = -EBADF; file = fget(epfd); if (!file) goto eexit_1; /* Get the "struct file *" for the target file */ tfile = fget(fd); if (!tfile) goto eexit_2; /* The target file descriptor must support poll */ error = -EPERM; if (!tfile->f_op || !tfile->f_op->poll) goto eexit_3; /* * We have to check that the file structure underneath the file descriptor * the user passed to us _is_ an eventpoll file. And also we do not permit * adding an epoll file descriptor inside itself. */ error = -EINVAL; if (file == tfile || !IS_FILE_EPOLL(file)) goto eexit_3; /* * At this point it is safe to assume that the "private_data" contains * our own data structure. */ ep = file->private_data; down_write(&ep->sem); /* Try to lookup the file inside our hash table */ epi = ep_find(ep, tfile, fd); error = -EINVAL; switch (op) { case EPOLL_CTL_ADD: if (!epi) { epds.events |= POLLERR | POLLHUP; error = ep_insert(ep, &epds, tfile, fd); } else error = -EEXIST; break; case EPOLL_CTL_DEL: if (epi) error = ep_remove(ep, epi); else error = -ENOENT; break; case EPOLL_CTL_MOD: if (epi) { epds.events |= POLLERR | POLLHUP; error = ep_modify(ep, epi, &epds); } else error = -ENOENT; break; } /* * The function ep_find() increments the usage count of the structure * so, if this is not NULL, we need to release it. */ if (epi) ep_release_epitem(epi); up_write(&ep->sem); eexit_3: fput(tfile); eexit_2: fput(file); eexit_1: DNPRINTK(3, (KERN_INFO "[%p] eventpoll: sys_epoll_ctl(%d, %d, %d, %p) = %d\n", current, epfd, op, fd, event, error)); return error; }
记得前文提到过eventpoll结构体包含一个变量struct rb_root rbr,这就是一颗红黑树的根结点,epoll_ctl的ADD、DEL、MOD操作,就是在操作这颗红黑树。先分析一下代码流程:
- 首先copy_from_user将用户传入的event_poll拷贝到epds中,以供自己使用
- file = fget(epfd),根据epoll文件的描述符拿到对应的文件file
- tfile = fget(fd),同理,根据fd拿到目标文件tfile
- ep = file->private_data,即拿到了epoll_create创建的eventpoll结构体变量,准备开始工作
- epi = ep_find(ep, tfile, fd),这里不详细讲解ep_find源码,只需要说明一下即可,ep_find即从ep中的红黑树中根据tfile和fd来查找epitem,还记得 epitem结构体吗,这是epoll的基本单元,每个被epoll_ctl添加过的fd都会保存在一个epitem变量中,每个epitem变量都是红 黑树的结点,如果不理解红黑树也不要紧,就简单把它看做一个Map,其Key为tfile+fd,Value即为epitem的指针,因此能够根据 ep_find查找到tfile+fd对应的epitem,当然,如果找到的epi==NULL,自然表明不存在了
- 接着根据op的三种类型分别操作:
-
EPOLL_CTL_ADD
首先epds.events |= POLLERR | POLLHUP确保“出错、连接挂起”被当做感兴趣事件,因为底层有义务将出错信息返回给应用;然后调用ep_insert生成一个epitem并插入到 ep对应的红黑树中;这里详细看一下ep_insert的实现:
struct ep_pqueue { // poll_table结构体在讲解poll实现那篇有说明,内部只包含一个回调函数指针 poll_table pt; // epitem指针 struct epitem *epi; }; static int ep_insert(struct eventpoll *ep, struct epoll_event *event, struct file *tfile, int fd) { int error, revents, pwake = 0; unsigned long flags; // 待创建的epitem变量的指针 struct epitem *epi; // ep_pqueue结构体变量 struct ep_pqueue epq; error = -ENOMEM; // 为epi分配空间 if (!(epi = EPI_MEM_ALLOC())) goto eexit_1; /* Item initialization follow here ... */ EP_RB_INITNODE(&epi->rbn); // 初始化红黑树结点 // 初始化各种链表 INIT_LIST_HEAD(&epi->rdllink); INIT_LIST_HEAD(&epi->fllink); INIT_LIST_HEAD(&epi->txlink); INIT_LIST_HEAD(&epi->pwqlist); // 初始化epi的ep指针指向ep,这样一来,只要拿到epi就可以拿到ep了 epi->ep = ep; // 初始化epi的ffd变量,该变量包含一个文件指针+文件描述符 EP_SET_FFD(&epi->ffd, tfile, fd); // 初始化event epi->event = *event; atomic_set(&epi->usecnt, 1); epi->nwait = 0; /* Initialize the poll table using the queue callback */ // 初始化ep_pqueue变量的epi指针为刚创建的epq epq.epi = epi; // 初始化epq中回调函数指针指向ep_ptable_queue_proc // ep_ptable_queue_proc见下文 init_poll_funcptr(&epq.pt, ep_ptable_queue_proc); /* * Attach the item to the poll hooks and get current event bits. * We can safely use the file* here because its usage count has * been increased by the caller of this function. */ // 这里再说明一下为什么会有ep_pqueue这个结构体,它很像一个 // 中转变量,我可以看到tfile->f_op->poll(tfile, &epq.pt) // 好像只用到了回调函数指针而已,那有必要用epq吗?这其实就是 // 内核的一种惯用手法,在回调函数中,通过poll_table指针偏移 // 即可拿到ep_pqueue,进而拿到对应的epitem指针 // tfile对应poll方法中,epq.pt方法会被调用,即ep_ptable_queue_proc // 会被执行,从而将等待队列项(见ep_ptable_queue_proc)挂载到 // 设备的等待队列上,当设备唤醒等待队列项时,ep_poll_callback将会被执行 revents = tfile->f_op->poll(tfile, &epq.pt); /* * We have to check if something went wrong during the poll wait queue * install process. Namely an allocation for a wait queue failed due * high memory pressure. */ if (epi->nwait < 0) goto eexit_2; spin_lock(&tfile->f_ep_lock); // 将epi的fllink链接到tfile的f_ep_links上 list_add_tail(&epi->fllink, &tfile->f_ep_links); spin_unlock(&tfile->f_ep_lock); /* We have to drop the new item inside our item list to keep track of it */ write_lock_irqsave(&ep->lock, flags); /* Add the current item to the rb-tree */ // 将创建并初始化好的epitem插入到eventpoll的红黑树中 ep_rbtree_insert(ep, epi); /* If the file is already "ready" we drop it inside the ready list */ // 因为刚才的file->f_op->poll执行之后,有可能对应file已经是ready状态了 // 如果发现的确是感兴趣的事件发生,并且当前epitem没有链接(即没有被收集到ep的 // rdllist中,简单说,不需要重复收集),则就将其链接到ep的rdllist上 if ((revents & event->events) && !EP_IS_LINKED(&epi->rdllink)) { // 将epi的rdllink结点链接到ep的rdllist头结点上 list_add_tail(&epi->rdllink, &ep->rdllist); /* Notify waiting tasks that events are available */ // 将ep从等待队列中唤醒,或者这样理解,这里已经找到满意结果了,不用在等待了 if (waitqueue_active(&ep->wq)) wake_up(&ep->wq); if (waitqueue_active(&ep->poll_wait)) pwake++; } write_unlock_irqrestore(&ep->lock, flags); /* We have to call this outside the lock */ if (pwake) ep_poll_safewake(&psw, &ep->poll_wait); DNPRINTK(3, (KERN_INFO "[%p] eventpoll: ep_insert(%p, %p, %d)\n", current, ep, tfile, fd)); return 0; eexit_2: ep_unregister_pollwait(ep, epi); /* * We need to do this because an event could have been arrived on some * allocated wait queue. */ write_lock_irqsave(&ep->lock, flags); if (EP_IS_LINKED(&epi->rdllink)) EP_LIST_DEL(&epi->rdllink); write_unlock_irqrestore(&ep->lock, flags); EPI_MEM_FREE(epi); eexit_1: return error; } // 回调函数 static void ep_ptable_queue_proc(struct file *file, wait_queue_head_t *whead, poll_table *pt) { // 通过pt拿到epitem指针 struct epitem *epi = EP_ITEM_FROM_EPQUEUE(pt); // 待创建的eppoll_entry结构体变量指针 struct eppoll_entry *pwq; if (epi->nwait >= 0 && (pwq = PWQ_MEM_ALLOC())) { // 这个为epoll的一个关键的地方,给pwq中的等待队列项初始化唤醒 // 回调函数,这里初始化为ep_poll_callback init_waitqueue_func_entry(&pwq->wait, ep_poll_callback); // 等待队列的头指针,例如,当执行file->f_op->poll,则whead即 // 为file对应设备的等待队列头指针 pwq->whead = whead; // pwq的base指针指向epi,这样只要拿到eppoll_entry就能拿到epitem了 pwq->base = epi; // 挂载pwq中等待队列项,当设备唤醒该项时,wait中回调函数会被调用 add_wait_queue(whead, &pwq->wait); // 将pwq的llink链接到epi的pwqlist上 list_add_tail(&pwq->llink, &epi->pwqlist); epi->nwait++; } else { /* We have to signal that an error occurred */ epi->nwait = -1; } } static int ep_poll_callback(wait_queue_t *wait, unsigned mode, int sync, void *key) { int pwake = 0; unsigned long flags; // 通过wait拿到eppoll_entry中的base,即拿到了epitem struct epitem *epi = EP_ITEM_FROM_WAIT(wait); // 通过epi的ep指针即拿到了eventpoll struct eventpoll *ep = epi->ep; DNPRINTK(3, (KERN_INFO "[%p] eventpoll: poll_callback(%p) epi=%p ep=%p\n", current, epi->file, epi, ep)); write_lock_irqsave(&ep->lock, flags); /* * If the event mask does not contain any poll(2) event, we consider the * descriptor to be disabled. This condition is likely the effect of the * EPOLLONESHOT bit that disables the descriptor when an event is received, * until the next EPOLL_CTL_MOD will be issued. */ if (!(epi->event.events & ~EP_PRIVATE_BITS)) goto is_disabled; /* If this file is already in the ready list we exit soon */ if (EP_IS_LINKED(&epi->rdllink)) goto is_linked; // 因为被设备唤醒,则说明当前epi对应的fd有事件ready // 则将其链接到ep的rdllist上 list_add_tail(&epi->rdllink, &ep->rdllist); is_linked: /* * Wake up ( if active ) both the eventpoll wait list and the ->poll() * wait list. */ // 已经找到结果了,不需要等待了,就notify一下吧,告诉大家不用再等了 if (waitqueue_active(&ep->wq)) wake_up(&ep->wq); if (waitqueue_active(&ep->poll_wait)) pwake++; is_disabled: write_unlock_irqrestore(&ep->lock, flags); /* We have to call this outside the lock */ if (pwake) ep_poll_safewake(&psw, &ep->poll_wait); return 1; }
-
EPOLL_CTL_ADD
-
ep_insert相关函数是整个epoll的核心,结合上面的图再来 看,ep_pqueue结构体及ep_ptable_queue_proc方法其实就是起着桥梁的作用,通过它们,有着ep_poll_callback 等待队列项被挂在到设备的等待队列上,当设备唤醒该等待队列项时,自然就将当前epitem链接到eventpoll的rdllist链表上。
-
EPOLL_CTL_DEL
调用的函数为epoll_remove,这里不打算详细讲解,其实很容易理解,就是将epitem从eventpoll的红黑树中移除,起到取消注册的作用。 -
EPOLL_CTL_MOD
调用的函数为epoll_modify,这里先看看它的实现:
static int ep_modify(struct eventpoll *ep, struct epitem *epi, struct epoll_event *event) { int pwake = 0; unsigned int revents; unsigned long flags; /* * Set the new event interest mask before calling f_op->poll(), otherwise * a potential race might occur. In fact if we do this operation inside * the lock, an event might happen between the f_op->poll() call and the * new event set registering. */ // 这个就是modify需要修改的地方,即修改对应的events epi->event.events = event->events; /* * Get current event bits. We can safely use the file* here because * its usage count has been increased by the caller of this function. */ // 这个地方不要感到奇怪,说明几点后大家应该就容易理解了: // 1、既然是modify则说明之前已经被add过,不需要重复挂等待队列,因此回调函数为NULL // 2、同时因为NULL参数,即说明不需要回调,也不会有挂等待队列的操作 // 该调用其实就是去file那里收集一下事件而已 revents = epi->ffd.file->f_op->poll(epi->ffd.file, NULL); write_lock_irqsave(&ep->lock, flags); /* Copy the data member from inside the lock */ // 这个就是modify需要修改的地方,即修改对应的data epi->event.data = event->data; /* * If the item is not linked to the hash it means that it's on its * way toward the removal. Do nothing in this case. */ // 这个if不准备详细讲,其实很简单,前面不是已经问过file得到revents吗? // 如果当前epi已经被链接的话,就看是否是感兴趣事件发生,如果是,则同样将其 // 添加到eventpoll的rdllist链表中,并notify if (EP_RB_LINKED(&epi->rbn)) { /* * If the item is "hot" and it is not registered inside the ready * list, push it inside. If the item is not "hot" and it is currently * registered inside the ready list, unlink it. */ if (revents & event->events) { if (!EP_IS_LINKED(&epi->rdllink)) { list_add_tail(&epi->rdllink, &ep->rdllist); /* Notify waiting tasks that events are available */ if (waitqueue_active(&ep->wq)) wake_up(&ep->wq); if (waitqueue_active(&ep->poll_wait)) pwake++; } } } write_unlock_irqrestore(&ep->lock, flags); /* We have to call this outside the lock */ if (pwake) ep_poll_safewake(&psw, &ep->poll_wait); return 0; }
可以看到,修改操作其实就修改红黑树中对应的epitem的event值,有个细节点需要注意,也就是内核不放弃任何一次机会,修改过程中也不忘问一下file的事件状态,如果有事件ready则同样将其链接到rdllist链表中。
epoll_wait
在讲解了epoll_ctl的过程之后,epoll_wait的确没什么内容了,也不想贴一大堆源码什么的,这里分几个点将其描述一下:
前文已经多次出现一个链表rdllist,该链表位于eventpoll结构体变量中,当ep_poll_callback回调函数被调用时,肯 定会将当前epitem链接进来,或者在ep_insert、ep_modify过程中,如果发现file有事件ready也会将当前epitem链接到 rdllist上,因此,我们可以猜测得到epoll_wait在做什么,看下面关键部分代码:
// 如果rdllist中还没有epitem时,就开始等待了 if (list_empty(&ep->rdllist)) { /* * We don't have any available event to return to the caller. * We need to sleep here, and we will be wake up by * ep_poll_callback() when events will become available. */ // 初始化等待队列,等待队列项对应的线程即为当前线程 init_waitqueue_entry(&wait, current); // 不用多说,先将当前线程挂到等待队列上,之后在调用schedule_timeout // 时,就开始了超时等待了 add_wait_queue(&ep->wq, &wait); for (;;) { /* * We don't want to sleep if the ep_poll_callback() sends us * a wakeup in between. That's why we set the task state * to TASK_INTERRUPTIBLE before doing the checks. */ // 这块内容比较熟悉,在poll讲解过程中也有说明,它与schedule_timeout配合 // 因为会被阻塞,这里先设置线程状态为可中断 set_current_state(TASK_INTERRUPTIBLE); // 整个循环的核心,其实就在看rdllist中是否有数据,或者等待超时 // 应征了前面的说明,epoll_wait只需要等着收集数据即可 if (!list_empty(&ep->rdllist) || !jtimeout) break; // 如果被中断。。。后面部分比较简单,可以参照poll那篇 if (signal_pending(current)) { res = -EINTR; break; } write_unlock_irqrestore(&ep->lock, flags); jtimeout = schedule_timeout(jtimeout); write_lock_irqsave(&ep->lock, flags); } remove_wait_queue(&ep->wq, &wait); set_current_state(TASK_RUNNING); }
其实还有一点需要说明,大家可能也会想到,rdllist中的epitem只能表示对应fd有事件ready,可是自始至终都没看到有地方回写revents,我们怎么知道到底是哪些事件ready了呢?
在ep_send_events函数中有这么一段代码:
list_for_each(lnk, txlist) { epi = list_entry(lnk, struct epitem, txlink); /* * Get the ready file event set. We can safely use the file * because we are holding the "sem" in read and this will * guarantee that both the file and the item will not vanish. */ revents = epi->ffd.file->f_op->poll(epi->ffd.file, NULL); /* * Set the return event set for the current file descriptor. * Note that only the task task was successfully able to link * the item to its "txlist" will write this field. */ epi->revents = revents & epi->event.events;
看到这段代码,应该很清楚了,只需要遍历链表,再去拿一次就好了,见关键代码:
revents = epi->ffd.file->f_op->poll(epi->ffd.file, NULL);
这一句在ep_modify中也有出现。
还有一点我这里故意隐瞒,其实不是我特别想说明的点,对理解epoll影响也不大,那就是收集结果不是直接从rdllist中进行的,这中间还有一个转移的过程,在epoll_wait的最后进行,关键代码如下:
static int ep_collect_ready_items(struct eventpoll *ep, struct list_head *txlist, int maxevents) { int nepi; unsigned long flags; // rdllist里存放的就是当前ready的epitem链表,且至少存在一个epitem struct list_head *lsthead = &ep->rdllist, *lnk; struct epitem *epi; write_lock_irqsave(&ep->lock, flags); // 遍历rdllist链表 for (nepi = 0, lnk = lsthead->next; lnk != lsthead && nepi < maxevents;) { // 先拿到epitem epi = list_entry(lnk, struct epitem, rdllink); lnk = lnk->next; /* If this file is already in the ready list we exit soon */ // 确保不会被重复链接到txlink上 if (!EP_IS_LINKED(&epi->txlink)) { /* * This is initialized in this way so that the default * behaviour of the reinjecting code will be to push back * the item inside the ready list. */ epi->revents = epi->event.events; /* Link the ready item into the transfer list */ // 将epi的txlink链接到ep的txlist上,简单的说 // 将对应的epitem链接到txlist链表上 list_add(&epi->txlink, txlist); nepi++; /* * Unlink the item from the ready list. */ // 因为已经被转移了,所以从rdllist链表中清除 EP_LIST_DEL(&epi->rdllink); } } write_unlock_irqrestore(&ep->lock, flags); return nepi; }
经过这一步,rdllist中当前的结果已经被转移到txlist中,之后如果有新加入到rdllist的话,本次epoll_wait不会再关心,不过可以留到下次再收集。
还记得写回events的过程吗?最后的工作,当前是遍历txlist链表,并将结果写回到用户空间中了。
总结
后面详细讲解epoll_create、epoll_ctl、epoll_wait只是为了让大家强化理解前面的那副图,这里讲解epoll并不涉 及到内存映射等优化点,只是为了让大家理解,epoll到底在干什么,到最后,留给大家的,也只是这幅图,或者更简单的一个点:原来回调函数是epoll 比poll高明的地方啊。至于为什么要创建一个文件来承载eventpoll,甚至采用红黑树来保存数据,都只是空间换时间而已。
PS. 本文地址:Java NIO 教程 ,请大家关注:黄金档
相关推荐
Java NIO(非阻塞I/O)是一种在Java中处理I/O操作的高效方式,它引入了选择器(Selector)的概念,使得一个单独的线程可以监控多个输入输出通道(Channels),大大提高了并发处理能力。在这个"java基于NIO选择器...
Java NIO的Selector实现主要由`sun.nio.ch.SelectorImpl`类完成,其内部使用了操作系统提供的多路复用I/O机制,如Linux的epoll、Windows的IOCP等。`select()`方法实际上会调用操作系统API,阻塞等待直到有事件发生。...
Selector是Java NIO框架中的核心组件,它使得单个线程能够管理多个通道(Channels),从而提高系统资源利用率并优化性能。下面我们将详细探讨Java NIO中的Selector机制。 1. **Selector的作用** Selector的主要功能...
java侧起server(NioUdpServer1.java),基于Java Nio的selector 阻塞等候,一个android app(NioUdpClient1文件夹)和一个java程序(UI.java)作为两个client分别向该server发数据,server收到后分别打印收到的消息...
Java_NIO类库Selector机制解析.docJava_NIO类库Selector机制解析.docJava_NIO类库Selector机制解析.docJava_NIO类库Selector机制解析.doc
Java NIO系列教程(六) Selector Java NIO系列教程(七) FileChannel Java NIO系列教程(八) SocketChannel Java NIO系列教程(九) ServerSocketChannel Java NIO系列教程(十) Java NIO DatagramChannel Java ...
Selector是Java NIO中的核心组件之一,它允许单个线程处理多个通道(channels)的读写事件,极大地提高了服务器的并发能力。本篇文章将深入探讨如何在Java NIO中使用Selector处理客户端的I/O请求。 首先,我们需要...
6. **多路复用器(Multiplexing)**:Java NIO的选择器实现了I/O多路复用,即单个线程可以同时处理多个连接,这在处理大量并发连接时非常有用。 7. **管道(Pipe)**:管道是两个线程间进行单向数据传输的通道。一...
### Java NIO Selector选择器详解 #### 一、Selector概览及作用 **Selector** 是 Java NIO (New I/O) 框架中的一个重要组成部分,主要用于检测一个或多个 **NIO Channel** 的状态,包括但不限于可读、可写、可连接...
java NIO和java并发编程的书籍java NIO和java并发编程的书籍java NIO和java并发编程的书籍java NIO和java并发编程的书籍java NIO和java并发编程的书籍java NIO和java并发编程的书籍java NIO和java并发编程的书籍java...
Java NIO,全称为Non-Blocking Input/Output(非阻塞输入/输出),是Java标准库提供的一种替代传统的I/O模型的新技术。自Java 1.4版本引入NIO后,它为Java开发者提供了更高效的数据传输方式,尤其是在处理大量并发...
"Java NIO Selector 机制解析" Java NIO(New I/O)类库是Java 1.4版本以后引入的新一代I/O机制,相比传统的I/O机制,NIO提供了高效、异步、多路复用的I/O操作模式。Selector机制是NIO类库中的一种核心机制,用于...
Java NIO的反应器模式设计与实现,首先涉及到理解Java NIO的基本概念,如通道(Channel)、缓冲区(Buffer)、选择器(Selector)等。通道是进行读写操作的基础,是连接I/O源和数据的端点。缓冲区是在NIO中用于数据...
`java.nio.Selector`是选择器的主要接口。 4. **管道(Pipe)**:在某些特定情况下,两个线程之间可以使用`java.nio.Pipe`进行单向数据传递。 5. **文件系统API**:NIO还提供了`java.nio.file`包,包含一系列与...
Java NIO(New IO)是一个可以替代标准Java IO API的IO API(从Java 1.4开始),Java...Java NIO引入了选择器的概念,选择器用于监听多个通道的事件(比如:连接打开,数据到达)。因此,单个的线程可以监听多个数据通道。
而Java NIO引入了选择器(Selector)和通道(Channel)的概念,允许单个线程同时处理多个连接,大大提高了系统在高并发环境下的性能。 本例子中的"NioServer"可能是一个简单的Java NIO服务器端程序,用于演示如何...
Java NIO(New Input/Output)是Java标准库提供的一种I/O模型,它与传统的 Blocking I/O(IO)相比,提供了更加高效的数据传输方式。在Java NIO中,"新"主要体现在非阻塞和多路复用这两个特性上,这使得NIO更适合于...
1. **Selector(选择器)**:选择器是NIO的核心组件,它能够监控多个通道(Channel)的状态变化,当某个通道准备进行读写操作时,选择器会通知用户线程。这样,一个线程就可以管理多个通道,实现多路复用。 2. **...