epoll源码剖析2

第一步:了解一些内核方面的知识:
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_interruptable

            set_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(&current->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;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值