在服务器的编程中经常会需要构造高性能的 I/O 模型,最常用的就是多路复用 I/O 模型。
多路复用的本质是同步非阻塞 I/O,多路复用的优势并不是单个连接处理的更快,而是在于能处理更多的链接。
在服务端的网络 I/O 编程过程中,需要同时处理多个客户端的数据时,可以利用多线程或者 I/O 多路复用技术进行处理。
在《I/O模型浅析》中已经简单介绍过多路复用I/O,接下来介绍 select、poll 和 epoll。
这三者的源码在 Linux kernel 源码中,想要查看源码需要下载,两种下载方式:
- 官方链接: https://www.kernel.org/
- 如果不能科学上网,下载速度将会很慢,可以使用上海交大的源下载:
http://ftp.sjtu.edu.cn/sites/ftp.kernel.org/pub/linux/kernel/
因为我使用的系统是 CentOS release 6.8 (Final)
,其中 linux 内核版本为 2.6.32
,所以我下载了 linux-2.6.32.9.tar.gz
。
其它版本可自行选择下载。
select介绍
select相关函数
通过 man select
命令可以查看 select 的函数原型如下:
1 | // 需要包含的头文件 |
具体参数详解:
- nfds: 整数值,指集合中所有文件描述符的范围,即所有文件描述符的最大值加1。
- readfds: 指向 fd_set 结构的指针,文件描述符集合,检查该组文件描述符的可读性。
- writefds: 指向 fd_set 结构的指针,文件描述符集合,检查该组文件描述符的可写性。
- exceptfds: 指向 fd_set 结构的指针,文件描述符集合,检查该组文件描述符的异常条件。
- timeout: 设定 select 的超时时间,其中:
- 值为NULL,则将 select() 设置为阻塞状态,当监视的文件描述符集合中的某一个描述符发生变化才会返回结果并向下执行。
- 值等于0,则将 select() 置为非阻塞状态,执行 select() 后立即返回,无论文件描述符是否发生变化。
- 值大于0,则将select()函数的超时时间设为这个值,在超时时间内阻塞,超时后返回结果。
函数返回:
- 正数:表示发生变化的文件描述符数量。
- 0:select 超时。
- -1:发生错误,将所有文件描述符集合清0,并通过 errno 输出错误详情。
以下是与 select() 函数相关的几个宏:
1 |
|
select使用
使用流程图如下:
实现简单的服务代码:
1 |
|
select实现原理
select 的源码在 fs/select.c
文件中。
Linux 中的系统函数调用入口都是由宏 SYSCALL_DEFINEx
定义的,其中 x
为函数参数个数。在 select.c
中可以找到 select() 的系统调用宏定义:
1 | SYSCALL_DEFINE5(select, int, n, fd_set __user *, inp, fd_set __user *, outp, |
主要处理函数在 core_sys_select() 中:
1 | int core_sys_select(int n, fd_set __user *inp, fd_set __user *outp, |
接下来是主角 do_select()
登场:
1 | int do_select(int n, fd_set_bits *fds, struct timespec *end_time) |
select 使用 bitmap 的方式来传递文件描述符集合,所以就会有最大长度限制,在 Linux 平台下 select 限制文件描述符只能有 1024
个,如果需要超过 1024,就需要修改内核代码并重新编译。
select 使用 bitmap 的方式来回传就绪的文件描述符集合,调用者需要循环遍历每一个位判断是否就绪。当文件描述符很多,但是空闲的文件描述符大大多于就绪的文件描述符的时候,效率就很低了,所以一般不建议修改文件描述符的限制数量。
在 include/linux/posix_types.h
中可以看到宏定义:
1 |