最近在读张彦飞编写的《深入理解Linux网络》,这本书讲得非常深,其中包含了大量linux底层源码。读的过程很痛苦,但对我而言哪怕只读懂了其中皮毛也能受益匪浅。
这篇文章就是在学习到epoll底层原理时的笔记记录
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_create()函数时,内核会创建一个struct eventpoll结构的内核对象
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_ctl()函数时,配合EPOLL_CTL_ADD参数可以向epoll对象添加socket
创建红黑树节点对象epitem
将等待事件添加到socket的等待队列中,设置数据就绪的回调函数为ep_poll_callback
将epitem插入rbr红黑树
调用epoll_wait()函数时,观察eventpoll→rdllist就绪链表里有没有数据,有数据就返回,没数据就把当前进程添加到eventpoll→wq等待队列链表中,设置等待项回调函数default_wake_function,然后阻塞自己。
socket的等待队列项:回调函数为ep_poll_callback,private为null
eventpoll的等待队列项:回调函数为default_wake_function,private指向等待该事件的用户进程
接收数据过程:
系统线程ksoftirqd根据skb包头中的IP、端口信息找到对应的socket内核对象
将处理后的包数据放入socket的接收队列中
调用socket的等待队列中等待项设置的回调函数ep_poll_callback
在ep_poll_callback内部会执行以下过程:
根据等待队列项找到epitem,进而找到eventpoll对象
将epitem添加到eventpoll→rdllist就绪队列中,查看eventpoll→wq等待队列是否有等待项
如果eventpoll→wq等待队列中没有等待项,说明用户进程并未阻塞,此时软中断结束
如果eventpoll→wq等待队列中有等待项,则调用其回调函数default_wake_function唤醒用户进程
两个不同的回调函数:
ep_poll_callback:调用epoll_ctl()函数时设置到socket对象上
default_wake_function:调用epoll_wait()函数时设置到epoll对象上
