?libev監視器介紹:libev監視器用法-CSDN博客
libev loop對象介紹:loop對象-CSDN博客
libev ev_loop_fork函數介紹:ev_loop_fork函數-CSDN博客
libev API吐血整理:https://download.csdn.net/download/qq_39466755/90794251?spm=1001.2014.3001.5503
用于解決fork函數導致子進程集成的fd集合失效問題
#include <stdio.h>
#include <unistd.h>
#include <sys/event.h>
#include <fcntl.h>void child_process(int kq) {printf("Child: Attempting to use inherited kqueue...\n");struct kevent events[1];int n = kevent(kq, NULL, 0, events, 1, NULL); // 無超時等待printf("Child: kevent returned %d events (expected: 1)\n", n);
}int main() {int kq = kqueue();int pipe_fd[2];pipe(pipe_fd);// 監控管道讀端struct kevent ev;EV_SET(&ev, pipe_fd[0], EVFILT_READ, EV_ADD, 0, 0, NULL);kevent(kq, &ev, 1, NULL, 0, NULL);// 觸發事件write(pipe_fd[1], "test", 5);pid_t pid = fork();if (pid == 0) {child_process(kq); // 子進程直接使用繼承的 kqueue_exit(0);} else {struct kevent events[1];int n = kevent(kq, NULL, 0, events, 1, NULL);printf("Parent: kevent returned %d events\n", n);}return 0;
}
運行結果
Child: Attempting to use inherited kqueue...
Child: kevent returned 0 events (expected: 1) # 子進程事件丟失!
Parent: kevent returned 1 events # 父進程正常
修改代碼子進程可以正常接收父進程的fd集合
#include <ev.h>
#include <stdio.h>
#include <unistd.h>
#include <signal.h>// 管道讀端回調
static void pipe_cb(struct ev_loop *loop, ev_io *w, int revents) {char buf[256];ssize_t n = read(w->fd, buf, sizeof(buf));printf("[%s] Received data: %.*s\n", getpid() == getppid() ? "Parent" : "Child", (int)n, buf);
}int main() {// 忽略 SIGPIPE(防止寫入關閉的管道導致進程退出)signal(SIGPIPE, SIG_IGN);struct ev_loop *loop = EV_DEFAULT;int pipe_fd[2];pipe(pipe_fd);// 監控管道讀端ev_io pipe_watcher;ev_io_init(&pipe_watcher, pipe_cb, pipe_fd[0], EV_READ);ev_io_start(loop, &pipe_watcher);// 寫入數據(觸發事件)write(pipe_fd[1], "hello", 6);pid_t pid = fork();if (pid == 0) {// ---------- 關鍵修復 ----------ev_loop_fork(loop); // 重置內核狀態// ------------------------------printf("Child: Started event loop\n");ev_run(loop, 0); // 子進程現在能正常接收事件_exit(0);} else {printf("Parent: Started event loop\n");ev_run(loop, 0);}return 0;
}
運行結果
Parent: Started event loop
[Parent] Received data: hello # 父進程正常接收
Child: Started event loop
[Child] Received data: hello # 子進程修復后也能接收
結合libev接口,父子進程共享循環時的正確用法
struct ev_loop *loop = EV_DEFAULT;
ev_io parent_watcher;
ev_io_init(&parent_watcher, parent_cb, pipe_fd[0], EV_READ);
ev_io_start(loop, &parent_watcher);pid_t pid = fork();
if (pid == 0) {// 子進程ev_loop_fork(loop); // 先重置后端// 添加子進程獨有的監視器ev_io child_watcher;ev_io_init(&child_watcher, child_cb, another_fd, EV_WRITE);ev_io_start(loop, &child_watcher);ev_run(loop, 0); // 現在能正確處理父/子監視器的事件
} else {// 父進程繼續原邏輯ev_run(loop, 0);
}
代碼解析:
????????libev 使用底層機制(如 epoll/kqueue)來監聽文件描述符。當調用 fork() 時,子進程會繼承父進程的 epoll 實例,但該實例可能已失效(內核狀態與用戶態不一致)。ev_loop_fork() 會重建后端(如重新創建 epoll 實例),確保事件循環在子進程中能正常工作。因為struct ev_loop *loop = EV_DEFAULT;已經創建了底層的事件監聽機制(如 epoll、kqueue 或 select 等,具體取決于系統支持)。
????????即使子進程不直接使用 pipe_fd[0],事件循環本身仍需正確的后端支持。
????????雖然子進程沒有主動使用 parent_watcher(監視 pipe_fd[0]),但該監視器仍存在于 loop 中(因為它是父進程注冊的)。未重置的事件循環可能會錯誤地嘗試處理這些繼承的監視器,導致未定義行為。
????????一般情況下都是搭配libev開源庫的API函數(ev_fork_init,ev_fork_start等)一起使用:
#include <ev.h>
#include <unistd.h>
#include <stdio.h>// fork 回調函數
void fork_cb(EV_P_ ev_fork *w, int revents) {printf("Child process (PID: %d) reinitializing event loop...\n", getpid());ev_loop_fork(EV_A); // 必須調用,重新初始化子進程的事件循環
}int main() {struct ev_loop *loop = EV_DEFAULT;struct ev_fork fork_watcher;// 初始化fork監視器ev_fork_init(&fork_watcher, fork_cb);ev_fork_start(loop, &fork_watcher); // 啟動監視器printf("Parent process (PID: %d) started. Forking...\n", getpid());pid_t pid = fork();if (pid == 0) {// 子進程:ev_loop_fork已在回調中調用ev_run(loop, 0); // 子進程事件循環} else if (pid > 0) {// 父進程代碼printf("Parent process continues (child PID: %d)\n", pid);sleep(2); // 模擬父進程工作} else {perror("fork failed");return 1;}return 0;
}