内核模块对接DS24xxx 1-Wire设备,实现用户空间可访问解码功能咨询
问题解答
为什么用filp_open()读取sysfs是错误的方式
- 内核中严格不推荐使用用户态风格的文件IO操作(比如
filp_open()/vfs_read()),这会破坏内核的上下文模型,甚至可能引发死锁、资源泄漏等问题。 - sysfs的节点并非普通磁盘文件,其背后是内核的
sysfs_ops接口,依赖用户态上下文才能正确工作,内核态直接调用文件操作函数大概率会失败,这就是你读取数据失败的核心原因。
最优实现方案:基于Linux 1-Wire子系统开发驱动模块
Linux内核已经内置了成熟的1-Wire子系统,你应该直接对接该子系统,而非通过sysfs中转。这种方式更符合内核驱动的设计规范,性能和稳定性也更有保障:
核心思路
- 注册1-Wire设备驱动:实现
w1_driver结构体,当系统枚举到你的DS24xxx设备时,probe函数会被触发,你可以在这里获取设备句柄。 - 直接调用1-Wire API访问硬件:使用子系统提供的
w1_read_block()、w1_write_block()等函数,直接读取EEPROM数据,无需通过sysfs。 - 暴露解码接口给用户空间:通过字符设备(
cdev)或sysfs节点,将解码后的数据提供给用户态应用。
简化代码示例
#include <linux/w1.h> #include <linux/cdev.h> #include <linux/fs.h> // 自定义设备结构体,保存1-Wire slave句柄和字符设备信息 struct ds24xxx_decoder_dev { struct w1_slave *slave; struct cdev cdev; dev_t dev_num; }; static struct class *decoder_class; static struct ds24xxx_decoder_dev *decoder_dev; // 用户空间读取解码数据的回调 static ssize_t decode_data_read(struct file *filp, char __user *buf, size_t count, loff_t *ppos) { struct ds24xxx_decoder_dev *dev = filp->private_data; u8 eeprom_data[64]; // 根据你的DS24xxx设备容量调整 char decoded_buf[256]; int ret, decoded_len; // 调用1-Wire子系统API读取EEPROM ret = w1_read_block(dev->slave, eeprom_data, sizeof(eeprom_data)); if (ret < 0) return ret; // 这里添加你的解码逻辑,将eeprom_data转换为可读的decoded_buf // 示例:简单转成十六进制字符串 decoded_len = scnprintf(decoded_buf, sizeof(decoded_buf), "%ph\n", eeprom_data); // 将解码后的数据拷贝到用户空间 return simple_read_from_buffer(buf, count, ppos, decoded_buf, decoded_len); } // 文件操作结构体 static const struct file_operations decoder_fops = { .owner = THIS_MODULE, .read = decode_data_read, .open = simple_open, }; // 1-Wire设备probe回调,设备枚举时触发 static int ds24xxx_decoder_probe(struct w1_slave *slave) { int ret; decoder_dev = devm_kzalloc(&slave->dev, sizeof(*decoder_dev), GFP_KERNEL); if (!decoder_dev) return -ENOMEM; decoder_dev->slave = slave; dev_set_drvdata(&slave->dev, decoder_dev); // 申请字符设备号 ret = alloc_chrdev_region(&decoder_dev->dev_num, 0, 1, "ds24xxx-decoder"); if (ret < 0) return ret; // 初始化字符设备 cdev_init(&decoder_dev->cdev, &decoder_fops); decoder_dev->cdev.owner = THIS_MODULE; ret = cdev_add(&decoder_dev->cdev, decoder_dev->dev_num, 1); if (ret < 0) { unregister_chrdev_region(decoder_dev->dev_num, 1); return ret; } // 创建设备节点,方便用户空间访问 device_create(decoder_class, &slave->dev, decoder_dev->dev_num, NULL, "ds24xxx-decoder"); return 0; } // 1-Wire设备remove回调 static void ds24xxx_decoder_remove(struct w1_slave *slave) { struct ds24xxx_decoder_dev *dev = dev_get_drvdata(&slave->dev); device_destroy(decoder_class, dev->dev_num); cdev_del(&dev->cdev); unregister_chrdev_region(dev->dev_num, 1); } // 1-Wire驱动结构体,替换为你的DS24xxx设备家族ID static struct w1_driver ds24xxx_decoder_driver = { .driver = { .name = "ds24xxx-decoder", .owner = THIS_MODULE, }, .probe = ds24xxx_decoder_probe, .remove = ds24xxx_decoder_remove, .id = W1_FAMILY_DS2430, // 示例:DS2430的家族ID,根据你的设备修改 }; static int __init ds24xxx_decoder_init(void) { int ret; decoder_class = class_create(THIS_MODULE, "ds24xxx-decoder"); if (IS_ERR(decoder_class)) return PTR_ERR(decoder_class); ret = w1_register_driver(&ds24xxx_decoder_driver); if (ret < 0) { class_destroy(decoder_class); return ret; } return 0; } module_init(ds24xxx_decoder_init); static void __exit ds24xxx_decoder_exit(void) { w1_unregister_driver(&ds24xxx_decoder_driver); class_destroy(decoder_class); } module_exit(ds24xxx_decoder_exit); MODULE_LICENSE("GPL"); MODULE_DESCRIPTION("DS24xxx EEPROM Decoder Driver"); MODULE_AUTHOR("Your Name");
更简单的替代方案:用户空间实现解码
如果你只是需要快速验证功能,或者不想深入内核开发,完全可以在用户空间编写程序:
- 直接读取
/sys/bus/w1/devices/w1_bus_master1/下的设备节点数据。 - 在用户空间完成数据解码逻辑,避免内核开发的复杂性。
这种方案对于新手来说门槛更低,无需处理内核的内存管理、锁机制等细节,调试也更方便。
内容的提问来源于stack exchange,提问作者Pea
相关产品推荐
相关产品推荐

