摘要
在学习 Redis 的过程中,发现 Redis 底层是复用了现成的 I/O多路复用模型(evport, epoll, kqueue, select),本篇博客就总结一下 Linux 内核中提供的三种模型。
select
fd_set
select()函数主要是建立在fd_set类型的基础上的。fd_set 是一组文件描述字(fd)的集合,它用一位来表示一个fd(下面会仔细介绍),对于fd_set类型通过下面四个宏来操作:
void FD_CLR(int fd, fd_set *set) //置 fd_set 中 fd 对应的位为 0 int FD_ISSET(int fd, fd_set *set) //判断 fd 在 fd_set 对应的位是否为 1 void FD_SET(int fd, fd_set *set) //将 fd_set 中 fd 对应的位置 1 void FD_ZERO(fd_set *set) //将 fd_set 中的所有位全部置 0
首先先看一下 fd_set 的结构体:
// from linux-3.10, win 下定义可能不同,一般为 4096(FD_SETSIZE = 64, long long int 类型数组,数组大小为 64, 所以 64 * 64 = 4096) // posix_types.h #undef __FD_SETSIZE #define __FD_SETSIZE 1024 typedef struct { unsigned long fds_bits[__FD_SETSIZE / (8 * sizeof(long))]; } __kernel_fd_set;
可以看出 fd_set 是一个结构体,该结构体包含了一个 unsigned long 类型的一个数组,数组大小为:1024/(8 * sizeof(long)), 在我的系统上(Ubuntu 16.04),sizeof(long) == sizeof(unsigned long) = 8。所以数组的大小为:1024/(8 * 8) = 16; 由于 fd_set 是以位来标识一个 fd 的,所以我们来计算这个数组表示的位数,数组中的每一个元素都是 long 类型,数组元素个数为 16, 所以得:bits = 8 * 8 * 16 = 1024(bit), 故可以标识 1024 个 fd。
可以通过下面的代码在自己的 Linux 系统上跑一下,参考代码:
#include <sys/time.h> #include <sys/time.h> #include <sys/types.h> #include <unistd.h> #include <stdio.h> int main() { printf("fd_set size = %d\n", sizeof(fd_set)); printf("long type size: %d\n", sizeof(long)); printf("unsigned long type size: %d\n", sizeof(unsigned long)); return 0; }