Linux v3.10 GPIO驱动poll延迟未生效及中断回调mutex合理性咨询
问题解答
一、驱动poll超时未生效的原因及修复
核心原因分析
从代码和dmesg日志来看,poll超时未生效主要由以下几点导致:
- poll函数逻辑违反设计原则
驱动的spiio_drv_poll函数中,检测到IrqOccurred为1时直接将其置0,这是错误的。poll的核心作用是查询事件是否就绪,而非消费事件。这种操作会导致:用户态poll返回后未调用read时,事件标志已被清零,后续中断触发又会立即置位,poll反复被唤醒,无法进入超时等待。 - 用户态测试代码未消费事件
测试代码中,poll检测到POLLIN事件后仅执行SPI传输,从未调用驱动的read函数。事件未被真正消费,加上poll函数提前清零标志,导致整个事件处理逻辑混乱。 - 中断频繁触发
从dmesg的时间戳可以看到,短时间内多次触发GPIO中断,poll被频繁唤醒,自然无法进入8ms的超时等待状态。
修复方案
- 修正驱动poll函数逻辑
移除poll函数中对IrqOccurred的修改,仅保留状态查询:static unsigned int spiio_drv_poll(struct file *file, struct poll_table_struct *wait) { printk("this is poll.\n"); poll_wait(file, &WaitQueue, wait); if (IrqOccurred) { printk(" an irq occurred. return %d\n", POLLIN); return POLLIN; } printk("no irq occurred, IrqOccurred = %d\n", IrqOccurred); return 0; } - 在read函数中消费事件
将IrqOccurred的清零操作移到read函数中,确保事件只有在被真正读取后才被标记为已处理:static ssize_t spiio_drv_read(struct file *file, char __user *buf, size_t size, loff_t *offset) { char userValue = gpio_get_value(SpiioGpio) ? 1 : 0; printk("read value is %d\n", userValue); // 消费事件,清零标志 mutex_lock(&RWLock); IrqOccurred = 0; mutex_unlock(&RWLock); if (copy_to_user(buf, &userValue, sizeof(char)) != 0) { printk("failed to copy to user\n"); return -1; } else { return sizeof(char); } } - 修改测试代码,触发read操作
在poll返回POLLIN事件后,调用read函数读取GPIO值,完成事件消费:while (1) { memset(buffer, 0, sizeof(buffer)); ret = poll(&pfd, 1, timeout_ms); revents = pfd.revents; if (ret > 0) { // 读取GPIO值,消费事件 ret2 = read(fd, buffer, sizeof(buffer)); if (ret2 > 0) { TransferSpi(SpiDevFd, &spi_settings, tx, sizeof(tx), rx); hex_dump(rx, ARRAY_SIZE(rx), 32, "RX"); } } else { printf("ret = %d\n", ret); exit(-1); } }
二、中断回调函数中使用mutex是否合适?
完全不合适,原因如下:
- mutex是可睡眠锁:当mutex被其他线程持有时,
mutex_lock会触发进程调度进入睡眠状态,但中断上下文(中断顶半部)不允许睡眠,强制调用会导致系统崩溃。 - 替代方案:
- 自旋锁(spinlock):适合中断上下文和进程上下文共享资源的场景,获取不到锁时会循环等待(自旋),不会触发睡眠:
#include <linux/spinlock.h> static spinlock_t RWLock; // 初始化 spin_lock_init(&RWLock); // 中断中使用 spin_lock(&RWLock); IrqOccurred = 1; spin_unlock(&RWLock); // 进程上下文使用 spin_lock(&RWLock); IrqOccurred = 0; spin_unlock(&RWLock); - 原子操作:如果仅操作
IrqOccurred这类整型变量,用原子操作更高效,无需锁:#include <linux/atomic.h> static atomic_t IrqOccurred = ATOMIC_INIT(0); // 中断中设置 atomic_set(&IrqOccurred, 1); // poll中查询 if (atomic_read(&IrqOccurred)) { return POLLIN; } // read中清零 atomic_set(&IrqOccurred, 0);
- 自旋锁(spinlock):适合中断上下文和进程上下文共享资源的场景,获取不到锁时会循环等待(自旋),不会触发睡眠:
内容的提问来源于stack exchange,提问作者Vimer
相关产品推荐
相关产品推荐

