用户态I2C设备驱动:ioctl与SMBus地址的互斥锁问题
关于多线程共享I2C总线文件描述符的线程安全问题
你的推理完全正确——必须为ioctl()设置地址+后续读写的操作序列加互斥锁,否则会出现读写目标设备错乱的问题,原因如下:
文件描述符的上下文特性
/dev/i2c-N的文件描述符在进程的所有线程中是共享的,ioctl(I2C_SLAVE)设置的目标设备地址是绑定在该fd的内核上下文里的。如果线程A刚执行完ioctl设置设备X的地址,还没发起读写,线程B就用同一个fd执行ioctl设置了设备Y的地址,那么线程A接下来的读写操作都会被发送到设备Y上,完全偏离预期目标。内核总线锁的局限性
Linux内核的I2C总线驱动本身带有互斥锁,用来防止多个线程同时在总线上发起传输,但这个锁只负责总线级别的并发保护,不处理用户态fd的地址上下文切换问题。内核只会保证同一时间只有一个线程在总线上传输,但不会校验当前传输的地址是否是该线程预期的——地址是用户态通过fd设置的,内核只认当前fd关联的地址值。
可行的解决方案
- 每个线程独立打开fd:这是最推荐的方式。每个线程单独调用
open("/dev/i2c-N", ...)获取自己的fd,每个fd拥有独立的地址上下文,线程之间的ioctl和读写操作互不干扰,无需额外加锁。 - 共用fd时加互斥锁:如果因为某些限制必须共用一个fd,必须用互斥锁(比如
pthread_mutex_t)将ioctl设置地址+后续所有读写操作的整个流程包裹起来,确保这一系列操作是原子执行的,中间不会被其他线程打断修改fd的目标地址。
注意:
i2c_smbus_write_byte_data()、i2c_smbus_read_byte_data()这类函数本质上是通过封装ioctl调用实现的,所以只要涉及同一个fd的地址切换和读写,就必须保证操作序列的原子性。
内容的提问来源于stack exchange,提问作者Sam Hammamy
相关产品推荐
相关产品推荐

