标签:fine 针对 cpu orm alt data- 数据结构 name 长度
因此这类帮人理清思路的文章尽可能的记成流水的方式,尽可能的简单明了。
define sleep_list; define wait_entry; wait_entry.task= current_task; wait_entry.callback = func1; if (something_not_ready); then # 进入堵塞路径 add_entry_to_list(wait_entry, sleep_list); go on: schedule(); if (something_not_ready); then goto go_on; endif del_entry_from_list(wait_entry, sleep_list); endif ...
something_ready; for_each(sleep_list) as wait_entry; do wait_entry.callback(...); if(wait_entry.exclusion); then break; endif done
一般而言,一个callback里面都是以下的逻辑:
common_callback_func(...) { do_something_private; wakeup_common; }
如今留个思考,假设实现select/poll,应该在wait_entry的callback上做什么文章呢?
.....
假设有N个socket被同一个task处理。怎么完毕多路复用逻辑呢?非常显然。我们要等待“数据可读”这个事件,而不是去等待“实际的数据”!。我们要堵塞在事件上,该事件就是“N个socket中有一个或多个socket上有数据可读”,也就是说,仅仅要这个堵塞解除,就意味着一定有数据可读,意味着接下来调用recv/recvform一定不会堵塞!还有一方面。这个task要同一时候排入全部这些socket的sleep_list上,期待随意一个socket仅仅要有数据可读。都能够唤醒该task。
那么,select/poll这类多路复用模型的设计就显而易见了。
select/poll的设计非常easy。为每个socket引入一个poll例程,该历程对于“数据可读”的推断例如以下:
poll() { ... if (接收队列不为空) { ev |= POLL_IN; } ... }
for_each_N_socket as sk; do event.evt = sk.poll(...); event.sk = sk; put_event_to_user; done;
epoll的wait_entry的callback要做的就是,将自己自行增加到这个ready_list中去。等待epoll_wait返回的时候。仅仅须要遍历ready_list就可以。epoll_wait睡眠在一个单独的队列(single_epoll_waitlist)上,而不是socket的睡眠队列上。
和select/poll不同的是,使用epoll的task不须要同一时候排入全部多路复用socket的睡眠队列,这些socket都拥有自己的队列,task仅仅须要睡眠在自己的单独队列中等待事件就可以,每个socket的wait_entry的callback逻辑为:
epoll_wakecallback(...) { add_this_socket_to_ready_list; wakeup_single_epoll_waitlist; }为此。epoll须要一个额外的调用,那就是epoll_ctrl ADD。将一个socket增加到epoll table中,它主要提供一个wakeup callback,将这个socket指定给一个epoll entry,同一时候会初始化该wait_entry的callback为epoll_wakecallback。整个epoll_wait以及协议栈的wakeup逻辑例如以下所看到的:
define wait_entry wait_entry.socket = this_socket; wait_entry.callback = epoll_wakecallback; add_entry_to_list(wait_entry, this_socket.sleep_list);
define single_wait_list define single_wait_entry single_wait_entry.callback = wakeup_common; single_wait_entry.task = current_task; if (ready_list_is_empty); then # 进入堵塞路径 add_entry_to_list(single_wait_entry, single_wait_list); go on: schedule(); if (sready_list_is_empty); then goto go_on; endif del_entry_from_list(single_wait_entry, single_wait_list); endif for_each_ready_list as sk; do event.evt = sk.poll(...); event.sk = sk; put_event_to_user; done;
add_this_socket_to_ready_list; wakeup_single_wait_list;
综合以上。能够给出以下的关于epoll的流程图。能够对照本文第一部分的流程图做比較
能够看出。epoll和select/poll的本质差别就是,在发生事件的时候,每个epoll item(也就是socket)都拥有自己单独的一个wakeup callback,而对于select/poll而言。仅仅有一个!这就意味着epoll中,一个socket发生事件,能够调用其独立的callback来处理它自身。从宏观上看,epoll的高效在于分离出了两类睡眠等待。一个是epoll本身的睡眠等待。它等待的是“随意一个socket发生事件”,即epoll_wait调用返回的条件,它并不适合直接睡眠在socket的睡眠队列上,假设真要这样,究竟睡谁呢?毕竟那么多socket...因此它仅仅睡自己。一个socket的睡眠队列一定要仅仅和它自己相关。因此还有一类睡眠等待是每个socket自身的,它睡眠在自己的队列上就可以。
两者究竟什么差别呢?
因此。对于缓冲区状态的变化。不能简单理解为有和无这么简单,而是数据包的到来和不到来。
ET和LT是中断的概念,假设你把数据包的到来。即插入到socket接收队列这件事理解成一个中断事件,所谓的边沿触发不就是这个概念吗?
在实现上。从以下的代码能够看出二者的差异。
epoll_wait for_each_ready_list_item as entry; do remove_from_ready_list(entry); event = entry.poll(...); if (event) then put_user; if (LT) then # 以下一次poll的结论为结果 add_entry_to_ready_list(entry); endif endif done
当然,对于LT而言。也有相似的问题,可是LT会激进地反馈数据可读,因此事件不会轻易由于你的编程错误而被丢弃。
对于LT而言,由于它会不断反馈,仅仅要有数据,你想什么时候读就能够什么时候读。它永远有“下一次poll”的机会主动探知是否有数据能够继续读。即便使用堵塞模式,仅仅要不要跨越堵塞边界造成其它socket饥饿。读多少数据均能够,可是对于ET而言,它在通知你的应用程序数据可读后。尽管新的数据到来还是会通知,可是你并不能控制新的数据一定会来以及什么时候来。所以你必须读全然部的数据才干离开,读全然部的时候意味着你必须能够探知数据为空,因此也就是说,你必须採用非堵塞模式,直到返回EAGIN错误。
3.epoll惊群,能够參考ngx的经验
4.epoll也可借鉴NAPI关中断的方案,直到Recv例程返回EAGIN或者错误发生,epoll的wakeup callback不再被调用。这意味着仅仅要缓冲区不为空。就算来了新的数据包也不会通知了。
a.仅仅要socket的epoll wakeup callback被调用,禁掉兴许的通知;
b.Recv例程在返回EAGIN或者错误的时候,開始兴许的通知。
Linux内核中网络数据包的接收-第二部分 select/poll/epoll
标签:fine 针对 cpu orm alt data- 数据结构 name 长度
原文地址:http://www.cnblogs.com/yfceshi/p/7097178.html