[NIO]linux的I/O多路复用
前言 上篇提到i/o多路复用,是通过单进程监听多个文件描述的状态,达到减少线程阻塞的目的。 内核(kernel)利用文件描述符(file descriptor)来访问文件。 文件描述符是非负整数。 打开现存文件或新建文件时(包括socket被打开),内核会返回一个文件描述符。 读写文件也需要使用文件描述符来指定待读写的文件。在linux环境下,进入/proc目录可以看到许多代表文件描述符的文件夹。 linux i/o多路复用的系统调用接口有三种,分别是 select,poll,epoll。 接口 作为一个学java的,了解一下java底层调用的函数,还是挺有助于理解的。 i/o多路复用原理 linux(2.6+)内核的事件wakeup callback机制,是linux i/o多路复用的原理。内核管理一个process的睡眠队列,当socket事件发生的时候,唤醒队列的process,调用callback函数完成通知。总体上会涉及两大逻辑:(1)睡眠等待逻辑;(2)唤醒逻辑。 1.睡眠等待逻辑:涉及select、poll、epoll_wait的阻塞等待逻辑 select、poll、epoll_wait陷入内核,判断监控的socket是否有关心的事件发生了,如果没,则为当前process构建一个wait_entry节点,然后插入到监控socket的sleep_list 进入循环的schedule直到关心的事件发生了 关心的事件发生后,将当前process的wait_entry节点从socket的sleep_list中删除。 2.唤醒逻辑。 socket的事件发生了,然后socket顺序遍历其睡眠队列,依次调用每个wait_entry节点的callback函数 直到完成队列的遍历或遇到某个wait_entry节点是排他的才停止。 一般情况下callback包含两个逻辑:1.wait_entry自定义的私有逻辑;2.唤醒的公共逻辑,主要用于将该wait_entry的process放入CPU的就绪队列,让CPU随后可以调度其执行。 select #include <sys/select.h> #include <sys/time.h> int select(int max_fd, fd_set *readset, fd_set *writeset, fd_set *exceptset, struct timeval *timeout) FD_ZERO(int fd, fd_set* fds) //清空集合 FD_SET(int fd, fd_set* fds) //将给定的描述符加入集合 FD_ISSET(int fd, fd_set* fds) //将给定的描述符从文件中删除 FD_CLR(int fd, fd_set* fds) //判断指定描述符是否在集合中 select 方法的第一个参数max_fd指待测试的fd(fd即文件描述符,一个socket会有一个文件描述符)个数,它的值是待测试的最大文件描述符加1,文件描述符从0开始到max_fd-1都将被测试。中间三个参数readset、writeset和exceptset指定要让内核测试读、写和异常条件的fd集合,如果不需要测试的可以设置为NULL。 select被调用的时候,被监控的readset(假设对socket的读事件感兴趣)会从用户空间复制到内核空间,然后遍历监听的socket,如果在超时或者有一个或多个socket产生了读事件,那么select唤醒线程,注意这里只是唤醒,并没有返回就绪的fd,接下来线程要再次遍历readset,收集可读事件。 select的问题是: 监听的socket数量有限,为了减少fd拷贝的性能损耗,限定了1024个文件描述符 线程被唤醒的时候,需要再次遍历fd列表。 poll #include <poll.h> int poll(struct pollfd fds[], nfds_t nfds, int timeout); typedef struct pollfd { int fd; // 需要被检测或选择的文件描述符 short events; // 对文件描述符fd上感兴趣的事件 short revents; // 文件描述符fd上当前实际发生的事件*/ } pollfd_t; poll换了个数据结构,解决了select其中一个问题:监听的数量有限。但实际上并有解决拷贝的性能损耗和需要再次遍历fd列表获取就绪事件。...