select的文件描述符集合 : fd_set

在使用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;

参考文章:https://www.cnblogs.com/scope-beyound/p/3628217.html

  • 3
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值