epoll工作原理
子非鱼nofish
2023年03月01日 20:17
收录于文集
共3篇

最近在读张彦飞编写的《深入理解Linux网络》,这本书讲得非常深,其中包含了大量linux底层源码。读的过程很痛苦,但对我而言哪怕只读懂了其中皮毛也能受益匪浅。

这篇文章就是在学习到epoll底层原理时的笔记记录

实现

代码块
clike
自动换行
复制代码
int main()
{
	listen_fd = socket(AF_INET, SOCK_STREAM, 0);  // 创建监听socket
	listen(listen_fd, SOMAXCONN);  // 让监听socket进入监听状态
	
	epoll_fd = epoll_create(...);  // 创建epoll对象
	epoll_ctl(epoll_fd, EPOLL_CTL_ADD, listen_fd, ...);  // 将监听socket添加到epoll对象中
	
	while(1){ // 开始循环监听
		int nfds = epoll_wait(epoll_fd, ...);
		// 处理所有就绪事件
    for (int i = 0; i < nfds; i++) {
				...
				conn_fd = accept(listen_fd, ...);
				epoll_ctl(epoll_fd, EPOLL_CTL_ADD, conn_fd, ...); // 将新的客户端套接字添加到epoll对象中
		}
	}
}
复制成功
  • epoll_create:创建一个结构为struct eventpoll的内核对象

  • epoll_ctl:向epoll对象添加要管理的socket连接

  • epoll_wait:等待epoll对象管理的socket连接上的IO事件

epoll内核对象的创建

调用epoll_create()函数时,内核会创建一个struct eventpoll结构的内核对象

代码块
clike
自动换行
复制代码
struct eventpoll{
	wait_queue_head_t wq;       // 等待队列链表头
	struct rb_root    rbr;      // 红黑树
	struct list_head  rdllist;  // 就绪链表
}
复制成功
  • wq:等待队列链表,数据就绪的时候通过wq找到阻塞在epoll对象上的用户进程

  • rbr:红黑树,支持大量socket连接的高效查找、插入、删除。通过红黑树管理添加进来的所有socket连接

  • rdllist:就绪链表,用于存放就绪的socket连接,不用遍历红黑树,只用在rdllist中就能找到就绪连接

为epoll对象添加socket

调用epoll_ctl()函数时,配合EPOLL_CTL_ADD参数可以向epoll对象添加socket

  1. 创建红黑树节点对象epitem

  2. 等待事件添加到socket的等待队列中,设置数据就绪的回调函数为ep_poll_callback

  3. 将epitem插入rbr红黑树

等待接收

调用epoll_wait()函数时,观察eventpoll→rdllist就绪链表里有没有数据,有数据就返回,没数据就把当前进程添加到eventpoll→wq等待队列链表中,设置等待项回调函数default_wake_function,然后阻塞自己。

  • socket的等待队列项:回调函数为ep_poll_callback,private为null

  • eventpoll的等待队列项:回调函数为default_wake_function,private指向等待该事件的用户进程

接收数据过程:

  1. 系统线程ksoftirqd根据skb包头中的IP、端口信息找到对应的socket内核对象

  2. 将处理后的包数据放入socket的接收队列

  3. 调用socket的等待队列中等待项设置的回调函数ep_poll_callback

在ep_poll_callback内部会执行以下过程:

  1. 根据等待队列项找到epitem,进而找到eventpoll对象

  2. 将epitem添加到eventpoll→rdllist就绪队列中,查看eventpoll→wq等待队列是否有等待项

  3. 如果eventpoll→wq等待队列中没有等待项,说明用户进程并未阻塞,此时软中断结束

  4. 如果eventpoll→wq等待队列中有等待项,则调用其回调函数default_wake_function唤醒用户进程

总结

两个不同的回调函数:

  • ep_poll_callback:调用epoll_ctl()函数时设置到socket对象上

  • default_wake_function:调用epoll_wait()函数时设置到epoll对象上