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

【操作系统百科】fd 化抽象

文章导航

分类入口
os
标签入口
#signalfd#eventfd#timerfd#pidfd#fd-abstraction

目录

Unix 哲学”一切皆文件”——Linux 更进一步:把信号、事件、定时器、进程都变成 fd,统一用 epoll/io_uring 等待。

一、先看图

flowchart TD
    EPOLL[epoll / io_uring]
    EPOLL --- SFD[signalfd<br/>信号 → fd]
    EPOLL --- EFD[eventfd<br/>计数器 → fd]
    EPOLL --- TFD[timerfd<br/>定时器 → fd]
    EPOLL --- PFD[pidfd<br/>进程 → fd]
    EPOLL --- SOCK[socket fd]
    EPOLL --- FILE[文件 fd]
    classDef center fill:#388bfd22,stroke:#388bfd,color:#adbac7;
    classDef leaf fill:#3fb95022,stroke:#3fb950,color:#adbac7;
    class EPOLL center
    class SFD,EFD,TFD,PFD,SOCK,FILE leaf

二、signalfd

2.1 问题

信号打断 epoll_wait → 处理复杂、不安全。

2.2 方案

sigset_t mask;
sigemptyset(&mask);
sigaddset(&mask, SIGINT);
sigaddset(&mask, SIGTERM);
sigprocmask(SIG_BLOCK, &mask, NULL);  // 阻塞传统信号

int sfd = signalfd(-1, &mask, SFD_NONBLOCK | SFD_CLOEXEC);
// 加入 epoll → read(sfd) 读取 struct signalfd_siginfo

信号变成可 read 的事件 → 和 socket 统一处理。

2.3 内核实现

signalfd_poll() 检查 pending signal → 加入 epoll 就绪。

三、eventfd

3.1 用途

进程/线程间的简单通知——内核维护一个 64 位计数器。

int efd = eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC);
// 通知方:
uint64_t val = 1;
write(efd, &val, sizeof(val));    // 计数器 += val
// 等待方:
uint64_t val;
read(efd, &val, sizeof(val));     // 读出计数器并清零

3.2 EFD_SEMAPHORE

int efd = eventfd(0, EFD_SEMAPHORE);
// read 每次返回 1,计数器减 1

3.3 KVM 的 eventfd

KVM 用 eventfd 连接 guest 中断和 host 事件:

四、timerfd

4.1 问题

setitimer / timer_create 用信号通知 → 不安全、不可组合。

4.2 方案

int tfd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK | TFD_CLOEXEC);
struct itimerspec its = {
    .it_value    = { .tv_sec = 1 },
    .it_interval = { .tv_sec = 1 },
};
timerfd_settime(tfd, 0, &its, NULL);
// 加入 epoll → read(tfd) 返回超时次数

4.3 精度

底层用 hrtimer → 纳秒精度(取决于硬件)。

五、pidfd(5.3+)

5.1 问题

waitpid(pid) 有 PID 重用竞争——进程退出后 PID 可能被新进程占用。

5.2 方案

int pidfd = pidfd_open(child_pid, 0);
// 加入 epoll → 子进程退出时就绪
// 或者
pidfd_send_signal(pidfd, SIGTERM, NULL, 0);  // 不怕 PID 重用

5.3 systemd 使用

systemd 254+ 用 pidfd 管理服务进程生命周期。

六、userfaultfd

int uffd = userfaultfd(O_NONBLOCK | O_CLOEXEC);
// 注册地址范围 → page fault 时通知到 uffd → 用户态处理

用于:

七、fd 化哲学

7.1 为什么把一切变成 fd

7.2 代价

八、统一事件循环模式

int epfd = epoll_create1(EPOLL_CLOEXEC);
epoll_ctl(epfd, EPOLL_CTL_ADD, listen_fd, &ev_listen);
epoll_ctl(epfd, EPOLL_CTL_ADD, signalfd,   &ev_signal);
epoll_ctl(epfd, EPOLL_CTL_ADD, timerfd,    &ev_timer);
epoll_ctl(epfd, EPOLL_CTL_ADD, eventfd,    &ev_notify);

while (1) {
    int n = epoll_wait(epfd, events, MAX, -1);
    for (int i = 0; i < n; i++) {
        dispatch(events[i]);
    }
}

网络连接、信号、定时、自定义事件 → 一个循环处理全部。

九、观察

ls -la /proc/<pid>/fd          # 查看 fd 类型
cat /proc/<pid>/fdinfo/<fd>    # fd 详情
lsof -p <pid>                  # anon_inode:[eventfd] 等

# 统计系统 fd 使用
cat /proc/sys/fs/file-nr       # 已分配 / 未用 / 上限

十、小结


参考文献

工具


上一篇select/poll 下一篇splice/tee/vmsplice

同主题继续阅读

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

2026-04-17 · os

【操作系统百科】信号:Unix 最拧巴的抽象

signal 在 Unix 里几乎等同「异步打断」,但它的 API 踩满雷:不可重入的 handler、ASS-safe 函数清单、SIGCHLD 丢失、多线程语义、SIG_DFL 的历史包袱。本文讲 kill/tgkill/rt_sigaction、signalfd、pidfd_send_signal、async-signal-safe 的真实边界,以及为什么新代码应该尽量把 signal 转成 fd。

2026-05-10 · os

【操作系统百科】fd 表与 struct file

fd 是用户态访问文件的句柄——但 fd 的共享语义在 fork/exec/dup/CLOEXEC 下极其微妙。本文讲 files_struct、fdtable、close_range、pidfd_getfd、cloexec 默认化趋势。

2026-04-17 · os

【操作系统百科】进程生命周期:clone → exec → exit → reap

一个进程从诞生到尸体被回收,在内核里走过六个阶段:clone → run → exec(可选)→ exit → zombie → release。本文按阶段讲 do_fork、bprm_execve、do_exit、release_task,以及 waitpid/pidfd/subreaper 的收尸规则、孤儿与僵尸的语义、systemd PID 1 的特殊性。

2026-04-27 · os

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

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


By .