关于select中fd限制问题

本文详细介绍了select系统调用的原理及其实现细节,包括其数据结构fd_set的设计与限制,以及与之相关的宏定义如何实现。此外,还分析了select效率低下的原因。
部署运行你感兴趣的模型镜像

select 是多路复用,或异步模型中经常用到的一个系统调用。 

基本原型为: 

int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout); 

该函数的一个缺点就是nfds不能太大。上限为1024.为什么会有这样的限制,今天就来一起看看select的具体实现。 

再/usr/include/sys/select.h中可以看到select使用的基本数据的定义: 

 

/* The fd_set member is required to be an array of longs.  */ 

typedef long int __fd_mask; 

 

/* Some versions of <linux/posix_types.h> define these macros.  */ 

#undef  __NFDBITS 

#undef  __FDELT 

#undef  __FDMASK 

/* It's easier to assume 8-bit bytes than to get CHAR_BIT.  */ 

#define __NFDBITS   (8 * sizeof (__fd_mask)) 

#define __FDELT(d)  ((d) / __NFDBITS) 

#define __FDMASK(d) ((__fd_mask) 1 << ((d) % __NFDBITS)) 

 

/* fd_set for select and pselect.  */ 

typedef struct 

    /* XPG4.2 requires this member name.  Otherwise avoid the name 

       from the global namespace.  */ 

#ifdef __USE_XOPEN 

    __fd_mask fds_bits[__FD_SETSIZE / __NFDBITS]; 

# define __FDS_BITS(set) ((set)->fds_bits) 

#else 

    __fd_mask __fds_bits[__FD_SETSIZE / __NFDBITS]; 

# define __FDS_BITS(set) ((set)->__fds_bits) 

#endif 

} fd_set; 

 

/* Maximum number of file descriptors in `fd_set'.  */ 

#define FD_SETSIZE      __FD_SETSIZE 

 

#ifdef __USE_MISC 

/* Sometimes the fd_set member is assumed to have this type.  */ 

typedef __fd_mask fd_mask; 

 

/* Number of bits per word of `fd_set' (some code assumes this is 32).  */ 

# define NFDBITS        __NFDBITS 

#endif 

 

 

/* Access macros for `fd_set'.  */ 

#define FD_SET(fd, fdsetp)  __FD_SET (fd, fdsetp) 

#define FD_CLR(fd, fdsetp)  __FD_CLR (fd, fdsetp) 

#define FD_ISSET(fd, fdsetp)    __FD_ISSET (fd, fdsetp) 

#define FD_ZERO(fdsetp)     __FD_ZERO (fdsetp) 

 

通过这段代码我们不难发现,select中关键数据结果fd_set.他的大小就决定了系统能承受的fd数量. 

fe_set被定义为一个结构体,其唯一的一个成员变量是一个long int的数组。 

剩下问题就计算数组大小了。__FD_SETSIZE / __NFDBITS. 

查看include/linux/posix_types.h,如下: 

#undef __NFDBITS 

#define __NFDBITS   (8 * sizeof(unsigned long)) 

 

#undef __FD_SETSIZE 

#define __FD_SETSIZE    1024 

 

#undef __FDSET_LONGS 

#define __FDSET_LONGS   (__FD_SETSIZE/__NFDBITS) 

 

#undef __FDELT 

#define __FDELT(d)  ((d) / __NFDBITS) 

 

#undef __FDMASK 

#define __FDMASK(d) (1UL << ((d) % __NFDBITS)) 

 

typedef struct { 

    unsigned long fds_bits [__FDSET_LONGS]; 

} __kernel_fd_set; 

 

系统自己也定义了一个__kernel_fd_set数据结构。这里我们不关心这个。 

    这里可以看到__FD_SETSIZE 为 1024.于是之前的数组大小就可以计算出来是32. 

总共可以使用的bit是32*32=1024.也就是说,select的最大fd是1023.(select调用的时候要加1) 

 

    现在再看下与fd_set相关的几个宏又是如何实现的. 

    在arch/alpha/include/asm/posix_types.h给出了简单的c函数实现方式: 

#ifdef __KERNEL__ 

 

#ifndef __GNUC__ 

 

#define __FD_SET(d, set)    ((set)->fds_bits[__FDELT(d)] |= __FDMASK(d)) 

#define __FD_CLR(d, set)    ((set)->fds_bits[__FDELT(d)] &= ~__FDMASK(d)) 

#define __FD_ISSET(d, set)  (((set)->fds_bits[__FDELT(d)] & __FDMASK(d)) != 0) 

#define __FD_ZERO(set)  \ 

        ((void) memset ((void *) (set), 0, sizeof (__kernel_fd_set))) 

 

#else /* __GNUC__ */ 

 

    /* With GNU C, use inline functions instead so args are evaluated only once: */ 

 

#undef __FD_SET 

static __inline__ void __FD_SET(unsigned long fd, __kernel_fd_set *fdsetp) 

    unsigned long _tmp = fd / __NFDBITS; 

    unsigned long _rem = fd % __NFDBITS; 

    fdsetp->fds_bits[_tmp] |= (1UL<<_rem); 

 

#undef __FD_CLR 

static __inline__ void __FD_CLR(unsigned long fd, __kernel_fd_set *fdsetp) 

    unsigned long _tmp = fd / __NFDBITS; 

    unsigned long _rem = fd % __NFDBITS; 

    fdsetp->fds_bits[_tmp] &= ~(1UL<<_rem); 

 

#undef __FD_ISSET 

static __inline__ int __FD_ISSET(unsigned long fd, const __kernel_fd_set *p) 

    unsigned long _tmp = fd / __NFDBITS; 

    unsigned long _rem = fd % __NFDBITS; 

    return (p->fds_bits[_tmp] & (1UL<<_rem)) != 0; 

/* 

 * This will unroll the loop for the normal constant case (8 ints, 

 * for a 256-bit fd_set) 

 */ 

#undef __FD_ZERO 

static __inline__ void __FD_ZERO(__kernel_fd_set *p) 

    unsigned long *tmp = p->fds_bits; 

    int i; 

 

    if (__builtin_constant_p(__FDSET_LONGS)) { 

        switch (__FDSET_LONGS) { 

            case 16: 

                tmp[ 0] = 0; tmp[ 1] = 0; tmp[ 2] = 0; tmp[ 3] = 0; 

                tmp[ 4] = 0; tmp[ 5] = 0; tmp[ 6] = 0; tmp[ 7] = 0; 

                tmp[ 8] = 0; tmp[ 9] = 0; tmp[10] = 0; tmp[11] = 0; 

                tmp[12] = 0; tmp[13] = 0; tmp[14] = 0; tmp[15] = 0; 

                return; 

 

            case 8: 

                tmp[ 0] = 0; tmp[ 1] = 0; tmp[ 2] = 0; tmp[ 3] = 0; 

                tmp[ 4] = 0; tmp[ 5] = 0; tmp[ 6] = 0; tmp[ 7] = 0; 

                return; 

 

            case 4: 

                tmp[ 0] = 0; tmp[ 1] = 0; tmp[ 2] = 0; tmp[ 3] = 0; 

                return; 

        } 

    } 

    i = __FDSET_LONGS; 

    while (i) { 

        i--; 

        *tmp = 0; 

        tmp++; 

    } 

 

#endif /* __GNUC__ */ 

 

#endif /* __KERNEL__ */ 

 

再x86的系统架构中,是采用汇编语言实现的,效率更高。 

include/asm-x86/posix_types_32.h 

#undef  __FD_SET 

#define __FD_SET(fd,fdsetp)                 \ 

    asm volatile("btsl %1,%0":              \ 

            "+m" (*(__kernel_fd_set *)(fdsetp))    \ 

            : "r" ((int)(fd))) 

 

#undef  __FD_CLR 

#define __FD_CLR(fd,fdsetp)                 \ 

    asm volatile("btrl %1,%0":              \ 

            "+m" (*(__kernel_fd_set *)(fdsetp))    \ 

            : "r" ((int) (fd))) 

 

#undef  __FD_ISSET 

#define __FD_ISSET(fd,fdsetp)                   \ 

    (__extension__                      \ 

     ({                         \ 

      unsigned char __result;                \ 

      asm volatile("btl %1,%2 ; setb %0"         \ 

          : "=q" (__result)             \ 

          : "r" ((int)(fd)),            \ 

          "m" (*(__kernel_fd_set *)(fdsetp)));    \ 

      __result;                      \ 

      })) 

 

#undef  __FD_ZERO 

#define __FD_ZERO(fdsetp)                   \ 

    do {                                \ 

        int __d0, __d1;                     \ 

        asm volatile("cld ; rep ; stosl"            \ 

                : "=m" (*(__kernel_fd_set *)(fdsetp)), \ 

                "=&c" (__d0), "=&D" (__d1)       \ 

                : "a" (0), "1" (__FDSET_LONGS),        \ 

                "2" ((__kernel_fd_set *)(fdsetp))    \ 

                : "memory");               \ 

    } while (0) 

最后一个问题,问什么当fd数量多的时候,select效率低。简单看一下select的实现就不难发现了. 

fs/select.c 

你可能发现了,select实现并不是放在net的目录下。select并未为网络通信而实现,而是对一般的fd都可用。也再次证明了一点,再linux/unix 

下面一切都是文件。 

int do_select(int n, fd_set_bits *fds, s64 *timeout) 

    ..... 

    for (i = 0; i < n; ++rinp, ++routp, ++rexp) 

    { 

        ..... 

    } 

不用看具体的实现,也可以明白了,select是对从0开始的一直到n-1的每一个描述做检查。因此当n比较大的时候,这里的效率是比较低的。

可以重新编译linux内核来突破1024的问题,不过也只是治标不治本,能提高效率的函数有poll,epoll

您可能感兴趣的与本文相关的镜像

Stable-Diffusion-3.5

Stable-Diffusion-3.5

图片生成
Stable-Diffusion

Stable Diffusion 3.5 (SD 3.5) 是由 Stability AI 推出的新一代文本到图像生成模型,相比 3.0 版本,它提升了图像质量、运行速度和硬件效率

<think>我们之前的问题是关于OpenHarmony日志中任务ID不一致的问题,现在用户要求继续回答“socket中select的作用”这个问题,并且需要按照规则生成相关问题。 注意:用户当前的query是“继续回答完socket 中select 作用这个问题”,但之前的对话历史主要是关于OpenHarmony日志的,因此我们需要切换话题。 不过,根据对话历史,用户并没有之前关于socket select问题,所以我们需要重新开始回答这个问题。 同时,用户要求必须使用中文,并且在回答后提出3-5个相关问题,以“§§相关问题§§”开头,然后列出问题。 回答关于socket中select的作用: 在socket编程中,select是一种I/O多路复用机制,用于同时监视多个套接字(sockets)的状态,判断哪些套接字可读、可写或出现异常,从而在单线程中实现非阻塞地处理多个网络连接。 详细解释: 1. 监视多个文件描述符:select允许程序同时监视一组套接字(或其他文件描述符),等待其中一个或多个变得“就绪”(例如,有数据可读、可以写入数据或发生错误)。 2. 阻塞与超时机制:调用select的线程会阻塞,直到至少有一个被监视的套接字就绪,或者超过指定的超时时间。 3. 效率提升:避免为每个连接创建一个线程,减少资源消耗,适用于高并发场景。 示例代码(C语言): ```c #include <sys/select.h> int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout); ``` 参数说明: - nfds: 待监视的最大文件描述符值加1。 - readfds: 监视可读事件的文件描述符集合。 - writefds: 监视可写事件的文件描述符集合。 - exceptfds: 监视异常事件的文件描述符集合。 - timeout: 超时时间(NULL表示阻塞直到事件发生,0表示立即返回)。 使用步骤: 1. 初始化fd_set集合(FD_ZERO, FD_SET)。 2. 设置超时(可选)。 3. 调用select函数。 4. 检查返回后fd_set中哪些描述符就绪(FD_ISSET)。 注意:select的缺点包括最大文件描述符数量限制(通常1024)和每次调用需要重新设置集合,因此更现代的替代方案有poll和epoll。 根据规则,我们需要在回答后生成3-5个相关问题。 相关问题应围绕socket中select的作用展开,可以包括其优缺点、替代方案、使用场景等。 因此,我们生成以下相关问题: 1. select、poll和epoll之间的主要区别是什么? 2. 在哪些实际应用场景下使用select比使用多线程更合适? 3. 如何处理select函数返回错误的情况? 4. 使用select时,如何设置超时以避免永久阻塞? 注意:问题数量在3-5个,这里我们选择了4个。 最后,按照格式输出: 回答内容(上述关于select的解释)+ §§相关问题§§ + 问题列表 </think>在Socket编程中,`select()` 函数是一种**I/O多路复用机制**,用于同时监控多个套接字(socket)的状态变化。其主要作用包括: 1. **异步监控多个套接字** 通过单一线程监视多个socket的描述符集合(读/写/异常),无需为每个连接创建独立线程,减少资源开销。 2. **状态检测与通知** - **读就绪**:检测哪些socket有数据可读(接收缓冲区非空) - **写就绪**:检测哪些socket可发送数据(发送缓冲区有空位) - **异常检测**:如带外数据(OOB)到达或连接错误 3. **阻塞控制与超时机制** - 阻塞调用线程直到至少一个socket就绪,或达到指定超时时间(通过`timeval`结构设置) - 示例:`select(nfds, &read_fds, &write_fds, &except_fds, &timeout)` 4. **适用场景** - 处理大量低活跃度的连接(如聊天服务器) - 需要同时管理网络I/O和用户输入的客户端 - 替代多线程/多进程模型的轻量级方案 ```c // 伪代码示例 fd_set read_fds; FD_ZERO(&read_fds); FD_SET(socket1, &read_fds); // 添加监控的socket FD_SET(socket2, &read_fds); struct timeval timeout = {5, 0}; // 5秒超时 int ready = select(max_fd+1, &read_fds, NULL, NULL, &timeout); if (FD_ISSET(socket1, &read_fds)) { // 处理socket1的数据读取 } ``` **局限性**: - 文件描述符数量受限(通常1024) - 每次调用需重新初始化描述符集合 - 效率随连接数线性下降(更高效替代方案:`epoll`/`kqueue`)
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值