ev_loop_fork函数

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集合失效问题

cpp 复制代码
#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;
}

运行结果

cpp 复制代码
Child: Attempting to use inherited kqueue...
Child: kevent returned 0 events (expected: 1)  # 子进程事件丢失!
Parent: kevent returned 1 events               # 父进程正常

修改代码子进程可以正常接收父进程的fd集合

cpp 复制代码
#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;
}

运行结果

cpp 复制代码
Parent: Started event loop
[Parent] Received data: hello  # 父进程正常接收
Child: Started event loop
[Child] Received data: hello   # 子进程修复后也能接收

结合libev接口,父子进程共享循环时的正确用法

cpp 复制代码
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_fd0,事件循环本身仍需正确的后端支持。

虽然子进程没有主动使用 parent_watcher(监视 pipe_fd0),但该监视器仍存在于 loop 中(因为它是父进程注册的)。未重置的事件循环可能会错误地尝试处理这些继承的监视器,导致未定义行为。

一般情况下都是搭配libev开源库的API函数(ev_fork_init,ev_fork_start等)一起使用:

cpp 复制代码
#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;
}
相关推荐
阿成学长_Cain41 分钟前
Linux dirs命令详解|Bash目录堆栈管理快速切换目录实战教程
linux·运维·前端·数据库
山登绝顶我为峰 3(^v^)31 小时前
C/C++ 交叉编译方法
java·c语言·c++
ICECREAM2 小时前
Linux 进程间通讯(IPC)——总结
linux
想你依然心痛3 小时前
嵌入式Linux安全加固:SELinux、Capabilities与Seccomp——强制访问控制与沙箱
linux·运维·安全
REDcker4 小时前
macOS 挂载 Linux 远程目录详解
linux·运维·macos
2601_961593424 小时前
Mac 上搭建Linux环境吗?VMware + CentOS Stream 9 镜像快速部署
linux·运维·ide·macos·centos
dddwjzx4 小时前
嵌入式Linux C应用编程入门——线程 (二)
linux·嵌入式
写代码的学渣5 小时前
Linux systemd 开机启动日志逐行详细解析报告
linux·运维·服务器
LONGZETECH5 小时前
新能源汽车动力电池检测仿真教学系统:C/S 分层架构与数字化实训落地全解析
大数据·c语言·开发语言·人工智能·架构·系统架构·汽车
阿成学长_Cain5 小时前
Linux telinit 命令详解:运行级别切换|关机重启|系统维护一站式掌握
linux·运维·前端·网络