epoll机制详解

      select,poll,epoll都是IO多路复用的机制。I/O多路复用就是通过一种机制,一个进程可以监视多个描述符,一旦某个描述符就绪,能够通知程序进行相应的读写操作。其中epoll是在2.6内核中提出的,是之前的select和poll的增强版本。相对于select和poll来说,epoll更加灵活

一.epoll的相关操作概览:

1.int epoll_create(int size);

int epoll_create(int size);

 

epoll_create 创建一个epoll对象,一般epollfd = epoll_create()

2.int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

第一个参数是创建的epoll对象

第二个参数是具体的操作类型,如添加、删除

epoll_ctl(epollfd, EPOLL_CTL_ADD, socket, EPOLLIN);//添加

epoll_ctl(epollfd, EPOLL_CTL_DEL, socket, EPOLLOUT);//删除

 

 

第三个是需要监听的fd,第四个是监听事件,该事件结构如下

struct epoll_event {

  __uint32_t events;  /* Epoll events */

  epoll_data_t data;  /* User data variable */

};

 

 __uint32_t events:

其中events是以下宏的集合

EPOLLIN :表示对应的文件描述符可以读(包括对端SOCKET正常关闭);

EPOLLOUT:表示对应的文件描述符可以写;

EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来);

EPOLLERR:表示对应的文件描述符发生错误;

EPOLLHUP:表示对应的文件描述符被挂断;

EPOLLET: 将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的。

EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里

  epoll_data_t data:

data可以存储对应文件描述符(fd)和任意自定义函数或类(ptr)

typedef union epoll_data
{
  void *ptr;
  int fd;
  uint32_t u32;
  uint64_t u64;
} epoll_data_t;

 

(3)int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);

int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);

这里是epoll主要的特点,select函数只是当有可读描述符后返回但并不知道具体是那个描述符,返回后还需要轮询,文件描述符数量越多,性能越差。而epoll的epoll_wait函数可以返回具体可操作的文件描述符,存储在events里。Maxevents告诉内核这个events有多大。

二.实现机制

来看具体的实现机制

1.某一进程调用epoll_create方法时,Linux内核会创建一个eventpoll结构体

struct eventpoll{

    ....

    struct rb_root  rbr;   //红黑树的根节点,存储着所有添加到epoll中的需要监控的事件


    struct list_head rdlist;    //双链表,存放着将要通过epoll_wait返回给用户的满足条件的事件

    ....

};

 rbr是该epoll对象监听的所有文件对象,rdlist是放进去的有事件发生的文件描述符。

在epoll中,对于每一个事件,都会建立一个epitem结构体

struct epitem{

    struct rb_node  rbn;//红黑树节点

    struct list_head    rdllink;//双向链表节点

    struct epoll_filefd  ffd;  //事件句柄信息

    struct eventpoll *ep;    //指向其所属的eventpoll对象

    struct epoll_event event; //期待发生的事件类型

}

 

双链表与存储着所有监听的红黑树里都是存放的epitem结构数据

 

2.epoll_ctl

将epoll_event结构拷贝到内核空间中并且判断加入的fd是否支持poll结构,并且从epfd->file->privatedata获取event_poll对象,根据op区分是添加删除还是修改,

对于修改删除是先在红黑树查找是否有相对应的fd,没找到就返回错误

对于插入,先创建一个与fd对应的epitem结构,并且初始化相关成员,然后指定了调用poll_wait时的回调函数用于数据就绪时唤醒进程,(其内部,初始化设备的等待队列,将该进程注册到等待队列)完成这一步, 我们的epitem就跟这个socket关联起来了, 当它有状态变化时,会通过ep_poll_callback()来通知.最后调用加入的fd的file operation->poll函数(最后会调用poll_wait操作)用于完成注册操作.

最后将epitem结构添加到红黑树中

3.epoll_wait

当调用epoll_wait检查是否有事件发生时,只需要检查eventpoll对象中的rdlist双链表中是否有epitem元素即可。如果rdlist不为空,则把发生的事件复制到用户态,同时将事件数量返回给用户

三.ET和LT的区别

首先从epoll的监听流程看起,当有文件描述符的状态改变时,会导致fd上的回调函数被调用,回调函数会将fd对应的epitem添加到rdlist,此时epoll_wait会检测到rdlist不为空,进程被唤醒,结束等待后会将rdlist中的epitem复制到txlist中,然后清空rdlist,epoll_wait继续阻塞等待,接着扫描txlist中的每个epitem,调用相关fd,然后有调用poll取得新的epoll_event,之后将取得的epoll_event发送到用户空间,用户空间会对这个事件进行处理,如果fd是LT模式监听会将其重新加入到rdlist,如果是ET就不在加入到rdlist

所以ET和LT的区别就在ET中的事件只添加到rdlist一次,处理后就删除了,所有当有数据读或写时,要一次读完或一次写完。LT是可以把事件多次添加到rdlist中,只有你主动的删除它。

 

参考文章:https://www.nowcoder.com/discuss/26226

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值