在看源码之前呢,非常有必要知道epoll的核心数据结构有哪些,一张图让你更容易看懂重要的数据结构~~(跟着图片走,让你不至于在epoll的源码里绕圈加懵圈→_→ Let’s go!)
/**
* 代表一个打开的文件。由内核在open时创建。当文件的所有实例都被关闭后,才释放该结构。
*/
struct file {
/**
* 用于通用文件对象链表的指针。
*/
struct list_head f_list;
/**
* 文件对应的目录项结构。除了用filp->f_dentry->d_inode的方式来访问索引节点结构之外,设备驱动程序的开发者们一般无需关心dentry结构。
*/
struct dentry *f_dentry;
/**
* 含有该文件的已经安装的文件系统。
*/
struct vfsmount *f_vfsmnt;
/**
* 与文件相关的操作。内核在执行open操作时,对这个指针赋值,以后需要处理这些操作时就读取这个指针。
* 不能为了方便而保存起来。也就是说,可以在任何需要的时候修改文件的关联操作。即"方法重载"。
*/
struct file_operations *f_op;
/**
* 文件对象的引用计数。
* 指引用文件对象的进程数。内核也可能增加此计数。
*/
atomic_t f_count;
/**
* 文件标志。如O_RONLY、O_NONBLOCK和O_SYNC。为了检查用户请求是否非阻塞式的操作,驱动程序需要检查O_NONBLOCK标志,其他标志较少用到。
* 检查读写权限应该查看f_mode而不是f_flags。
*/
unsigned int f_flags;
/**
* 文件模式。FMODE_READ和FMODE_WRITE分别表示读写权限。
*/
mode_t f_mode;
/**
* 当前的读写位置。它是一个64位数。如果驱动程序需要知道文件中的当前位置,可以读取这个值但是不要去修改它。
* read/write会使用它们接收到的最后那个指针参数来更新这一位置。
*/
loff_t f_pos;
/**
* 通过信号进行邋IO进程通知的数据。
*/
struct fown_struct f_owner;
/**
* 用户的UID和GID.
*/
/**
* open系统调用在调用驱动程序的open方法前将这个指针置为NULL。驱动程序可以将这个字段用于任何目的或者忽略这个字段。
* 驱动程序可以用这个字段指向已分配的数据,但是一定要在内核销毁file结构前在release方法中释放内存。
* 它是跨系统调用时保存状态的非常有用的资源。
*/
**void *private_data;**
……
}
该结构体用来保存与epoll节点关联的多个文件描述符,保存的方式是使用红黑树实 现的hash表。至于为什么要保存,下文有详细解释。它与被监听的文件描述符一一对应
struct eventpoll {
rwlock_t lock;//读写锁
struct rw_semaphore sem;//读写信号量
wait_queue_head_t wq;
/* Wait queue used by file->poll() */
wait_queue_head_t poll_wait;
//已经完成的操作事件的队列。
struct list_head rdllist;
/* RB-Tree root used to store monitored fd structs */
struct rb_root rbr;//保存epoll监视的文件描述符
};
struct epitem {
struct rb_node rbn;//红黑树,用来保存eventpoll
/*
链表中的每个结点即为epitem中的rdllink,当epitem所对应的fd存在已经就绪的I/O事件,ep_poll_callback回调函数会将该结点连接到eventpoll中的rdllist循环链表中去,这样就将就绪的epitem都串起来了。*/
struct list_head rdllink;
//这个结构体对应的被监听的文件描述符信息
struct epoll_filefd ffd;
int nwait;//poll操作中事件的个数
//双向链表,保存着被监视文件的等待队列,指向包含此epitem的所有poll wait queue,insert时,pwqlist=eppoll_entry->llink;
struct list_head pwqlist;
//指向eventpoll,多个epitem对应一个eventpoll
struct eventpoll *ep;
//记录发生的事件和对应的fd
struct epoll_event event;
atomic_t usecnt;//引用计数
/*双向链表,用来链接被监视的文件描述符对应的struct file。因为file里有f_ep_link, 用来保存所有监视这个文件的epoll节点 */
struct list_head fllink;
struct list_head txlink;//双向链表,用来保存传输队列
//文件描述符的状态,在收集和传输时用来锁住空的事件集合
unsigned int revents;
};
//等待队列节点
struct eppoll_entry
{
/* List header used to link this structure to the "struct epitem" */
struct list_head llink;
//指向其对应的epitem
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;
};
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;
//指向其对应的epitem
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;
};
由于epitem对应一个被监视的文件,所以通过base可以方便地得到被监视的文件信 息。又因为一个文件可能有多个事件发生,所以用llink链接这些事件。
1. intepoll_create(int size);
它是要创建新inode、 新的file、新的fd。而ep_file_init则要创建一个struct eventpoll结构,并把它放入file>private_data,注意,这个private_data后面还要用到的。 初始化后,建立file与inode的连接关系(file->dentry->inode)、初始化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);
if (error)
goto eexit_1;
//获取eventpoll文件系统,初始化,file->private_data=ep;
error = ep_file_init(file);
return fd;
}
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 = get_empty_filp(); //申请file数据结构
if (!file)
goto eexit_1;
/* Allocates an inode from the eventpoll file system */
inode = ep_eventpoll_inode(); //申请inode数据结构
error = PTR_ERR(inode);
if (IS_ERR(inode))
goto eexit_2;
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 = 0;
file->f_flags = O_RDONLY;
file->f_op = &eventpoll_fops;
file->f_mode = FMODE_READ;
file->f_version = 0;
file->private_data = NULL;
/* Install the new setup file into the allocated fd. *///将task_struct和file连接起来 current->file->fd[fd]=file
fd_install(fd, file);
*efd = fd;
*einode = inode;
*efile = file;
return 0;
}
static int ep_file_init(struct file *file)
{
struct eventpoll *ep;
//生成eventpoll对象 kmalloc
if (!(ep = kmalloc(sizeof(struct eventpoll), GFP_KERNEL)))
return -ENOMEM;
//初始化eventpoll
memset(ep, 0, sizeof(*ep));
rwlock_init(&ep->lock);
init_rwsem(&ep->sem);
init_waitqueue_head(&ep->wq);
init_waitqueue_head(&ep->poll_wait);
INIT_LIST_HEAD(&ep->rdllist);
ep->rbr = RB_ROOT;
//将file和eventpoll连接起来
file->private_data = ep;
DNPRINTK(3, (KERN_INFO "[%p] eventpoll: ep_file_init() ep=%p\n",
current, ep));
return 0;
}
2. intepoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
该函数支持三种操作:EPOLL_CTL_ADD(给fd注册事件)、EPOLL_CTL_DEL(删除fd上的注册事件)、EPOLL_CTL_MOD(修改fd上的注册事件)。
<1>将用户空间的epoll_event拷贝到内核空间,在epoll_ctl只拷贝一次,不用每次都从用户空间拷贝。
<2>通过epfd找到需要操作的eventpoll
<3>在eventpoll->rbr中查找fd是否存在
<4>根据op操作选择insert/remove/modify
sys_epoll_ctl(int epfd, int op, int fd, struct epoll_event __user *event)
{
int error;
struct file *file, *tfile;
struct eventpoll *ep;
struct epitem *epi;
struct epoll_event epds;
DNPRINTK(3, (KERN_INFO "[%p] eventpoll: sys_epoll_ctl(%d, %d, %d, %p)\n",
current, epfd, op, fd, event));
error = -EFAULT;
if (EP_OP_HASH_EVENT(op) &&
copy_from_user(&epds, event, sizeof(struct epoll_event)))//将用户空间的epoll_event拷贝到内核空间
goto eexit_1;
error = -EBADF;
file = fget(epfd); //获得epfd的file结构
if (!file)
goto eexit_1;
tfile = fget(fd);//获得fd的file结构
if (!tfile)
goto eexit_2;
/* The target file descriptor must support poll */
error = -EPERM;
if (!tfile->f_op || !tfile->f_op->poll)
goto eexit_3;
/*
* We have to check that the file structure underneath the file descriptor
* the user passed to us _is_ an eventpoll file. And also we do not permit
* adding an epoll file descriptor inside itself.
*/
error = -EINVAL;
if (file == tfile || !IS_FILE_EPOLL(file))
goto eexit_3;
ep = file->private_data;//获得epfd的eventpoll结构
down_write(&ep->sem);
/* Try to lookup the file inside our hash table */
epi = ep_find(ep, tfile, fd);//在eventpoll->rbr中查找fd是否存在
error = -EINVAL;
switch (op) {
case EPOLL_CTL_ADD:
if (!epi) {
epds.events |= POLLERR | POLLHUP;
//给fd上注册epoll_event事件
error = ep_insert(ep, &epds, tfile, fd);
} else
error = -EEXIST;
break;
case EPOLL_CTL_DEL:
if (epi)
//删除fd上的epoll_event事件
error = ep_remove(ep, epi);
else
error = -ENOENT;
break;
case EPOLL_CTL_MOD:
if (epi) {
epds.events |= POLLERR | POLLHUP;
//更改fd上注册的事件
error = ep_modify(ep, epi, &epds);
} else
error = -ENOENT;
break;
}
ep_insert()
这儿引入了一个ep_pqueue结构体,主要是给epitem绑定一个回调函数。
<1>首先定义了一个epitem变量,对三个头指针初始化,并将epitem->ep指向该eventpoll,通过用户传进来的参数event对ep内部变量epitem->epollevent赋值。通过EP_SET_FFD将目标文件file和epitem关联,这样,epitem、eventpoll和file关联起来了。
<2>然后,给epitem注册回调函数。调用该回调函数时,分配了一个eppoll_entry等待队列节点,初始化并将eppoll_entry等待队列节点挂到epitem中,设置fd的回调函数ep_poll_callback。该回调函数会在fd上有事件发生时由设备驱动调用。
<3>最后,将epitem挂到文件系统的等待队列中,将epitem插入eventpoll的rbtree中。判断当前插入的event是否刚好发生,如果是,将epitem加入到rdlist中,并对epoll上的wait队列调用wakeup。
static int ep_insert(struct eventpoll *ep, struct epoll_event *event,
struct file *tfile, int fd)
{
int error, revents, pwake = 0;
unsigned long flags;
struct epitem *epi;
struct ep_pqueue epq;
error = -ENOMEM;
if (!(epi = EPI_MEM_ALLOC()))
goto eexit_1;
/* Item initialization follow here ... */
//初始化epitem节点
EP_RB_INITNODE(&epi->rbn);
INIT_LIST_HEAD(&epi->rdllink);
INIT_LIST_HEAD(&epi->fllink);
INIT_LIST_HEAD(&epi->txlink);
INIT_LIST_HEAD(&epi->pwqlist);
epi->ep = ep; //epitem和eventpol建立连接
EP_SET_FFD(&epi->ffd, tfile, fd); //将目标文件file和epitem关联
epi->event = *event; //epitem的事件设置为用户传入的事件
atomic_set(&epi->usecnt, 1);
epi->nwait = 0;
epq.epi = epi;
//设置回调函数
init_poll_funcptr(&epq.pt, ep_ptable_queue_proc);
//调用回调函数
revents = tfile->f_op->poll(tfile, &epq.pt);
spin_lock(&tfile->f_ep_lock);
//将epitem挂到文件系统的等待队列(file->f_ep_links)中,
list_add_tail(&epi->fllink, &tfile->f_ep_links);
spin_unlock(&tfile->f_ep_lock);
//将epitem挂到ep->rbtree
ep_rbtree_insert(ep, epi);
if ((revents & event->events) && !EP_IS_LINKED(&epi->rdllink))
{
list_add_tail(&epi->rdllink, &ep->rdllist)
if (waitqueue_active(&ep->wq))
wake_up(&ep->wq);
if (waitqueue_active(&ep->poll_wait))
pwake++;
}
write_unlock_irqrestore(&ep->lock, flags);
/* We have to call this outside the lock */
if (pwake)
ep_poll_safewake(&psw, &ep->poll_wait);
DNPRINTK(3, (KERN_INFO "[%p] eventpoll: ep_insert(%p, %p, %d)\n",current, ep, tfile, fd));
return 0;
}
上面的代码就是ep_insert中要做的最重要的事:创建struct eppoll_entry,设置其唤醒回调函数为 ep_poll_callback,然后加入设备等待队列(注意这里的whead就是上一章所说的每个设备驱动都要带的 等待队列)。只有这样,当设备就绪,唤醒等待队列上的等待着时,ep_poll_callback就会被调用。每次 调用poll系统调用,操作系统都要把current(当前进程)挂到fd对应的所有设备的等待队列上,可以想 象,fd多到上千的时候,这样“挂”法很费事;而每次调用epoll_wait则没有这么罗嗦,epoll只在epoll_ctl 时把current挂一遍(这第一遍是免不了的)并给每个fd一个命令“好了就调回调函数”,如果设备有事件 了,通过回调函数,会把fd放入rdllist,而每次调用epoll_wait就只是收集rdllist里的fd就可以了 ——epoll巧妙的利用回调函数,实现了更高效的事件驱动模型。
//这是在insert中注册的回调函数ep_ptable_queue_proc
//调用该回调函数时,分配了一个eppoll_entry等待队列节点,初始化并将eppoll_entry等待队列节点挂到epitem中,设置fd的回调函数ep_poll_callback。
static void ep_ptable_queue_proc(struct file *file, wait_queue_head_t *whead,
poll_table *pt)
{
struct epitem *epi = EP_ITEM_FROM_EPQUEUE(pt);
struct eppoll_entry *pwq;
if (epi->nwait >= 0 && (pwq = PWQ_MEM_ALLOC())) {
//创建epoll_entry并设置回调函数
init_waitqueue_func_entry(&pwq->wait, ep_poll_callback);
//初始化eppoll_entry
pwq->whead = whead;
pwq->base = epi;
add_wait_queue(whead, &pwq->wait);
//将eppoll_entry插入epitem中
list_add_tail(&pwq->llink, &epi->pwqlist);
epi->nwait++;
} else {
/* We have to signal that an error occurred */
epi->nwait = -1;
}
}
现在我们猜也能猜出来ep_poll_callback会干什么了——肯定是把红黑树上的收到event的epitem(代表 每个fd)插入ep->rdllist中,这样,当epoll_wait返回时,rdllist里就都是就绪的fd了! 当fd上有事件就绪时,该回调函数被设备驱动程序触发,将epitem加入ep->rdllist中(ep->rdllist=epitem->rdllink)并唤醒当前进程(ep->wq)
static int ep_poll_callback(wait_queue_t *wait, unsigned mode, int sync, void *key)
{
//把struct epitem放到struct eventpoll的rdllist中去
list_add_tail(&epi->rdllink, &ep->rdllist);
wake_up(&ep->wq);
}
3. intepoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
sys_epoll_wait()首先获得ep=file->private_data;再调用函数ep_poll()完成真正的epoll_wait。
在ep_poll中
<1>首先检测参数的合法性,如果timeout时间>0,则转换成jtimeout时间
<2>判断eventpoll的rdllist上是否有事件就绪,如果当前rdllist为空,将current进程挂到eventpoll->wq中,将当前进程的状态设置成TASK_INTERRUPTIBLE,调用schedule_timeout让出CPU的执行。
<3>如果当前rdllist不为空,调用ep_events_transfer()函数,将rdllist中就绪的事件通知给用户空间。
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;
jtimeout = timeout == -1 || timeout > (MAX_SCHEDULE_TIMEOUT - 1000) / HZ ? MAX_SCHEDULE_TIMEOUT: (timeout * HZ + 999) / 1000;
retry:
write_lock_irqsave(&ep->lock, flags);
res = 0;
//如果现在ep->rdllist上无就绪事件发生
if (list_empty(&ep->rdllist))
{
//将当前进程挂到ep->wq中
init_waitqueue_entry(&wait, current);
add_wait_queue(&ep->wq, &wait);
for (;;)
{
//将当前进程的状态设置成TASK_INTERRUPTIBLE
set_current_state(TASK_INTERRUPTIBLE);
if (!list_empty(&ep->rdllist) || !jtimeout)
break;
if (signal_pending(current))
{
res = -EINTR;
break;
}
write_unlock_irqrestore(&ep->lock, flags);
//调用schedule_timeout让出CPU的执行
jtimeout = schedule_timeout(jtimeout);
write_lock_irqsave(&ep->lock, flags);
}
remove_wait_queue(&ep->wq, &wait);
set_current_state(TASK_RUNNING);
}
/* Is it worth to try to dig for events ? */
eavail = !list_empty(&ep->rdllist);
write_unlock_irqrestore(&ep->lock, flags);
//调用ep_events_transfer函数,将就绪事件发给用户空间
if (!res && eavail &&!(res = ep_events_transfer(ep, events, maxevents)) && jtimeout)
goto retry;
return res;
}
ep_events_transfer函数
<1>定义list_head txlist;
<2>ep_collect_ready_items()将ep->rdllist中就绪的事件拷贝到txlist中;
<3>ep_send_events()将txlist中的事件发给用户空间;
<4>ep_reinject_items()如果是ET模式,将通知过的事件从rdllist中删除;如果是LT模式,将该事件重新插入rdllist中。
static int ep_events_transfer(struct eventpoll *ep,
struct epoll_event __user *events, int maxevents)
{
int eventcnt = 0;
struct list_head txlist;
INIT_LIST_HEAD(&txlist);
/*
* We need to lock this because we could be hit by
* eventpoll_release_file() and epoll_ctl(EPOLL_CTL_DEL).
*/
down_read(&ep->sem);
/* Collect/extract ready items */
if (ep_collect_ready_items(ep, &txlist, maxevents) > 0) {
/* Build result set in userspace */
eventcnt = ep_send_events(ep, &txlist, events);
/* Reinject ready items into the ready list */
ep_reinject_items(ep, &txlist);
}
up_read(&ep->sem);
return eventcnt;
}
static int ep_collect_ready_items(struct eventpoll *ep, struct list_head *txlist, int maxevents)
{
int nepi;
unsigned long flags;
struct list_head *lsthead = &ep->rdllist, *lnk;
struct epitem *epi;
write_lock_irqsave(&ep->lock, flags);
for (nepi = 0, lnk = lsthead->next; lnk != lsthead && nepi < maxevents;)
{
epi = list_entry(lnk, struct epitem, rdllink);
lnk = lnk->next;
/* If this file is already in the ready list we exit soon */
if (!EP_IS_LINKED(&epi->txlink))
{
/*
* This is initialized in this way so that the default
* behaviour of the reinjecting code will be to push back
* the item inside the ready list.
*/
epi->revents = epi->event.events;
/* Link the ready item into the transfer list */
list_add(&epi->txlink, txlist);
nepi++;
/*
* Unlink the item from the ready list.
*/
EP_LIST_DEL(&epi->rdllink);
}
}
write_unlock_irqrestore(&ep->lock, flags);
return nepi;
}
static int ep_send_events(struct eventpoll *ep, struct list_head *txlist, struct epoll_event __user *events)
{
int eventcnt = 0;
unsigned int revents;
struct list_head *lnk;
struct epitem *epi;
/*
* We can loop without lock because this is a task private list.
* The test done during the collection loop will guarantee us that
* another task will not try to collect this file. Also, items
* cannot vanish during the loop because we are holding "sem".
*/
list_for_each(lnk, txlist)
{
epi = list_entry(lnk, struct epitem, txlink);
/*
* Get the ready file event set. We can safely use the file
* because we are holding the "sem" in read and this will
* guarantee that both the file and the item will not vanish.
*/
revents = epi->ffd.file->f_op->poll(epi->ffd.file, NULL);
/*
* Set the return event set for the current file descriptor.
* Note that only the task task was successfully able to link
* the item to its "txlist" will write this field.
*/
epi->revents = revents & epi->event.events;
if (epi->revents)
{
if (__put_user(epi->revents,
&events[eventcnt].events) ||
__put_user(epi->event.data,
&events[eventcnt].data))
return -EFAULT;
if (epi->event.events & EPOLLONESHOT)
epi->event.events &= EP_PRIVATE_BITS;
eventcnt++;
}
}
return eventcnt;
}
static void ep_reinject_items(struct eventpoll *ep, struct list_head *txlist)
{
int ricnt = 0, pwake = 0;
unsigned long flags;
struct epitem *epi;
write_lock_irqsave(&ep->lock, flags);
while (!list_empty(txlist))
{
epi = list_entry(txlist->next, struct epitem, txlink);
/* Unlink the current item from the transfer list */
EP_LIST_DEL(&epi->txlink);
/*
* If the item is no more linked to the interest set, we don't
* have to push it inside the ready list because the following
* ep_release_epitem() is going to drop it. Also, if the current
* item is set to have an Edge Triggered behaviour, we don't have
* to push it back either.
*/
if (EP_RB_LINKED(&epi->rbn) && !(epi->event.events & EPOLLET) &&
(epi->revents & epi->event.events) && !EP_IS_LINKED(&epi->rdllink)) {
list_add_tail(&epi->rdllink, &ep->rdllist);
ricnt++;
}
}
if (ricnt)
{
/*
* Wake up ( if active ) both the eventpoll wait list and the ->poll()
* wait list.
*/
if (waitqueue_active(&ep->wq))
wake_up(&ep->wq);
if (waitqueue_active(&ep->poll_wait))
pwake++;
}
write_unlock_irqrestore(&ep->lock, flags);
/* We have to call this outside the lock */
if (pwake)
ep_poll_safewake(&psw, &ep->poll_wait);
}