epoll内核实现简单分析记录

来源:互联网 发布:淘宝退款卖家一直拒绝 编辑:程序博客网 时间:2024/06/14 05:36

1 epoll三个系统调用函数原型

#include <sys/epoll.h>int  epoll_create(int  size);int  epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);int  epoll_wait(int epfd, struct epoll_event* events, int maxevents. int timeout);

第一,epoll_create中的size被忽略,但是小于0会出错
第二,epoll_create之后得到文件描述符,也可以作为被epoll的管理对象
第三,epoll_wait中的events是一块可用内存空间,epoll是不会为我们分配内存的

2 epoll数据结构
epoll数据结构主要有epoll事件epoll_event,监听系统本身eventpoll ,被监听的对象epitem,以及被监听对象在设备等待队列中的存在形式eppoll_entry。
如果我们将epoll子系统比喻为国家社会,那么eventpoll 可视为家庭(法律意义上和传统道德观念上),epitem是家中的个体(你,你的父母,哥哥姐姐,爷爷奶奶等),eppoll_entry代表家庭个体在其他社会组织中的个体身份(如你父亲是某个学校的老师,但在家中还是你的父亲),而epoll_event代表了各种事务(如,你回到家了这件事)

1) 代表epoll监听事件的epoll_event

//感兴趣的事件和被触发的事件 struct epoll_event {     __uint32_t events; /* Epoll events */     epoll_data_t data; /* User data variable */};//保存触发事件的某个文件描述符相关的数据(与具体使用方式有关) typedef union epoll_data {     void *ptr;     int fd;     __uint32_t u32;     __uint64_t u64; } epoll_data_t;

events可以是以下几个宏的集合:
EPOLLIN :表示对应的文件描述符可以读(包括对端SOCKET正常关闭);
EPOLLOUT:表示对应的文件描述符可以写;
EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来);
EPOLLERR:表示对应的文件描述符发生错误;
EPOLLHUP:表示对应的文件描述符被挂断;
EPOLLET: 将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的。
EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里

2) 代表epoll监听本身在内核中的存在

/* * This structure is stored inside the "private_data" member of the file * structure and represents the main data structure for the eventpoll * interface. */struct eventpoll {    /* Protect the access to this structure */    //对本数据结构的访问    spinlock_t lock;    /*     * This mutex is used to ensure that files are not removed     * while epoll is using them. This is held during the event     * collection loop, the file cleanup path, the epoll file exit     * code and the ctl operations.     */    //防止使用时被删除    struct mutex mtx;    /* Wait queue used by sys_epoll_wait() */    //sys_epoll_wait() 使用的等待队列 --> 用于epoll_pwait()事件的等待队列    wait_queue_head_t wq;    /* Wait queue used by file->poll() */    //file->poll()使用的等待队列    wait_queue_head_t poll_wait;    /* List of ready file descriptors */    //事件满足条件的链表,就绪链表    struct list_head rdllist;    /* RB tree root used to store monitored fd structs */    //用于管理所有fd的红黑树(树根)    struct rb_root rbr;    /*     * This is a single linked list that chains all the "struct epitem" that     * happened while transferring ready events to userspace w/out     * holding ->lock.     */    //将事件到达的fd进行链接起来发送至用户空间    struct epitem *ovflist;    /* wakeup_source used when ep_scan_ready_list is running */    struct wakeup_source *ws;    /* The user that created the eventpoll descriptor */    struct user_struct *user;    struct file *file;    /* used to optimize loop detection check */    int visited;    struct list_head visited_list_link;};

3) 代表被监听文件描述符在epoll子系统的存在的epitem

 //当向系统中添加一个fd时,就创建一个epitem结构体,这是内核管理epoll的基本数据结构struct epitem {    union {        /* RB tree node links this structure to the eventpoll RB tree */        //用于主结构管理的红黑树        struct rb_node rbn;        /* Used to free the struct epitem */        struct rcu_head rcu;    };    /* List header used to link this structure to the eventpoll ready list */    //事件就绪队列    struct list_head rdllink;    /*     * Works together "struct eventpoll"->ovflist in keeping the     * single linked chain of items.     */    //用于主结构体中的链表    struct epitem *next;    /* The file descriptor information this item refers to */    //这个结构体对应的被监听的文件描述符信息    struct epoll_filefd ffd;    /* Number of active wait queue attached to poll operations */    //poll操作中事件的个数    int nwait;    /* List containing poll wait queues */    //双向链表,保存着被监视文件的等待队列,功能类似于select/poll中的poll_table    struct list_head pwqlist;    /* The "container" of this item */    //该项属于哪个主结构体(多个epitm从属于一个eventpoll)    struct eventpoll *ep;    /* List header used to link this item to the "struct file" items list */    //双向链表,用来链接被监视的文件描述符对应的struct file。因为file里有f_ep_link,用来保存所有监视这个文件的epoll节点    struct list_head fllink;    /* wakeup_source used when EPOLLWAKEUP is set */    struct wakeup_source __rcu *ws;    /* The structure that describe the interested events and the source fd */    //注册的感兴趣的事件,也就是用户空间的epoll_event    struct epoll_event event;};

4) 代表epitem在设备等待队列中存在的eppoll_entry

//回调是为了将epitem中的rdllink结构加入到ready list中struct eppoll_entry {    /* List header used to link this structure to the "struct epitem" */    struct list_head llink;    /* The "base" pointer is set to the container "struct epitem" */    //所属epitem    struct epitem *base;    /*     * Wait queue item that will be linked to the target file wait     * queue head.     */    //作为一元素挂入被监听fd的wait队列中    wait_queue_t wait;    /* The wait queue head that linked the "wait" wait queue item */    //被监听fd的等待队列,如果fd为socket,那么whead为sock->sk_sleep    wait_queue_head_t *whead;};

当我们访问设备时,如果设备暂时不可用,那么进程将被挂到设备等待队列中,并且将让出CPU控制权,处于休眠状态。
eppoll_entry主要完成epitem和epitem事件发生时的callback函数之间的关联。首先将eppoll_entry的whead指向fd的设备等待队列(同select中的wait_address),然后初始化eppoll_entry的base变量指向epitem,最后通过add_wait_queue将epoll_entry挂载到fd的设备等待队列上。当在设备硬件数据到来时,硬件中断处理函数中会唤醒该等待队列上等待的进程时,会调用唤醒函数ep_poll_callback(ep_poll_callback: 当fd上出发事件后,将epitem中的rdllink节点加入到readlist中(epfd-file->eventpoll->rdlist))

3 epoll简单实例

#include <unistd.h>#include <iostream>#include <sys/epoll.h>using namespace std;int main(void){    int epfd,nfds;    //ev用于注册事件,数组用于返回要处理的事件    struct epoll_event ev,events[5];    //只需要监听一个描述符——标准输出    epfd=epoll_create(1);    ev.data.fd=STDOUT_FILENO;    //监听读状态同时设置ET(边缘触发)模式    ev.events=EPOLLOUT|EPOLLET;    //注册epoll事件,监听标准输入    epoll_ctl(epfd,EPOLL_CTL_ADD,STDOUT_FILENO,&ev);    for(;;)   {      //获取发生的事件信息,不设定超时      nfds=epoll_wait(epfd,events,5,-1);      for(int i=0;i<nfds;i++)     {         if(events[i].data.fd==STDOUT_FILENO)             cout<<"hello world!"<<endl;     }   }};

4 epoll子系统初始化
主要是初始化内存分配的两个cache——存放epitem的eventpoll_epi和存放eventpoll_pwq的eppoll_entry

static int __init eventpoll_init(void){    struct sysinfo si;    si_meminfo(&si);    /*     * Allows top 4% of lomem to be allocated for epoll watches (per user).     */     // 限制可添加到epoll的最多的描述符数量      max_user_watches = (((si.totalram - si.totalhigh) / 25) << PAGE_SHIFT) /        EP_ITEM_COST;    BUG_ON(max_user_watches < 0);    /*     * Initialize the structure used to perform epoll file descriptor     * inclusion loops checks.     */    // 初始化递归检查队列      //epoll本身也是文件,也可以被poll/select/epoll监视,如果epoll之间互相监视就有可能导致死循环。    ep_nested_calls_init(&poll_loop_ncalls);    /* Initialize the structure used to perform safe poll wait head wake ups */    ep_nested_calls_init(&poll_safewake_ncalls);    /* Initialize the structure used to perform file's f_op->poll() calls */    ep_nested_calls_init(&poll_readywalk_ncalls);    /*     * We can have many thousands of epitems, so prevent this from     * using an extra cache line on 64-bit (and smaller) CPUs     */    BUILD_BUG_ON(sizeof(void *) <= 8 && sizeof(struct epitem) > 128);    /* Allocates slab cache used to allocate "struct epitem" items */    // epoll 使用的slab分配器分别用来分配epitem和eppoll_entry      //存放item的cache    epi_cache = kmem_cache_create("eventpoll_epi", sizeof(struct epitem),            0, SLAB_HWCACHE_ALIGN | SLAB_PANIC, NULL);    /* Allocates slab cache used to allocate "struct eppoll_entry" */    //存放eppoll_entry的cache    pwq_cache = kmem_cache_create("eventpoll_pwq",            sizeof(struct eppoll_entry), 0, SLAB_PANIC, NULL);    return 0;}

5 epoll_create系统调用内核实现
涉及到两个系统调用,最终都会调用epoll_create1。该系统调用实质上干了两件事:
第一,构建eventpoll对象,该对象包含一个红黑树rbr用于存储epitem对象(也就是为epoll_ctl时添加监听文件描述符在epoll系统中对应的数据结构),等待队列wq ,绪链表rdllist等
第二,构建file对象,以适配万物皆文件的理念,同时将eventpoll对象ep挂到file对象的private_data对象上

/* * Open an eventpoll file descriptor. */SYSCALL_DEFINE1(epoll_create1, int, flags){    int error, fd;    //每次epoll_create会得到一个eventpoll对象,代表了epoll监听体系中一个小集团    //eventpoll对象中有用于管理所有被监听fd的红黑树(树根)    //eventpoll对象中有事件满足条件的链表,就绪链表    //eventpoll对象中有个wait_queue_head_t对象wq,当进程调epoll_wait被阻塞时(rdllist为空)    //当前进程将被挂到该队列,当rdllist不为空时,将会唤醒该队列上的进程    struct eventpoll *ep = NULL;    struct file *file;    /* Check the EPOLL_* constant for consistency.  */    BUILD_BUG_ON(EPOLL_CLOEXEC != O_CLOEXEC);    if (flags & ~EPOLL_CLOEXEC)        return -EINVAL;    /*     * Create the internal data structure ("struct eventpoll").     */    //每个epoll fd(epfd)对应的主要数据结构    //为ep分配内存并进行初始化,因为这个数据结构不像epitem那么常用,没有用到slab    error = ep_alloc(&ep);    if (error < 0)        return error;    /*     * Creates all the items needed to setup an eventpoll file. That is,     * a file structure and a free file descriptor.     */    //调用anon_inode_getfd 新建一个file instance,也就是epoll可以看成一个文件(匿名文件)。因此我们可以看到epoll_create会返回一个fd。    fd = get_unused_fd_flags(O_RDWR | (flags & O_CLOEXEC));    if (fd < 0) {        error = fd;        goto out_free_ep;    }    //epoll所管理的所有的fd都是放在一个大的结构eventpoll(红黑树)中,    //将epoll文件系统的操作集对象赋值给file,并将eventpoll对象挂到file的private_data对象上(sys_epoll_ctl会取用)    file = anon_inode_getfile("[eventpoll]", &eventpoll_fops, ep,                 O_RDWR | (flags & O_CLOEXEC));    if (IS_ERR(file)) {        error = PTR_ERR(file);        goto out_free_fd;    }    ep->file = file;    //将文件对象挂到当前文件描述符表中    fd_install(fd, file);    return fd;out_free_fd:    put_unused_fd(fd);out_free_ep:    ep_free(ep);    return error;}

6 epoll_ctl系统调用内核实现
主要干两件事:
第一,构建epitem,挂到epoll红黑树上
第二,构建eppoll_entry挂到设备等待队列,其中eppoll_entry包含回调函数ep_poll_callback和对应的epitem

/* * The following function implements the controller interface for * the eventpoll file that enables the insertion/removal/change of * file descriptors inside the interest set. */SYSCALL_DEFINE4(epoll_ctl, int, epfd, int, op, int, fd,        struct epoll_event __user *, event){    int error;    int full_check = 0;    struct fd f, tf;    //每个epoll fd(epfd)对应的主要数据结构    struct eventpoll *ep;    //当向系统中添加一个fd时,就创建一个epitem结构体,这是内核管理epoll的基本数据结构    //构建该对象将插入eventpoll对象中的红黑树中集中管理    struct epitem *epi;    //存储user空间传入的描述监听事件的对象    struct epoll_event epds;    //应该是用于循环监听检测的    struct eventpoll *tep = NULL;    error = -EFAULT;    //判断参数的合法性,将 __user *event 复制给 epds。    if (ep_op_has_event(op) &&        copy_from_user(&epds, event, sizeof(struct epoll_event)))        goto error_return;    error = -EBADF;    //得到epoll的file对象    f = fdget(epfd);    if (!f.file)        goto error_return;    /* Get the "struct file *" for the target file */    //得到被监听对象的file对象    tf = fdget(fd);    if (!tf.file)        goto error_fput;    /* The target file descriptor must support poll */    error = -EPERM;    //操作集必须有poll函数    if (!tf.file->f_op->poll)        goto error_tgt_fput;    /* Check if EPOLLWAKEUP is allowed */    if (ep_op_has_event(op))        ep_take_care_of_epollwakeup(&epds);    /*     * 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 (f.file == tf.file || !is_file_epoll(f.file))        goto error_tgt_fput;    /*     * epoll adds to the wakeup queue at EPOLL_CTL_ADD time only,     * so EPOLLEXCLUSIVE is not allowed for a EPOLL_CTL_MOD operation.     * Also, we do not currently supported nested exclusive wakeups.     */    if (epds.events & EPOLLEXCLUSIVE) {        if (op == EPOLL_CTL_MOD)            goto error_tgt_fput;        if (op == EPOLL_CTL_ADD && (is_file_epoll(tf.file) ||                (epds.events & ~EPOLLEXCLUSIVE_OK_BITS)))            goto error_tgt_fput;    }    /*     * At this point it is safe to assume that the "private_data" contains     * our own data structure.     */    //在create时存入进去的(anon_inode_getfd),现在取用。    ep = f.file->private_data;    /*     * When we insert an epoll file descriptor, inside another epoll file     * descriptor, there is the change of creating closed loops, which are     * better be handled here, than in more critical paths. While we are     * checking for loops we also determine the list of files reachable     * and hang them on the tfile_check_list, so we can check that we     * haven't created too many possible wakeup paths.     *     * We do not need to take the global 'epumutex' on EPOLL_CTL_ADD when     * the epoll file descriptor is attaching directly to a wakeup source,     * unless the epoll file descriptor is nested. The purpose of taking the     * 'epmutex' on add is to prevent complex toplogies such as loops and     * deep wakeup paths from forming in parallel through multiple     * EPOLL_CTL_ADD operations.     */    mutex_lock_nested(&ep->mtx, 0);    if (op == EPOLL_CTL_ADD) {        if (!list_empty(&f.file->f_ep_links) ||                        is_file_epoll(tf.file)) {            full_check = 1;            mutex_unlock(&ep->mtx);            mutex_lock(&epmutex);            if (is_file_epoll(tf.file)) {                error = -ELOOP;                //epoll本身也是文件,也可以被poll/select/epoll监视,如果epoll之间互相监视就有可能导致死循环。                //epoll的实现中,所有可能产生递归调用的函数都由函函数 ep_call_nested 进行包裹,                //递归调用过程中出现死循环或递归过深就会打破死循环和递归调用直接返回。                if (ep_loop_check(ep, tf.file) != 0) {                    clear_tfile_check_list();                    goto error_tgt_fput;                }            } else                list_add(&tf.file->f_tfile_llink,                            &tfile_check_list);            mutex_lock_nested(&ep->mtx, 0);            if (is_file_epoll(tf.file)) {                tep = tf.file->private_data;                mutex_lock_nested(&tep->mtx, 1);            }        }    }    /*     * Try to lookup the file inside our RB tree, Since we grabbed "mtx"     * above, we can be sure to be able to use the item looked up by     * ep_find() till we release the mutex.     */    //防止重复添加(在ep的红黑树中查找是否已经存在这个fd)    epi = ep_find(ep, tf.file, fd);    error = -EINVAL;    switch (op) {    //增加监听一个fd    case EPOLL_CTL_ADD:        //没有找到的情况下才添加        if (!epi) {            //默认包含POLLERR和POLLHUP事件            epds.events |= POLLERR | POLLHUP;             //在ep的红黑树中插入这个fd对应的epitm结构体。             //实际上也是调用 ep_ptable_queue_proc ,注册回调函数 ep_poll_callback             //事件发生时,ep_poll_callback被调用,将epitm插入ep的就绪队列rdllist            error = ep_insert(ep, &epds, tf.file, fd, full_check);        } else          //重复添加(在ep的红黑树中查找已经存在这个fd)            error = -EEXIST;        if (full_check)            clear_tfile_check_list();        break;    case EPOLL_CTL_DEL:        if (epi)            error = ep_remove(ep, epi);        else            error = -ENOENT;        break;    case EPOLL_CTL_MOD:        if (epi) {            if (!(epi->event.events & EPOLLEXCLUSIVE)) {                epds.events |= POLLERR | POLLHUP;                error = ep_modify(ep, epi, &epds);            }        } else            error = -ENOENT;        break;    }    if (tep != NULL)        mutex_unlock(&tep->mtx);    mutex_unlock(&ep->mtx);error_tgt_fput:    if (full_check)        mutex_unlock(&epmutex);    fdput(tf);error_fput:    fdput(f);error_return:    return error;}

7 epoll_wait系统调用内核实现
当设备就绪时,会唤醒等待队列上的进程,同时会取出等待队列上的eppoll_entry元素,调用其回调函数,对于epoll来说,回调函数为ep_poll_callback

epoll_wait做的事情就是检查就绪队列是否为空,如果为空就休眠,如果不为空就取出小于等于要求事件对象

/* * Implement the event wait interface for the eventpoll file. It is the kernel * part of the user space epoll_wait(2). */SYSCALL_DEFINE4(epoll_wait, int, epfd, struct epoll_event __user *, events,        int, maxevents, int, timeout){    int error;    struct fd f;    struct eventpoll *ep;    /* The maximum number of event must be greater than zero */    if (maxevents <= 0 || maxevents > EP_MAX_EVENTS)        return -EINVAL;    /* Verify that the area passed by the user is writeable */    //检查用户空间传入的events指向的内存是否可写。参见__range_not_ok()。    if (!access_ok(VERIFY_WRITE, events, maxevents * sizeof(struct epoll_event)))        return -EFAULT;    /* Get the "struct file *" for the eventpoll file */    //获取epfd对应的eventpoll文件的file实例,file结构是在epoll_create中创建。    f = fdget(epfd);    if (!f.file)        return -EBADF;    /*     * We have to check that the file structure underneath the fd     * the user passed to us _is_ an eventpoll file.     */    error = -EINVAL;    //通过检查epfd对应的文件操作是不是eventpoll_fops 来判断epfd是否是一个eventpoll文件。如果不是则返回EINVAL错误。    if (!is_file_epoll(f.file))        goto error_fput;    /*     * At this point it is safe to assume that the "private_data" contains     * our own data structure.     */    //得到eventpoll对象    ep = f.file->private_data;    /* Time to fish for events ... */    //实际功能函数执行    error = ep_poll(ep, events, maxevents, timeout);error_fput:    fdput(f);    return error;}
0 0
原创粉丝点击