1. 理解阻塞与非阻塞I/O的本质差异在嵌入式Linux开发中设备文件的读写操作存在两种根本不同的模式阻塞(Blocking)与非阻塞(Non-blocking)。这两种模式决定了应用程序与硬件交互时的行为特性也是驱动开发者必须掌握的核心概念。阻塞模式下当进程发起一个I/O操作时如读取开发板上的传感器数据如果设备当前没有数据可读进程会进入休眠状态TASK_INTERRUPTIBLE让出CPU资源。这种休眠状态会一直持续到以下两种情况之一发生设备数据就绪如传感器有新数据产生收到信号中断如用户按下CtrlC典型的阻塞调用示例fd open(/dev/sensor, O_RDONLY); // 默认以阻塞方式打开 read(fd, buf, sizeof(buf)); // 若无数据则休眠在此处而非阻塞模式下无论设备是否就绪I/O调用都会立即返回。对于没有数据可读的情况非阻塞读取会返回-1并设置errno为EAGAIN或EWOULDBLOCK。这种模式通常需要配合轮询机制使用适合需要同时监控多个设备的场景。非阻塞调用的典型代码结构fd open(/dev/sensor, O_RDONLY | O_NONBLOCK); // 显式指定非阻塞 while(1) { ret read(fd, buf, sizeof(buf)); if(ret -1 errno EAGAIN) { usleep(100000); // 适当延时避免CPU占用过高 continue; } // 处理有效数据... }关键区别阻塞调用会让出CPU资源而非阻塞调用会立即返回并保持进程活跃。选择哪种模式取决于应用场景对实时性和资源占用的要求。2. Sinlinx A33开发板的等待队列实现机制2.1 等待队列在内核中的数据结构Linux内核通过等待队列(wait_queue)实现阻塞I/O的休眠唤醒机制。在Sinlinx A33的Linux内核中每个可能引发阻塞的设备都会维护自己的等待队列头struct wait_queue_head { spinlock_t lock; struct list_head head; };驱动开发者需要在自己的设备结构体中包含这个队列头struct a33_sensor_dev { struct cdev cdev; wait_queue_head_t read_queue; atomic_t data_ready; // 数据就绪标志 // 其他设备特定字段... };2.2 初始化等待队列在设备初始化阶段通常在probe函数或open函数中需要初始化等待队列头init_waitqueue_head(dev-read_queue); atomic_set(dev-data_ready, 0); // 初始状态无数据2.3 休眠与唤醒的底层原理当进程因阻塞调用进入休眠时内核会执行以下操作将当前进程加入设备的等待队列设置进程状态为TASK_INTERRUPTIBLE调用schedule()让出CPU当中断发生或设备数据就绪时驱动需要调用wake_up系列函数唤醒队列中的进程wake_up_interruptible(dev-read_queue); // 唤醒单个进程 wake_up_interruptible_all(dev-read_queue); // 唤醒所有等待进程实际开发中发现在Sinlinx A33的多核环境下必须使用内存屏障确保标志变量的可见性。建议使用atomic_t类型配合smp_mb()屏障指令。3. poll系统调用的驱动实现详解3.1 file_operations中的poll方法要使设备文件支持poll/select系统调用驱动需要实现file_operations中的poll方法static unsigned int a33_sensor_poll(struct file *filp, poll_table *wait) { struct a33_sensor_dev *dev filp-private_data; unsigned int mask 0; poll_wait(filp, dev-read_queue, wait); if (atomic_read(dev-data_ready)) mask | POLLIN | POLLRDNORM; // 可读事件 return mask; }3.2 poll_table_entry与等待队列的关联当用户空间调用poll时内核会为每个被监控的文件描述符创建poll_table_entry调用驱动的poll方法将当前进程加入各个设备的等待队列关键数据结构关系进程task_struct → poll_wqueues → poll_table_entry → wait_queue_t → wait_queue_head_t3.3 典型的事件标志位驱动poll方法需要返回适当的事件标志POLLIN有数据可读POLLOUT可写而不阻塞POLLERR发生错误POLLHUP设备断开在Sinlinx A33的实际开发中我们发现GPIO中断触发的设备需要额外处理POLLPRI高优先级数据标志用于处理紧急信号。4. 阻塞与非阻塞的驱动代码实战4.1 支持两种模式的read实现一个完整的read函数需要处理两种打开方式static ssize_t a33_sensor_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos) { struct a33_sensor_dev *dev filp-private_data; DEFINE_WAIT(wait); int ret; if (filp-f_flags O_NONBLOCK) { if (!atomic_read(dev-data_ready)) return -EAGAIN; } else { for (;;) { prepare_to_wait(dev-read_queue, wait, TASK_INTERRUPTIBLE); if (atomic_read(dev-data_ready)) break; if (signal_pending(current)) { ret -ERESTARTSYS; goto out; } schedule(); } finish_wait(dev-read_queue, wait); } // 实际数据拷贝操作... atomic_set(dev-data_ready, 0); return ret; out: finish_wait(dev-read_queue, wait); return ret; }4.2 中断处理中的唤醒机制在Sinlinx A33的中断服务程序中需要设置标志并唤醒等待进程static irqreturn_t a33_sensor_isr(int irq, void *dev_id) { struct a33_sensor_dev *dev dev_id; // 处理硬件中断... atomic_set(dev-data_ready, 1); smp_mb(); // 确保标志写入对其他核可见 wake_up_interruptible(dev-read_queue); return IRQ_HANDLED; }4.3 用户空间的两种使用模式对比阻塞模式下的典型应用代码int fd open(/dev/a33_sensor, O_RDONLY); char buf[32]; read(fd, buf, sizeof(buf)); // 阻塞直到数据就绪非阻塞模式配合poll的典型结构int fd open(/dev/a33_sensor, O_RDONLY | O_NONBLOCK); struct pollfd fds { .fd fd, .events POLLIN }; while(1) { int ret poll(fds, 1, 1000); // 1秒超时 if(ret 0 (fds.revents POLLIN)) { read(fd, buf, sizeof(buf)); // 此时不会阻塞 } // 其他任务处理... }调试经验在Sinlinx A33上发现当同时监控多个设备时非阻塞模式配合epoll的性能明显优于基本poll特别是在高频数据采集场景下。5. 性能优化与常见问题排查5.1 避免惊群效应当多个进程等待同一设备时简单的wake_up_all可能导致所有进程都被唤醒但只有一个能获取资源称为惊群效应。在Sinlinx A33驱动中我们采用以下优化// 修改唤醒方式为 wake_up_interruptible_nr(dev-read_queue, 1); // 只唤醒1个进程5.2 正确处理信号中断阻塞操作可能被信号中断驱动必须妥善处理if (wait_event_interruptible(dev-read_queue, atomic_read(dev-data_ready))) { return -ERESTARTSYS; // 被信号中断 }5.3 调试技巧与工具在Sinlinx A33开发过程中这些调试方法特别有用使用ftrace跟踪等待队列状态echo 1 /sys/kernel/debug/tracing/events/sched/sched_wakeup/enable cat /sys/kernel/debug/tracing/trace_pipe通过/proc/ /wchan查看进程阻塞位置使用strace观察用户空间poll/read调用行为5.4 真实案例超时处理不当导致CPU占用100%曾遇到一个案例开发者混合使用了poll的超时和非阻塞read但没有正确处理EAGAIN导致忙等待。正确的做法应该是// 错误示例CPU高占用 while(poll(fds, 1, 100) 0) { ret read(fd, buf, sizeof(buf)); // 非阻塞模式下立即返回 // 没有延时导致循环高速运行 } // 正确改进 while(1) { if(poll(fds, 1, 100) 0) { while((ret read(fd, buf, sizeof(buf))) ! -1) { // 处理数据... } if(ret -1 errno ! EAGAIN) break; } // 其他低优先级任务... usleep(10000); // 适当让步CPU }在Sinlinx A33的实际测量中优化后的版本将CPU占用从接近100%降到了不足5%。