好莱坞原则和 epoll
Posted on Sun 09 May 2021 in Journal
Thinking
人的本性就是喜新厌旧,一成不变的东西多数人不会喜欢,每个人都乐于看见自己的想法和点子被人喜爱和采纳,自己的工作和成绩被人认可和称颂,没人喜欢凡事听命于人,少有人愿意默默无闻,大多数人都有虚荣心。
Quote
How to initiate, receive, demultiplex, dispatch, and process events in networked systems:
- Reactor,
- Proactor,
- Asynchronous Completion Token, and
- Acceptor-Connector
Program
主动发送,被动接收,网络编程就是这样,不要阻塞,不要等待,就象著名的好莱坞原则那样 - “不要打电话给我们,我们会打给你”。
Hollywood Principle — 'Don't call us, we'll call you'
According to https://man7.org/linux/man-pages/man7/epoll.7.html,
The epoll API performs a similar task to poll: monitoring multiple file descriptors to see if I/O is possible on any of them.
The epoll API can be used either as an edge-triggered or a level-triggered interface and scales well to large numbers of watched file descriptors.
The central concept of the epoll API is the epoll instance, an in-kernel data structure which, from a user-space perspective, can be considered as a container for two lists:
-
兴趣列表 The interest list (sometimes also called the epoll set): 关注的文件句柄 the set of file descriptors that the process has registered an interest in monitoring.
-
就绪列表 The ready list: the set of file descriptors that are "ready" for I/O. 有I/O活动的文件句柄 The ready list is a subset of (or, more precisely, a set of references to) the file descriptors in the interest list. The ready list is dynamically populated by the kernel as a result of I/O activity on those file descriptors.
现代Linux系统对于epoll 提供了三个系统调用 * epoll_create * epoll_ctl * epoll_wait
一个epoll集连接到由epoll_create创建的文件描述符。 然后通过epoll_ctl注册某些文件描述符的兴趣。 最后,实际的等待由epoll_wait开始。
epoll事件分发接口既可以表示为边缘触发(ET: Edge Trigger)也可以表示为 水平触发(LT: Level Trigger)。
Q. 边缘触发ET和水平触发LT有什么区别?
以如下步骤为例:
- The file descriptor that represents the read side of a pipe ( RFD ) is added inside the epoll device.
- Pipe writer writes 2Kb of data on the write side of the pipe.
- A call to epoll_wait(2) is done that will return RFD as ready file descriptor.
- The pipe reader reads 1Kb of data from RFD.
- A call to epoll_wait(2) is done.
If the RFD file descriptor has been added to the epoll interface using the EPOLLET flag, the call to epoll_wait(2) done in step 5 will probably hang because of the available data still present in the file input buffers and the remote peer might be expecting a response based on the data it already sent.
在边缘触发方式下,第5步或许会hang住,因为依然有 data 在输入缓冲里,而发送数据的远端可能正在期望对于发送的数据的一个响应
其原因是,边缘触发事件分发仅在事件发生在受监视文件上时才传送事件。 因此,在第5步中,调用者可能最终等待输入缓冲区中已经存在的某些数据。
在上面的示例中,由于在步骤 2中完成写操作,将在 RFD(读文件句柄) 上生成一个事件,并且在步骤3中使用该事件。 由于在步骤4中完成的读取操作不会占用整个缓冲区数据,因此在步骤5中对epoll_wait 的调用可能会无限期锁定。
epoll接口与EPOLLET标志(Edge Triggered)一起使用时,应使用非阻塞文件描述符,以避免阻塞读写操作处理多个文件描述符的任务。
边缘触发一定要用非阻塞方式,通常建议使用边缘触发,原因在于不会这种方式不会对同一不变的状态多次触发,仅在状态改变才作通知
需要注意的事项有:
1) with non-blocking file descriptors 使用非阻塞文件描述符
2) by going to wait for an event only after read(2) or write(2) return EAGAIN 只有在 read/write返回 EAGAIN 之后才去等待其他事件
而当用作水平触发(LT)时,epoll绝对是一种更快的poll,并且可以在任何使用后者的地方使用,因为它具有相同的语义。
由于即使在接收到多个数据块的情况下也可以生成边缘触发的epoll多个事件,因此调用者可以选择指定EPOLLONESHOT标志,以在epoll_wait()接收到事件后告诉epoll禁用关联的文件描述符。
当指定了EPOLLONESHOT标志时,调用者负责使用带有EPOLL_CTL_MOD的epoll_ctl(2)重新设置文件描述符。
常用流程为 1) 添加一个文件句柄 fd 到 epoll watch set
int fdEpoll = epoll_create(MAX_FD_SIZE); // the parameter is ignored since Linux 2.6.8, but should be greater than 0
if(fdEpoll < 0)
return -1;
struct epoll_event evt;
int sock;
memset(&evt, 0, sizeof(evt));
evt.events = EPOLLIN;
evt.data.fd=sock;
int nRet = epoll_ctl(fdEpoll, EPOLL_CTL_ADD, sock, &evt);
if(nRet < 0)
return -2;
2) 等待感兴趣的事件触发 wait the interesting event trigger
struct epoll_event ev, *events;
for(;;) {
nfds = epoll_wait(kdpfd, events, maxevents, -1);
for(n = 0; n < nfds; ++n) {
if(events[n].data.fd == listener) {
client = accept(listener, (struct sockaddr *) &local,
&addrlen);
if(client < 0){
perror("accept");
continue;
}
setnonblocking(client);
ev.events = EPOLLIN | EPOLLET;
ev.data.fd = client;
if (epoll_ctl(kdpfd, EPOLL_CTL_ADD, client, &ev) < 0) {
fprintf(stderr, "epoll set insertion error: fd=%d0,
client);
return -1;
}
}
else
do_use_fd(events[n].data.fd);
}
}
FAQ
-
问题 使用EPOLLET标志(边沿触发的行为)时,是否需要连续读取/写入文件描述符直到EAGAIN?
-
回答 从epoll_wait(2)接收到一个事件应该提示您该文件描述符已准备就绪,可以执行请求的I / O操作。您必须考虑到它准备就绪,直到下一次(非阻塞)读/写产生EAGAIN为止。何时以及如何使用文件描述符完全取决于您。
对于面向数据包/令牌的文件(例如,数据报套接字,标准模式下的终端),检测读/写I / O空间结束的唯一方法是继续读/写直到EAGAIN。
对于面向流的文件(例如管道,FIFO,流套接字),还可以通过检查从目标文件描述符读取/写入到目标文件描述符的数据量来检测读取/写入I / O空间已用完的情况。例如,如果通过要求读取一定数量的数据来调用read(2),而read(2)返回的字节数较少,则可以确定已经用完了文件描述符的读取I / O空间。使用write(2)进行写入时也是如此。 (如果不能保证受监视的文件描述符始终引用面向流的文件,请避免使用后一种技术。)
注意事项:在边缘触发方式下,保险的方式是一条道跑到黑,读到read return -1, errno = EAGAIN (读完了) 或 read return 0 (连接关闭了) 当然 ,如果你知道了缓冲区中有多少数据,通过read 想读取一定量的数据,结果只返回了很少的数据,那么也可得知要读取的缓冲空间已经耗尽了?通过 ioctl 的 FIONREAD 命令? 从Linux 2.6.17 开始, 可以用 EPOLLRDHUP 来检测socket 的正常关闭, 注意不是 EPOLLHUP (这个是指异常的关闭)
Words
- breakthrough 英 [ˈbreɪkθruː] 美 [ˈbreɪkθruː] n. 突破;突破性进展
Science and engineering breakthroughs are rapidly changing the way we live our lives
-
These events are not predictable and can happen at any time.
-
This led to the deaths of many more species.
-
An explosion is a rapid, violent release of energy.
-
The respiratory system includes nose and lungs.
-
herd 英 [hɜːd] 美 [hɜːrd] n. 兽群,畜群;放牧人 vi. 成群,聚在一起 vt. 放牧;使成群 n. (Herd)人名;(英、芬)赫德
-
judaism 英 [ˈdʒuːdeɪɪzəm] 美 [ˈdʒuːdiɪzəm,ˈdʒuːdeɪɪzəm]
n. 犹太教;(总称)犹太人;犹太主义
- wreckage 英 [ˈrekɪdʒ] 美 [ˈrekɪdʒ]
n. (失事船或飞机等的)残骸;(船只等的)失事
- point of view
Learn to understand things from another person's point of view
- Jews
美 [dʒuːz]
n. 犹太人,犹太教 Followers of Judaism are called Jews
- Hindus
英 [,hin'du:] 美 [,hin'du:]
n. 印度教徒
Some Hindus believe in the cycle of birth, life, death and rebirth
- immune 英 [ɪˈmjuːn] 美 [ɪˈmjuːn]
adj. 免疫的;免于……的,免除的 n. 免疫者;免除者
Sneezing and fever are examples of how the immune system works