kqueue/epoll 是两个网上出现频率比较高的关键字,epoll实现原理及源码网上已经有很多blog分析,关于
select/poll/epoll、kqueque的优缺点也不再解释。kqueue实现原理的文章网上资料比较少, 基本上就
Jonathan Lemon的一篇论文, Jonathan Lemon也是Kqueue的发明者。文章链接:
Kqueue: A generic and scalable event notification facility
本文主要说明一下其中的kqueue的实现部分,从网络收发部分看kqueue和epoll的使用方式和实现原理都
比较类似.这篇文章介绍了kqueue的使用方法:使用 kqueue 在 FreeBSD 上开发高性能应用服务器.
kqueue的是主要实现逻辑在kern_event.c 里面(freebsd 4.1)
在kqueue实现中,比较关键的是一个knote结构体,该结构体在内核空间对应于应用层的kevent结构体.
knote将事件源(被监控节点tcp/socket), 事件源是否有事件发生和knote所在的kqueue联系起来. knote
之间也有联系这个后面具体分析.另一个比较关键的数据结构是kqueue自己,包含两个功能:1)包含一个有
事件发生需要通知应用层的knotes队列,也就是已完成事件队列. 2) 保存并跟踪应用层注册的的需要监听
的事件和描述符.kqueue有三个子结构体来实现上面的功能:
--1. 一个队列,用来保存active的knotes节点
--2. 一个小hashtable 用来查找那些没有对应描述符的knotes节点
--3, 一个线性的描述符array,这个array和进程打开的文件描述符表一致
上述的hashtable 和线性数组都是延迟分配的, 存放描述符array可以自动扩展, kqueue必须记录这些所
有用户注册的knotes, 当kqueue被close,属于它的knotes都会被释放. 描述符array可以保证当用用户关闭一
个描述符如(socket fd)时, kqueue对应的knotes 节点会别释放.
事件注册:
最初, 应用程序调用kqueue() 来分配一个新的kqueue(一下简称KQ), 涉及到了分配一个新的kqueue描
述符、kqueue结构体、和一个指向已打开文件描述符table的指针, 这个时候并没有给这个给array和
hashtable分配空间.应用然后调用kevent()传递一个changelist指针(参见kevent使用说明),changelist
中的kevents从用户空间copy到内核空间, 然后对每一个kevents调用register(). register()先在KQ中
查找是否有匹配的knotes, 如果过没有,表明第一次添加,分配一个新的knotes(有EV_ADD标记).根据传递来
的kevent信息对新建的knotes进行初始化,并调用attacth()将knote连接到事件源(如tcp收包).这个连接操
作是通过一个叫filter(不明白为啥叫filter)的attach函数. 之后将knote添加到KQ的hashtable或array
中.如果在处理changelist发生错误, 发生错误的kevent则会copy到evenlist返回给应用层.当changelist
的所有事件都处理成功后 kqueue_scan()才会开始检查是否有active的事件.
Filters:
每一个filter都包含三个函数{attach, detach, filter},filter根据事件源类型决定(如tcp/udp/file分别有自己的filter)
--attach() : 将knote加入到要监听的事件源中.
--deattch(): 将knote从某一事件源中取消.
--filter(): 当事件源(TCP)有事件发生时,如