在使用select时我们必定会使用到fd_set,那么fd_set究竟是什么呢?
一、fd_set的相关介绍
在网络编程中,经常用到selec系统调用来判断套接字上是否存在数据可读,或者能否向一个套接字写入数据。其原型为:
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
其中,fd_set是一个socket集合,常用如下宏来对fd_set进行操作:
FD_CLR( s, *set) //从set中删除句柄s;
FD_ISSET( s, *set) //检查句柄s是否存在与set中;
FD_SET( s, *set ) //把句柄s添加到set中;
FD_ZERO( *set ) //把set队列初始为空.
试想由你实现这样一个集合,可以往里添加任意0~1024之间的数(FD_SET操作),也可以将加入到集合中的数移除——移除一个(FD_CLR操作)或全部(FD_ZERO),你会如何实现?
一种比较好的思路是使用位图bitmap,往集合了添加n时只需将第n个bit位置1,移除n时只需将第n个比特置为0,移除所有数据时,只需将所有bit置为0,可以通过memset操作来实现。fd_set的实现就是采用位图bitmap(关于位图可以参考《编程珠玑》第一章)。
其定义如下:
#define __NFDBITS (8 * sizeof(unsigned long)) //每个ulong型可以表示多少个bit,
#define __FD_SETSIZE 1024 //socket最大取值为1024
#define __FDSET_LONGS (__FD_SETSIZE/__NFDBITS) //bitmap一共有1024个bit,共需要多少个ulong
typedef struct {
unsigned long fds_bits [__FDSET_LONGS]; //用ulong数组来表示bitmap
} __kernel_fd_set;
typedef __kernel_fd_set fd_set;
对应的操作如下:
/*
每个ulong为32位,可以表示32个bit。
fd >> 5 即 fd / 32,找到对应的ulong下标i;fd & 31 即fd % 32,找到在ulong[i]内部的位置
*/
/*设置对应的bit*/
#define __FD_SET(fd, fdsetp) (((fd_set *)(fdsetp))->fds_bits[(fd) >> 5] |= (1<<((fd) & 31)))
/*清除对应的bit*/
#define __FD_CLR(fd, fdsetp) (((fd_set *)(fdsetp))->fds_bits[(fd) >> 5] &= ~(1<<((fd) & 31)))
/*判断对应的bit是否为1*/
#define __FD_ISSET(fd, fdsetp) ((((fd_set *)(fdsetp))->fds_bits[(fd) >> 5] & (1<<((fd) & 31))) != 0)
/*memset bitmap*/
#define __FD_ZERO(fdsetp) (memset (fdsetp, 0, sizeof (*(fd_set *)(fdsetp))))
二、fd_set在内核中的使用
在文章select与poll的内核实现简述中我们说到select函数最终会遍历每个文件描述符,调用每个设备的poll函数。但是具体是如何遍历的,在这里我们将会详细说明。
在core_sys_select
函数中,申请了一段连续地址的内存,并将这块内存进行了6平分,分别对应文件描述符的in、out、err,以及返回时所用的res_in、res_out和err。
/*n是最大文件描述符,来计算我们需要多大的内存*/
size = FDS_BYTES(n);
/*stack_fds是内核中所准备的一块连续的内存,以long为单位而不是以字节为单位*/
bits = stack_fds;
/*如果需要的内存大于内核准备的栈,则重新申请内存。除以6是因为有6个描述符集合*/
if (size > sizeof(stack_fds) / 6) {
/* Not enough space in on-stack array; must use kmalloc */
ret = -ENOMEM;
bits = kmalloc(6 * size, GFP_KERNEL);
if (!bits)
goto out_nofds;
}
/*申请了连续的内存后,平分到6个映射*/
fds.in = bits;
fds.out = bits + size;
fds.ex = bits + 2*size;
fds.res_in = bits + 3*size;
fds.res_out = bits + 4*size;
fds.res_ex = bits + 5*size;
/*将文件描述符从用户空间拷贝到内核空间*/
if ((ret = get_fd_set(n, inp, fds.in)) ||
(ret = get_fd_set(n, outp, fds.out)) ||
(ret = get_fd_set(n, exp, fds.ex)))
goto out;
/*清空输出使用的文件描述符集合*/
zero_fd_set(n, fds.res_in);
zero_fd_set(n, fds.res_out);
zero_fd_set(n, fds.res_ex);
/*调用do_select函数对描述符集进行遍历*/
ret = do_select(n, &fds, end_time);
在do_select
中对每个描述符进行了遍历。
/*声明每个文件描述符集的地址指针*/
unsigned long *rinp, *routp, *rexp, *inp, *outp, *exp;
/*对每个地址指针赋首地址*/
inp = fds->in; outp = fds->out; exp = fds->ex;
rinp = fds->res_in; routp = fds->res_out; rexp = fds->res_ex;
/*开始进行遍历,这里遍历的步长是long大小
而且是in、out和err一起进行遍历
*/
for (i = 0; i < n; ++rinp, ++routp, ++rexp) {
unsigned long in, out, ex, all_bits, bit = 1, mask, j;
unsigned long res_in = 0, res_out = 0, res_ex = 0;
/*取出in、out、err地址上的值,是long类型的*/
in = *inp++; out = *outp++; ex = *exp++;
/*进行相或,只要不为0就说明这块地址的描述符需要检查*/
all_bits = in | out | ex;
/*如果为0了,说明long长度的位图都是空的那就继续循环*/
if (all_bits == 0) {
i += BITS_PER_LONG;
continue;
}
/*在long长度的地址中进行细致检查,每个位进行查看*/
for (j = 0; j < BITS_PER_LONG; ++j, ++i, bit <<= 1) {
struct fd f;
if (i >= n)
break;
if (!(bit & all_bits))
continue;
/*取出文件描述符*/
f = fdget(i);
if (f.file) {
const struct file_operations *f_op;
f_op = f.file->f_op;
mask = DEFAULT_POLLMASK;
if (f_op->poll) {
wait_key_set(wait, in, out,
bit, busy_flag);
/*通过poll函数获得设备状态掩码*/
mask = (*f_op->poll)(f.file, wait);
}
fdput(f);
/*是否是in集合的,并且状态发生变化*/
if ((mask & POLLIN_SET) && (in & bit)) {
res_in |= bit;
retval++;
wait->_qproc = NULL;
}
/*是否是out集合的,并且状态发生变化*/
if ((mask & POLLOUT_SET) && (out & bit)) {
res_out |= bit;
retval++;
wait->_qproc = NULL;
}
/*是否是err集合的,并且状态发生变化*/
if ((mask & POLLEX_SET) && (ex & bit)) {
res_ex |= bit;
retval++;
wait->_qproc = NULL;
}
}
}
/*将返回的值赋到原来的集合中*/
if (res_in)
*rinp = res_in;
if (res_out)
*routp = res_out;
if (res_ex)
*rexp = res_ex;