第一步:了解一些内核方面的知识:
1、等待队列waitqueue:队列头(wait_queue_head_t)往往是资源生产者, 队列成员(wait_queue_t)往往是资源消费者, 当队列头的资源就绪后, 会逐个执行每个成员指定的回调函数, 来通知它们资源已经就绪了。2、内核的poll机制 :当应用程序调用poll函数的时候,会调用到系统调用sys_poll函数,该函数最终调用do_poll函数,do_poll函数中有一个死循环,在里面又会利用do_pollfd函数去调用驱动中的poll函数(fds中每个成员的字符驱动程序都会被扫描到),驱动程序中的Poll函数的工作有两个,一是调用poll_wait 函数,把进程挂到等待队列中去,二是确定相关的fd是否有内容可 读,如果可读,就返回1,否则返回0,如果返回1 ,do_poll函数中的count++,然后 do_poll函数然后判断三个条件如果成立就直接跳出,如果不成立,就睡眠timeout个jiffes这么长的时间,如果在这段时间内没有其他进程去唤醒它,那么第二次执行判断的时候就会跳出死循环。简而言之当存在一个fd被poll使用, 则它必须满足poll操作,即给自己分配有一个等待队列头,给主动调用该fd的某个进程分配一个等待队列成员, 将该进程添加到fd的等待队列里面去, 并指定资源就绪时的回调函数.。
3、epollfd本身也是个fd, 所以它本身也可以被epoll
4、 fd是文件描述符,在内核态与之对应的是struct file结构,可以看作是内核态的文件描述符。
5、spinlock, 自旋锁, 必须要非常小心使用的锁,尤其是调用spin_lock_irqsave()的时候, 中断关闭, 不会发生进程调度, 被保护的资源其它CPU也无法访问. 这个锁是很强力的, 所以只能锁一些非常轻量级的操作。
6、引用计数在内核中是非常重要的概念,内核代码里面经常有些release,free释放资源的函数几乎不加任何锁,这是因为这些函数往往是在对象的引用计数变成0时被调用,既然没有进程在使用在这些对象,自然也不需要加锁,struct file 是持有引用计数的。
file_operations:可以调用除setlease之外的所有文件操作,而不需要在所有文件系统中都持有大内核锁。
struct file_operations {
struct module *owner;
loff_t (*llseek) (struct file *, loff_t, int);
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
int (*readdir) (struct file *, void *, filldir_t);
unsigned int (*poll) (struct file *, struct poll_table_struct *);
long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
int (*mmap) (struct file *, struct vm_area_struct *);
int (*open) (struct inode *, struct file *);
int (*flush) (struct file *, fl_owner_t id);
int (*release) (struct inode *, struct file *);
int (*fsync) (struct file *, int datasync);
int (*aio_fsync) (struct kiocb *, int datasync);
int (*fasync) (int, struct file *, int);
int (*lock) (struct file *, int, struct file_lock *);
ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
int (*check_flags)(int);
int (*flock) (struct file *, int, struct file_lock *);
ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
int (*setlease)(struct file *, long, struct file_lock **);
};
第二步:了解主要的数据结构
1.eventpoll
// epoll的核心实现对应于一个epollfd,每创建一个epollfd, 内核就会分配一个eventpoll与之对应,可以称之为内核下的epollfd
struct eventpoll {
spinlock_t lock; //自旋锁 ,保护此结构访问
struct mutex mtx; //此互斥锁用于确保不会删除文件
wait_queue_head_t wq; // sys_epoll_wait()使用的等待队列
wait_queue_head_t poll_wait; //file->poll()使用的等待队列,
struct list_head rdllist; //就绪文件描述符列表
struct rb_root rbr; //用于存储监视的fd的红黑树的根节点
struct epitem *ovflist; //将事件到达的fd进行连接, 并发送至用户空间,
struct user_struct *user; //创建eventpoll描述符的用户
};
2、struct epitem
添加到eventpoll接口的每个fd都将具有链接到“rbr”红黑树的结构m
struct epitem { //epitem 表示一个被监听的fd,一个加入到eventepoll的文件
struct rb_node rbn; //将此项目链接到eventpoll 的RB树的结点
struct list_head rdllink; //将此项目链接到eventpoll.rdllist
struct epitem *next; //将此项目链接到主结构中的ovflist链表
struct epoll_filefd ffd; //此项目对应被监听的fd信息
int nwait; //附加到轮询操作的活动等待队列数,
struct list_head pwqlist; //双向链表,保存被监视的文件的等待队列, 同一个文件上可能会监视多种事件
struct eventpoll *ep; //该epitem所属的eventpoll,多个epitm属于一个eventpoll
struct list_head fllink; //双向链表,用来链接被监视的fd对应的struct file,
struct epoll_event event; //注册感兴趣事件和fd,epoll_ctl 传入的用户数据
};
3、epoll_filefd
epoll_filefd {
struct file *file;
int fd;
};
struct file {
union {
struct list_head fu_list;
struct rcu_head fu_rcuhead;
} f_u;
struct path f_path;
#define f_dentry f_path.dentry
#define f_vfsmnt f_path.mnt
const struct file_operations *f_op; //内核文件操作
spinlock_t f_lock; /* f_ep_links, f_flags, no IRQ */
#ifdef CONFIG_SMP
int f_sb_list_cpu;
#endif
atomic_long_t f_count;
unsigned int f_flags;
fmode_t f_mode;
loff_t f_pos;
struct fown_struct f_owner;
const struct cred *f_cred;
struct file_ra_state f_ra;u64 f_version;
#ifdef CONFIG_SECURITY
void *f_security;
#endif
void *private_data; //epoll_create创建的fd用来保存eventpoll#ifdef CONFIG_EPOLL
struct list_head f_ep_links; //由fs/eventpoll.c用于将所有接口链接到此文件
#endif /* #ifdef CONFIG_EPOLL */
struct address_space *f_mapping;
#ifdef CONFIG_DEBUG_WRITECOUNT
unsigned long f_mnt_write_state;
#endif
};
4、eppoll_entry
struct eppoll_entry { //等待队列的结构体
struct list_head llink; //双向链表,用来链接被监视的fd对应的struct epitem
struct epitem *base; //设置struct epitem类型的base指针
wait_queue_t wait; //将目标等待队列项链接到等待队列的头部
wait_queue_head_t *whead; //等待队列的头部,链接“wait”等待队列项,即该fd对应的设备等待队列
};
5、其它
struct ep_pqueue {
poll_table pt;
struct epitem *epi;
};ypedef void (*poll_queue_proc)(struct file *, wait_queue_head_t *, struct poll_table_struct *);
typedef struct poll_table_struct {
poll_queue_proc qproc;
unsigned long key;
} poll_table;struct ep_send_events_data {
int maxevents;
struct epoll_event __user *events;
};struct epoll_event {
__u32 events; //epoll事件
__u64 data; //用户数据
} EPOLL_PACKED;static const struct file_operations eventpoll_fops = {
.release = ep_eventpoll_release,
.poll = ep_eventpoll_poll,
.llseek = noop_llseek,
}
第三步:剖析epoll主要的四个方法:epoll_init()、epoll_create()、epoll_ctl()、epoll_wait()。1、epoll是个模块,所以先看看模块的入口eventpoll_init()
static int __init eventpoll_init(void)
{
struct sysinfo si;
si_meminfo(&si);
//允许将前4%的内存分配给EPOLL监视(每个用户) //PAGE_SHIFT为12
max_user_watches = (((si.totalram - si.totalhigh) / 25) << PAGE_SHIFT) /
EP_ITEM_COST;
//初始化用于唤醒执行安全的poll等待队列的队头 wq
ep_nested_calls_init(&poll_safewake_ncalls);
//初始化用于执行文件f_op->poll()的结构调用 poll_wait
ep_nested_calls_init(&poll_readywalk_ncalls);
//epoll用kmem_cache_create(slab分配器)分配高速缓存——内存池用来存放struct epitem和struct eppoll_entry。
//分配用于struct epitem的缓存模块
epi_cache = kmem_cache_create("eventpoll_epi", sizeof(struct epitem),
0, SLAB_HWCACHE_ALIGN | SLAB_PANIC, NULL);
//分配用于struct eppoll_entry的缓存模块
pwq_cache = kmem_cache_create("eventpoll_pwq",sizeof(struct eppoll_entry),
0, SLAB_PANIC, NULL);return 0;
}
2、epoll_create()
SYSCALL_DEFINE1(epoll_create, int, size)
{
if (size <= 0)
return -EINVAL;
return sys_epoll_create1(0);
} //epoll_create()判断size是否大于零,随后直接调用epoll_create1
SYSCALL_DEFINE1(epoll_create1, int, flags)
{
int error;
//设置主描述符
struct eventpoll *ep = NULL;
//检查EPOLL_*常量的一致性
BUILD_BUG_ON(EPOLL_CLOEXEC != O_CLOEXEC);
//epoll中两个重要的标记 flags & O_CLOEXEC
if (flags & ~EPOLL_CLOEXEC)
return -EINVAL;//调用ep_alloc(),创建内部数据结构 struct eventpoll,随后分析如果创建失败,直接return
error = ep_alloc(&ep);
if (error < 0)
return error;//创建所需的所有项目为设置poll文件,即文件结构和空闲文件描述符。
error = anon_inode_getfd("[eventpoll]", &eventpoll_fops, ep,
O_RDWR | (flags & O_CLOEXEC));
//anon_inode_getfd:随后见源码
//在第一次运行create函数时,匿名创建一个fd(epollfd),epollfd本身并不存在一个真正与之对应的文件,
//所以内核需要创建一个"虚拟"的文件,并为之分配真正的struct file结构,并且分配真正的fd;
//eventpoll_fops:fpos即file operations,即当对文件进行操作时,fpos里的函数指针指向真正的操作实现。
//ep:即struct epollevent,把struct epollevent作为一个私有数据保存在fd的struct file的private指针中。
//即可通过fd找到struct file,可通过struct file找到eventpoll。
if (error < 0)
ep_free(ep);
return error;
}
//epoll_creat()返回的文件描述符将作为其它所有epoll系统调用的第一个参数,用来指定访问的内核事件表
anon_inode_getfd():
/*
创建一个新的文件实例,方法是将其连接到一个匿名inode,以及一个描述文件的“class”的目录项
@name: 新文件的“类”的名称。
@fops: 新文件的操作类型
@priv: 新文件的私有数据(将是文件的private_data)
@flags: 新文件的flag
通过将新文件加载到单个inode上来创建新文件。这对于不需要有完整的inode才能正确操作的文件很有用。
使用anon_inode_getfd()创建的所有文件将共享一个inode,从而节省内存并避免file/inode/dentry设置的代码重复。返回新的描述符或错误代码。
*/
int anon_inode_getfd(const char *name, const struct file_operations *fops,void *priv, int flags)
{
int error, fd;
struct file *file
error = get_unused_fd_flags(flags);
if (error < 0)
return error;
fd = error;
file = anon_inode_getfile(name, fops, priv, flags);
if (IS_ERR(file)) {
error = PTR_ERR(file);
goto err_put_unused_fd;
}
fd_install(fd, file);
return fd;
err_put_unused_fd:
put_unused_fd(fd);
return error;
}
//分配一个eventpoll结构并初始化
static int ep_alloc(struct eventpoll **pep)
{
int error;
struct user_struct *user;
//主描述符
struct eventpoll *ep;
//获取当前用户信息
user = get_current_user();
error = -ENOMEM;
ep = kzalloc(sizeof(*ep), GFP_KERNEL);
if (unlikely(!ep))
goto free_uid;//一系列初始化
spin_lock_init(&ep->lock);
mutex_init(&ep->mtx);
init_waitqueue_head(&ep->wq);
init_waitqueue_head(&ep->poll_wait);
INIT_LIST_HEAD(&ep->rdllist);
//RB树的根节点
ep->rbr = RB_ROOT;
ep->ovflist = EP_UNACTIVE_PTR;
ep->user = user;
*pep = ep;
return 0;free_uid:
free_uid(user);
return error;
}
//epoll_ctl函数实现eventpoll文件的控制器接口,该接口允许在兴趣集内插入/删除/更改文件描述符。
//epfd:epollfd、op:ADD,DEL,MOD、fd:需要监听的文件描述符
3、epoll_ctl()
SYSCALL_DEFINE4(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;error = -EFAULT;
//如果用户需要,拷贝用户参数
if (ep_op_has_event(op) &&
copy_from_user(&epds, event, sizeof(struct epoll_event)))
goto error_return;//获取eventpoll文件的“struct file*” epoll_creat返回的自己创建的文件
error = -EBADF;
file = fget(epfd);
if (!file)
goto error_return;//获取目标文件的“struct file*” 实例
tfile = fget(fd);
if (!tfile)
goto error_fput;//目标文件描述符必须支持poll
error = -EPERM;
if (!tfile->f_op || !tfile->f_op->poll)
goto error_tgt_fput;//检查用户传递给us_is_an eventpoll文件的文件描述符下的文件结构 也不允许在其内部添加EPOLL文件描述符。
error = -EINVAL;
if (file == tfile || !is_file_epoll(file))
goto error_tgt_fput;//file->private_data 获取epoll_create()创建的eventpoll
ep = file->private_data;
//接下来的操作有可能修改数据结构内容,加锁”mtx“确保文件不会被删除
mutex_lock(&ep->mtx);/RB树中查找文件,因为抓取了上面的“mtx”,可以确保能够使用ep_find()查找到的项目,直到我们释放互斥锁。
//操作文件,在eventpolll中存储文件描述符信息的BR树中查找指定fd的epitem实例epi = ep_find(ep, tfile, fd);
error = -EINVAL;
switch (op) {
case EPOLL_CTL_ADD:
//添加文件描述符,判断是否存在,未找到则表示第一次调用,插入内核事件表,已存在则退出
if (!epi) {
epds.events |= POLLERR | POLLHUP;
error = ep_insert(ep, &epds, tfile, fd);
} else
error = -EEXIST;
break;
//删除文件描述符
case EPOLL_CTL_DEL:
if (epi)
error = ep_remove(ep, epi);
else
error = -ENOENT;
break;
//修改文件描述符
case EPOLL_CTL_MOD:
if (epi) {
epds.events |= POLLERR | POLLHUP;
error = ep_modify(ep, epi, &epds);
} else
error = -ENOENT;
break;
}
//操作已完成,解锁,释放“mtx”,
mutex_unlock(&ep->mtx);error_tgt_fput:
fput(tfile);
error_fput:
fput(file);
error_return:return error;
}
struct file *fget(unsigned int fd)
{
struct file *file;
struct files_struct *files = current->files;rcu_read_lock();
file = fcheck_files(files, fd);
if (file) {
if (!atomic_long_inc_not_zero(&file->f_count)) {
rcu_read_unlock();
return NULL;
}
}
rcu_read_unlock();
return file;
}fput():
void fput(struct file *file)
{
if (atomic_long_dec_and_test(&file->f_count))
__fput(file);
}atomic_long_dec_and_test():
static inline int atomic_long_dec_and_test(atomic_long_t *l)
{
atomic_t *v = (atomic_t *)l;return atomic_dec_and_test(v);
}//在eventpoll树中搜索文件。RB树操作受“mtx”互斥的保护,并且必须在保持“mtx”加锁的情况下调用ep_find()。
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_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;
}
/*
epi = ep_find(ep, tfile, fd);
ep:ep = file->private_data,通过eventpoll的struct file 的private_data成员查找到在epoll_create时创建的struct eventpoll
tfile:目标文件的struct file
epoll_find():剖析发现原来是struct eventpoll的rbr成员即为红黑树的根!而红黑树上的结点均为struct epitem。一个由epoll_reate新创建fd,系统会给它维护一个真正的epoll文件,该带有一个struct eventpoll结构,这个结构上拥有一个红黑树,而这个红黑树就是每次epoll_ctl时fd存放的地方,即每一个epitem结点的位置!
*/
//必须在保持“mtx”加锁的情况下调用
ep_insert()
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;
//查看是否到达最大监听数
if (unlikely(atomic_read(&ep->user->epoll_watches) >= max_user_watches))return -ENOSPC;
//申请一个epi空间(epitem)
if (!(epi = kmem_cache_alloc(epi_cache, GFP_KERNEL)))
return -ENOMEM;//初始化epi空间(epitem)
INIT_LIST_HEAD(&epi->rdllink);
INIT_LIST_HEAD(&epi->fllink);
INIT_LIST_HEAD(&epi->pwqlist);
epi->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;//使用队列回调初始化poll_table
//跳转到init_poll_funcptr()发现该操作实际是在初始化一个poll_table,
//其实是在指定调用poll_wate时的回调函数为ep_table_queue_proc()
init_poll_funcptr(&epq.pt, ep_ptable_queue_proc);
/* static inline void init_poll_funcptr(poll_table *pt, poll_queue_proc qproc)
{
pt->qproc = qproc;
pt->key = ~0UL;
}
ypedef void (*poll_queue_proc)(struct file *, wait_queue_head_t *, struct poll_table_struct *);
typedef struct poll_table_struct {
poll_queue_proc qproc;
unsigned long key;
} poll_table;*///将epitem添加到poll的监视队列并获取当前epitem的位置。
//可以安全使用,因调用者已经增加了它的使用计数。在此操作完成后,poll回调可以开始使用新的epitem。
//在epoll主动poll某个fd时,用来将epitem与指定的fd关联起来,关联的办法即使用等待队列(wait queue)
//调用f_op->poll()时系统调用ep_eventpoll_poll()revents = tfile->f_op->poll(tfile, &epq.pt);
/*static unsigned int ep_eventpoll_poll(struct file *file, poll_table *wait)
{
int pollflags;
struct eventpoll *ep = file->private_data;
poll_wait(file, &ep->poll_wait, wait);//继续查找想要的事件是否存在于就绪列表中可用。这需要在ep_call_nested()的监督下完成,因为在列出的文件上完成的对f_op->poll()的调用应该在这里重新输入。
pollflags = ep_call_nested(&poll_readywalk_ncalls, EP_MAX_NESTS,
ep_poll_readyevents_proc, ep, ep, current);return pollflags != -1 ? pollflags : 0;
}*///必须检查在poll的等待队列在插入的过程中是否出现错误,可能存在由于等待队列的分配存在高存储器压力而失败
error = -ENOMEM;
if (epi->nwait < 0)
goto error_unregister;spin_lock(&tfile->f_lock);
//每个文件将会把所有监听的epitem链接起来
list_add_tail(&epi->fllink, &tfile->f_ep_links);
spin_unlock(&tfile->f_lock);//将epitem添加到RB树中(RB树操作受“mtx”保护),在“mtx”加锁的情况下调用ep_rbtree_insert()
ep_rbtree_insert(ep, epi);spin_lock_irqsave(&ep->lock, flags);
//将新的epitem放在列表中以监视它,如果文件已经“就绪”,我们将其放入就绪列表中
if ((revents & event->events) && !ep_is_linked(&epi->rdllink)) {
list_add_tail(&epi->rdllink, &ep->rdllist);//通知等待任务事件可用
if (waitqueue_active(&ep->wq))
wake_up_locked(&ep->wq);
if (waitqueue_active(&ep->poll_wait))
pwake++;
}spin_unlock_irqrestore(&ep->lock, flags);
atomic_inc(&ep->user->epoll_watches);
if (pwake)
ep_poll_safewake(&ep->poll_wait);return 0;
error_unregister:
ep_unregister_pollwait(ep, epi);
//事件可能已经到达某个位置的等待队列。我们并不关心fp->ovflist列表,因为它仅在由“mtx”绑定的区段内使用/清理,并且ep_insert()在保持“mtx”加锁的情况下被调用。spin_lock_irqsave(&ep->lock, flags);
if (ep_is_linked(&epi->rdllink))
list_del_init(&epi->rdllink);
spin_unlock_irqrestore(&ep->lock, flags);kmem_cache_free(epi_cache, epi);
return error;
}
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 = kmem_cache_alloc(pwq_cache, GFP_KERNEL))) {
init_waitqueue_func_entry(&pwq->wait, ep_poll_callback);//初始化创建struct epoll_entry等待队列, 指定ep_poll_callback为唤醒时的回调函数
//当监听的fd发生状态改变时,也就是队列头部被唤醒的时候,指定的回调函数将会被调用pwq->whead = whead;
pwq->base = epi;
//将刚刚分配的等待队列队员加入到队列头部,头部由fd所控制
add_wait_queue(whead, &pwq->wait);
list_add_tail(&pwq->llink, &epi->pwqlist);
//nwait记录了当前epitem加入到了多少等待队列
epi->nwait++;} else {
//发送信号通知发生了一个错误
epi->nwait = -1;
}
}
//这是传递给等待队列唤醒机制的回调。当存储的文件描述符有事件要报告时,将调用它。
static int ep_poll_callback(wait_queue_t *wait, unsigned mode, int sync, void *key)
{
int pwake = 0;
unsigned long flags;
//从等待队列获取epitem.需要知道哪个进程挂载到这个设备
struct epitem *epi = ep_item_from_wait(wait);
struct eventpoll *ep = epi->ep;spin_lock_irqsave(&ep->lock, flags);
//如果事件掩码不包含任何poll event,我们认为描述符被禁用(不再关注)。可能是EPOLLONESHOT位的影响,
//该位在接收到事件禁用描述符,直到发出下一个EPOLL_CTL_MOD。
//如果一个epitem被设置为EPOLLONESHOT,则这个epitem上的事件在被拷到用户态之后,
//这个epitem上的事件将被清空(不会从epoll中删除),即当epitem上的事件触发时,用户不关注,if (!(epi->event.events & ~EP_PRIVATE_BITS))
goto out_unlock; //禁用描述符,不再关注//检查与回调一起出现的事件。在这个阶段,并不是每个设备都报告回调的“key”参数中的事件。
//我们需要能够在这里处理这两种情况,因此在事件匹配测试之前对“key”!=null进行测试。if (key && !((unsigned long) key & epi->event.events))
goto out_unlock;//如果我们将事件传递到用户空间,我们就不能持有锁(因为我们正在访问用户内存,且因为f_op->poll()语义)。
//在这段时间内发生的所有事件都链接在ep->ovflist中,稍后重新排队。
//即在callback被调用时,epoll_wait()已经返回,则程序可能循坏获取events,
//这时内核将此刻发生的events的epitem用ep_ovflist()连接起来,在下一次epoll_wait()时返回给用户if (unlikely(ep->ovflist != EP_UNACTIVE_PTR)) {
if (epi->next == EP_UNACTIVE_PTR) {
epi->next = ep->ovflist;
ep->ovflist = epi;
}
goto out_unlock;
}
if (!ep_is_linked(&epi->rdllink))
//将当前的epitem放入rdist如果已经在就绪列表中则很快就退出
list_add_tail(&epi->rdllink, &ep->rdllist);//唤醒eventpoll等待列表和poll()等待列表(如果处于活动状态)。
if (waitqueue_active(&ep->wq))
wake_up_locked(&ep->wq);
//如果epollfd也在被poll, 那就唤醒队列里面的所有成员
if (waitqueue_active(&ep->poll_wait))
pwake++;out_unlock:
spin_unlock_irqrestore(&ep->lock, flags);if (pwake)
ep_poll_safewake(&ep->poll_wait);return 1;
}
epoll_wait():
SYSCALL_DEFINE4(epoll_wait, int, epfd, struct epoll_event __user *, events, int, maxevents, int, timeout)
{
int error;
struct file *file;
struct eventpoll *ep;
//事件的最大数量必须大于零
if (maxevents <= 0 || maxevents > EP_MAX_EVENTS)
return -EINVAL;//验证用户传递的区域是否可写,内核对应用程序采取的策略是绝对不信任,所以内核跟应用程序之间的数据交互大都是copy,不允许指针引用,epoll_wait()需要内核返回数据给用户空间,内存由程序提供,所以内核需要验证用户传递的空间是否可写
if (!access_ok(VERIFY_WRITE, events, maxevents * sizeof(struct epoll_event))) {
error = -EFAULT;
goto error_return;
}//获取eventpoll文件*的“struct file*”
error = -EBADF;
file = fget(epfd);
if (!file)
goto error_return;//检查用户传递给us_is_an的eventpoll文件是不是真正的eventpollfd文件
error = -EINVAL;
if (!is_file_epoll(file))
goto error_fput;//file->private_data 获取epoll_create()创建的eventpoll
ep = file->private_data;//调用ep_poll()等待事件到来
error = ep_poll(ep, events, maxevents, timeout);error_fput:
fput(file);
error_return:return error;
}
static int ep_poll(struct eventpoll *ep, struct epoll_event __user *events,int maxevents, long timeout)
{
int res, eavail, timed_out = 0;
unsigned long flags;
long slack;
wait_queue_t wait;
struct timespec end_time;
ktime_t expires, *to = NULL;//计算等待时间
if (timeout > 0) {
ktime_get_ts(&end_time);
timespec_add_ns(&end_time, (u64)timeout * NSEC_PER_MSEC);
slack = select_estimate_accuracy(&end_time);
to = &expires;
*to = timespec_to_ktime(end_time);
} else if (timeout == 0) {
timed_out = 1;
}retry:
spin_lock_irqsave(&ep->lock, flags);res = 0;
//如果rdlist不为空,将可用事件可以返回给调用者
if (list_empty(&ep->rdllist)) {//为空 我们没有任何可用事件可以返回给调用者。我们需要在这里等待,当事件可用时,我们将被ep_poll_callback()唤醒。
//初始化等待队列,wait表示当前进程,将当前进程挂载到ep结构的等待队列init_waitqueue_entry(&wait, current);
__add_wait_queue_exclusive(&ep->wq, &wait);for (;;) {
//如果ep_poll_callback()在两者之间向发送一个唤醒,我们就不再等待。
//因此在执行检查之前将任务状态设置为task_interruptableset_current_state(TASK_INTERRUPTIBLE);
//判断是否为空、不为空返回,等待时间到达,不继续等待返回
if (!list_empty(&ep->rdllist) || timed_out)
break;
//如果当前有信号产生则也返回
if (signal_pending(current)) {
res = -EINTR;
break;
}spin_unlock_irqrestore(&ep->lock, flags);
if (!schedule_hrtimeout_range(to, slack, HRTIMER_MODE_ABS))
timed_out = 1;
spin_lock_irqsave(&ep->lock, flags);
}
__remove_wait_queue(&ep->wq, &wait);set_current_state(TASK_RUNNING);
}
eavail = !list_empty(&ep->rdllist) || ep->ovflist != EP_UNACTIVE_PTR;spin_unlock_irqrestore(&ep->lock, flags);
//尝试将事件传输到用户空间。如果我们得到0个事件并且仍然有剩余的超时,我们就继续尝试。
//如果一切正常, 有event发生, 就开始准备返回给用户空间了if (!res && eavail &&!(res = ep_send_events(ep, events, maxevents)) && !timed_out)
goto retry;return res;
}ep_poll_callback()的调用时机由被监听的fd具体实现,因为等待队列的队列头为fd所持有 ,epoll和进程只是单纯的等待
//实现eventpoll文件的事件等待接口。它是用户空间EPOLL_pwait(2)的内核部分。
#ifdef HAVE_SET_RESTORE_SIGMASK
SYSCALL_DEFINE6(epoll_pwait, int, epfd, struct epoll_event __user *, events,
int, maxevents, int, timeout, const sigset_t __user *, sigmask,size_t, sigsetsize)
{
int error;
sigset_t ksigmask, sigsaved;//如果呼叫者希望在等待期间设置某一信号掩码,可以在这里应用它。
if (sigmask) {
if (sigsetsize != sizeof(sigset_t))
return -EINVAL;
if (copy_from_user(&ksigmask, sigmask, sizeof(ksigmask)))
return -EFAULT;
sigdelsetmask(&ksigmask, sigmask(SIGKILL) | sigmask(SIGSTOP));
sigprocmask(SIG_SETMASK, &ksigmask, &sigsaved);
}error = sys_epoll_wait(epfd, events, maxevents, timeout);
//如果我们改变了信号掩码,我们需要恢复原来的。如果我们在等待时收到信号,我们还没有恢复信号掩码,并且在恢复信号掩码之前,我们允许do_signal()在返回用户空间的路上传递信号
if (sigmask) {
if (error == -EINTR) {
memcpy(¤t->saved_sigmask, &sigsaved,
sizeof(sigsaved));
set_restore_sigmask();
} else
sigprocmask(SIG_SETMASK, &sigsaved, NULL);
}
return error;
}
#endif /* HAVE_SET_RESTORE_SIGMASK */
//向用户空间发送就绪事件表
static int ep_send_events(struct eventpoll *ep,struct epoll_event __user *events, int maxevents)
{
// ep_send_events_data结构体的 esed
struct ep_send_events_data esed;
esed.maxevents = maxevents;
esed.events = events;return ep_scan_ready_list(ep, ep_send_events_proc, &esed);
}struct ep_send_events_data {
int maxevents;
struct epoll_event __user *events;
};static int ep_scan_ready_list(struct eventpoll *ep,int (*sproc)(struct eventpoll *,struct list_head *, void *),void *priv)
{
int error, pwake = 0;
unsigned long flags;
struct epitem *epi, *nepi;
LIST_HEAD(txlist);// “mtx”加锁,因为我们可能会被eventpoll_release_file()和epoll_ctl()命中,而修改文件。
mutex_lock(&ep->mtx);//获取就绪列表,并将原始列表重新插入空列表,将ep->ovflist置为null,使在不带锁的情况下循环发生时的事件不会丢失。
//我们不能让poll直接回调ep->rdllist排队,因为我们希望“sproc”回调能够以无锁的方式进行。//所有监听到events的epitem都链到rdllist上了
spin_lock_irqsave(&ep->lock, flags);
//将ep->rdllist链表加入到txlist链表中去,这样的话rdllist链表就为空了
list_splice_init(&ep->rdllist, &txlist);
ep->ovflist = NULL;
spin_unlock_irqrestore(&ep->lock, flags);
//调用回调函数遍历epitem拷贝到用户空间
error = (*sproc)(ep, &txlist, priv);spin_lock_irqsave(&ep->lock, flags);
//在“sproc”回调花费的时间内,一些其他事件可能已经被poll回调排队进入ovflist。我们将它们重新插入到这里的主就绪列表中。
for (nepi = ep->ovflist; (epi = nepi) != NULL;
nepi = epi->next, epi->next = EP_UNACTIVE_PTR) {//检查epitem是否已存在,在“sproc”回调执行期间,可能存在epitem被排队到ep->ovflist中,但“txlist”可能已经包含它们,下面的list_splice()会处理它们。
if (!ep_is_linked(&epi->rdllink))
list_add_tail(&epi->rdllink, &ep->rdllist);
}//我们需要将ep->ovflist设置为EP_UNACTIVE_PTR,以便在释放锁后,事件将以正常方式在ep->rdllist中排队
ep->ovflist = EP_UNACTIVE_PTR;
//把txlist里留下的fd返还给rdllist以便下次还能从rdllist里发现它。
list_splice(&txlist, &ep->rdllist);if (!list_empty(&ep->rdllist)) {
//唤醒eventpoll等待列表和 poll()等待列表(如果处于活动状态)(在释放锁定后延迟)。
if (waitqueue_active(&ep->wq))
wake_up_locked(&ep->wq);
if (waitqueue_active(&ep->poll_wait))
pwake++;
}
spin_unlock_irqrestore(&ep->lock, flags);mutex_unlock(&ep->mtx);
if (pwake)
ep_poll_safewake(&ep->poll_wait);return error;
}
static int ep_send_events_proc(struct eventpoll *ep, struct list_head *head,void *priv)
{
struct ep_send_events_data *esed = priv;
int eventcnt;
unsigned int revents;
struct epitem *epi;
struct epoll_event __user *uevent;//可以在没有锁的情况下循环,因为被传递了一个任务私有列表。
//epitem不能在循环期间消失,因为在ep_scan_ready_list()调用期间“mtx”保持加锁状态。
for (eventcnt = 0, uevent = esed->events;
!list_empty(head) && eventcnt < esed->maxevents;) {
epi = list_first_entry(head, struct epitem, rdllink);list_del_init(&epi->rdllink);
//获取事件掩码将掩码拷贝给内核空间
revents = epi->ffd.file->f_op->poll(epi->ffd.file, NULL) & epi->event.events;//如果事件掩码与调用者请求的掩码相同,则将事件传递到用户空间。同样在ep_scan_ready_list()调用期间“mtx”保持加锁状态,因此来自用户空间的任何操作都不能更改该epitem。
if (revents) {
if (__put_user(revents, &uevent->events) ||
__put_user(epi->event.data, &uevent->data)) {
list_add(&epi->rdllink, head);
return eventcnt ? eventcnt : -EFAULT;
}
eventcnt++;
uevent++;
if (epi->event.events & EPOLLONESHOT)
epi->event.events &= EP_PRIVATE_BITS;#define EP_PRIVATE_BITS (EPOLLONESHOT | EPOLLET)
else if (!(epi->event.events & EPOLLET)) {
//如果这个文件是用LT模式添加的,我们需要插入回就绪列表中,以便下一次调用toepoll_wait()将再次检查事件可用性。
//在这一点上,没有人可以插入到ep->rdllist除了我们。epoll_ctl()调用者被保持“mtx”加锁的ep_scan_ready_list()锁定
//epoll回调将在ep->ovflist中对它们进行排队。
//如果这个文件是ET模式添加的,epitem将不会再进入就绪队列,直到fd彻底改变list_add_tail(&epi->rdllink, &ep->rdllist);
}
}
}return eventcnt;
}