epoll的实现主要依赖于一个文件系统eventpoll。
epoll使用中有三个重要的函数:epoll_create(), epoll_ctl(), epoll_wait
epoll有四个重要的数据结构:struct eventpoll, struct epitem,struct epoll_event, struct eppoll_entry
struct eventpoll
{
rwlock_t lock;
struct rw_semaphore sem;
wait_queue_head_t wq;
wait_queue_head_t poll_wait;
//链接的是epitem->rdllink,该epitem对应的fd有事件就绪。
struct list_head rdllist;
//该连接监听的fd的epitem
struct rb_root rbr;
};
struct epitem
{
//红黑树的根节点,它的结点都为epitem变量。方便查找和删除
struct rb_node rbn;
//链表中的每个结点即为epitem中的rdllink,当epitem所对应的fd存在已经就绪的I/O事件,ep_poll_callback回调函数会将该结点连接到eventpoll中的rdllist循环链表中去,这样就将就绪的epitem都串起来了。
struct list_head rdllink;
//将fd和file绑定起来
struct epoll_filefd ffd;
int nwait;
//指向包含此epitem的所有poll wait queue,insert时,pwqlist=eppoll_entry->llink;
struct list_head pwqlist;
//eventpoll的指针,每个epitem都有这样一个指针,它指向对应的eventpoll变量。只要拿到了epitem,就可以根据它找到eventpoll
struct eventpoll *ep;
//存放从用户空间拷贝的epoll_event
struct epoll_event event;
atomic_t usecnt;
//通过这个节点,将epitem挂到file->f_op_links文件操作等待队列中
struct list_head fllink;
//通过这个节点,将epitem挂到transfer链表中
struct list_head txlink;
//判断是否要重新插入rdllist中 ET/LT
unsigned int revents;
};
struct epoll_event
{
__u32 events;//关心的事件EPOLLIN/EPOLLOUT/EPOLLONESHOT
__u64 data;//fd
};
//等待队列节点
struct eppoll_entry
{
/* List header used to link this structure to the "struct epitem" */
struct list_head llink;
//指向其e对应的pitem
void *base;
//等待队列的项,insert时,将该等待队列挂在设备驱动的等待队列中,并设置wait->proc=ep_poll_callback。事件就绪时,设备状态发生改变,设备驱动调用回调函数,将epi->rdllink挂到ep->rdllist上
wait_queue_t wait;
/* The wait queue head that linked the "wait" wait queue item */
wait_queue_head_t *whead;
};
1.epoll_create
该函数的作用是创建一系列数据结构并建立之间的连接关系,为epoll_ctl做准备。
创建fd、inode、file结构体,初始化后,建立file与inode的连接关系(file->dentry->inode)、task_struct与file的连接关系(files->fd[fd]=file)
生成eventpoll对象,初始化wq、poll_wait、rdllist三个等待队列和红黑树根节点rbroot,建立file与eventpoll的连接关系(file->private_data=ep)
在epoll_create中,size是只要>=0即可,size没有实际意义。
asmlinkage long sys_epoll_create(int size)
{
int error, fd;
struct inode *inode;
struct file *file;
if (size <= 0)
goto eexit_1;
//获取fd、inode、file结构体,初始化,建立连接关系。
error= ep_getfd(&fd, &inode, &file);
//获取eventpoll文件系统,初始化,file->private_data=ep;
error= ep_file_init(file);
return fd;
}
下面是ep_getfd()函数的实现过程,重要的代码我都做了注释,有兴趣的同学自行阅读。
该函数创建fd、inode、file结构体,初始化后,建立file与inode的连接关系、task_struct与file的连接关系
static int ep_getfd(int *efd, struct inode **einode, struct file **efile)
{
struct qstr this;
char name[32];
struct dentry *dentry;
struct inode *inode;
struct file *file;
int error, fd;
/* Get an ready to use file */
error = -ENFILE;
//申请file数据结构
file = get_empty_filp();
if (!file)
goto eexit_1;
/* Allocates an inode from the eventpoll file system */
//申请inode数据结构
inode = ep_eventpoll_inode();
error = PTR_ERR(inode);
if (IS_ERR(inode))
goto eexit_2;
/* Allocates a free descriptor to plug the file onto */
//获得文件描述符
error = get_unused_fd();
if (error < 0)
goto eexit_3;
fd = error;
/*
* Link the inode to a directory entry by creating a unique name
* using the inode number.
*/
error = -ENOMEM;
sprintf(name, "[%lu]", inode->i_ino);
this.name = name;
this.len = strlen(name);
this.hash = inode->i_ino;
//file和inode必须通过dentry结构才能连接起来,除了做file和inode的桥梁,这个数据结构本身没有什么大的作用
dentry = d_alloc(eventpoll_mnt->mnt_sb->s_root, &this);
if (!dentry)
goto eexit_4;
dentry->d_op = &eventpollfs_dentry_operations;
//dentry->d_inode=inode
d_add(dentry, inode);
//初始化file结构体
file->f_vfsmnt = mntget(eventpoll_mnt);
file->f_dentry = dentry;
file->f_mapping = inode->i_mapping;
file->f_pos &#