一、epoll相关的数据结构

最重要的两个数据结构是红黑树和就绪链表红黑树用于管理所有的文件描述符fd,就绪链表用于保存有事件发生的文件描述符。

当向系统中添加一个fd时,就创建一个epitem结构体。eventpoll用于管理所有的epitem。

struct epitem
{struct rb_node  rbn;        // 用于主结构管理的红黑树struct list_head  rdllink;  // 事件就绪链表struct epitem  *next;       // 用于主结构体中的链表struct epoll_filefd  ffd;   // 这个结构体对应的被监听的文件描述符信息int  nwait;                 // poll操作中事件的个数struct list_head  pwqlist;  // 双向链表,保存着被监视文件的等待队列struct eventpoll  *ep;      // 该项属于哪个主结构体(多个epitm从属于一个eventpoll)struct list_head  fllink;   // 双向链表,用来链接被监视的文件描述符对应的structstruct epoll_event  event;  // 注册的感兴趣的事件,也就是用户空间的epoll_event
}struct eventpoll
{spin_lock_t       lock;         // 对本数据结构的访问struct mutex      mtx;          // 防止使用时被删除/** 等待队列可以看作保存进程的容器,在阻塞进程时,将进程放入等待队列;* 当唤醒进程时,从等待队列中取出进程*/ wait_queue_head_t   wq;         // sys_epoll_wait() 使用的等待队列wait_queue_head_t   poll_wait;  // file->poll()使用的等待队列struct list_head    rdllist;    // 就绪链表struct rb_root      rbr;        // 用于管理所有fd的红黑树(树根)struct epitem      *ovflist;    // 将事件到达的fd进行链接起来发送至用户空间
}
12345678910111213141516171819202122232425262728

在这里给大家强烈推荐一个视频linux下的epoll实战揭秘——支撑亿级IO的底层基石

另外需要C/C++ Linux服务器架构师学习资料加qun获取(资料包括C/C++,Linux,golang技术,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCP/IP,协程,DPDK,ffmpeg等),免费分享

二、epoll_create函数

epoll_create函数的功能是创建eventpoll。

long sys_epoll_create(int size)
{struct eventpoll *ep;ep_alloc(&ep); // 为ep分配内存并进行初始化fd = anon_inode_getfd("[eventpoll]", &eventpoll_fops, ep, O_RDWR | (flags & O_CLOEXEC));return fd;
}
1234567

三、epoll_ctl函数

epoll_ctl函数的功能是对文件描述符进行增删改查。

asmlinkage long sys_epoll_ctl(int epfd, int op, int fd, struct epoll_event __user *event)
{int error;struct file *file,*tfile;struct eventpoll *ep;struct epoll_event epds;error = -FAULT;// 判断参数的合法性,将 __user *event 复制给 epds。if(ep_op_has_event(op) && copy_from_user(&epds,event,sizeof(struct epoll_event)))goto error_return; //省略跳转到的代码file  = fget (epfd); // epoll fd 对应的文件对象tfile = fget(fd);    // fd 对应的文件对象// 在create时存入进去的(anon_inode_getfd),现在取用。ep = file->private->data;mutex_lock(&ep->mtx);// 防止重复添加(在ep的红黑树中查找是否已经存在这个fd)epi = epi_find(ep, tfile, fd);switch(op){...case EPOLL_CTL_ADD:  // 增加监听一个fdif (!epi){epds.events |= EPOLLERR | POLLHUP;     // 默认包含POLLERR和POLLHUP事件。error = ep_insert(ep, &epds, tfile, fd);  // 在ep的红黑树中插入这个fd对应的epitm结构体。} else  // 重复添加(在ep的红黑树中查找已经存在这个fd)。error = -EEXIST;break;...}return error;
}
123456789101112131415161718192021222324252627282930313233343536

四、ep_insert函数

ep_insert函数的功能是插入一个文件描述符到红黑树上。

static int ep_insert(struct eventpoll *ep, struct epoll_event *event, struct file *tfile, int fd)
{/* 初始化epitem */// 设置监听注册函数为ep_ptable_queue_proc, &epq.pt->qproc = ep_ptable_queue_procinit_poll_funcptr(&epq.pt, ep_ptable_queue_proc);/** 执行f_op->poll(tfile, &epq.pt)时,XXX_poll(tfile, &epq.pt)函数会执行poll_wait(),* poll_wait()会调用epq.pt.qproc函数,即ep_ptable_queue_proc。*/revents = tfile->f_op->poll(tfile, &epq.pt);spin_lock(&tfile->f_ep_lock);// 将epitem与它需要监听的文件链接起来,list_add_tail是内核函数,表示添加一个结点到链表尾部list_add_tail(&epi->fllink, &tfile->f_ep_lilnks);spin_unlock(&tfile->f_ep_lock);// 将epitem插入红黑树ep_rbtree_insert(ep, epi);/** 如果要监视的fd状态改变,并且还没有加入到就绪链表中,则将当前的* epitem加入到就绪链表中。如果有进程正在等待该文件的状态就绪,则* 唤醒一个等待的进程。*/if ((revents & event->events) && !ep_is_linked(&epi->rdllink)) {list_add_tail(&epi->rdllink, &ep->rdllist);/* 唤醒epoll_wait()当前epoll实例的用户 */if (waitqueue_active(&ep->wq))wake_up_locked(&ep->wq);/* 当前epoll文件已就绪 */if (waitqueue_active(&ep->poll_wait))pwake++;}
}
12345678910111213141516171819202122232425262728293031323334353637383940

五、ep_poll_callback函数

所以ep_poll_callback函数主要的功能是当被监视文件的等待事件就绪时,将文件描述符对应的epitem实例添加到就绪链表中,导致rdlist不空,进程被唤醒,epoll_wait()得以继续执行,之后内核会将就绪链表中的事件从内核空间拷贝到用户空间。

// 将epitem挂载到资源文件的监听队列上
static void ep_ptable_queue_proc(struct file *file, wait_queue_head_t *whead, poll_table *pt)
{/* 获取epitem */struct epitem *epi = ep_item_from_epqueue(pt);struct eppoll_entry *pwq;/* 从slab分配一个eppoll_entry结构,然后进行相应的初始化 */if (epi->nwait >= 0 && (pwq = kmem_cache_alloc(pwq_cache, GFP_KERNEL))) {/* * 初始化一个等待队列节点,其中唤醒函数设置为ep_poll_callback* 唤醒回调函数为ep_poll_callback!!!*/init_waitqueue_func_entry(&pwq->wait, ep_poll_callback);/* 还要保存资源文件监听队列的队列头whead */pwq->whead = whead;pwq->base = epi;/* * 将eppoll_entry挂载到资源文件的监听队列中,* add_wait_queue表示将队列元素加入到等待队列头部,并设置非互斥等待*/add_wait_queue(whead, &pwq->wait);list_add_tail(&pwq->llink, &epi->pwqlist);/* 增加等待计数 */epi->nwait++;}else{epi->nwait = -1;}
}static int ep_poll_callback(wait_queue_t *wait, unsigned mode, int sync, void *key)
{int pwake = 0;unsigned long flags;/* 通过eppoll_entry中的wait获取对应的epitem */struct epitem *epi = ep_item_from_wait(wait);/* 获取epitem所属的eventpoll */struct eventpoll *ep = epi->ep;/* spinlock加锁(不允许休眠):保护eventpoll的访问 */spin_lock_irqsave(&ep->lock, flags);/* 如果我们想要监听的事件events为空,那么资源文件就绪时,nothing to do */if (!(epi->event.events & ~EP_PRIVATE_BITS))goto out_unlock;/* 判断当前fd状态是否是我们关心的事件events */if (key && !((unsigned long) key & epi->event.events))goto out_unlock;if (unlikely(ep->ovflist != EP_UNACTIVE_PTR)) {if (epi->next == EP_UNACTIVE_PTR) {epi->next = ep->ovflist;ep->ovflist = epi;}goto out_unlock;}/* 如果epitem没有被挂载到所属eventpoll中的就绪链表,就将其添加到就绪链表尾 */if (!ep_is_linked(&epi->rdllink))list_add_tail(&epi->rdllink, &ep->rdllist);/* 唤醒epoll_wait()当前epoll实例的用户 */if (waitqueue_active(&ep->wq))wake_up_locked(&ep->wq);/* 当前epoll文件已就绪 */if (waitqueue_active(&ep->poll_wait))pwake++;out_unlock:spin_unlock_irqrestore(&ep->lock, flags);if (pwake)ep_poll_safewake(&ep->poll_wait);return 1;
}
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687

六、epoll_wait函数

epoll_wait调用ep_poll,当rdlist(无就绪fd)时挂起当前进程,直到rdlist不空时进程才被唤醒。然后就将就绪的events和data发送到用户空间(ep_send_events()),如果ep_send_events()返回的事件数为0,并且还有超时时间剩余(jtimeout),那么我们retry,期待不要空手而归。

SYSCALL_DEFINE4(epoll_wait, int, epfd, struct epoll_event __user *, events, int, maxevents, int, timeout)
{int error;struct file *file;struct eventpoll *ep;/* 参数验证 */if (maxevents <= 0 || maxevents > EP_MAX_EVENTS)return -EINVAL;/* 验证events数组区域,当前用户是否能够访问 */if (!access_ok(VERIFY_WRITE, events, maxevents * sizeof(struct epoll_event))) {error = -EFAULT;goto error_return;}error = -EBADF;/* 获取eventpoll文件描述符对应的struct file结构 */file = fget(epfd);if (!file)goto error_return;error = -EINVAL;/* 验证epfd指向的文件是否是epoll文件 */if (!is_file_epoll(file))goto error_fput;/* 取出挂载到epoll文件中的eventpoll */ep = file->private_data;/* 调用ep_poll()等待事件的到来 */error = ep_poll(ep, events, maxevents, timeout);error_fput:fput(file);
error_return:return error;
}static int ep_poll(struct eventpoll *ep, struct epoll_event __user *events, int maxevents, long timeout)
{int res, eavail;unsigned long flags;long jtimeout;wait_queue_t wait;/* 处理睡眠时间:将毫秒数转化为HZ */jtimeout = (timeout < 0 || timeout >= EP_MAX_MSTIMEO) ?MAX_SCHEDULE_TIMEOUT : (timeout * HZ + 999) / 1000;retry:/* spinlock加锁:保护eventpoll的访问 */spin_lock_irqsave(&ep->lock, flags);res = 0;/* 如果就绪链表为空,说明还没有任何events就绪 */if (list_empty(&ep->rdllist)) {/* 初始化等待队列节点,设置等待状态为互斥等待 */init_waitqueue_entry(&wait, current);wait.flags |= WQ_FLAG_EXCLUSIVE;/* 将刚刚初始化的等待队列节点挂载到eventpoll中的等待队列 */__add_wait_queue(&ep->wq, &wait);for (;;) {/* * 设置程序运行状态为可中断阻塞,因为我们希望能够接收到* ep_insert()、ep_modify()、ep_poll_callback()的唤醒*/set_current_state(TASK_INTERRUPTIBLE);/* events就绪或者超时,跳出循环 */if (!list_empty(&ep->rdllist) || !jtimeout)break;/* 出现未决信号,设置返回值为-EINTR并跳出循环 */if (signal_pending(current)) {res = -EINTR;break;}spin_unlock_irqrestore(&ep->lock, flags);/* 休眠...等待超时或者被就绪资源唤醒 */jtimeout = schedule_timeout(jtimeout);spin_lock_irqsave(&ep->lock, flags);}/* 从等待队列中卸载 */__remove_wait_queue(&ep->wq, &wait);/* 恢复程序运行状态 */set_current_state(TASK_RUNNING);}/* 判断是否有资源就绪 */eavail = !list_empty(&ep->rdllist) || ep->ovflist != EP_UNACTIVE_PTR;spin_unlock_irqrestore(&ep->lock, flags);if (!res && eavail && !(res = ep_send_events(ep, events, maxevents)) && jtimeout)goto retry;return res;
}
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111

七、ep_send_events函数

ep_send_events函数,它扫描txlist中的每个epitem,调用其关联的fd对应的的poll方法,取得fd上较新的events(防止之前events被更新)即revents,之后将revents和相应的data拷贝(__put_user())到用户空间。如果这个epitem对应的fd是LT模式监听且取得的events是用户所关心的,则将其重新加入回rdlist,如果是ET模式则不再加入rdlist

// 将就绪的events传递到用户空间
static int ep_send_events(struct eventpoll *ep, struct epoll_event __user *events, int maxevents)
{struct ep_send_events_data esed;esed.maxevents = maxevents;esed.events = events;return ep_scan_ready_list(ep, ep_send_events_proc, &esed);
}// 扫描就绪链表
static int ep_scan_ready_list(struct eventpoll *ep,int (*sproc)(struct eventpoll *, struct list_head *, void *),void *priv)
{int error, pwake = 0;unsigned long flags;struct epitem *epi, *nepi;/* 初始化一个链表 */LIST_HEAD(txlist);/* mutex加锁 */mutex_lock(&ep->mtx);/* spinlock加锁:保护eventpoll的访问 */spin_lock_irqsave(&ep->lock, flags);/** 将eventpoll就绪链表中的所有节点全部链接到txlist上,* 之后eventpoll就绪队列为空*/list_splice_init(&ep->rdllist, &txlist);/* * 设置eventpoll.ovflist,使得接下来新就绪的events被挂载到* eventpoll.ovflist而不是就绪队列 */ep->ovflist = NULL;spin_unlock_irqrestore(&ep->lock, flags);error = (*sproc)(ep, &txlist, priv);/* spinlock加锁:保护eventpoll的访问 */spin_lock_irqsave(&ep->lock, flags);/* * 我们在调用ep_send_events_proc()将就绪队列中的事件交付* 给用户的期间,新就绪的events被挂载到eventpoll.ovflist* 所以我们需要遍历eventpoll.ovflist将所有已就绪的epitem* 重新挂载到就绪链表中,等待下一次epoll_wait()进行交付*/for (nepi = ep->ovflist; (epi = nepi) != NULL; nepi = epi->next, epi->next = EP_UNACTIVE_PTR) {if (!ep_is_linked(&epi->rdllink))list_add_tail(&epi->rdllink, &ep->rdllist);}ep->ovflist = EP_UNACTIVE_PTR;/* * 将调用ep_send_events_proc()之后剩余的未交付的epitem重新splice到* eventpoll的就绪链表上 */list_splice(&txlist, &ep->rdllist);/* * 注意到epoll_wait()中,将wait_queue_t的等待状态设置为互斥等待,因此* 每次被唤醒的只有一个节点。现在我们已经将eventpoll中就绪队列里的事件* 尽量向用户交付了,但是在交付时,可能没有交付完全(1.交付过程中出现了* 错误 2.使用了LT模式),也有可能在过程中又发生了新的事件。也就是这次* epoll_wait()调用后,还剩下一些就绪资源,那么我们再次唤醒一个等待节点* 让别的用户也享用一下资源** 从这里已经可以看出内核对于epoll惊群的解决方案:ET模式:* 1. 每次只唤醒一个节点* 2. 事件交付后不再将事件重新挂载到就绪队列*/if (!list_empty(&ep->rdllist)) {/* 唤醒epoll_wait()当前epoll实例的用户 */if (waitqueue_active(&ep->wq))wake_up_locked(&ep->wq);/* 当前epoll文件已就绪 */if (waitqueue_active(&ep->poll_wait))pwake++;}spin_unlock_irqrestore(&ep->lock, flags);mutex_unlock(&ep->mtx);if (pwake)ep_poll_safewake(&ep->poll_wait);return error;
}// 将events和data发送到用户空间
static int ep_send_events_proc(struct eventpoll *ep, struct list_head *head, void *priv)
{struct ep_send_events_data *esed = priv;int eventcnt;unsigned int revents;struct epitem *epi;struct epoll_event __user *uevent;/* * 遍历就绪链表,eventcnt记录已交付的events的数量* uevent指向esed中封装的events数组,这个数组用于将已就绪events返回给用户*/for (eventcnt = 0, uevent = esed->events;!list_empty(head) && eventcnt < esed->maxevents;) {epi = list_first_entry(head, struct epitem, rdllink);/* 将epitem从head就绪队列中卸载 */list_del_init(&epi->rdllink);/* 从资源文件当前状态中提取出我们所关心的events,拿到最新的数据 */revents = epi->ffd.file->f_op->poll(epi->ffd.file, NULL) & epi->event.events;/* 如果有我们所关心的events发生 */if (revents) {/* * 使用__put_user将数据在用户空间和内核空间互相拷贝** 将events拷贝到用户空间** 若拷贝失败,那么就将该epitem重新添加到就绪链表上,* 然后返回已交付的events的数量*/if (__put_user(revents, &uevent->events) || __put_user(epi->event.data, &uevent->data)) {/* 复制失败了 */list_add(&epi->rdllink, head);return eventcnt ? eventcnt : -EFAULT;}/* 更新已交付的event的数量 */eventcnt++;/* 指向events数组中的下一元素 */uevent++;/* * 如果是ET模式, epitem是不会再进入到就绪链表,* 除非fd再次发生了状态改变, ep_poll_callback被调用。* 如果是LT模式,不但会将对应的数据返回给用户,并且会将当前的epitem再次加入到rdllist中。* 这样如果下次再次被唤醒就会给用户空间再次返回事件。当然如果这个* 被监听的fd确实没事件也没数据了, epoll_wait会返回一个0。*/if (epi->event.events & EPOLLONESHOT)epi->event.events &= EP_PRIVATE_BITS;else if (!(epi->event.events & EPOLLET)) {list_add_tail(&epi->rdllink, &ep->rdllist);}}}return eventcnt;
}
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155

八、epoll的工作过程详解

通过上面的源码分析可以看出epoll的工作过程如下:

1. epoll_wait()调用ep_poll(),如果就绪链表rdlist为空,则挂起当前进程,直到rdlist不为空时被唤醒,这个时候会调用ep_send_events()将实际发生的事件reventsdata从内核空间拷贝到用户空间(拷贝调用的是__put_user,并不存在什么共享内存之类的)。

2. 当文件描述符fd的状态改变时(buffer由不可读变为可读或由不可写变为可写),导致相应fd上的回调函数ep_poll_callback() 被调用。

3. ep_poll_callback()将有事件发生的文件描述符(epitem)加入到就绪链表rdlist 中,这时候就绪链表不为空,epoll_wait() 进程被唤醒。

4. ep_send_events()会扫描就绪链表,调用每个文件描述符的poll函数返回revents,之后将reventsdata从内核空间拷贝到用户空间。如果是ET模式, epitem是不会再进入到就绪链表,除非fd再次发生了状态改变, ep_poll_callback被调用。如果是LT模式,不但会将对应的数据返回给用户,并且会将当前的epitem再次加入到rdllist中。这样如果下次再次被唤醒就会给用户空间再次返回事件。

android4.0网络编程配书源码_linux网络编程之epoll源码重要部分详解相关推荐

  1. Android4.0 以后不允许在主线程进行网络连接

    Android4.0 以后不允许在主线程进行网络连接,否则会出现 android.os.NetworkOnMainThreadException.因此,必须另起一个线程进行网络连接方面的操作. pac ...

  2. Python30 网络编程通讯协议,1.学习网络编程的目的 2.什么是互联网 3.c/s结构 4.通讯基本要素 5.OSI模型...

    今日内容: 网络通讯协议 1.学习网络编程的目的 2.什么是互联网 3.c/s结构 4.通讯基本要素 5.OSI模型 思维路线 目的是要链接互联网中的其他计算机 物理层 用物理介质链接其他计算机 数据 ...

  3. Linux网络编程 | 多路复用I/O :select、poll、epoll、水平触发与边缘触发、惊群问题

    文章目录 多路复用IO 多路复用IO的概念 多路复用IO与多线程/多进程的并发 多路复用IO模型进行服务器并发处理 多线程/多进程进行服务器并发处理 select 工作原理 接口 优缺点 select ...

  4. 找不到网络名_书荒粮草,网络小说推荐大合集:文笔一流,拒绝套路

    网络小说走过这么多年,已经成为大家日常生活离不开的陪伴,也涌现出了众多经典.但是网文看多了,难免会感到书荒,特别是对满满的套路文和小白文丧失了兴趣. 关注小编,每天给书荒的朋友提供粮草.今天给大家介绍 ...

  5. 网络编程之epoll原理

    作为一名java后端开发人员,笔者对于网络编程,一直是有一种既熟悉又陌生的感觉.熟悉是因为这个词会经常接触到,前端请求后端接口.本应用远程调用其它服务器上的应用,都会涉及到网络编程:陌生则是因为在平常 ...

  6. pandas dataframe缺失值(np.nan)处理:识别缺失情况、删除、0值填补、均值填补、中位数填补、加缺失标签、插值填充详解及实例

    pandas dataframe缺失值(np.nan)处理:识别缺失情况.删除.0值填补.均值填补.中位数填补.加缺失标签.插值填充详解及实例 isnull().natna().isna().fill ...

  7. Android 网络编程之OkHttp源码解析

    前言:OkHttp框架是Android的网络请求框架,无数的项目都在使用着这个框架,重要性不言而喻; 本文会将OKHTTP的源码进行拆解,每个部分来单独学习,由简入深,循序渐进,篇幅较长,建议收藏,慢 ...

  8. 网络编程(三):Linux 网络IO模型、select、pool、epoll 内核设计

    Linux网络IO模型 同步和异步,阻塞和非阻塞 同步和异步 关注的是调用方是否主动获取结果 同步: 同步的意思就是调用方需要主动等待结果的返回 异步: 异步的意思就是不需要主动等待结果的返回,而是通 ...

  9. Android 网络编程系列(5)Volley 网络框架入门

    前言 上篇文章中我们对 HttpUrlConnection 的相关用法稍作介绍,可以看到如果不对它进行封装,那么每次使用时就必须写很多重复的代码,并且需要自己创建线程进行网络连接,获取到响应结果后还需 ...

最新文章

  1. 字典类型处理函数及方法实例解析
  2. U8远程接入客户端重新安装问题
  3. linux 进程线程拓展
  4. java.util.Collections类使用(很多秘密,网上资料亲测)
  5. 无人驾驶(基于计算机视觉的高精度地图)
  6. Java-jdk下载以前版本需要的账号
  7. TASKCTL连接不到服务端的几种情况和解决办法
  8. QPainter文档
  9. Python四舍五入保留两位小数
  10. Live Writer 2011 , OutLook 2010,FeedDemon软件的设置,Silverlight Tools 4 和 Windows Phone Tools 的安装...
  11. delphi基本语法(摘自博主:沈金强)
  12. 5G关键厂商推动2019年推出下一代网络;大疆发布首条企业级无人机产品Matrice 200│IoT黑板报...
  13. 由皮鞋引起的Java娱乐中文版
  14. Direct Show学习方法
  15. Java eight
  16. 自己电脑做服务器需要装什么系统,自己电脑做服务器需要什么配置
  17. python爬虫之无界面模式操作/scrapy框架
  18. java fx:ria_虚拟小组讨论:RIA的当前和未来状态
  19. 百度App应用创新大赛参赛作品:国民教育委员会
  20. uber开源_uber m3是一个开源的大型scaltime系列指标平台

热门文章

  1. shell字符串是否以a-zA-Z字母开头
  2. Python基础学习总结、学习展望
  3. scala的下载以及环境变量配置(一)
  4. CSS中定义多个链接样式,在CSS中定义【导航栏】超链接样式
  5. Unity2020.1新功能探路:图形渲染相关更新
  6. OpenShift 4 之Knative(3) - 通过事件触发Serverless服务
  7. 解决RHEL无法访问NTFS
  8. Jupyter.net:使用Jupyter进行交互式计算的Windows应用程序
  9. 客户端Blazor中的应用程序设置
  10. SQLite 3.30.0 发布,世界上使用量最大的数据库引擎