sunzixun 发表于 2013-1-29 22:35:09

<Linux Kernel>eventepoll

首先介绍一下Epoll 主要需要3个级别的锁
<div class="Section0"> 
 * 1) epmutex   互斥锁mutex
 * 2) ep->mtx   互斥锁mutex
 * 3) ep->lock   自旋锁spinlock
 
需要的顺序是从1-3 . 
 
1 ) 需要自旋锁 : 是因为我们掌管着 poll 回调方法内部的资源 , 这些资源的触发可能发生在中断上下文 的wake_up 中 所以呢我们不能再poll 回调方法里面睡眠 因此自旋吧~
 
 
2 ) 在事件传输的循环中(内核空间--> 用户空间) 我们可以中断睡眠由通过一个 copy_to_user, 所以我们需要一个允许我们睡眠的锁 ,这就是mutex啦 
 
3 ) 他在事件传输的循环中被需要, 在epoll_ctl(EPOLL_CTL_DEL) and during eventpoll_release_file()
 
然后呢,我们需要一个全局的mutex锁 ,来序列化 eventpoll_release_file()
 
 
举个例子 比如: ep_free().
ep_free() 需要这个锁 在epoll  file 清除的过程中 , 同样 eventpoll_release_file()也需要 : 比如以下情况
如果一个file 已经被压入 epoll 的集合里面 然后直接close 却没有通过poll_ctl(EPOLL_CTL_DEL).
 
 
 
最后放弃 ep->mux 而使用全局的 epmutex(和ep->lock 一起) 工作 也是可能的
 
但是 使用 ep->mtx 将使接口具有可扩展性 , 需要持有epmutex 的事件往往很稀少 , 然而 
对于一个一个普通的操作  ep->mutex 将保证良好的扩展性
 
 
 
 
 
* Maximum number of nesting allowed inside epoll sets */\Epoll 集合允许的最大嵌套数#define EP_MAX_NESTS 4Epoll 监听的最多事件 (庞大到可以忽视)#define EP_MAX_EVENTS (INT_MAX / sizeof(struct epoll_event))  
 
 
 
 
我打算从系统调用入口开始分析 
 
/**这里的 SYSCALL_DEFINEx(name ,__VA_ARGS__)其实就是通过一系列的宏变化 *最后变成asmlinkage long sys##name(__SC_DECL##x(__VA_ARGS__));*这样是不是很眼熟了, 没错"sys_xxx" linux Syscalls.h系统调用函数名的标准格式 直接*对应unistd,h 中的调用号 , 直接对于 syscall(xx,xx) .. 不解释..*/SYSCALL_DEFINE1(epoll_create1, int, flags){int error;struct eventpoll *ep = NULL;/*这里通过一个小技巧 ,能让编译的时候就发现代码开发过程中潜在的问题,而不是坐等*crash ,具体使用有各种不同的校验, *比如#define BUILD_BUG_ON(condition) ((void)sizeof(char))如果条件*满足就直接char[-1] 哈哈*/BUILD_BUG_ON(EPOLL_CLOEXEC != O_CLOEXEC);/*如果没有设置 EPOLL_CLOEXEC)就直接返回 -EINVAL给上层 */if (flags & ~EPOLL_CLOEXEC)return -EINVAL;/* *创建一个内部用的数据结构 struct eventpoll*/error = ep_alloc(&ep);if (error < 0)return error;/*开始前的准备 建立一个struct file 和filefd */error = anon_inode_getfd("", &eventpoll_fops, ep, O_RDWR | (flags & O_CLOEXEC));if (error < 0)ep_free(ep);return error;}  
 
 
 
然后就开始看看上面2个没讲到的函数 和一个重要的数据结构
 
 
/*还记得struct file里面那个神奇的 private_data么, 现在它就用来指向eventpoll 不用我说就知道, 这个结构是整个epoll操作的外部接口*/struct eventpoll {/* Protect the this structure access 自旋锁保护这个内部数据结构*/spinlock_t lock;/* *这个互斥锁用来确保 file fd 在epoll 使用他们的时候不能被移出, 在 *1 event 集合循环的时候 2 file fd清除过程中 3 epoll fd 退出和epoll_ctl操作 */struct mutex mtx;/* sys_epoll_wait() 用的等待队列 */wait_queue_head_t wq;/* file->poll() [驱动层接口] 用的等待队列*/wait_queue_head_t poll_wait;/* List of ready file descriptors 已经准备好的file fd链表 */struct list_head rdllist;/* RB tree root used to store monitored fd structs*用来储存所有file fd 的红黑树的根*/struct rb_root rbr;/* * This is a single linked list that chains all the "struct epitem" that * happened while transfering ready events to userspace w/out * holding ->lock. 这是一个伟大复杂的结构。。。 形成一个包含 struct epitem结构的单链表 , 在传输就绪事件到用户空间用到 */struct epitem *ovflist;/* The user that created the eventpoll descriptor   *存放了建立这个 epoll fd用户的信息(进程数 挂起信号等等 总有一天会是成熟的跟踪系统) <看了很多代码不得不佩服老外,他们在正常的开发中 ,往往能为以后的调试 统计 分析留下接口,这才是做系统的人,而我们国内公司很多功能完成后 想增加点外围接口想扩展一下,东改西改本来性能就不高的代码 最后惨不忍睹>*/struct user_struct *user;};/*然后就来先简单说一下 struct epitem *ovflist; 这个核心的和存储位置有关的数据结构*/struct epitem {/* RB tree node used to link this structure to the eventpoll RB tree   *在 eventpoll 红黑树种的节点*/struct rb_node rbn;/* List header used to link this structure to the eventpoll ready list      *用来连接本结构体到 eventpoll 中的就绪链表 ,没错就是上面的rdllist */struct list_head rdllink;/* * Works together "struct eventpoll"->ovflist in keeping the * single linked chain of items. 构成上面说的 ovflist单链表 */struct epitem *next;/* The file descriptor information this item refers to 除了来本身的file fd 还指向了对应的内核file 结构* 后面会看到这个结构很有用*/struct epoll_filefd ffd;/*   依附于 file()->poll 操作的活跃 eppoll_entry数目*/int nwait;/* List containing poll wait queues 把上面的队列串成链表*/struct list_head pwqlist;/* The "container" of this item互相保留引用 */struct eventpoll *ep;/* List header used to link this item to the "struct file" items list连接自身到struct file链表*/struct list_head fllink;/* The structure that describe the interested events and the source fd 描述这个file fd感兴趣的事件 */struct epoll_event event;};  
 
 
好了先说简单说了说几个主要结构, 下面就来看  ep_alloc 用来创建内部用的数据结构 struct eventpoll 
 
 
static int ep_alloc(struct eventpoll **pep){int error;struct user_struct *user;struct eventpoll *ep;/*给当前 task_struct -> cred -> struct user_struct 增加引用计数*/user = get_current_user();error = -ENOMEM;    /*内核缓冲池中 分配一个ep结果*/ep = kzalloc(sizeof(*ep), GFP_KERNEL);    /*当然一般情况下不会失败*/if (unlikely(!ep))goto free_uid;    /*初始化 自旋锁 互斥锁 2条等待队列 一个就绪链表*/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);/*当然是根了*/ep->rbr = RB_ROOT;    /*表明没激活 但以初始化 惯用伎俩*/ep->ovflist = EP_UNACTIVE_PTR;ep->user = user;    /*成功返回吧 ~*/*pep = ep;return 0;free_uid:free_uid(user); /*当然是减少引用基数 (原子的)*/eturn error;}  
 
 
 
 
 
 
 
下面就是epoll_create里的最后一个关键函数了 anno_inode_getfd


这里传入了4个参数,最主要是


2 fops 新file fd 对应的文件操作结构
3 priv : struct  file 结构中的 private_data
 
其实 这里返回的不是一个传统意义上的inode节点 自然没有那么成熟和强大的功能
所有通过 annon_inode_getfd 创建的 file fd  都共享一个简单的inode 节点
这样就可以节省内核的内存 , 避免代码 在 file indoe 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;/*只要知道这里创建了一个fild fd 依附上了一个匿名的static inode 节点 就可以了*/file = anon_inode_getfile(name, fops, priv, flags);if (IS_ERR(file)) {error = PTR_ERR(file);goto err_put_unused_fd;}/*这里用了rcu 锁 就为了把file结构安装在fdtable表上 */fd_install(fd, file);return fd;err_put_unused_fd:put_unused_fd(fd);return error;} 
 
 
 
好了 相信大家看了上面的有点晕,  其实很明显, 因为 epoll_create的 描述符 是epoll自己用的, 并没有任何实体对应 ,所以呢 就用了anon_inode_getfd ,所以大家会发现在 proc fd里面会看到一个 


 annon_inode:  是不是和上面的入参  很像呢, 哈哈   proc 文件系统就是这个德性
 
里面的目录阿 ,名字啊  都是跟着内核层次 名字来了 你会越看越熟悉.... (是不是有点废话) 
 
 
 
待续..... 
 
 
 
页: [1]
查看完整版本: <Linux Kernel>eventepoll