Linux中epoll()函数的底层实现

来源:互联网 发布:网络用语1是什么意思 编辑:程序博客网 时间:2024/05/29 15:30

epoll()是由epoll_create()、epoll_ctl()、epoll_wait()三个系统调用实现。

epoll_create()

  • epoll_create()调用内核函数sys_epoll_create(),在参数检查后,sys_epoll_create()中,最为重要的两个内核函数为ep_getfd()、ep_file_init();

    1.在 ep_getfd()中为epoll事件分配file、inode、以及文件描述符fd;

        file = get_empty_filp();        //在该函数中初始化一系列文件结构以及参数检查        //f = kmem_cache_alloc(filp_cachep, GFP_KERNEL);//分配空间        //eventpoll_init_file(f);        inode = ep_eventpoll_inode();        //分配节点空间以及初始化        //struct inode *inode = new_inode(eventpoll_mnt->mnt_sb);        error = get_unused_fd();        //分配文件描述符fd        fd = error;
    2.分配dentry结构体的空间并利用中间结构体qstr将其初始化,将file结构体初始化,并将当前进程与file、dentry、inode联系在一起;
    sprintf(name, "[%lu]", inode->i_ino);    this.name = name;    this.len = strlen(name);    this.hash = inode->i_ino;    //通过中间结构体将inode与dentry联系在一起    dentry = d_alloc(eventpoll_mnt->mnt_sb->s_root, &this);    dentry->d_op = &eventpollfs_dentry_operations;    d_add(dentry, inode);    //初始化file结构体    file->f_vfsmnt = mntget(eventpoll_mnt);    file->f_dentry = dentry;    //file与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;    fd_install(fd, file);    //files->fd[fd] = file;    //将file插入到当前进程PCB监测文件结构体中
    3.在ep_file_init(),分配eventpoll结构体的空间并初始化,将file与该结构体联系起来。
    if (!(ep = kmalloc(sizeof(struct eventpoll), GFP_KERNEL)))        return -ENOMEM;    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->private_data = ep;

epoll_ctl()

  • epoll_ctl()调用内核函数sys_epoll_ctl(),在参数检查后,通过file结构体找到eventpoll结构体,再从其中的红黑树根节点遍历寻找新的sockfd是否已存在,最后通过判断操作OP,确定是否添加、删除或者更改。

    1.获取到eventpoll结构体,然后寻找sockfd是否已经存在;

    ep = file->private_data;    epi = ep_find(ep, tfile, fd);
   2.判断OP操作是什么并作出相应处理;
    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;        }
   3.在该函数中最为重要的是ep_insert()函数,在ep_insert()中在申请epitem节点并初始化后,为此监听的sockfd在设备驱动中注册了一个回调函数,并将此节点插至eventpoll的红黑树中以便查找监听。
    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;    EP_SET_FFD(&epi->ffd, tfile, fd);    epi->event = *event;    atomic_set(&epi->usecnt, 1);    epi->nwait = 0;    //将epitem初始化    epq.epi = epi;    init_poll_funcptr(&epq.pt, ep_ptable_queue_proc);    //ep_pqueue结构体中有一个指向epitem的指针,还有一个回调函数    //在此注册第一个回调函数    revents = tfile->f_op->poll(tfile, &epq.pt);    //调用第一个回调函数,并在其中注册第二个回调函数ep_poll_callback    //以下皆为将初始化好的epitem挂入监听的结构中    list_add_tail(&epi->fllink, &tfile->f_ep_links);    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++;    }

epoll_wait()

  • epoll_wait()调用内核函数sys_epoll_wait(),在参数检查后,该函数大部分功能由ep_poll()完成,在ep_poll()中,会不断的检查eventpoll中rdllist是否为空,如果为空就一直处于浅舒眠,只要检测到rdllist不为空,就通过txlist发送给用户。
    1.循环判断rdllist是否为空,为空睡眠,不空发送给用户;
retry:    if (list_empty(&ep->rdllist)    {        init_waitqueue_entry(&wait, current);        add_wait_queue(&ep->wq, &wait);        for (;;)        {            set_current_state(TASK_INTERRUPTIBLE);            //rdllist不空,break跳出循环,而不空的条件是有事件时设备驱动调用回调函数将事件加入rdllist            if (!list_empty(&ep->rdllist) || !jtimeout)                break;            if (signal_pending(current))            {                res = -EINTR;                break;            }            write_unlock_irqrestore(&ep->lock, flags);            jtimeout = schedule_timeout(jtimeout);            write_lock_irqsave(&ep->lock, flags);        }        remove_wait_queue(&ep->wq, &wait);        set_current_state(TASK_RUNNING);    }    //此中ep_events_transfer()实现向用户传送数据,将txlist作为中间链实现ET和LT    //如果ET,将rdllist断链并链入txlist中由txlist发送给用户    //如果LT,会将事件重复加入rdlliat中再次通知用户    if (!res && eavail &&!(res = ep_events_transfer(ep, events, maxevents)) && jtimeout)        goto retry;

epoll()实现至此,在剖析源码过程中,其实还有若干未解,如:

if (EP_OP_HASH_EVENT(op) && copy_from_user(&epds, event, sizeof(struct epoll_event)))        goto eexit_1;

该判断,前半句为判断OP事件是否为删除,如不是删除返回值为真,可执行从用户态拷贝数据到内核态,但是在if条件判断后,执行的操作为退出?

此篇博客还有诸多不完善之处,如有错误和完善意见,麻烦大家发送至xiongduoru@163.com,欢迎大家指正~