Linux 2.6.22+ 內核引入三種特殊文件描述符,將信號、定時器、事件通知統一抽象為「可讀的文件描述符」,使它們能無縫接入 select/poll/epoll 事件循環,極大簡化了異步編程模型。
| FD 類型 | 引入版本 | 核心作用 | 典型應用 |
|---|---|---|---|
| signalfd | 2.6.22 | 信號 → 可讀 FD | 統一信號處理、避免信號處理函數的可重入問題 |
| timerfd | 2.6.25 | 定時器 → 可讀 FD | 高精度定時任務、事件循環整合定時器 |
| eventfd | 2.6.22 | 計數器 → 可讀 FD | 線程/進程間通知、用戶態與內核通信 |
#includeint signalfd(int fd, const sigset_t *mask, int flags);
fd = -1:創建新 FD;fd > 0:修改現有 FD 監聽的信號集flags:SFD_NONBLOCK (非阻塞)、SFD_CLOEXEC (執行 exec 時關閉)#include#include #include #include #include #define handle_error(msg) do { perror(msg); exit(EXIT_FAILURE); } while (0) int main() { sigset_t mask; sigemptyset(&mask); sigaddset(&mask, SIGINT); // Ctrl+C sigaddset(&mask, SIGQUIT); // Ctrl+\ // 關鍵:阻塞信號,防止默認處理動作 if (sigprocmask(SIG_BLOCK, &mask, NULL) == -1) handle_error("sigprocmask"); int sfd = signalfd(-1, &mask, 0); if (sfd == -1) handle_error("signalfd"); struct signalfd_siginfo fdsi; for (;;) { ssize_t s = read(sfd, &fdsi, sizeof(fdsi)); if (s != sizeof(fdsi)) handle_error("read"); if (fdsi.ssi_signo == SIGINT) printf("Got SIGINT\n"); else if (fdsi.ssi_signo == SIGQUIT) { printf("Got SIGQUIT\n"); exit(EXIT_SUCCESS); } } }
#includeint timerfd_create(int clockid, int flags); int timerfd_settime(int fd, int flags, const struct itimerspec *new, struct itimerspec *old); int timerfd_gettime(int fd, struct itimerspec *curr);
struct itimerspec {
struct timespec it_interval; // 週期間隔
struct timespec it_value; // 首次觸發時間
};
struct timespec {
time_t tv_sec; // 秒
long tv_nsec; // 納秒
};
int fd = timerfd_create(CLOCK_MONOTONIC, 0); // 單調時鐘,不受系統時間調整影響
struct itimerspec ts = {
.it_value = {5, 0}, // 5 秒後首次觸發
.it_interval = {2, 0} // 之後每 2 秒觸發
};
timerfd_settime(fd, 0, &ts, NULL);
uint64_t exp;
for (;;) {
read(fd, &exp, sizeof(exp)); // 阻塞等待,exp 為未處理的到期次數
printf("Timer triggered %llu times\n", exp);
}
編譯:g++ -lrt timerfd.cc -o timerfd (需鏈接 rt 庫)
uint64_t 計數器write:計數器 += 寫入值 (8 字節)read:讀取計數器值並**清零**EFD_SEMAPHORE 模式:read 只減 1,不清零 (信號量語義)int efd = eventfd(0, EFD_NONBLOCK);
if (fork() == 0) { // 子進程:寫入
for (int i = 1; i < argc; i++) {
uint64_t val = strtoull(argv[i], NULL, 0);
write(efd, &val, sizeof(val));
}
exit(0);
} else { // 父進程:讀取
sleep(2);
uint64_t val;
read(efd, &val, sizeof(val));
printf("Parent read: %llu\n", val);
}
epoll_wait 一處處理,避免多線程同步複雜度