Epoll 作为Linux 系统中的核心武器之一,在高吞吐、高并发的IO系统中经常遇见Epoll的身影,Redis、Nginx、Skynet等都使用到了IO多路复用技术。我们可以先创建一个epoll对象,再将fd(File Descriptor 文件描述符对象)注册到epoll对象里,之后便能通过调用epoll对象的查询函数来获取准备好事件的fd对象。

epoll提供了三个API接口,分别为epoll_create、epoll_ctrl、epoll_wait,下面分别说明这三个接口的用处。

epoll_create函数

函数声明:int epoll_create(int size);

__size参数 相当于提供给内核一个提示,当前需要监听的fd个数

返回值:当前Epoll对象的fd

Unxi中一切皆文件,在Epoll中也是 。这里Epoll便是一个虚拟文件对象,epoll_create函数了创建一个代表Epoll对象的fd 。这里生成一个epoll专用的文件描述符 在内核申请一空间,用来存放你想关注的socket fd上是否发生以及发生了什么事件。

注意:当创建好epoll句柄后,它变回占用一个fd值。使用完epoll后,必须调用close函数来关闭,不然就可能导致fd被耗尽。

epoll_ctl函数

函数声明:int epoll_ctl (int epfd, int op, int fd,struct epoll_event *event);

epfd:通过epoll_create方法创建的Epoll 对象

op:将要进行的操作例如注册事件,可能的取值EPOLL_CTL_ADD (添加监听的fd)、EPOLL_CTL_MOD (修改监听的fd)、EPOLL_CTL_DEL (删除监听的fd)

__fd:表示关联的文件描述符(需要操作的fd对象)

__event:表示用于描述需要监听的fd对象的感兴趣事件类型

返回值:如果调用0表示成功,-1表示失败

struct epoll_event结构如下

// 保存触发事件的某个文件描述符相关数据typedef union epoll_data {void *ptr;int fd;__uint32_t u32;__uint64_t u64;} epoll_data_t;​// 感兴趣的事件和被触发的事件struct epoll_event {__uint32_t events; epoll_data_t data;};

这个函数用于控制创建的Epoll 文件描述符上的事件,可以通过该函数注册事件,修改事件,删除事件

【文章福利】:免费领取更多C/C++ Linux服务器、大厂面试题、技术视频和学习路线图,资料包括(C/C++,Linux,golang技术,内核,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCP/IP,协程,DPDK,ffmpeg,大厂面试题 等)有需要的可以点击 793599096 加群领取哦~

epoll_wait函数

函数声明:epoll_wait (int epfd, struct epoll_event *events, int maxevents, int timeout);

__epfd:由epoll_create 生成的epoll专用的文件描述符

__events:分配好的epoll_event结构体数组 用于接收fd对象的缓冲区

__maxevents:每次能够处理的事件数 这一次调用可以接收多少准备好的fd对象,通常设置为events参数的长度

__timeout:如果没有准备好的事件对象,那么等待多久返回,-1阻塞,0非阻塞

返回值:返回events缓冲区中有效的fd个数,也即准备好事件的fd个数

该函数用于收集在epoll监控的事件中已经发生的事件,获取Epoll对象监听的fd对象列表,我们可以通过该参数实现我们通过epoll_ctl添加到Epoll对象中监听的且准备好事件的fd对象。

关于边缘触发(ET)、水平触发(LT)两种工作模式:

Epoll对象对于监听的fd处理方式有两种:边缘触发(edge-triggered)ET、 水平触发(level-triggered)LT。在默认情况下,epoll采用 LT模式工作,这时可以处理阻塞和非阻塞套接字。ET模式的效率要比 LT模式高,它只支持非阻塞套接字。在ET模式下,当我们通过epoll_wait函数获取到准备好的事件后,如果没有处理完成所有事件,那么再次调用epoll_wait函数将不会再次返回该fd,而LT模式下如果fd的事件没有处理完成,那么在下一次调用epoll_wait函数时将会返回该fd。ET模式仅当状态发生变化的时候才获得通知,这里所谓的状态的变化并不包括缓冲区中还有未处理的数据,也就是说,如果要采用ET模式,需要一直read/write到出错为止,这就是为什么采用ET模式只接收了一部分数据就不会通知,而LT模式只要有数据没有处理就会一直通知下去。

Nginx默认采用的就是ET(边缘触发)

总结:epoll_create函数用于创建对象,而epoll_ctl函数用于对Epoll对象CRUD,epoll_wait函数用于对Epoll对象进行查询操作。对月ET和LT来说:ET模式一个事件只会触发一次,若是该事件中的数据未处理完成,再下一个事件到来后,就不会再次返回监听的fd。对LT来说的话,若是数据未处理完毕,就可以再次调用epoll_wait函数处理未处理完成数据的fd。

eventpoll_init过程

static int __init eventpoll_init(void){int error;init_MUTEX(&epsem);ep_poll_safewake_init(&psw);​epi_cache = kmem_cache_create("eventpoll_epi", sizeof(struct epitem),0, SLAB_HWCACHE_ALIGN|EPI_SLAB_DEBUG|SLAB_PANIC,NULL, NULL);​pwq_cache = kmem_cache_create("eventpoll_pwq",sizeof(struct eppoll_entry), 0,EPI_SLAB_DEBUG|SLAB_PANIC, NULL, NULL);​error = register_filesystem(&eventpoll_fs_type);if (error)goto epanic;​eventpoll_mnt = kern_mount(&eventpoll_fs_type);error = PTR_ERR(eventpoll_mnt);if (IS_ERR(eventpoll_mnt))goto epanic;​DNPRINTK(3, (KERN_INFO "[%p] eventpoll: successfully initialized.\n",current));return 0;​epanic:panic("eventpoll_init() failed\n");}

向系统中添加一个fd时变回会创建一个epitem结构体

sys_epoll_create原理

用于创建epoll对象同时返回fd

long sys_epoll_create(int size){int error, fd;struct eventpoll *ep;struct inode *inode;struct file *file;​DNPRINTK(3, (KERN_INFO "[%p] eventpoll: sys_epoll_create(%d)\n", current, size));​// ep_alloc为eventpoll分配内存并初始化error = -EINVAL;if (size <= 0 || (error = ep_alloc(&ep)) != 0)goto eexit_1;​ // 初始化epoll对象 创建eventpoll相关的数据结构,包括file、inode和fd等信息error = ep_getfd(&fd, &inode, &file, ep); if (error)goto eexit_2;​DNPRINTK(3, (KERN_INFO "[%p] eventpoll: sys_epoll_create(%d) = %d\n", current, size, fd));​return fd; // 返回fd​eexit_2:ep_free(ep);kfree(ep);eexit_1:DNPRINTK(3, (KERN_INFO "[%p] eventpoll: sys_epoll_create(%d) = %d\n", current, size, error));return error;}

原文链接: https://www.cnblogs.com/daomeidan/p/16784728.html