usb挂起与文件描述符
问题分析
当 USB 设备挂起时,已经打开的文件描述符通常不会自动关闭,这是因为:
-
文件描述符是内核资源:即使底层硬件不可用,文件描述符仍存在于进程中
-
引用计数机制:只要进程持有引用,描述符就不会自动释放
-
USB 挂起 ≠ 设备移除:挂起是电源管理状态,设备仍在系统总线上
可能的原因
1. 描述符状态变化
// 典型的现象
int fd = open("/dev/ttyUSB0", O_RDWR | O_NOCTTY);
// 挂起 USB 设备
system("echo suspend > /sys/bus/usb/devices/X-X/power/level");
// fd 仍然有效,但 I/O 操作会失败或阻塞
2. 检查描述符状态的几种方法
# 查看文件描述符信息
ls -l /proc/<PID>/fd/
ls -l /proc/<PID>/fdinfo/
# 查看 USB 设备状态
lsusb -t
cat /sys/bus/usb/devices/X-X/power/runtime_status
3. 程序示例:检测挂起状态
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/ioctl.h>
#include <linux/serial.h>
int check_port_status(int fd) {
struct serial_struct serinfo;
if (ioctl(fd, TIOCGSERIAL, &serinfo) < 0) {
perror("TIOCGSERIAL failed");
return -1;
}
// 检查端口状态
printf("Port type: %d\n", serinfo.type);
printf("Port flags: 0x%x\n", serinfo.flags);
return 0;
}
int main() {
int fd = open("/dev/ttyUSB0", O_RDWR | O_NOCTTY);
if (fd < 0) {
perror("open failed");
return 1;
}
printf("Descriptor opened: fd=%d\n", fd);
// 尝试读取(可能会阻塞或失败)
char buf[10];
int n = read(fd, buf, sizeof(buf));
if (n < 0) {
printf("Read failed: %s (errno=%d)\n", strerror(errno), errno);
}
close(fd);
return 0;
}
解决方案
方案1:监控 USB 事件并重新初始化
// 使用 udev 监控 USB 事件
#include <libudev.h>
#include <poll.h>
void monitor_usb_events() {
struct udev *udev = udev_new();
struct udev_monitor *mon = udev_monitor_new_from_netlink(udev, "udev");
udev_monitor_filter_add_match_subsystem_devtype(mon, "usb", NULL);
udev_monitor_enable_receiving(mon);
int fd = udev_monitor_get_fd(mon);
struct pollfd pfd = {.fd = fd, .events = POLLIN};
while (1) {
poll(&pfd, 1, -1);
if (pfd.revents & POLLIN) {
struct udev_device *dev = udev_monitor_receive_device(mon);
const char *action = udev_device_get_action(dev);
const char *syspath = udev_device_get_syspath(dev);
printf("USB event: %s on %s\n", action, syspath);
if (strcmp(action, "suspend") == 0) {
// 处理挂起事件
handle_suspend_event();
} else if (strcmp(action, "resume") == 0) {
// 处理恢复事件
handle_resume_event();
}
udev_device_unref(dev);
}
}
}
方案2:使用非阻塞模式和超时
// 设置非阻塞 I/O 和超时
int setup_serial_timeout(int fd) {
// 设置非阻塞
int flags = fcntl(fd, F_GETFL, 0);
fcntl(fd, F_SETFL, flags | O_NONBLOCK);
// 设置超时
struct termios options;
tcgetattr(fd, &options);
options.c_cc[VMIN] = 0; // 最小字符数
options.c_cc[VTIME] = 10; // 超时(1秒 = 10*0.1秒)
tcsetattr(fd, TCSANOW, &options);
return 0;
}
方案3:完整的重连机制
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <sys/epoll.h>
#define MAX_EVENTS 10
typedef struct {
int fd;
char device[64];
int connected;
} serial_port_t;
void reconnect_port(serial_port_t *port) {
if (port->fd >= 0) {
close(port->fd);
port->fd = -1;
}
int retries = 5;
while (retries-- > 0) {
port->fd = open(port->device, O_RDWR | O_NOCTTY | O_NONBLOCK);
if (port->fd >= 0) {
printf("Reconnected to %s\n", port->device);
port->connected = 1;
// 重新配置串口参数
struct termios options;
tcgetattr(port->fd, &options);
cfsetispeed(&options, B9600);
cfsetospeed(&options, B9600);
tcsetattr(port->fd, TCSANOW, &options);
break;
}
sleep(1);
}
if (port->fd < 0) {
fprintf(stderr, "Failed to reconnect to %s\n", port->device);
port->connected = 0;
}
}
int main() {
serial_port_t port = {
.fd = -1,
.device = "/dev/ttyUSB0",
.connected = 0
};
// 初始连接
reconnect_port(&port);
int epoll_fd = epoll_create1(0);
struct epoll_event ev, events[MAX_EVENTS];
ev.events = EPOLLIN | EPOLLERR | EPOLLHUP;
ev.data.fd = port.fd;
epoll_ctl(epoll_fd, EPOLL_CTL_ADD, port.fd, &ev);
while (1) {
int nfds = epoll_wait(epoll_fd, events, MAX_EVENTS, 1000);
for (int i = 0; i < nfds; i++) {
if (events[i].events & EPOLLHUP) {
printf("Device disconnected or suspended\n");
port.connected = 0;
reconnect_port(&port);
}
if (events[i].events & EPOLLIN) {
char buffer[256];
ssize_t n = read(port.fd, buffer, sizeof(buffer));
if (n > 0) {
// 处理数据
} else if (n < 0 && errno != EAGAIN) {
// 错误处理
port.connected = 0;
reconnect_port(&port);
}
}
}
}
return 0;
}
建议的调试步骤
-
确认挂起状态
cat /sys/bus/usb/devices/<bus>-<port>/power/runtime_status -
监控内核消息
dmesg -w # 或 journalctl -f -k -
测试文件描述符
# 检查文件描述符是否还有效 ls -l /proc/self/fd/3 stat /proc/self/fd/3 -
使用 strace 跟踪
strace -e openat,close,read,write -p <PID>
最佳实践
-
总是检查 I/O 操作的返回值
-
实现超时和重试机制
-
使用非阻塞模式或 select/poll/epoll
-
监控 udev 事件来响应硬件状态变化
-
实现优雅的重连逻辑
文件描述符在 USB 挂起时不会自动消失是正常行为,你需要主动检测和处理挂起/恢复事件,并实现相应的错误恢复机制。

浙公网安备 33010602011771号