ARM嵌入式MQTT客户端pthread中epoll_wait后read阻塞问题求助
解决GPIO边沿检测线程中read阻塞的问题
问题根源分析
你的代码出现线程中read阻塞的核心原因有两个:
- GPIO sysfs接口的边沿触发通知依赖EPOLLPRI事件而非EPOLLIN,错误的事件监听导致epoll触发后无有效数据可读,进而阻塞read;
- 打开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
相关产品推荐
相关产品推荐

