macOS/iOS中的I/O多路复用方案kqueue
macOS/iOS中的I/O多路复用方案kqueue
XNU内核
Mach+BSD内核
尽管XUN的绝对核心是Mach,但是XUN向用户态提供的主要接口是BSD接口。BSD层是建立在Mach之上,是XNU中一个不可分割的一部分。BSD负责提供可靠的、现代的API。其内容包括:
UNIX进程模型
POSIX线程模型及相关的同步原语
UNIX用户和组
网络协议栈
文件访问系统
设备访问
kqueue是BSD中使用的内核事件通知机制,一个kqueue指的是一个描述符,这个描述符会阻塞等待直到一个特定的类型和种类的事件发生。用户态或者内核的进程可以等待这个描述符,因而kqueue提供了一种用于一个或多个进程同步的简单且高效的方法。kqueue和kevent(表示事件的数据结构)构成了内核异步I/O的基础。 kqueue 是 FreeBSD 上的一种的多路复用机制。它是针对传统的 select/poll 处理大量的文件描述符性能较低效而开发出来的。注册一堆描述符到 kqueue 以后,当其中的描述符状态发生变化时, kqueue 将一次性通知应用程序哪些描述符可读、可写或出错了。 kqueue 支持多种类型的文件描述符,包括 socket 、信号、定时器、 AIO 、 VNODE 、 PIPE; kqueue 对 AIO , POSIX 的异步 IO系列的支持,是异步行为完成通知机制之一。
常见的io复用有select、poll、epoll、kqueue等。其中epoll为Linux系统独有,kqueue则在众多unix系统中存在。在go语言中kqueue相关代码位于syscall库中。
event_t
是Kevent()
操作的最基本的事件结构。kqueue
是freebsd
内核中的一个事件队列kernel queue
。Kevent()
是一个系统调用,也是kqueue
的用户界面,是对kqueue
进行添加,删除操作的用户态的界面。
type Kevent_t struct {
Ident uint64 //该事件关联的描述符,常见的有socket fd,file fd, signal fd等
Filter int16 //事件的类型,比如读事件EVFILT_READ,写事件EVFILT_WRITE,信号事件EVFILT_SIGNAL
Flags uint16 //事件的行为,也就是对kqueue的操作,下面介绍几个常用的
//如EV_ADD:添加到kqueue中,EV_DELETE从kqueue中删除
//EV_ONESHOT:一次性或事件,kevent返回后从kqueue中删除
//EV_CLEAR:事件通知给用户后,事件的状态会重置,
Fflags uint32
Data int64
Udata *byte //用户指定的数据
}
func Kevent(kq int, //Kqueue返回的唯一参数值,标记着一个内核队列
changes, //需要对kqueue进行修改的事件集合,kqueue通过此参数完成对事件的修改
events []Kevent_t, //返回的已经就绪的事件列表
timeout *Timespec) //超时控制,不指定事件表示一直等待事件发生,否则只等待一段时间
(n int, err error) { //返回已经就绪的事件数量
...
...
}
三种现在高效的I/O模型
最常见的I/O复用模型 select select先阻塞,有活动套接字才返回。与阻塞I/O相比,select会有两次系统调用,但是select能处理多个套接字。
信号驱动 signal driven I/O (SIGIO) 只有UNIX系统支持,与I/O multiplexing (select and poll)相比,它的优势是,免去了select的阻塞与轮询,当有活跃套接字时,由注册的handler处理。
完全异步的I/O asynchronous I/O (the POSIX aio_functions) 很少有*nix系统支持,windows的IOCP则是此模型,完全异步的I/O复用机制,因为纵观上面其它四种模型,至少都会在由kernel copy data to appliction时阻塞。而该模型是当copy完成后才通知application,可见是纯异步的。好像只有windows的完成端口是这个模型,效率也很出色。
几种模型的比较 从图中可以看出,越往后,阻塞越少,理论上效率也是最优。
epoll,kqueue比select高级,主要是因为他们无轮询,因为他们用callback取代了。 当套接字比较多的时候,每次select()都要通过遍历FD_SETSIZE个Socket来完成调度,不管哪个Socket是活跃的,都遍历一遍。这会浪费很多CPU时间。如果能给套接字注册某个回调函数,当他们活跃时,自动完成相关操作,那就避免了轮询,这正是epoll与kqueue做的。
- 只有IOCP是asynchronous I/O,其他机制或多或少都会有一点阻塞。
- select低效是因为每次它都需要轮询。但低效也是相对的,视情况而定,也可通过良好的设计改善
- epoll, kqueue是Reacor模式,IOCP是Proactor模式。
- java nio包是select模型。