土法炼钢兴趣小组的算法知识备份

【操作系统百科】select 与 poll

文章导航

分类入口
os
标签入口
#select#poll#io-multiplex#fd-set#ppoll

目录

这两个系统调用已经被 epoll 全面超越。但理解它们——知道为什么该退场——有助于理解 I/O 多路复用的演进。

一、先看图

flowchart LR
    subgraph select
        FDS[fd_set<br/>位图 1024 bit] --> SCAN1[内核遍历<br/>每个 bit]
        SCAN1 --> COPY1[copy_to_user<br/>修改后的 fd_set]
    end
    subgraph poll
        POLLFD[struct pollfd 数组] --> SCAN2[内核遍历<br/>每个 pollfd]
        SCAN2 --> COPY2[copy_to_user<br/>设置 revents]
    end
    subgraph epoll
        ADD[epoll_ctl 一次] --> CB[回调驱动]
        CB --> READY[就绪链表<br/>O(就绪数)]
    end

    classDef old fill:#f0883e22,stroke:#f0883e,color:#adbac7;
    classDef new fill:#3fb95022,stroke:#3fb950,color:#adbac7;
    class FDS,SCAN1,COPY1,POLLFD,SCAN2,COPY2 old
    class ADD,CB,READY new

二、select

2.1 API

fd_set readfds;
FD_ZERO(&readfds);
FD_SET(sockfd, &readfds);
int n = select(sockfd + 1, &readfds, NULL, NULL, &timeout);
if (FD_ISSET(sockfd, &readfds)) { /* 可读 */ }

2.2 问题

2.3 内核实现

// fs/select.c
int core_sys_select(int n, fd_set __user *inp, ...)
{
    // copy_from_user → 逐 fd poll → 无就绪则 schedule_timeout → 再试
}

三、poll

3.1 API

struct pollfd fds[2];
fds[0].fd = sockfd;
fds[0].events = POLLIN;
int n = poll(fds, 2, timeout_ms);
if (fds[0].revents & POLLIN) { /* 可读 */ }

3.2 改进

3.3 仍有的问题

四、ppoll 与 pselect

4.1 信号安全

// 原子地设置 signal mask + poll
int ppoll(struct pollfd *fds, nfds_t nfds,
          const struct timespec *tmo_p, const sigset_t *sigmask);

解决 select/poll 与 signal 的竞争:

// 错误模式
sigprocmask(SIG_BLOCK, &mask, &oldmask);
// ← 信号可能在这里到达
select(...);  // 永远等待

// 正确模式
pselect(..., &mask);  // 原子操作

五、性能对比

方案 复杂度 fd 上限 每次 syscall 开销
select O(maxfd) 1024 copy fd_set × 2
poll O(nfds) 无硬限制 copy pollfd[] × 2
epoll O(就绪数) 无硬限制 无需 copy fd 集合

10K fd 场景:select/poll 每次 syscall 处理 10K fd;epoll 只处理就绪的几十个。

六、还有哪些代码该用 select/poll?

6.1 可以用的场景

6.2 不该用的场景

七、实现细节

7.1 do_select(select 内核路径)

// fs/select.c
static int do_select(int n, fd_set_bits *fds, struct timespec64 *end_time)
{
    for (;;) {
        for (i = 0; i < n; i++) {
            mask = vfs_poll(f.file, wait);  // 逐 fd 检查
        }
        if (retval || timed_out || signal_pending(current))
            break;
        poll_schedule_timeout(wait, ...);
    }
}

7.2 do_poll(poll 内核路径)

类似,遍历 pollfd 数组。

八、历史

从 O(n) 到 O(就绪数) 到零 syscall——20 年的演进。

九、观察

strace -e select,poll,ppoll,pselect6 <command>
# 查看哪些进程还在用 select/poll
bpftrace -e 'tracepoint:syscalls:sys_enter_select { @[comm] = count(); }'

十、小结


参考文献

工具


上一篇epoll 内部 下一篇fd 化抽象

同主题继续阅读

把当前热点继续串成多页阅读,而不是停在单篇消费。

2026-04-27 · os

【操作系统百科】内存回收

Linux 内存回收是 VM 最复杂的子系统之一。本文讲 active/inactive LRU、kswapd 与 direct reclaim、watermark 三线、swappiness 的真实含义、MGLRU 改造、memcg 回收与 PSI。

2026-04-28 · os

【操作系统百科】交换

swap 还值得开吗?本文讲 swap area 基础、swap cache、zram 压缩内存、zswap 前端压缩池、swappiness 的真实含义、容器里的 swap 策略,以及为什么现代 Android 全靠 zram 不靠磁盘。

2026-05-03 · os

【操作系统百科】Slab/SLUB 分配器

buddy 只管页粒度(4K+),内核大多数对象只有几十到几百字节。slab/SLUB 在 buddy 之上做对象级缓存。本文讲 slab 历史、SLUB 接手、SLOB 退场、kmem_cache、per-CPU cache、KASAN 集成。

2026-05-07 · os

【操作系统百科】用户态分配器

glibc malloc、tcmalloc、jemalloc、mimalloc 各有哲学。本文讲 arena、thread cache、size class、madvise 返还策略、碎片与 RSS 膨胀、如何根据负载选分配器。


By .