You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

ARM嵌入式MQTT客户端pthread中epoll_wait后read阻塞问题求助

解决GPIO边沿检测线程中read阻塞的问题

问题根源分析

你的代码出现线程中read阻塞的核心原因有两个:

  1. GPIO sysfs接口的边沿触发通知依赖EPOLLPRI事件而非EPOLLIN,错误的事件监听导致epoll触发后无有效数据可读,进而阻塞read;
  2. 打开GPIO value文件时未设置非阻塞模式,即使无数据read也会一直等待。

另外,epoll_wait返回-1大概率是被信号中断(比如MQTT操作触发的信号),但这不会导致read阻塞,只需忽略该错误继续循环即可。

具体修改方案

1. 修改文件打开模式,添加非阻塞标记

打开GPIO value文件时,加上O_NONBLOCK,确保read操作不会阻塞:

fd = open("/sys/class/gpio/gpio13/value", O_RDONLY | O_NONBLOCK);

2. 修正epoll监听的事件类型

将监听事件改为EPOLLPRI | EPOLLET,这是GPIO sysfs边沿触发的标准通知事件:

ev.events = EPOLLPRI | EPOLLET;

3. 完善read操作的错误处理

添加对read返回值的错误判断,处理非阻塞情况下的EAGAIN(无数据可读):

nn = read(events.data.fd, &strvalue, 1);
if (nn == -1) {
    if (errno != EAGAIN) {
        perror("read failed");
        // 可选:处理严重错误,比如关闭fd退出线程
    }
    continue;
} else if (nn == 0) {
    printf("EOF reached\n");
    continue;
}

4. 处理epoll_wait的中断错误

当epoll_wait返回-1时,判断是否为信号中断(EINTR),这种情况无需处理,直接继续循环:

ret = epoll_wait(ep, &events, 1, -1);
if (ret == -1) {
    if (errno == EINTR) {
        continue; // 被信号中断,继续等待
    }
    perror("epoll_wait failed");
    break; // 其他错误,退出循环
}

完整修正后的detection函数

// detection function call as pthread
void *detection(void *args){

    char strvalue[1];
    int ret;
    int nn;
    int ep;
    int fd;

    struct epoll_event ev, events;

    ep = epoll_create1(0);
    if (ep == -1) {
        perror("epoll_create1 failed");
        return NULL;
    }
    
    fd = open("/sys/class/gpio/gpio13/value", O_RDONLY | O_NONBLOCK);
    if (fd == -1) {
        perror("open gpio value failed");
        close(ep);
        return NULL;
    }
    
    nn = read(fd, &strvalue, 1);
    if (nn > 0) {
        printf("Initial value = %c\n", strvalue[0]);
        lseek(fd, 0, SEEK_SET);
    } else if (nn == -1 && errno != EAGAIN) {
        perror("initial read failed");
        close(fd);
        close(ep);
        return NULL;
    }
    
    ev.events = EPOLLPRI | EPOLLET;
    ev.data.fd = fd;

    ret = epoll_ctl(ep, EPOLL_CTL_ADD, fd, &ev);
    if (ret == -1) {
        perror("epoll_ctl add failed");
        close(fd);
        close(ep);
        return NULL;
    }
    printf("ret=%d\n", ret);
    
    while (1)
    {
        printf("Waiting\n");
        
        ret = epoll_wait(ep, &events, 1, -1);
        if (ret == -1) {
            if (errno == EINTR) {
                printf("epoll_wait interrupted by signal\n");
                continue;
            }
            perror("epoll_wait failed");
            break;
        }
        printf("ret=%d\n", ret);
        
        if(ret > 0) {
            lseek(fd, 0, SEEK_SET);
            printf("fd=%d\n", events.data.fd);

            nn = read(events.data.fd, &strvalue, 1);
            if (nn == -1) {
                if (errno != EAGAIN) {
                    perror("read failed");
                }
                continue;
            } else if (nn == 0) {
                printf("EOF on gpio value file\n");
                continue;
            }
            printf("nn=%d\n", nn);
            printf("value = %c\n", strvalue[0]);
            
            // 在这里添加MQTT消息发送逻辑
        }
        
    }
    
    // 清理资源
    close(fd);
    close(ep);
    return NULL;
}

额外注意事项

  • 确保GPIO的edge配置正确为both,你已经完成这一步,无需修改;
  • 主函数中使用pthread_cancel终止线程时,建议先设置线程取消状态为可取消,或者在检测线程中设置取消点(比如pthread_testcancel()),避免资源泄漏;
  • 如果MQTT客户端库会发送信号,确保信号处理不会影响epoll的正常运行。

内容的提问来源于stack exchange,提问作者dirandad

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.03 19:55:15