/**
* @param size 废弃,但是不能小于0
* *
@returns 返回一个epoll句柄(即一个文件描述符)
*/
int epoll_create(int size);
系统函数调用值得注意的是,参数size不再是指定监听的文件描述符个数,它已经被忽视了,但是为了保持兼容,不能小于0。当然我们更关心的是内核态是怎么做的,在Linux源码目录fs/eventpoll.c文件中定义了对应的系统调用:
SYSCALL_DEFINE1(epoll_create, int, size)
{
if (size <= 0)
return -EINVAL;
return do_epoll_create(0);
}
可以看到函数传递的size并没有被最终传递下去,但是如小于0则会被返回错误。继续往下看函数调用:
/*****************************************************************************
Prototype : do_epoll_create
Description : 创建epoll 文件描述符
Input : int flags
Output : None
Return Value : static
Calls :
Called By :
History :
1.Date : 2019/5/3
Author :
Modification : Created function
*****************************************************************************/
static int do_epoll_create(int flags)
{
int error, fd;
struct eventpoll *ep = NULL;
struct file *file;
if (flags & ~EPOLL_CLOEXEC)
return -EINVAL;
/* 分配struct eventpoll结构体并初始化 */
error = ep_alloc(&ep);
/* 获取当前进程一个没有使用的文件描述符 */
fd = get_unused_fd_flags(O_RDWR | (flags & O_CLOEXEC));
/* fd绑定任意iNode,并创建返回 struct file结构体,并将ep指针赋值file的私有数据域 */
file = anon_inode_getfile("[eventpoll]", &eventpoll_fops, ep,
O_RDWR | (flags & O_CLOEXEC));
/* 将file指针赋值给eventpoll */
ep->file = file;
/* 将fd和file关联 */
fd_install(fd, file);
return fd;
}
其实这个的文件描述符创建,我们关心的应该是eventpoll_fops
函数操作集合,以及将创建的ep
保存在file
的私有数据中。同样该关心的结构体则是struct eventpoll *ep
它有哪些数据成员,以及这些数据是怎样组织起来的?
epoll_ctl
函数:/**
* @param epfd 用epoll_create所创建的epoll句柄
* @param op 表示对epoll监控描述符控制的动作
* *
EPOLL_CTL_ADD(注册新的fd到epfd)
* EPOLL_CTL_MOD(修改已经注册的fd的监听事件)
* EPOLL_CTL_DEL(epfd删除一个fd)
* *
@param fd 需要监听的文件描述符
* @param event 告诉内核需要监听的事件
* *
@returns 成功返回0,失败返回-1, errno查看错误信息
*/
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
struct epoll_event
{
__uint32_t events; /* epoll 事件 */
epoll_data_t data; /* 用户传递的数据 */
}
/* *
events : {EPOLLIN, EPOLLOUT, EPOLLPRI,
EPOLLHUP, EPOLLET, EPOLLONESHOT}
*/
typedef union epoll_data
{
void *ptr;
int fd;
uint32_t u32;
uint64_t u64;
} epoll_data_t;
同样我们也关心一下内核是怎么实现的:
/*****************************************************************************
Prototype : SYSCALL_DEFINE4
Description : 定义epoll_ctl函数,添加、删除监听描述符
Input : epfd epoll的描述符,fd 被监听的描述符
Output : None
Return Value :
Calls :
Called By :
History :
1.Date : 2019/5/3
Author :
Modification : Created function
*****************************************************************************/
SYSCALL_DEFINE4(epoll_ctl, int, epfd, int, op, int, fd,
struct epoll_event __user *, event)
{
int error;
int full_check = 0;
struct fd f, tf;
struct eventpoll *ep;
struct epitem *epi;
struct epoll_event epds;
struct eventpoll *tep = NULL;
error = -EFAULT;
/* 判断是否有传递event参数 */
if (ep_op_has_event(op) &&
copy_from_user(&epds, event, sizeof(struct epoll_event)))
goto error_return;
error = -EBADF;
/* 获取struct fd结构体,存储有关文件的信息,fops等 */
f = fdget(epfd);
if (!f.file)
goto error_return;
/* Get the "struct file *" for the target file */
/* 获取目的文件的struct fd结构体 */
tf = fdget(fd);
if (!tf.file)
goto error_fput;
/* The target file descriptor must support poll */
error = -EPERM;
/* 检查目的文件是否支持epoll */
if (!file_can_poll(tf.file))
goto error_tgt_fput;
/* Check if EPOLLWAKEUP is allowed */
if (ep_op_has_event(op))
ep_take_care_of_epollwakeup(&epds);
error = -EINVAL;
/* 检查加入epoll的fd本身不能是epoll类型的 */
if (f.file == tf.file || !is_file_epoll(f.file))
goto error_tgt_fput;
/* 通过文件结构体的私有数据获取到struct eventpoll,ep是创建epoll fd的时候创建的 */
ep = f.file->private_data;
if (op == EPOLL_CTL_ADD)
{
if (!list_empty(&f.file->f_ep_links) ||
is_file_epoll(tf.file))
{
if (is_file_epoll(tf.file))
{
if (ep_loop_check(ep, tf.file) != 0)
{
clear_tfile_check_list();
}
}
else
{
list_add(&tf.file->f_tfile_llink, &tfile_check_list);
}
if (is_file_epoll(tf.file))
{
tep = tf.file->private_data;
}
}
}
/* 遍历rb tree 查找加入epoll的文件描述符对应的struct epitem */
epi = ep_find(ep, tf.file, fd);
error = -EINVAL;
switch (op)
{
case EPOLL_CTL_ADD:
if (!epi)
{
/* epi为空表面之前没有加入到红黑树中,则需要将节点加入到rb tree中,系统会默认为添加EPOLLERR EPOLLHUP事件 */
epds.events |= EPOLLERR | EPOLLHUP;
error = ep_insert(ep, &epds, tf.file, fd, full_check);
}
else
{
if (full_check)
clear_tfile_check_list();
break;
}
case EPOLL_CTL_DEL:
{
if (epi)
/* 将epi移除rb tree */
error = ep_remove(ep, epi);
else
error = -ENOENT;
break;
}
case EPOLL_CTL_MOD:
if (epi)
{
if (!(epi->event.events & EPOLLEXCLUSIVE))
{
epds.events |= EPOLLERR | EPOLLHUP;
error = ep_modify(ep, epi, &epds);
}
}
else
error = -ENOENT;
break;
}
return error;
}
这个函数看起来稍微有点复杂,首先是进行了一堆参数错误检查,其实关注流程我们只需要看几个重要的函数:
1. epi = ep_find(ep, tf.file, fd);
2. error = ep_insert(ep, &epds, tf.file, fd, full_check);
3. error = ep_remove(ep, epi);
4. error = ep_modify(ep, epi, &epds);
首先函数ep_find
是为了保证对应fd(被监听的文件描述符)的epi确实存在,否则都不做操作,这里其实我们就可以分析该函数,分析前大概想想,这个函数肯定要去遍历整个数据结构体,找到对应的项,然后返回。那么其实这个数据结构就是epi的数据存储方式。
那么什么是epi?epi对应的结构体代表什么?我们可以在ep_insert
函数找到答案。我们首先看看ep_find
函数:
static struct epitem *ep_find(struct eventpoll *ep, struct file *file, int fd)
{
int kcmp;
struct rb_node *rbp;
struct epitem *epi, *epir = NULL;
struct epoll_filefd ffd;
ep_set_ffd(&ffd, file, fd);
for (rbp = ep->rbr.rb_root.rb_node; rbp; ) {
epi = rb_entry(rbp, struct epitem, rbn);
kcmp = ep_cmp_ffd(&ffd, &epi->ffd);
if (kcmp > 0)
rbp = rbp->rb_right;
else if (kcmp < 0)
rbp = rbp->rb_left;
else {
epir = epi;
break;
}
}
return epir;
}
/* Compare RB tree keys */
static inline int ep_cmp_ffd(struct epoll_filefd *p1,
struct epoll_filefd *p2)
{
return (p1->file > p2->file ? +1:
(p1->file < p2->file ? -1 : p1->fd - p2->fd));
}
从rbp = ep->rbr.rb_root.rb_node
其实就可以看出epi存的数据结构体是一颗红黑树,红黑树的root节点,在ep(创建epoll fd会创建一个ep)中保存。
那么epi存的是什么?我们继续看函数ep_insert
函数:
static int ep_insert(struct eventpoll *ep, const struct epoll_event *event,
struct file *tfile, int fd, int full_check)
{
/* 从slab池中分配epi节点 */
if (!(epi = kmem_cache_alloc(epi_cache, GFP_KERNEL)))
return -ENOMEM;
/* 初始化epi */
INIT_LIST_HEAD(&epi->rdllink);
INIT_LIST_HEAD(&epi->fllink);
INIT_LIST_HEAD(&epi->pwqlist);
epi->ep = ep; /* 设置指向ep的指针 */
/* 存储fd和file的值 */
ep_set_ffd(&epi->ffd, tfile, fd);
epi->event = *event;
epi->nwait = 0;
epi->next = EP_UNACTIVE_PTR;
epq.epi = epi;
/* epi加入到rb tree中 */
ep_rbtree_insert(ep, epi);
}
/* Setup the structure that is used as key for the RB tree */
static inline void ep_set_ffd(struct epoll_filefd *ffd,
struct file *file, int fd)
{
ffd->file = file;
ffd->fd = fd;
}
函数中删除了一些代码,便于更好的理解,我们可以理解为向epoll中加入一个fd,就会创建一个epi(struct epitem *epi;)
并加入到ep中的rb tree保存。
由代码分析可以得到如图这样一个数据关系,这个数据关系有利于对代码的理解,毕竟C语言操作的对象都是数据~
这次写的博客时间较短,可能会存在一些疏漏和不严谨,博客有一部分内容是借鉴https://www.cnblogs.com/sduzh/p/6714281.html来的,写这篇文章的主要目的还是自己学习总结和记录,O(∩_∩)O谢谢