linux驱动中如何自动生成设备文件节点?

发布时间 2023-04-16 21:24:26作者: MaxBruce

原文:https://blog.csdn.net/eurphan_y/article/details/104326735

linux驱动中如何自动生成设备文件节点?
一、自动生成设备文件的必要性
    在我们初学驱动开发的时候,我们的设备文件需要我们在知道设备号之后,使用命令 “mknod c|b dev_name major minor” 来生成,这样的设备文件生成方法在实际项目中显然是不行的,当驱动程序过多的时候,我们很难有精力来管理这么多的设备文件。因此,在驱动中,使得设备文件能够自动生成非常有必要。下面,将介绍在两种不同目录下生成设备文件的方法。

二、如何自动生成设备文件节点
1. mdev机制
    udev 是一个用户程序,在 Linux 下通过 udev 来实现设备文件的创建与删除,udev 可以检测系统中硬件设备状态,可以根据系统中硬件设备状态来创建或者删除设备文件。比如使用insmod或modprobe 命令成功加载驱动模块以后就自动在/dev 目录下创建对应的设备节点文件,使用rmmod 命令卸载驱动模块以后就删除掉/dev 目录下的设备节点文件。使用 busybox 构建根文件系统的时候,busybox 会创建一个 udev的简化版本—mdev,所以在嵌入式 Linux 中我们使用mdev 来实现设备节点文件的自动创建与删除,Linux 系统的热插拔事件也由 mdev 管理,在/etc/init.d/rcS 文件中如下语句:

echo /sbin/mdev > /proc/sys/kernel/hotplug
1
    上述命令设置热插拔事件由 mdev 来管理,关于 udev 或 mdev 更加详细的工作原理这里就不详细探讨了,我们重点来学习一下如何通过 mdev 来实现设备文件节点的自动创建与删除。

2. 在/dev下生成设备文件节点
2.1 创建和删除类
    自动创建设备节点的工作是在驱动程序的入口函数中完成的,一般在 cdev_add 函数后面添加自动创建设备节点相关代码。首先要创建一个 class 类,class 是个结构体,定义在文件include/linux/device.h 里面。class_create 是类创建函数,class_create 是个宏定义,内容如下:

#define class_create(owner, name) \
({ \
static struct lock_class_key __key; \
__class_create(owner, name, &__key); \
})

struct class *__class_create(struct module *owner, const char *name, struct lock_class_key *key)

    根据上述代码,将宏 class_create 展开以后内容如下:

struct class *class_create (struct module *owner, const char *name)
1
    class_create 一共有两个参数,参数 owner 一般为 THIS_MODULE,参数 name 是类名字。返回值是个指向结构体 class 的指针,也就是创建的类。
    卸载驱动程序的时候需要删除掉类,类删除函数为 class_destroy,函数原型如下:

void class_destroy(struct class *cls);
1
    参数 cls 就是要删除的类。

2.2 创建和删除设备
    一小节创建好类以后还不能实现自动创建设备节点,我们还需要在这个类下创建一个设备。使用 device_create 函数在类下面创建设备,device_create 函数原型如下:

struct device *device_create(struct class*class,
struct device *parent,
dev_t devt,
void *drvdata,
const char *fmt, ...)

    device_create 是个可变参数函数,参数 class 就是设备要创建哪个类下面;参数 parent 是父设备,一般为 NULL,也就是没有父设备;参数 devt 是设备号;参数 drvdata 是设备可能会使用的一些数据,一般为 NULL;参数 fmt 是设备名字,如果设置 fmt=xxx 的话,就会生成/dev/xxx这个设备文件。返回值就是创建好的设备。
    同样的,卸载驱动的时候需要删除掉创建的设备,设备删除函数为 device_destroy,函数原型如下:

void device_destroy(struct class *class, dev_t devt)
1
    参数 class 是要删除的设备所处的类,参数 devt 是要删除的设备号。

2.3 参考示例
struct user_dev_t {
struct class *class; /* 类 */
struct device *device; /* 设备 */
dev_t devid;
};
struct user_dev_t user_dev;

#define DEV_CLASS_NAME "yyy"
#define DEV_NAME "xxx"

/* 驱动入口函数 */
static int __init xxx_init(void)
{
/* 创建类 */
user_dev.class = class_create(THIS_MODULE, DEV_CLASS_NAME);
/* 创建设备 */
user_dev.device = device_create(user_dev.class, NULL, user_dev.devid, NULL, DEV_NAME);
return 0;
}

/* 驱动出口函数 */
static void __exit xxx_exit(void)
{
/* 删除设备 */
device_destroy(user_dev.class, user_dev.devid);
/* 删除类 */
class_destroy(user_dev.class);
}

module_init(xxx_init);
module_exit(xxx_exit);

3. 在/sys/class中生成设备文件节点
    有时候,我们会采取在 /sys/class 中生成设备文件节点,而不是 /dev 中。

3.1 创建、删除类和设备
    首先,我们需要使用结构struct class_attribute创建一个属性数组,来组织设备文件的读写函数,struct class_attribute定义如下:

struct class_attribute {
struct attribute attr;
ssize_t (*show)(struct class *class, struct class_attribute *attr,
char *buf);
ssize_t (*store)(struct class *class, struct class_attribute *attr,
const char *buf, size_t count);
};

    attr为属性,show函数为读文件操作函数,store函数为写文件操作函数。
其中,结构struct attribute结构定义如下:

struct attribute {
const char *name;
umode_t mode;
#ifdef CONFIG_DEBUG_LOCK_ALLOC
bool ignore_lockdep:1;
struct lock_class_key *key;
struct lock_class_key skey;
#endif
};

    name为设备文件名称,mode为模式。
    然后,需要定义一个struct class结构,为其成员class_attrs指定为上述结构。结构全部定义完毕之后,便可以使用函数class_register将class注册到系统。class_register定义如下:

#define class_register(class) \
({ \
static struct lock_class_key __key; \
__class_register(class, &__key); \
})

int __class_register(struct class *cls, struct lock_class_key *key)

其为一个宏,其中, 参数class为上述定义的struct class。我们将其展开后得到如下接口:

int class_register(struct class *cls);
1
在驱动卸载的时候,需要删除这个class,使用函数class_unregister,其定义如下:

void class_unregister(struct class *cls);
1
3.2 参考示例
    在下面的参考示例中,会生成设备文件 /sys/class/eurphan/xxx 。

/*
* @description : sysfs文件节点读函数
* @param - class : 传递给驱动的class
* @param - attr : sysfs文件节点属性
* @param - buff : 接收数据的缓存
* @return : 读取到的数据个数
*/
static ssize_t xxx_show(struct class *class, struct class_attribute *attr, char *buff)
{
return 0;
}

/*
* @description : sysfs文件节点写函数
* @param - class : 传递给驱动的class
* @param - attr : sysfs文件节点属性
* @param - buff : 写入的数据缓存
* @param - count : 写入的数据个数
* @return : 实际写入的数据个数
*/
static ssize_t xxx_store(struct class *class, struct class_attribute *attr, const char *buff, size_t count)
{
return count;
}
/* class属性结构 */
static struct class_attribute xxx_class_attr[] = {
__ATTR(xxx, 0644, xxx_show, xxx_store),
__ATTR_NULL,
};

/* class结构 */
static struct class xxx_class = {
.name = "eurphan",
.owner = THIS_MODULE,
.class_attrs = xxx_class_attr,
};

/*
* @description : 驱动卸载函数
* @param : 无
* @return : 加载是否成功
*/
static __init int xxx_init(void)
{
class_register(&xxx_class);
return 0;
}

/*
* @description : 驱动卸载函数
* @param : 无
* @return : 无
*/
static __exit void xxx_exit(void)
{
class_unregister(&xxx_class);
}

module_init(xxx_init);
module_exit(xxx_exit);
————————————————
版权声明:本文为CSDN博主「eurphan_y」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/eurphan_y/article/details/104326735