EPOLL的内核实现

1. select/poll的缺点

     A. 每次调用时重复的从用户态读入参数

     B. 每次调用时全量的扫描文件描述符

     C. 每次调用开始,将进程加入到每个文件描述符的等待队列,在调用结束后又把进程从等待队列中删除。

     D. 在不修改内核的情况下,select最多支持1024个文件描述符。

     

2. 文件系统中的一些重要结构

在linux中,进程通过file_struct结构与文件关联,而文件通过等待队列与进程关联,进而形成一种多对多的关系。

首先,文件对象struct file_struct

[cpp]  view plain  copy
  1. struct files_struct {    
  2.     atomic_t        count;              //自动增量    
  3.     struct fdtable  *fdt;    
  4.     struct fdtable  fdtab;    
  5.     fd_set      close_on_exec_init;     //执行exec时需要关闭的文件描述符集合  
  6.     fd_set      open_fds_init;          //当前打开文件的文件描述符屏蔽字  
  7.     struct file  *fd_array[NR_OPEN_DEFAULT];//文件对象数组    
  8.     spinlock_t  file_lock;      
  9. };   
该结构在进程的task_struct中使用,用于保存进程当前打开的文件集合。其中struct file称为文件对象,保存文件的信息,这里我们仅列出我们可能会用到的成员

[html]  view plain  copy
  1. struct file{  
  2.      ....  
  3.      struct file_operations *f_op;  
  4.      ...  
  5. }  

在struct file_operations对象中存储对文件对象可以进行各种操作的指针:

[cpp]  view plain  copy
  1. struct file_operations {  
  2.   struct module *owner;  
  3.   loff_t(*llseek) (struct file *, loff_t, int);  
  4.   ssize_t(*read) (struct file *, char __user *, size_t, loff_t *);  
  5.   ssize_t(*aio_read) (struct kiocb *, char __user *, size_t, loff_t);  
  6.   ssize_t(*write) (struct file *, const char __user *, size_t, loff_t *);  
  7.   ssize_t(*aio_write) (struct kiocb *, const char __user *, size_t, loff_t);  
  8.   int (*readdir) (struct file *, void *, filldir_t);  
  9.   unsigned int (*poll) (struct file *, struct poll_table_struct *);  
  10.   int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);  
  11.   int (*mmap) (struct file *, struct vm_area_struct *);  
  12.   int (*open) (struct inode *, struct file *);  
  13.   int (*flush) (struct file *);  
  14.   int (*release) (struct inode *, struct file *);  
  15.   int (*fsync) (struct file *, struct dentry *, int datasync);  
  16.   int (*aio_fsync) (struct kiocb *, int datasync);  
  17.   int (*fasync) (intstruct file *, int);  
  18.   int (*lock) (struct file *, intstruct file_lock *);  
  19.   ssize_t(*readv) (struct file *, const struct iovec *, unsigned long, loff_t *);  
  20.   ssize_t(*writev) (struct file *, const struct iovec *, unsigned long, loff_t *);  
  21.   ssize_t(*sendfile) (struct file *, loff_t *, size_t, read_actor_t, void __user *);  
  22.   ssize_t(*sendpage) (struct file *, struct page *, intsize_t, loff_t *, int);  
  23.   unsigned long (*get_unmapped_area) (struct file *, unsigned long,  
  24.          unsigned long, unsigned long,  
  25.          unsigned long);  
  26. };  


3. epoll模型

epoll自己保存传入的文件描述符,同时通过设备等待队列唤醒时调用“回调函数”实现事件的通知。

epoll模型将select/poll单个的操作拆分:   


[html]  view plain  copy
  1. int epoll_create(int size);     
  2. int epoll_ctl(int epfd, int op, int fd ,struct epoll_event *event);  
  3. int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);  
epoll机制实现了自己特有的文件系统eventpoll filesystem。

epoll_create闯将一个属于该文件系统的文件,并返回其文件描述符。struct eventpoll 保存了epoll文件节点的扩展信息,该结构保存在private_data域中,每个由epoll_create得到的文件描述符都分配了一个该结构体,让我们来看一下struct eventpll的内部结构:

[cpp]  view plain  copy
  1. struct eventpoll {    
  2.     /* 用于维护自身的状态,可用于中断上下文 */    
  3.     spinlock_t lock;    
  4.     /*  
  5.      * 用户进程上下文中  
  6.      */    
  7.     struct mutex mtx;    
  8.     /* 进程等待队列,由 sys_epoll_wait()使用,调用epoll_wait时,休眠在这里 */    
  9.     wait_queue_head_t wq;    
  10.     /* 进程等待队列,由 file->poll()使用 ,epollfd本身被poll时,休眠在这里*/    
  11.     wait_queue_head_t poll_wait;    
  12.     /* 就绪文件描述符链表 */    
  13.     struct list_head rdllist;    
  14.     /* 红黑树头节点,该红黑树用于存储要监控的文件描述符 */    
  15.     struct rb_root rbr;    
  16.     /*  
  17.      * ready事件的临时存放链表  
  18.      */    
  19.     struct epitem *ovflist;    
  20.     /* 创建eventpoll descriptor的用户 */    
  21.     struct user_struct *user;    
  22. };    

epoll_ctl 接口加入该epoll描述符监听的套接字属于socket filesystem,每一个都对应一个epitem结构体,该结构以红黑树的方式存储,eventpoll中的rbr成员指向该红黑树的root节点,而有监听事件到来的套接字结构以双向连表的形式保存,其头结点对应eventpoll中的rdllist成员。

[cpp]  view plain  copy
  1. struct epitem {    
  2.     /*红黑树节点 */    
  3.     struct rb_node rbn;    
  4.     /*就绪描述符链表节点 */    
  5.     struct list_head rdllink;    
  6.     /*  
  7.      * Works together "struct eventpoll"->ovflist in keeping the  
  8.      * single linked chain of items.  
  9.      */    
  10.     struct epitem *next;    
  11.     /* 本结构对应的文件描述符信息 */    
  12.     struct epoll_filefd ffd;    
  13.     /* Number of active wait queue attached to poll operations */    
  14.     int nwait;    
  15.     /* List containing poll wait queues */    
  16.     struct list_head pwqlist;    
  17.     /* The "container" of this item */    
  18.     struct eventpoll *ep;    
  19.     /* List header used to link this item to the "struct file" items list */    
  20.     struct list_head fllink;    
  21.     /* The structure that describe the interested events and the source fd */    
  22.     struct epoll_event event;    
  23. };   

上述结构中fllink是指向文件系统链表的立案表头,struct file 称为文件结构,一般代表一个打开的文件描述符。

而epoll_filefd结构则表明了epitem对应的文件描述符信息:

[cpp]  view plain  copy
  1. struct epoll_filefd {  
  2.    struct file *file;  //文件结构指针  
  3.    int fd;};           //对应的文件描述符  


struct epoll_event event则表明了感兴趣的事件和原始的fd:

[html]  view plain  copy
  1. struct epoll_event  
  2. {  
  3.   uint32_t events;  /*想要监听的事件 */  
  4.   epoll_data_t data;    /* 用户数据变量,可以用来存放一些用户自定义的信息 */  
  5. } __attribute__ ((__packed__));  
  6.   
  7. typedef union epoll_data  
  8. {  
  9.   void *ptr;   //指向自定义数据  
  10.   int fd;  
  11.   uint32_t u32;  
  12.   uint64_t u64;  
  13. } epoll_data_t;  


看完上面两个结构,让我们来看一下epoll_ctl的真身:

[cpp]  view plain  copy
  1. SYSCALL_DEFINE4(epoll_ctl, int, epfd, int, op, int, fd,    
  2.         struct epoll_event __user *, event)    
  3. {    
  4.     int error;    
  5.     struct file *file, *tfile;    
  6.     struct eventpoll *ep;    
  7.     struct epitem *epi;    
  8.     struct epoll_event epds;    
  9.     DNPRINTK(3, (KERN_INFO "[%p] eventpoll: sys_epoll_ctl(%d, %d, %d, %p)/n",    
  10.              current, epfd, op, fd, event));    
  11.     error = -EFAULT;    
  12.     if (ep_op_has_event(op) &&    
  13.         copy_from_user(&epds, event, sizeof(struct epoll_event)))    
  14.         goto error_return;    
  15.     /* Get the "struct file *" for the eventpoll file */    
  16.     error = -EBADF;    
  17.     file = fget(epfd);    
  18.     if (!file)    
  19.         goto error_return;    
  20.     /* Get the "struct file *" for the target file */    
  21.     tfile = fget(fd);    
  22.     if (!tfile)    
  23.         goto error_fput;    
  24.     /* The target file descriptor must support poll */    
  25.     error = -EPERM;    
  26.     if (!tfile->f_op || !tfile->f_op->poll)    
  27.         goto error_tgt_fput;    
  28.     /*  
  29.      * We have to check that the file structure underneath the file descriptor  
  30.      * the user passed to us _is_ an eventpoll file. And also we do not permit  
  31.      * adding an epoll file descriptor inside itself.  
  32.      */    
  33.     error = -EINVAL;    
  34.     if (file == tfile || !is_file_epoll(file))    
  35.         goto error_tgt_fput;    
  36.     /*  
  37.      * At this point it is safe to assume that the "private_data" contains  
  38.      * our own data structure.  
  39.      */    
  40.     ep = file->private_data;    
  41.     mutex_lock(&ep->mtx);    
  42.     /*  
  43.      * Try to lookup the file inside our RB tree, Since we grabbed "mtx"  
  44.      * above, we can be sure to be able to use the item looked up by  
  45.      * ep_find() till we release the mutex.  
  46.      */    
  47.     epi = ep_find(ep, tfile, fd);    
  48.     error = -EINVAL;    
  49.     switch (op) {    
  50.     case EPOLL_CTL_ADD:    
  51.         if (!epi) {    
  52.             epds.events |= POLLERR | POLLHUP;    
  53.             error = ep_insert(ep, &epds, tfile, fd);    
  54.         } else    
  55.             error = -EEXIST;    
  56.         break;    
  57.     case EPOLL_CTL_DEL:    
  58.         if (epi)    
  59.             error = ep_remove(ep, epi);    
  60.         else    
  61.             error = -ENOENT;    
  62.         break;    
  63.     case EPOLL_CTL_MOD:    
  64.         if (epi) {    
  65.             epds.events |= POLLERR | POLLHUP;    
  66.             error = ep_modify(ep, epi, &epds);    
  67.         } else    
  68.             error = -ENOENT;    
  69.         break;    
  70.     }    
  71.     mutex_unlock(&ep->mtx);    
  72. error_tgt_fput:    
  73.     fput(tfile);    
  74. error_fput:    
  75.     fput(file);    
  76. error_return:    
  77.     DNPRINTK(3, (KERN_INFO "[%p] eventpoll: sys_epoll_ctl(%d, %d, %d, %p) = %d/n",    
  78.              current, epfd, op, fd, event, error));    
  79.     return error;    
  80. }  

代码的逻辑很简单,注释也很清楚。上面我们已经介绍了文件对象结构,因此我们这里主要关注剩下的流程,以添加一个监听事件为例:

[cpp]  view plain  copy
  1. static int ep_insert(struct eventpoll *ep, struct epoll_event *event,struct file *tfile, int fd)    
  2. {    
  3.     int error, revents, pwake = 0;    
  4.     unsigned long flags;    
  5.     struct epitem *epi;    
  6.     struct ep_pqueue epq;    
  7.     /* 不允许超过最大监听个数,每个用户均有一个监听上限*/    
  8.     if (unlikely(atomic_read(&ep->user->epoll_watches) >=    
  9.              max_user_watches))    
  10.         return -ENOSPC;    
  11.     if (!(epi = kmem_cache_alloc(epi_cache, GFP_KERNEL)))    
  12.         return -ENOMEM;    
  13.     /* Item initialization follow here ... */    
  14.     INIT_LIST_HEAD(&epi->rdllink);    
  15.     INIT_LIST_HEAD(&epi->fllink);    
  16.     INIT_LIST_HEAD(&epi->pwqlist);    
  17.     epi->ep = ep;    
  18.     ep_set_ffd(&epi->ffd, tfile, fd);    
  19.     epi->event = *event;    
  20.     epi->nwait = 0;    
  21.     epi->next = EP_UNACTIVE_PTR;    
  22.     /* Initialize the poll table using the queue callback */    
  23.     epq.epi = epi;    
  24.     init_poll_funcptr(&epq.pt, ep_ptable_queue_proc);  //注册事件响应的回调函数  
  25.     /*  
  26.      * Attach the item to the poll hooks and get current event bits.  
  27.      * We can safely use the file* here because its usage count has  
  28.      * been increased by the caller of this function. Note that after  
  29.      * this operation completes, the poll callback can start hitting  
  30.      * the new item.  
  31.      */    
  32.     revents = tfile->f_op->poll(tfile, &epq.pt);  //执行poll函数,对于socket来说,执行tcp_poll函数   
  33.     /*  
  34.      * We have to check if something went wrong during the poll wait queue  
  35.      * install process. Namely an allocation for a wait queue failed due  
  36.      * high memory pressure.  
  37.      */    
  38.     error = -ENOMEM;    
  39.     if (epi->nwait < 0)    
  40.         goto error_unregister;    
  41.     /* Add the current item to the list of active epoll hook for this file */    
  42.     spin_lock(&tfile->f_ep_lock);    
  43.     list_add_tail(&epi->fllink, &tfile->f_ep_links);    
  44.     spin_unlock(&tfile->f_ep_lock);    
  45.     /*  
  46.      * Add the current item to the RB tree. All RB tree operations are  
  47.      * protected by "mtx", and ep_insert() is called with "mtx" held.  
  48.      */    
  49.     ep_rbtree_insert(ep, epi);    
  50.     /* We have to drop the new item inside our item list to keep track of it */    
  51.     spin_lock_irqsave(&ep->lock, flags);    
  52.     /* If the file is already "ready" we drop it inside the ready list */    
  53.     if ((revents & event->events) && !ep_is_linked(&epi->rdllink)) {    
  54.         list_add_tail(&epi->rdllink, &ep->rdllist);    
  55.         /* Notify waiting tasks that events are available */    
  56.         if (waitqueue_active(&ep->wq))    
  57.             wake_up_locked(&ep->wq);    
  58.         if (waitqueue_active(&ep->poll_wait))    
  59.             pwake++;    
  60.     }    
  61.     spin_unlock_irqrestore(&ep->lock, flags);    
  62.     atomic_inc(&ep->user->epoll_watches);    
  63.     /* We have to call this outside the lock */    
  64.     if (pwake)    
  65.         ep_poll_safewake(&psw, &ep->poll_wait);    
  66.     DNPRINTK(3, (KERN_INFO "[%p] eventpoll: ep_insert(%p, %p, %d)/n",    
  67.              current, ep, tfile, fd));    
  68.     return 0;    
  69. error_unregister:    
  70.     ep_unregister_pollwait(ep, epi);    
  71.     /*  
  72.      * We need to do this because an event could have been arrived on some  
  73.      * allocated wait queue. Note that we don't care about the ep->ovflist  
  74.      * list, since that is used/cleaned only inside a section bound by "mtx".  
  75.      * And ep_insert() is called with "mtx" held.  
  76.      */    
  77.     spin_lock_irqsave(&ep->lock, flags);    
  78.     if (ep_is_linked(&epi->rdllink))    
  79.         list_del_init(&epi->rdllink);    
  80.     spin_unlock_irqrestore(&ep->lock, flags);    
  81.     kmem_cache_free(epi_cache, epi);    
  82.     return error;    
  83. }    


本函数将epitem对象添加到eventpoll中,实际上是将用户的输入保存到eventpoll文件系统中。init_poll_funcptr函数将ep_ptable_queue_proc函数注册到poll table中,然后程序的下一步是调用tfile的poll函数,并且函数的第二个参数为poll table。ep_ptable_queue_proc函数的主要作用是注册等待函数,并添加到指定的等待队列,第一次调用后此信息已经存在,无需在poll函数中在此调用了。

[cpp]  view plain  copy
  1. static void ep_ptable_queue_proc(struct file *file, wait_queue_head_t *whead,poll_table *pt)    
  2. {    
  3.     struct epitem *epi = ep_item_from_epqueue(pt);    
  4.     struct eppoll_entry *pwq;    
  5.     if (epi->nwait >= 0 && (pwq = kmem_cache_alloc(pwq_cache, GFP_KERNEL))) {    
  6.        /* 为监听套接字注册一个等待回调函数,在唤醒时调用*/    
  7.         init_waitqueue_func_entry(&pwq->wait, ep_poll_callback);    
  8.         pwq->whead = whead;    
  9.         pwq->base = epi;    
  10.         add_wait_queue(whead, &pwq->wait);    
  11.         list_add_tail(&pwq->llink, &epi->pwqlist);    
  12.         epi->nwait++;    
  13.     } else {    
  14.         /* We have to signal that an error occurred */    
  15.         epi->nwait = -1;    
  16.     }    
  17. }    
实际上,如果我们仅是使用epoll则poll函数到底是什么我们是不需要关系的,严格来说,具体poll函数是怎么样的要看通过epoll_ctl传入的套接字类型。对于tcp套接字来说可以按照创建流程找到其对应的函数为tcp_poll, 该函数将进程注册到sock成员的sk_sleep等待队列中,在对应的IO事件中该队列被唤醒,进而调用相应的等待回调函数。
让我们来看一下注册的ep_poll_callback:

[cpp]  view plain  copy
  1. static int ep_poll_callback(wait_queue_t *wait, unsigned mode, int sync, void *key)    
  2. {    
  3.     int pwake = 0;    
  4.     unsigned long flags;    
  5.     struct epitem *epi = ep_item_from_wait(wait);    
  6.     struct eventpoll *ep = epi->ep;    
  7.     DNPRINTK(3, (KERN_INFO "[%p] eventpoll: poll_callback(%p) epi=%p ep=%p/n",    
  8.              current, epi->ffd.file, epi, ep));    
  9.     /* 对eventpoll的spinlock加锁,因为是在中断上下文中*/    
  10.     spin_lock_irqsave(&ep->lock, flags);    
  11.     /* 没有事件到来  
  12.      * If the event mask does not contain any poll(2) event, we consider the  
  13.      * descriptor to be disabled. This condition is likely the effect of the  
  14.      * EPOLLONESHOT bit that disables the descriptor when an event is received,  
  15.      * until the next EPOLL_CTL_MOD will be issued.  
  16.      */    
  17.     if (!(epi->event.events & ~EP_PRIVATE_BITS))    
  18.         goto out_unlock;    
  19.     /*  
  20.      * If we are trasfering events to userspace, we can hold no locks  
  21.      * (because we're accessing user memory, and because of linux f_op->poll()  
  22.      * semantics). All the events that happens during that period of time are  
  23.      * chained in ep->ovflist and requeued later on.  
  24.      */    
  25.     if (unlikely(ep->ovflist != EP_UNACTIVE_PTR)) {    
  26.         if (epi->next == EP_UNACTIVE_PTR) {    
  27.             epi->next = ep->ovflist;    
  28.             ep->ovflist = epi;    
  29.         }    
  30.         goto out_unlock;    
  31.     }    
  32.     /* If this file is already in the ready list we exit soon */    
  33.     if (ep_is_linked(&epi->rdllink))    
  34.         goto is_linked;    
  35.         /* 加入ready queue*/    
  36.     list_add_tail(&epi->rdllink, &ep->rdllist);    
  37. is_linked:    
  38.     /*  
  39.      * Wake up ( if active ) both the eventpoll wait list and the ->poll()  
  40.      * wait list.  
  41.      */    
  42.     if (waitqueue_active(&ep->wq))    
  43.         wake_up_locked(&ep->wq);    
  44.     if (waitqueue_active(&ep->poll_wait))    
  45.         pwake++;    
  46. out_unlock:    
  47.     spin_unlock_irqrestore(&ep->lock, flags);    
  48.     /* We have to call this outside the lock */    
  49.     if (pwake)    
  50.         ep_poll_safewake(&psw, &ep->poll_wait);    
  51.     return 1;    
  52. }    
这里有两个队列,一个是epoll_wait调用中使用的eventpoll等待队列,用于判断是否有监听套接字可用;另一个对应于每个套接字的等待队列sk_sleep,用于判断每个监听套接字上的时间,该队列唤醒后调用ep_poll_callback,在该函数中又调用wakeup函数来唤醒前一种队列,来通知epoll_wait调用进程。

epoll_wait中调用下面的等待函数,一旦其被ep_poll_callback唤醒,则调用ep_send_events把事件复制到用户控件,进而epoll_wait返回。

[cpp]  view plain  copy
  1. static int ep_poll(struct eventpoll *ep, struct epoll_event __user *events,    
  2.            int maxevents, long timeout)    
  3. {    
  4.     int res, eavail;    
  5.     unsigned long flags;    
  6.     long jtimeout;    
  7.     wait_queue_t wait;    
  8.     /*  
  9.      * Calculate the timeout by checking for the "infinite" value ( -1 )  
  10.      * and the overflow condition. The passed timeout is in milliseconds,  
  11.      * that why (t * HZ) / 1000.  
  12.      */    
  13.     jtimeout = (timeout < 0 || timeout >= EP_MAX_MSTIMEO) ?    
  14.         MAX_SCHEDULE_TIMEOUT : (timeout * HZ + 999) / 1000;    
  15. retry:    
  16.     spin_lock_irqsave(&ep->lock, flags);    
  17.     res = 0;    
  18.     if (list_empty(&ep->rdllist)) {    
  19.         /*  
  20.          * We don't have any available event to return to the caller.  
  21.          * We need to sleep here, and we will be wake up by  
  22.          * ep_poll_callback() when events will become available.  
  23.          */    
  24.         init_waitqueue_entry(&wait, current);    
  25.         wait.flags |= WQ_FLAG_EXCLUSIVE;    
  26.         __add_wait_queue(&ep->wq, &wait);    
  27.         for (;;) {    
  28.             /*  
  29.              * We don't want to sleep if the ep_poll_callback() sends us  
  30.              * a wakeup in between. That's why we set the task state  
  31.              * to TASK_INTERRUPTIBLE before doing the checks.  
  32.              */    
  33.             set_current_state(TASK_INTERRUPTIBLE);    
  34.             if (!list_empty(&ep->rdllist) || !jtimeout)    
  35.                 break;    
  36.             if (signal_pending(current)) {    
  37.                 res = -EINTR;    
  38.                 break;    
  39.             }    
  40.             spin_unlock_irqrestore(&ep->lock, flags);    
  41.             jtimeout = schedule_timeout(jtimeout);    
  42.             spin_lock_irqsave(&ep->lock, flags);    
  43.         }    
  44.         __remove_wait_queue(&ep->wq, &wait);    
  45.         set_current_state(TASK_RUNNING);    
  46.     }    
  47.     /* Is it worth to try to dig for events ? */    
  48.     eavail = !list_empty(&ep->rdllist);    
  49.     spin_unlock_irqrestore(&ep->lock, flags);    
  50.     /*  
  51.      * Try to transfer events to user space. In case we get 0 events and  
  52.      * there's still timeout left over, we go trying again in search of  
  53.      * more luck.  
  54.      */    
  55.     if (!res && eavail &&    
  56.         !(res = ep_send_events(ep, events, maxevents)) && jtimeout)    
  57.         goto retry;    
  58.     return res;    
  59. }    

使用epoll的整个流程可以总结如下(copy来的):


EPOLL的内核实现_第1张图片


4. struct sock结构

该结构主要对应于一个socket描述符,通过上面的讲述再根据sock的结构,我们大致可以勾勒出一个事件到来到事件返回的全过程,涉及到Linux网络协议栈的等后面学习了再补充,当前仅贴出该数据结构:

[cpp]  view plain  copy
  1. struct sock {  
  2.         struct sock_common  __sk_common;  
  3. #define sk_family       __sk_common.skc_family  
  4. #define sk_state        __sk_common.skc_state  
  5. #define sk_reuse        __sk_common.skc_reuse  
  6. #define sk_bound_dev_if     __sk_common.skc_bound_dev_if  
  7. #define sk_node         __sk_common.skc_node  
  8. #define sk_bind_node        __sk_common.skc_bind_node  
  9. #define sk_refcnt       __sk_common.skc_refcnt  
  10.         unsigned char       sk_shutdown : 2,  
  11.                             sk_no_check : 2,  
  12.                             sk_userlocks : 4;  
  13.         unsigned char       sk_protocol;  
  14.         unsigned short      sk_type;  
  15.         int         sk_rcvbuf;  
  16.         socket_lock_t       sk_lock;  
  17.         wait_queue_head_t   *sk_sleep;  
  18.         struct dst_entry    *sk_dst_cache;  
  19.         struct xfrm_policy  *sk_policy[2];  
  20.         rwlock_t        sk_dst_lock;  
  21.         atomic_t        sk_rmem_alloc;  
  22.         atomic_t        sk_wmem_alloc;  
  23.         atomic_t        sk_omem_alloc;  
  24.         struct sk_buff_head sk_receive_queue;  
  25.         struct sk_buff_head sk_write_queue;  
  26.         int         sk_wmem_queued;  
  27.         int         sk_forward_alloc;  
  28.         unsigned int        sk_allocation;  
  29.         int         sk_sndbuf;  
  30.         int         sk_route_caps;  
  31.         int         sk_hashent;  
  32.         unsigned long       sk_flags;  
  33.         unsigned long           sk_lingertime;  
  34.   
  35.         struct {  
  36.             struct sk_buff *head;  
  37.             struct sk_buff *tail;  
  38.         } sk_backlog;  
  39.         struct sk_buff_head sk_error_queue;  
  40.         struct proto        *sk_prot;  
  41.         struct proto        *sk_prot_creator;  
  42.         rwlock_t        sk_callback_lock;  
  43.         int         sk_err,  
  44.                     sk_err_soft;  
  45.         unsigned short      sk_ack_backlog;  
  46.         unsigned short      sk_max_ack_backlog;  
  47.         __u32           sk_priority;  
  48.         struct ucred        sk_peercred;  
  49.         int         sk_rcvlowat;  
  50.         long            sk_rcvtimeo;  
  51.         long            sk_sndtimeo;  
  52.         struct sk_filter        *sk_filter;  
  53.         void            *sk_protinfo;  
  54.         struct timer_list   sk_timer;  
  55.         struct timeval      sk_stamp;  
  56.         struct socket       *sk_socket;  
  57.         void            *sk_user_data;  
  58.         struct page     *sk_sndmsg_page;  
  59.         struct sk_buff      *sk_send_head;  
  60.         __u32           sk_sndmsg_off;  
  61.         int         sk_write_pending;  
  62.         void            *sk_security;  
  63.         void            (*sk_state_change)(struct sock *sk);//状态改变时调用    
  64.         void            (*sk_data_ready)(struct sock *sk, int bytes);//有数据可读时调用,即读事件  
  65.         void            (*sk_write_space)(struct sock *sk);//有数据可写时调用,即写事件  
  66.         void            (*sk_error_report)(struct sock *sk);//套接字错误时调用  
  67.         int             (*sk_backlog_rcv)(struct sock *sk,  
  68.                         struct sk_buff *skb);         
  69.         void            (*sk_destruct)(struct sock *sk);//套接字被释放时调用。  
  70.     };  
在该结构的最后有几个回调函数,其主要的作用就是在套接字状态发生变化时执行的回调,见名思意。


5. epoll使用范例

[cpp]  view plain  copy
  1. int epoll_create(int size); //size 表示要监听的文件描述符数量  
  2. int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);   
  3. int epoll_wait(int epfd, struct epoll_event* events, int maxevents, int timeout); // timeout = -1 阻塞; timeout = 0 立即返回  


epoll_event的结构如下:

[cpp]  view plain  copy
  1. typedef union epoll_data {    
  2.     void *ptr;    
  3.     int fd;    
  4.     __uint32_t u32;    
  5.     __uint64_t u64;    
  6. } epoll_data_t;      
  7. struct epoll_event {    
  8.     __uint32_t events; /* Epoll events */    
  9.     epoll_data_t data; /* User data variable */    
  10. };    

events可以是一下几个标志的集合:

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


而epoll_ctl的op参数可以为一下值:

EPOLL_CTL_ADD:注册新的fd到epfd中;
EPOLL_CTL_MOD:修改已经注册的fd的监听事件;
EPOLL_CTL_DEL:从epfd中删除一个fd;


随手附上一个简单的例子,不提供任何保证:

[cpp]  view plain  copy
  1. #define MAX_CONNECTION 1024  
  2. struct self_define_data{  
  3.     int data;  
  4.   
  5. };  
  6.   
  7. int main(int argc, char* argv[]){  
  8.   
  9.     int listen_fd,client_fd,flag;  
  10.   
  11.     struct sockaddr_in server,client;  
  12.     struct epoll_event ee,event_list[20];  
  13.   
  14.       
  15.     listen_fd = socket(AF_INET,SOCK_STREAM,0);  
  16.     /* 
  17.     flag = fcntl(listen_fd,F_GETFL,0); 
  18.     flag |= O_NONBLOCK; 
  19.     if(fcntl(listen_fd,F_SETFL,flag) < 0){ 
  20.         perror("set non_block failed"); 
  21.         return -1; 
  22.     } 
  23.     */  
  24.     ioctl(listen_fd,FIONBIO,&n);  
  25.       
  26.     bzero(&server,sizeof(struct sockaddr_in));  
  27.     server.sin_family = AF_INET;  
  28.     inet_aton("127.0.0.1",&server.sin_addr);  
  29.     server.sin_port = htons(80);  
  30.     bind(listen_fd, (struct sockaddr*)&server, sizeof(struct sockaddr));  
  31.     listen(listen_fd,5);  
  32.   
  33.   
  34.   
  35.   
  36.     int ep = epoll_create(MAX_CONNECTION); // int cycle->connection  
  37.     if(-1 == ep){  
  38.         perror("epoll_create failed.");  
  39.         return -1;  
  40.     }  
  41.       
  42.     struct self_define_data *self;  
  43.     self->data = 10;  
  44.   
  45.     ee.events = EPOLLIN|EPOLLOUT|EPOLLET;  
  46.     ee.data.ptr = (void*)self;  
  47.     ee.data.fd = listen_fd;   
  48.     epoll_ctl(ep,EPOLL_CTL_ADD,listen_fd, &ee);  
  49.   
  50.     while(1){  
  51.           
  52.         int num = epoll_wait(ep,event_list,20,-1);  
  53.         for(int i = 0; i < num; i++){  
  54.             struct sef_define_data s = event_list[i].data.ptr;  
  55.             if(10 == s.data){  
  56.                 uint32_t revent = event_list[i].events;//revent中包含返回的事件如EPOLLIN  
  57.                 if(event_list[i].data.fd == listen_fd){  
  58.                     client_fd = accept(listen_fd,(struct sockaddr*)&client,sizeof(struct sockaddr));  
  59.                     //do someting  
  60.                     if(client_fd < 0)  
  61.                         break;  
  62.                     close(client_fd);  
  63.                 }  
  64.   
  65.             }     
  66.         }  
  67.   
  68.     }  
  69.   
  70.     if(-1 == close(ep)){  
  71.         perror("epoll_close failed.");  
  72.         return -1;  
  73.     }  
  74.   
  75.   
  76.     return 0;  
  77. }  

你可能感兴趣的:(网络通信)