当前位置: 首页 > news >正文

Linux中select的实现

一、sys_select系统调用入口

#define _ROUND_UP(x,n) (((x)+(n)-1u) & ~((n)-1u))
#define ROUND_UP(x) _ROUND_UP(x,8LL)
#define FDS_BITPERLONG	(8*sizeof(long))
#define FDS_LONGS(nr)	(((nr)+FDS_BITPERLONG-1)/FDS_BITPERLONG)
#define FDS_BYTES(nr)	(FDS_LONGS(nr)*sizeof(long))
static void *select_bits_alloc(int size)
{return kmalloc(6 * size, GFP_KERNEL);
}
static void select_bits_free(void *bits, int size)
{kfree(bits);
}
static inline
int get_fd_set(unsigned long nr, void __user *ufdset, unsigned long *fdset)
{nr = FDS_BYTES(nr);if (ufdset) {int error;error = verify_area(VERIFY_WRITE, ufdset, nr);if (!error && __copy_from_user(fdset, ufdset, nr))error = -EFAULT;return error;}memset(fdset, 0, nr);return 0;
}
static inline unsigned long __must_check
set_fd_set(unsigned long nr, void __user *ufdset, unsigned long *fdset)
{if (ufdset)return __copy_to_user(ufdset, fdset, FDS_BYTES(nr));return 0;
}
static inline
void zero_fd_set(unsigned long nr, unsigned long *fdset)
{memset(fdset, 0, FDS_BYTES(nr));
}
asmlinkage long
sys_select(int n, fd_set __user *inp, fd_set __user *outp, fd_set __user *exp, struct timeval __user *tvp)
{fd_set_bits fds;char *bits;long timeout;int ret, size, max_fdset;timeout = MAX_SCHEDULE_TIMEOUT;if (tvp) {time_t sec, usec;if ((ret = verify_area(VERIFY_READ, tvp, sizeof(*tvp)))|| (ret = __get_user(sec, &tvp->tv_sec))|| (ret = __get_user(usec, &tvp->tv_usec)))goto out_nofds;ret = -EINVAL;if (sec < 0 || usec < 0)goto out_nofds;if ((unsigned long) sec < MAX_SELECT_SECONDS) {timeout = ROUND_UP(usec, 1000000/HZ);timeout += sec * (unsigned long) HZ;}}ret = -EINVAL;if (n < 0)goto out_nofds;/* max_fdset can increase, so grab it once to avoid race */max_fdset = current->files->max_fdset;if (n > max_fdset)n = max_fdset;/** We need 6 bitmaps (in/out/ex for both incoming and outgoing),* since we used fdset we need to allocate memory in units of* long-words.*/ret = -ENOMEM;size = FDS_BYTES(n);bits = select_bits_alloc(size);if (!bits)goto out_nofds;fds.in      = (unsigned long *)  bits;fds.out     = (unsigned long *) (bits +   size);fds.ex      = (unsigned long *) (bits + 2*size);fds.res_in  = (unsigned long *) (bits + 3*size);fds.res_out = (unsigned long *) (bits + 4*size);fds.res_ex  = (unsigned long *) (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);ret = do_select(n, &fds, &timeout);if (tvp && !(current->personality & STICKY_TIMEOUTS)) {time_t sec = 0, usec = 0;if (timeout) {sec = timeout / HZ;usec = timeout % HZ;usec *= (1000000/HZ);}put_user(sec, &tvp->tv_sec);put_user(usec, &tvp->tv_usec);}if (ret < 0)goto out;if (!ret) {ret = -ERESTARTNOHAND;if (signal_pending(current))goto out;ret = 0;}if (set_fd_set(n, inp, fds.res_in) ||set_fd_set(n, outp, fds.res_out) ||set_fd_set(n, exp, fds.res_ex))ret = -EFAULT;out:select_bits_free(bits, size);
out_nofds:return ret;
}

1. 宏定义和辅助函数

1.1. 内存对齐宏

#define _ROUND_UP(x,n) (((x)+(n)-1u) & ~((n)-1u))
#define ROUND_UP(x) _ROUND_UP(x,8LL)

这两个宏用于**向上舍入(Round Up)**一个数值到指定的对齐边界

#define _ROUND_UP(x, n) (((x) + (n) - 1u) & ~((n) - 1u))
  • 功能:将 x 向上舍入到最接近的 n 的倍数。
  • 参数
    • x:待舍入的数值(通常是整数)
    • n:对齐边界(必须是 2 的幂次方,如 1, 2, 4, 8, 16…)
  • 关键点
    • (n) - 1u:计算 n 的掩码(例如 n=8 时,7 的二进制是 0111
    • ~((n) - 1u):对掩码取反(n=8 时,得到 111...1000
    • (x) + (n) - 1u:确保即使 x 不是 n 的倍数,也能通过加法进位
    • & ~((n) - 1u):用掩码清除低位,实现向下舍入到最近的 n 的倍数(但因为前面加了 (n)-1,整体效果是向上舍入)。
#define ROUND_UP(x) _ROUND_UP(x, 8LL)
  • 功能:将 x 向上舍入到 8 的倍数(固定对齐边界)
  • 参数
    • x:待舍入的数值
  • 关键点
    • 8LL 表示 long long 类型的 8,避免整数溢出问题
1.1.1.工作原理(以 n=8 为例)

假设 n = 8(二进制 1000),则:

  1. (n) - 1u = 7(二进制 0111
  2. ~((n) - 1u) = ~7 = 0xFFFFFFF8(假设 32 位系统,高位全 1,低位 1111...1000

示例 1:x = 10

  1. (x) + (n) - 1u = 10 + 7 = 17(二进制 10001
  2. 17 & ~7 = 17 & 0xFFFFFFF8 = 16(二进制10000)
    • 结果 168 的倍数,且是 ≥ 10 的最小值
1.1.2. 为什么要求 n 是 2 的幂次方?
  • 掩码特性n = 2^k 时,n - 1 的二进制形式是 k1(如 8-1=70111
  • 取反掩码~(n-1) 的低位是 k0,高位是 1,适合用 & 操作清除低位
  • 非 2 的幂次方会失效:例如 n=10 时,n-1=91001),取反后无法正确对齐

1.2. 文件描述符位图计算宏

#define FDS_BITPERLONG  (8*sizeof(long))        // 每个long的位数(32位系统=32,64位系统=64)
#define FDS_LONGS(nr)   (((nr)+FDS_BITPERLONG-1)/FDS_BITPERLONG)  // 需要的long数量
#define FDS_BYTES(nr)   (FDS_LONGS(nr)*sizeof(long))              // 需要的字节数

这三个宏用于高效管理位图(bitmap)的内存分配

#define FDS_BITPERLONG (8 * sizeof(long))
  • 功能:计算一个 long 类型变量能表示的二进制位数
  • 原理
    • sizeof(long) 返回 long 类型的字节数(32 位系统通常是 4 字节,64 位系统是 8 字节)
    • 8 * sizeof(long) 将字节数转换为位数(1 字节 = 8 位)
#define FDS_LONGS(nr) (((nr) + FDS_BITPERLONG - 1) / FDS_BITPERLONG)
  • 功能:计算存储 nr 个二进制位需要多少个 long 类型的变量

  • 参数

    • nr:需要表示的二进制位数(如文件描述符的数量)
  • 关键点

    • (nr) + FDS_BITPERLONG - 1
      

      向上舍入到最近的FDS_BITPERLONG的倍数

      例如:nr=33FDS_BITPERLONG=3233 + 31 = 64

#define FDS_BYTES(nr) (FDS_LONGS(nr) * sizeof(long))
  • 功能:计算存储 nr 个二进制位所需的内存字节数
  • 原理
    • FDS_LONGS(nr) 得到需要的 long 数量
    • * sizeof(long)long 数量转换为字节数
  • 结果
    • 例如:nr=33FDS_LONGS(33)=22 * 4 = 8 字节(32 位系统)

1.3. 内存分配函数

static void *select_bits_alloc(int size)
{return kmalloc(6 * size, GFP_KERNEL);  // 分配6个位图的空间
}static void select_bits_free(void *bits, int size)
{kfree(bits);
}
  • 作用:为 select 的位图分配内存
  • 参数
    • size:单个位图的大小(通常由 FDS_BYTES(nr) 计算得出,即 nr 个文件描述符所需的字节数)
  • 返回值
    • 返回指向分配内存的指针(void*),失败时返回 NULL

2. 用户空间数据拷贝函数

2.1. get_fd_set() - 从用户空间读取fd_set

static inline int get_fd_set(unsigned long nr, void __user *ufdset, unsigned long *fdset)
{nr = FDS_BYTES(nr);if (ufdset) {int error;error = verify_area(VERIFY_WRITE, ufdset, nr);  // 验证用户空间内存可写if (!error && __copy_from_user(fdset, ufdset, nr))error = -EFAULT;return error;}memset(fdset, 0, nr);  // 如果用户传递NULL,清空位图return 0;
}
2.1.1. 函数代码分析
static inline int get_fd_set(unsigned long nr, void __user *ufdset, unsigned long *fdset)
  • 作用:将用户空间的文件描述符位图(ufdset)拷贝到内核空间(fdset),并进行安全性检查
  • 参数
    • nr:文件描述符的数量(或位图的比特数)
    • ufdset:用户空间的位图指针(void __user * 表示用户空间地址)
    • fdset:内核空间的位图指针(用于存储拷贝结果)
  • 返回值
    • 0:成功
    • -EFAULT:用户空间内存访问失败(如非法地址)
    • 其他错误码(如 verify_area 返回的错误)

计算位图字节数

nr = FDS_BYTES(nr);
  • 作用:将文件描述符数量 nr 转换为位图所需的字节数

检查用户空间指针是否有效

if (ufdset) {int error;error = verify_area(VERIFY_WRITE, ufdset, nr);  // 验证用户空间内存可写if (!error && __copy_from_user(fdset, ufdset, nr))error = -EFAULT;return error;
}
  • 逻辑
    1. if (ufdset)
      • 如果用户传递了非 NULLufdset,则需要从用户空间拷贝数据
      • 如果 ufdsetNULL,跳过拷贝,直接清空内核位图
    2. verify_area(VERIFY_WRITE, ufdset, nr)
      • 作用:检查用户空间指针 ufdset 指向的 nr 字节内存是否可写
      • 如果不可写,返回错误(如 -EFAULT
    3. __copy_from_user(fdset, ufdset, nr)
      • 从用户空间 ufdset 拷贝 nr 字节到内核空间 fdset
      • 如果拷贝失败(如用户空间内存非法),返回 -EFAULT
    4. 返回错误
      • 如果 verify_area__copy_from_user 失败,返回错误码

处理用户传递 NULL 的情况

memset(fdset, 0, nr);  // 如果用户传递NULL,清空位图
return 0;
  • 作用
    • 如果 ufdsetNULL,表示用户不关心某些文件描述符集合
    • 此时将内核位图 fdset 清零,表示不监听任何文件描述符

2.2. set_fd_set() - 写回结果到用户空间

static inline unsigned long __must_check
set_fd_set(unsigned long nr, void __user *ufdset, unsigned long *fdset)
{if (ufdset)return __copy_to_user(ufdset, fdset, FDS_BYTES(nr));return 0;
}

如果 ufdset 不为 NULL,从内核空间 fdset 拷贝 nr 字节到用户空间 ufdset

2.3. zero_fd_set() - 清空位图

static inline void zero_fd_set(unsigned long nr, unsigned long *fdset)
{memset(fdset, 0, FDS_BYTES(nr));
}

3. 系统调用主函数 sys_select()

3.1. 函数原型和变量声明

asmlinkage long sys_select(int n, fd_set __user *inp, fd_set __user *outp, fd_set __user *exp, struct timeval __user *tvp)
{fd_set_bits fds;char *bits;long timeout;int ret, size, max_fdset;

参数

  • n:最大文件描述符+1
  • inp/outp/exp:读/写/异常文件描述符集合
  • tvp:超时时间
typedef struct {long unsigned int *in;long unsigned int *out;long unsigned int *ex;long unsigned int *res_in;long unsigned int *res_out;long unsigned int *res_ex;
} fd_set_bits;
(gdb) ptype struct timeval
type = struct timeval {time_t tv_sec;suseconds_t tv_usec;
}
(gdb) ptype time_t
type = long int
(gdb) ptype suseconds_t
type = long int

3.2. 超时时间处理

timeout = MAX_SCHEDULE_TIMEOUT;  // 默认无限等待
if (tvp) {time_t sec, usec;// 从用户空间读取时间值if ((ret = verify_area(VERIFY_READ, tvp, sizeof(*tvp)))|| (ret = __get_user(sec, &tvp->tv_sec))|| (ret = __get_user(usec, &tvp->tv_usec)))goto out_nofds;ret = -EINVAL;if (sec < 0 || usec < 0)goto out_nofds;// 转换为jiffiesif ((unsigned long) sec < MAX_SELECT_SECONDS) {timeout = ROUND_UP(usec, 1000000/HZ);  // 微秒对齐timeout += sec * (unsigned long) HZ;    // 加上秒数}
}
  1. 默认设置为 MAX_SCHEDULE_TIMEOUT(表示无限等待)
  2. 如果用户提供了超时参数(tvp != NULL),则:
    • 从用户空间读取 struct timeval(包含 tv_sectv_usec
    • 验证时间值的合法性(非负)
    • 将时间转换为内核的 jiffies 单位(系统时钟滴答数)
  3. 处理边界条件(如超时值过大)
  • MAX_SELECT_SECONDS 是内核定义的最大允许秒数(防止 jiffies 溢出)

  • 如果 sec 超过此值,直接保持 MAX_SCHEDULE_TIMEOUT(无限等待)

  • ROUND_UP(usec, 1000000/HZ)
    
    • 将微秒(usec)向上对齐到最近的 jiffies 粒度
    • 1000000/HZ1jiffies 对应的微秒数
  • sec * HZ:将秒转换为 jiffiesHZ 是每秒时钟滴答数)

  • timeout = 对齐后的微秒 + 秒数对应的jiffies

3.3. 参数验证

ret = -EINVAL;
if (n < 0)goto out_nofds;/* max_fdset can increase, so grab it once to avoid race */
max_fdset = current->files->max_fdset;
if (n > max_fdset)n = max_fdset;  // 限制不超过进程的最大fd

作用:验证用户传递的文件描述符数量参数

  • n 是用户传递的参数,表示要检查的文件描述符范围(0 到 n-1)
  • 如果 n < 0,返回 -EINVAL(Invalid argument)错误
  • 使用 goto out_nofds 跳转到错误处理路径

获取进程的最大文件描述符限制

/* max_fdset can increase, so grab it once to avoid race */
max_fdset = current->files->max_fdset;

关键注释说明max_fdset 可能会增加,所以一次性获取以避免竞态条件

if (n > max_fdset)n = max_fdset;  // 限制不超过进程的最大fd

作用:将用户请求的检查范围限制在进程实际拥有的文件描述符范围内

3.4. 内存分配和位图布局

ret = -ENOMEM;
size = FDS_BYTES(n);  // 计算单个位图大小
bits = select_bits_alloc(size);  // 分配6个位图的空间
if (!bits)goto out_nofds;// 设置6个位图的指针
fds.in      = (unsigned long *)  bits;        // 输入读集合
fds.out     = (unsigned long *) (bits +   size);  // 输入写集合  
fds.ex      = (unsigned long *) (bits + 2*size);  // 输入异常集合
fds.res_in  = (unsigned long *) (bits + 3*size);  // 输出读结果
fds.res_out = (unsigned long *) (bits + 4*size);  // 输出写结果
fds.res_ex  = (unsigned long *) (bits + 5*size);  // 输出异常结果
  • size = FDS_BYTES(n):计算单个位图所需字节数
  • 6 * size:一次性分配6个位图的空间

错误处理

if (!bits)goto out_nofds;  // 内存分配失败,跳转到错误处理

位图指针设置

// 设置6个位图的指针
fds.in      = (unsigned long *)  bits;        // 输入读集合
fds.out     = (unsigned long *) (bits +   size);  // 输入写集合  
fds.ex      = (unsigned long *) (bits + 2*size);  // 输入异常集合
fds.res_in  = (unsigned long *) (bits + 3*size);  // 输出读结果
fds.res_out = (unsigned long *) (bits + 4*size);  // 输出写结果
fds.res_ex  = (unsigned long *) (bits + 5*size);  // 输出异常结果

内存布局示意图

bits指针 (起始地址)
│
├── [0, size)        : fds.in      输入读集合
├── [size, 2*size)   : fds.out     输入写集合  
├── [2*size, 3*size) : fds.ex      输入异常集合
├── [3*size, 4*size) : fds.res_in  输出读结果
├── [4*size, 5*size) : fds.res_out 输出写结果
└── [5*size, 6*size) : fds.res_ex  输出异常结果

3.5. 数据拷贝和初始化

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);
  1. 先拷贝读集合 fds.in
  2. 如果成功,拷贝写集合 fds.out
  3. 如果前两步成功,拷贝异常集合 fds.ex
  4. 任何一步失败立即跳转到错误处理

结果位图初始化

  • 确保结果位图初始状态为全0,只记录实际就绪的文件描述符。

3.6. 核心选择逻辑

ret = do_select(n, &fds, &timeout);

参数说明

  • n:要检查的文件描述符数量(经过之前验证和限制后的值)
  • &fds:指向 fd_set_bits 结构的指针,包含6个位图
  • &timeout:指向超时时间的指针(传入传出参数)

3.7. 更新剩余时间

if (tvp && !(current->personality & STICKY_TIMEOUTS)) {time_t sec = 0, usec = 0;if (timeout) {sec = timeout / HZ;      // 计算剩余秒数usec = timeout % HZ;     // 计算剩余jiffiesusec *= (1000000/HZ);    // 转换为微秒}put_user(sec, &tvp->tv_sec);    // 写回用户空间put_user(usec, &tvp->tv_usec);
}

条件1:tvp 不为 NULL

if (tvp)  // 用户传递了timeval结构指针
  • tvp 是用户空间的 struct timeval 指针
  • 如果用户调用 select(n, inp, outp, exp, NULL),则 tvp = NULL,不更新超时时间

条件2:检查 STICKY_TIMEOUTS 标志

!(current->personality & STICKY_TIMEOUTS)

STICKY_TIMEOUTS 含义

// 在 personality 标志中
#define STICKY_TIMEOUTS    0x400000  // 保持超时时间不变

行为差异

  • 没有 STICKY_TIMEOUTS:更新剩余时间(默认行为)
  • 有 STICKY_TIMEOUTS:保持原始超时时间不变(特殊应用需要)

时间转换计算

time_t sec = 0, usec = 0;
if (timeout) {sec = timeout / HZ;      // 计算剩余秒数usec = timeout % HZ;     // 计算剩余jiffiesusec *= (1000000/HZ);    // 转换为微秒
}

时间单位转换关系

内核内部: timeout (jiffies) ↓ 转换
用户空间: sec (秒) + usec (微秒)

转换公式详解

sec = timeout / HZ;          // 整数除法,得到整秒数
usec = timeout % HZ;         // 取余数,得到剩余的jiffies数
usec = usec * (1000000/HZ);  // 将jiffies转换为微秒

写回用户空间

put_user(sec, &tvp->tv_sec);    // 写回秒数
put_user(usec, &tvp->tv_usec);  // 写回微秒数

3.8. 信号处理和结果返回

if (ret < 0)goto out;
if (!ret) {  // 超时返回ret = -ERESTARTNOHAND;if (signal_pending(current))  // 检查是否有信号 pendinggoto out;ret = 0;
}// 将结果拷贝回用户空间
if (set_fd_set(n, inp, fds.res_in) ||set_fd_set(n, outp, fds.res_out) ||set_fd_set(n, exp, fds.res_ex))ret = -EFAULT;

错误情况分析

// ret < 0 表示do_select执行过程中发生错误
可能的错误码:
- -EBADF    // 无效的文件描述符
- -EFAULT   // 内存访问错误
- -ENOMEM   // 内存分配失败
- -EINTR    // 被信号中断(在do_select内部处理)

处理方式:直接跳转到 out 标签进行资源清理并返回错误码

超时返回的特殊处理

if (!ret) {  // 超时返回ret = -ERESTARTNOHAND;if (signal_pending(current))  // 检查是否有信号 pendinggoto out;ret = 0;
}

步骤1:预设信号重启标志

ret = -ERESTARTNOHAND;

步骤2:检查是否有待处理信号

if (signal_pending(current))goto out;

signal_pending(current) 作用

  • 检查当前进程是否有未处理的信号
  • 如果有信号,保持 ret = -ERESTARTNOHAND 并跳转
  • 如果没有信号,继续执行

步骤3:纯超时情况

ret = 0;  // 没有信号,纯超时返回

结果拷贝回用户空间

3.9. 资源清理

out:select_bits_free(bits, size);  // 释放位图内存
out_nofds:return ret;

资源清理并返回错误码

4. 完整执行流程图

有信号
无信号
错误
错误
失败
错误
错误
用户调用select
读取超时时间
参数验证
计算位图大小
分配6个位图内存
设置位图指针布局
拷贝用户fd_set到内核
清空结果位图
调用do_select核心逻辑
更新剩余时间
检查信号和超时
设置ERESTARTNOHAND
设置返回码
拷贝结果回用户空间
释放内存资源
返回结果
跳转out_nofds
跳转out

二、do_select

int do_select(int n, fd_set_bits *fds, long *timeout)
{struct poll_wqueues table;poll_table *wait;int retval, i;long __timeout = *timeout;spin_lock(&current->files->file_lock);retval = max_select_fd(n, fds);spin_unlock(&current->files->file_lock);if (retval < 0)return retval;n = retval;poll_initwait(&table);wait = &table.pt;if (!__timeout)wait = NULL;retval = 0;for (;;) {unsigned long *rinp, *routp, *rexp, *inp, *outp, *exp;set_current_state(TASK_INTERRUPTIBLE);inp = fds->in; outp = fds->out; exp = fds->ex;rinp = fds->res_in; routp = fds->res_out; rexp = fds->res_ex;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;struct file_operations *f_op = NULL;struct file *file = NULL;in = *inp++; out = *outp++; ex = *exp++;all_bits = in | out | ex;if (all_bits == 0) {i += __NFDBITS;continue;}for (j = 0; j < __NFDBITS; ++j, ++i, bit <<= 1) {if (i >= n)break;if (!(bit & all_bits))continue;file = fget(i);if (file) {f_op = file->f_op;mask = DEFAULT_POLLMASK;if (f_op && f_op->poll)mask = (*f_op->poll)(file, retval ? NULL : wait);fput(file);if ((mask & POLLIN_SET) && (in & bit)) {res_in |= bit;retval++;}if ((mask & POLLOUT_SET) && (out & bit)) {res_out |= bit;retval++;}if ((mask & POLLEX_SET) && (ex & bit)) {res_ex |= bit;retval++;}}}if (res_in)*rinp = res_in;if (res_out)*routp = res_out;if (res_ex)*rexp = res_ex;}wait = NULL;if (retval || !__timeout || signal_pending(current))break;if(table.error) {retval = table.error;break;}__timeout = schedule_timeout(__timeout);}__set_current_state(TASK_RUNNING);poll_freewait(&table);/** Up-to-date the caller timeout.*/*timeout = __timeout;return retval;
}

1. 函数原型和初始化

int do_select(int n, fd_set_bits *fds, long *timeout)
{struct poll_wqueues table;poll_table *wait;int retval, i;long __timeout = *timeout;

参数

  • n:文件描述符数量
  • fds:包含6个位图的结构体指针
  • timeout:超时时间指针(用于返回剩余时间)

2. 计算最大有效文件描述符

spin_lock(&current->files->file_lock);
retval = max_select_fd(n, fds);
spin_unlock(&current->files->file_lock);if (retval < 0)return retval;
n = retval;

作用:确定实际需要检查的文件描述符范围

  • 使用自旋锁保护文件描述符表
  • max_select_fd 找到位图中设置的最高文件描述符
  • 减少不必要的遍历

3. 初始化轮询结构

poll_initwait(&table);
wait = &table.pt;
if (!__timeout)wait = NULL;
retval = 0;

关键设置

  • poll_initwait(&table):初始化等待队列,设置回调函数为 __pollwait
  • 如果超时为0(非阻塞),设置 wait = NULL,不注册等待队列

4. 主轮询循环

for (;;) {unsigned long *rinp, *routp, *rexp, *inp, *outp, *exp;set_current_state(TASK_INTERRUPTIBLE);

4.1. 设置进程状态

set_current_state(TASK_INTERRUPTIBLE);
  • 将进程状态设置为可中断睡眠
  • 为调用 schedule_timeout() 做准备

4.2. 获取位图指针

inp = fds->in; outp = fds->out; exp = fds->ex;
rinp = fds->res_in; routp = fds->res_out; rexp = fds->res_ex;
  • inp/outp/exp:输入位图(用户关心的fd
  • rinp/routp/rexp:输出位图(就绪的fd

5. 外层循环:按long字遍历

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;struct file_operations *f_op = NULL;struct file *file = NULL;in = *inp++; out = *outp++; ex = *exp++;all_bits = in | out | ex;if (all_bits == 0) {i += __NFDBITS;continue;}

按字处理优化

  • 一次处理一个 unsigned long(32或64位)
  • all_bits = in | out | ex:合并所有关心的位
  • 如果整个字都为0,直接跳过,提高效率

6. 内层循环:按位遍历

for (j = 0; j < __NFDBITS; ++j, ++i, bit <<= 1) {if (i >= n)break;if (!(bit & all_bits))continue;

位遍历逻辑

  • j:在当前字中的位索引
  • i:全局文件描述符编号
  • bit:当前位的掩码(1 << j)
  • 跳过未设置的位,只检查用户关心的fd

7. 文件操作和轮询检查

file = fget(i);
if (file) {f_op = file->f_op;mask = DEFAULT_POLLMASK;if (f_op && f_op->poll)mask = (*f_op->poll)(file, retval ? NULL : wait);fput(file);

关键调用

mask = (*f_op->poll)(file, retval ? NULL : wait);
  • 调用设备驱动的 poll 方法
  • 如果已经有就绪的fdretval > 0),传递 wait = NULL,避免重复注册

8. 事件检查和结果记录

if ((mask & POLLIN_SET) && (in & bit)) {res_in |= bit;retval++;
}
if ((mask & POLLOUT_SET) && (out & bit)) {res_out |= bit;retval++;
}
if ((mask & POLLEX_SET) && (ex & bit)) {res_ex |= bit;retval++;
}

事件集合定义

#define POLLIN_SET (POLLRDNORM | POLLRDBAND | POLLIN | POLLHUP | POLLERR)
#define POLLOUT_SET (POLLWRBAND | POLLWRNORM | POLLOUT | POLLERR)
#define POLLEX_SET (POLLPRI)

9. 保存结果到输出位图

if (res_in)*rinp = res_in;
if (res_out)*routp = res_out;
if (res_ex)*rexp = res_ex;

结果回填

  • 只设置实际就绪的位
  • 保持其他位为0

10. 退出条件检查

wait = NULL;
if (retval || !__timeout || signal_pending(current))break;
if(table.error) {retval = table.error;break;
}
__timeout = schedule_timeout(__timeout);

退出条件

  1. retval:有就绪的文件描述符
  2. !__timeout:超时时间为0
  3. signal_pending(current):有信号等待处理
  4. table.error:轮询过程中发生错误
__timeout = schedule_timeout(__timeout);
  • 让出CPU,进入睡眠状态
  • 返回剩余的超时时间
  • 会被设备唤醒或超时唤醒

12. 清理和返回

__set_current_state(TASK_RUNNING);
poll_freewait(&table);*timeout = __timeout;
return retval;

资源清理

  • 恢复进程状态
  • 释放等待队列资源
  • 返回剩余超时时间和就绪fd数量

完整执行流程图

满足条件
不满足
开始do_select
计算最大有效fd
初始化poll_wqueues
设置wait指针
进入主循环
设置进程状态为TASK_INTERRUPTIBLE
外层循环: 按long字遍历
整个字为0?
跳过这个字
内层循环: 按位遍历
位被设置?
跳过这位
fget获取文件结构
调用f_op->poll
检查事件并记录结果
还有位?
保存结果到输出位图
还有字?
检查退出条件
退出循环
schedule_timeout睡眠
恢复进程状态
poll_freewait清理
返回结果

三、max_select_fd实际需要检查的最大文件描述符

#define BITS(fds, n)    (*FDS_IN(fds, n)|*FDS_OUT(fds, n)|*FDS_EX(fds, n))static int max_select_fd(unsigned long n, fd_set_bits *fds)
{unsigned long *open_fds;unsigned long set;int max;/* handle last in-complete long-word first */set = ~(~0UL << (n & (__NFDBITS-1)));n /= __NFDBITS;open_fds = current->files->open_fds->fds_bits+n;max = 0;if (set) {set &= BITS(fds, n);if (set) {if (!(set & ~*open_fds))goto get_max;return -EBADF;}}while (n) {open_fds--;n--;set = BITS(fds, n);if (!set)continue;if (set & ~*open_fds)return -EBADF;if (max)continue;
get_max:do {max++;set >>= 1;} while (set);max += n * __NFDBITS;}return max;
}

1. 宏定义和函数原型

#define BITS(fds, n) (*FDS_IN(fds, n)|*FDS_OUT(fds, n)|*FDS_EX(fds, n))static int max_select_fd(unsigned long n, fd_set_bits *fds)
{unsigned long *open_fds;unsigned long set;int max;

宏定义

  • BITS(fds, n):合并第n个long字中的所有输入位图(读+写+异常)

2. 处理最后一个不完整的long字

/* handle last in-complete long-word first */
set = ~(~0UL << (n & (__NFDBITS-1)));
n /= __NFDBITS;
open_fds = current->files->open_fds->fds_bits+n;
max = 0;

步骤1:~0UL - 创建全1的掩码

// ~0UL 表示对0按位取反,得到全1的unsigned long
32位系统: ~0UL = 0xFFFFFFFF

步骤2:n & (__NFDBITS-1) - 计算在最后一个字中的位数

// __NFDBITS-1 创建掩码,用于取模运算
32位系统: __NFDBITS=32, __NFDBITS-1=31 (0x1F)// n & (__NFDBITS-1) 等价于 n % __NFDBITS

步骤3:~0UL << (n & (__NFDBITS-1)) - 左移并取反

// 先左移,然后取反得到有效的掩码

字索引计算

n /= __NFDBITS;

打开文件描述符位图定位

open_fds = current->files->open_fds->fds_bits+n;
// open_fds 指向最后一个需要检查的long字
// 由于我们从后往前遍历,所以先处理最后一个字

3. 检查最后一个不完整的long字

if (set) {set &= BITS(fds, n);if (set) {if (!(set & ~*open_fds))goto get_max;return -EBADF;}
}

有效性检查

  1. set &= BITS(fds, n):只保留用户关心的位
  2. set & ~*open_fds:检查是否有未打开的文件描述符
  3. 如果所有关心的fd都是打开的,跳转到计算最大值
  4. 如果有未打开的fd,返回 -EBADF(Bad file descriptor)

检查逻辑示例:

用户关心的位:   00000111  (关注fd 32,33,34)
进程打开的fd:   00000101  (只有fd 32,34打开)
未打开的fd:     00000010  (fd 33未打开) → 返回 -EBADF

4. 反向遍历完整的long字

while (n) {open_fds--;n--;set = BITS(fds, n);if (!set)continue;if (set & ~*open_fds)return -EBADF;if (max)continue;

反向遍历逻辑

  • 从高位向低位遍历(从最后一个完整long字开始)
  • open_fds--:移动到前一个long字
  • n--:减少long字索引
  • 检查每个long字的有效性
  • 如果已经找到最大值(max != 0),继续检查但不重新计算

5. 计算最大文件描述符

get_max:do {max++;set >>= 1;} while (set);max += n * __NFDBITS;
}

最大值计算

  • do-while 循环:计算当前long字中最高位的设置位置
  • max += n * __NFDBITS:加上基础偏移量

四、测试select

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/select.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>int main() {fd_set readfds;struct timeval tv;int ret;char buffer[1024];printf("=== select 系统调用测试程序 ===\n");printf("程序将监控标准输入(stdin)\n");printf("请输入文字,程序将在5秒超时内等待输入...\n\n");while (1) {// 清空文件描述符集合FD_ZERO(&readfds);// 将标准输入(文件描述符0)添加到读集合FD_SET(STDIN_FILENO, &readfds);// 设置5秒超时tv.tv_sec = 5;tv.tv_usec = 0;printf("等待输入(5秒超时)...");fflush(stdout);// 调用select系统调用ret = select(STDIN_FILENO + 1, &readfds, NULL, NULL, &tv);if (ret == -1) {// 错误情况perror("select错误");break;} else if (ret == 0) {// 超时情况printf("超时!没有检测到输入。\n\n");continue;} else {// 有文件描述符就绪if (FD_ISSET(STDIN_FILENO, &readfds)) {printf("检测到输入!\n");// 读取输入ssize_t bytes_read = read(STDIN_FILENO, buffer, sizeof(buffer) - 1);if (bytes_read > 0) {buffer[bytes_read] = '\0';  // 添加字符串结束符printf("读取到 %zd 字节: %s", bytes_read, buffer);// 检查退出条件if (strncmp(buffer, "quit", 4) == 0 || strncmp(buffer, "exit", 4) == 0) {printf("退出程序。\n");break;}} else if (bytes_read == 0) {printf("标准输入已关闭。\n");break;} else {perror("读取错误");}}printf("\n");}}return 0;
}

1.编译执行验证

编译

gcc select_test.c -o select_test

执行

./select_test

验证,预期输入输出

请输入文字,程序将在5秒超时内等待输入...等待输入(5秒超时)...test
检测到输入!
读取到 5 字节: test等待输入(5秒超时)...超时!没有检测到输入。等待输入(5秒超时)...
http://www.dtcms.com/a/453200.html

相关文章:

  • /UI2/CL_JSON=>DESERIALIZE :JSON反序列化
  • MySQL主主复制+Keepalived高可用集群搭建与故障切换实战
  • 幼儿网站源代码室内设计案例去什么网站
  • Spring Framework源码解析——BeanFactoryAware
  • Linux系统--进程通信初解
  • 企业网站如何建设报告jsp简述网站开发流程
  • VS2022创建项目工程笔记
  • 【学习笔记05】C++11新特性学习总结(下)
  • RNN、LSTM与GRU模型
  • 基于华为云IOT设计的粮仓环境监测系统_303
  • 天津做网站企业保险公司网站策划
  • Linux-> TCP 编程2
  • 视频批量混剪、批量拼接,维多快剪-批量创作插件使用说明
  • JAVA算法练习题day30
  • 网站怎么做平台长沙官网制作
  • 做网站分前台后端吗怎样做一个网站平台
  • C++:异常处理与智能指针实战指南
  • 做芯片外贸生意上哪个网站深圳高端做网站公司
  • AutoCoder Nano 是一款轻量级的编码助手, 利用大型语言模型(LLMs)帮助开发者编写, 理解和修改代码。
  • Easyx使用(对弈类小作品)
  • 网站设计东莞wordpress 评论加星
  • AI(学习笔记第十课) 使用langchain的AI tool
  • 算法基础 典型题 堆
  • UVa 463 Polynomial Factorization
  • 老题新解|十进制转二进制
  • 数字信号处理 第八章(多采样率数字信号处理)
  • 网站制作农业免费封面设计在线制作生成
  • 多线程:三大集合类
  • html css js网页制作成品——化妆品html+css+js (7页)附源码
  • OpenAI战略转型深度解析:从模型提供商到全栈生态构建者的野望