关于正确使用sysfs_notify_dirent()的示例及文档求助
正确使用sysfs_notify_dirent()实现sysfs属性的poll支持(原子上下文场景)
没问题,我正好有过类似的驱动开发经验,给你拆解一下sysfs_notify_dirent()的正确用法,结合你的ISR场景给个完整示例:
首先得明确核心原因:sysfs_notify()需要先获取目标属性所在目录的struct dentry,过程中可能涉及睡眠操作,绝对不能在ISR这种原子上下文里调用。而sysfs_notify_dirent()直接使用提前保存的struct sysfs_dirent指针,不需要睡眠,完美适配ISR场景。
下面是完整的代码示例,一步步对应你的需求:
1. 定义设备结构体,保存关键指针
我们需要提前保存sysfs属性对应的sysfs_dirent指针,以及适配原子上下文的同步机制:
#include <linux/device.h> #include <linux/interrupt.h> #include <linux/sysfs.h> #include <linux/spinlock.h> #include <linux/wait.h> struct my_device { struct device dev; struct sysfs_dirent *attr_sd; // 保存目标属性的sysfs_dirent指针 spinlock_t data_lock; // 保护数据的自旋锁(适配ISR上下文) wait_queue_head_t wait_queue; // poll等待队列,用于挂起用户进程 bool has_new_data; // 标记是否有新数据待读取 };
2. 实现sysfs属性的show函数
这个函数负责向用户空间返回数据,同时重置新数据标志:
static ssize_t my_attr_show(struct device *dev, struct device_attribute *attr, char *buf) { struct my_device *my_dev = container_of(dev, struct my_device, dev); ssize_t ret; // 自旋锁保护,避免ISR和用户空间的操作冲突 spin_lock(&my_dev->data_lock); // 这里简化处理,实际替换成你的数据读取逻辑即可 ret = sprintf(buf, "%d\n", my_dev->has_new_data ? 1 : 0); my_dev->has_new_data = false; // 读取完成后重置标志 spin_unlock(&my_dev->data_lock); return ret; } // 定义只读属性,权限设为用户可读 static DEVICE_ATTR(my_attr, S_IRUGO, my_attr_show, NULL);
3. 实现poll函数
用户空间调用poll()/epoll()时会触发这个函数,我们需要检查数据状态并管理等待队列:
static unsigned int my_attr_poll(struct file *filp, poll_table *wait) { struct my_device *my_dev = container_of(filp->private_data, struct my_device, dev); unsigned int mask = 0; // 将当前进程添加到等待队列,没有数据时会休眠 poll_wait(filp, &my_dev->wait_queue, wait); spin_lock(&my_dev->data_lock); if (my_dev->has_new_data) { // 有新数据,返回POLLIN让用户空间知道可读 mask |= POLLIN | POLLRDNORM; } spin_unlock(&my_dev->data_lock); return mask; }
4. 设备初始化:创建属性并保存sysfs_dirent
在probe函数(进程上下文,允许睡眠)中创建sysfs属性,并获取对应的sysfs_dirent指针:
static int my_device_probe(struct platform_device *pdev) { struct my_device *my_dev; int ret; my_dev = devm_kzalloc(&pdev->dev, sizeof(*my_dev), GFP_KERNEL); if (!my_dev) return -ENOMEM; // 初始化同步机制 spin_lock_init(&my_dev->data_lock); init_waitqueue_head(&my_dev->wait_queue); my_dev->has_new_data = false; // 注册设备(这里假设是platform设备,根据你的实际设备类型调整) my_dev->dev.parent = &pdev->dev; ret = devm_device_add(&pdev->dev, &my_dev->dev); if (ret) return ret; // 创建sysfs属性 ret = sysfs_create_file(&my_dev->dev.kobj, &dev_attr_my_attr.attr); if (ret) return ret; // 获取属性对应的sysfs_dirent指针,必须在可睡眠上下文调用 my_dev->attr_sd = sysfs_get_dirent(my_dev->dev.kobj.sd, dev_attr_my_attr.attr.name); if (!my_dev->attr_sd) { sysfs_remove_file(&my_dev->dev.kobj, &dev_attr_my_attr.attr); return -ENOENT; } // 注册IRQ(替换成你的实际中断号和触发方式) ret = devm_request_irq(&pdev->dev, YOUR_IRQ_NUMBER, my_device_isr, IRQF_TRIGGER_RISING, "my_device_irq", my_dev); if (ret) { sysfs_put_dirent(my_dev->attr_sd); sysfs_remove_file(&my_dev->dev.kobj, &dev_attr_my_attr.attr); return ret; } platform_set_drvdata(pdev, my_dev); return 0; }
5. ISR中调用sysfs_notify_dirent()
当ISR检测到新数据时,标记状态并通知用户空间:
static irqreturn_t my_device_isr(int irq, void *dev_id) { struct my_device *my_dev = dev_id; // 标记有新数据,自旋锁保护避免并发问题 spin_lock(&my_dev->data_lock); my_dev->has_new_data = true; spin_unlock(&my_dev->data_lock); // 关键操作:通知用户空间属性有更新,触发poll唤醒 sysfs_notify_dirent(my_dev->attr_sd); return IRQ_HANDLED; }
6. 设备移除:清理资源
一定要记得释放sysfs_dirent的引用,避免内存泄漏:
static int my_device_remove(struct platform_device *pdev) { struct my_device *my_dev = platform_get_drvdata(pdev); // 释放sysfs_dirent的引用计数 sysfs_put_dirent(my_dev->attr_sd); // 移除sysfs属性 sysfs_remove_file(&my_dev->dev.kobj, &dev_attr_my_attr.attr); return 0; }
关键注意事项
sysfs_get_dirent()必须在可睡眠上下文调用:比如probe函数,因为它内部可能会获取锁,不能在ISR里执行。sysfs_notify_dirent()可以安全在原子上下文使用:它不需要分配内存或睡眠,只是唤醒等待该属性的poll进程。- 必须配对使用
sysfs_get_dirent()和sysfs_put_dirent():避免sysfs_dirent的引用计数泄漏。 - 数据同步必须用自旋锁:ISR和用户空间的show/poll函数都会访问
has_new_data,自旋锁是原子上下文安全的同步方式。
内容的提问来源于stack exchange,提问作者Twoism
相关产品推荐
相关产品推荐

