好莱坞原则和 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有什么区别?

以如下步骤为例:

  1. The file descriptor that represents the read side of a pipe ( RFD ) is added inside the epoll device.
  2. Pipe writer writes 2Kb of data on the write side of the pipe.
  3. A call to epoll_wait(2) is done that will return RFD as ready file descriptor.
  4. The pipe reader reads 1Kb of data from RFD.
  5. 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