Linux 採用三層結構管理開啟檔案,實現「一切皆檔案」的統一介面:
task_struct 中擁有一張 FD 表,索引即為檔案描述符(0/1/2 分別對應 stdin/stdout/stderr)。每項包含:FD_CLOEXEC)struct file)包含:O_RDONLY/O_WRONLY/O_APPEND/O_NONBLOCK 等)lseek 操作對象)struct vnode),包含:open() 時從磁碟讀入記憶體,實現快速存取dup()/fcntl(F_DUPFD) 複製 FD,父子進程 fork() 後共享檔案表項,位移量同步變化。open() 同一檔案,各自擁有位移量與存取模式,但底層 v-node 共享,修改可見。sendmsg()/recvmsg() 傳遞 SCM_RIGHTS 控制訊息,實現 FD 傳遞(參見 UNP 15.7)。主進程 listen() 後 fork() 多個子進程,各自 accept()。當連線到達,所有阻塞在 accept() 的子進程被喚醒,只有一個取得連線,其餘重新睡眠 → 驚群,浪費 CPU。
SO_REUSEPORT(Linux 3.9+)讓核心負載均衡。accept() + 多執行緒/事件循環(epoll/kqueue/IOCP)處理。accept()(accept_mutex),Nginx/Apache 採用。單進程 FD 上限預設 1024(ulimit -n 查看)。高併發下 accept() 返回 EMFILE。Muduo 庫作者陳碩方案:
int idlefd = open("/dev/null", O_RDONLY | O_CLOEXEC); // 預佔一個 FD
// 事件循環中
connfd = accept4(listenfd, ..., SOCK_NONBLOCK | SOCK_CLOEXEC);
if (connfd == -1 && errno == EMFILE) {
close(idlefd); // 釋放預佔 FD
idlefd = accept(listenfd, NULL, NULL); // 拿到新連線 FD
close(idlefd); // 立即關閉,拒絕服務
idlefd = open("/dev/null", O_RDONLY | O_CLOEXEC); // 重新佔位
continue;
}
此技巧確保事件循環不忙等,並優雅拒絕新連線而非崩潰。
fork() 後 exec(),預設保留所有 FD(除設定 FD_CLOEXEC)。可用於向新程式傳遞已開啟的檔案/通訊通道。
執行緒共享進程 FD 表,不可在某執行緒 close() 供其他執行緒使用的 FD,否則導致競態錯誤。需協調 FD 生命週期或使用連線池。