Linux设备模型8(基于Linux6.6)---class介绍
一、 class概述
在设备模型中,bus、device、device driver等等,都比较好理解,因为它们对应了实实在在的东西,所有的逻辑都是围绕着这些实体展开的。class是虚拟出来的,只是为了抽象设备的共性。
在内核中,class 是由 struct class 结构表示的。它描述了一个设备类别,并负责将属于该类别的设备与用户空间进行交互。每个 class 都有一组与之相关的属性(通过 class_attribute 定义),这些属性通过 sysfs 机制暴露给用户空间,允许用户读取或修改设备的状态。
class 的作用主要体现在以下几个方面:
-
设备分类:将功能相似的设备分组。例如,字符设备、块设备、网络设备等可以分别属于不同的类。
-
提供统一的接口:每个类下的设备可以通过统一的接口进行操作和管理。例如,
/sys/class目录下的文件可以用来表示设备的状态和属性。 -
与
sysfs集成:通过sysfs,设备的属性可以暴露给用户空间,用户可以通过读取或写入属性来与设备进行交互。
类是一个设备的高层视图,抽象出了底层的实现细节。类允许用户空间使用设备所提供的功能,而不关心设备是如何连接的,和它们是如何工作的。类子系统是向用户空间导出信息的最好方法。
类的核心数据结构是class,在分析device中介绍过,这个class不如bus,device,driver好理解,我理解它的主要功能是一是设备管理,二是抽象出一类设备的公共属性。
前面分析subsys_private时说过,class和bus_type很类似,我们在设备总线驱动模型中创建设备时,要指定它所属的bus,那么在创建类设备的时候也需要指定它所从属的类,class也离不开kobject,因此如果你了解总线设备驱动模型,你就会发现,其实真的都是差不多的东西。
设备驱动一般在注册的时候都会调用此类class的一些函数,主要作用就是在sys目录里面创建一些节点,比如cd到/sys/class下面可以看到这一类的设备。

二、 数据结构描述
2.1、 Class数据结构
struct class是class的抽象,它的定义如下:include/linux/device/class.h
struct class {
const char *name;
const struct attribute_group **class_groups;
const struct attribute_group **dev_groups;
int (*dev_uevent)(const struct device *dev, struct kobj_uevent_env *env);
char *(*devnode)(const struct device *dev, umode_t *mode);
void (*class_release)(const struct class *class);
void (*dev_release)(struct device *dev);
int (*shutdown_pre)(struct device *dev);
const struct kobj_ns_type_operations *ns_type;
const void *(*namespace)(const struct device *dev);
void (*get_ownership)(const struct device *dev, kuid_t *uid, kgid_t *gid);
const struct dev_pm_ops *pm;
};
name,class的名称,会在“/sys/class/”目录下体现。
class_atrrs,该class的默认attribute,会在class注册到内核时,自动在“/sys/class/xxx_class”下创建对应的attribute文件。
dev_groups,该class下每个设备的attribute,会在设备注册到内核时,自动在该设备的sysfs目录下创建对应的attribute文件。
dev_kobj,表示该class下的设备在/sys/dev/下的目录,现在一般有char和block两个,如果dev_kobj为NULL,则默认选择char。
dev_uevent,当该class下有设备发生变化时,会调用class的uevent回调函数。
class_release,用于release自身的回调函数。
dev_release,用于release class内设备的回调函数。在device_release接口中,会依次检查Device、Device Type以及Device所在的class,是否注册release接口,如果有则调用相应的release接口release设备指针。
p,见subsys_private。
class_attribute:include/linux/device/class.h
struct class_attribute {
struct attribute attr;
ssize_t (*show)(const struct class *class, const struct class_attribute *attr,
char *buf);
ssize_t (*store)(const struct class *class, const struct class_attribute *attr,
const char *buf, size_t count);
};
2.2、 Class_interface数据结构
struct class_interface是这样的一个结构:它允许class driver在class下有设备添加或移除的时候,调用预先设置好的回调函数(add_dev和remove_dev)。那调用它们做什么呢?想做什么都行(),由具体的class driver实现。
该结构的定义如下:include/linux/device/class.h
struct class_interface {
struct list_head node;
const struct class *class;
int (*add_dev) (struct device *dev);
void (*remove_dev) (struct device *dev);
};
node,连入subsys_private的interfaces的节点。
class,接口所属class
add_dev,设备添加到所属class时调用的回调函数
remove_dev,设备删除时调用的回调函数
将设备从子系统加入或删除时,会遍历subsys_private的interfaces中的所有class_interface,并调用add_dev/remove_dev。
在device_add的最后面有使用到class_interface:drivers/base/core.c
int device_add(struct device *dev)
{
struct subsys_private *sp;
struct device *parent;
struct kobject *kobj;
struct class_interface *class_intf;
int error = -EINVAL;
struct kobject *glue_dir = NULL;
...
sp = class_to_subsys(dev->class);
if (sp) {
mutex_lock(&sp->mutex);
/* tie the class to the device */
klist_add_tail(&dev->p->knode_class, &sp->klist_devices);
/* notify any interfaces that the device is here */
list_for_each_entry(class_intf, &sp->interfaces, node)
if (class_intf->add_dev)
class_intf->add_dev(dev);
mutex_unlock(&sp->mutex);
subsys_put(sp);
}
...
}
可以看到在注册完一个设备后,都要通知所有interface,这个事件,具体的通知函数还是由具体的class来实现。当设备添加或移除的时候,调用class_interface预先设置好的回调函数。
2.3、class、class_interface和kobject族的关系

三、 功能及内部逻辑解析
3.1、 class的功能
看完上面的东西,class到底提供了什么功能?怎么使用呢?先看一下现有Linux系统中有关class的状况:

可以看到class下面的所有设备都是device设备下面的符号链接。class只是一个管理者和分类着。
看上面的例子,发现misc class也没做什么实实在在的事儿,它(misc class)的功能,仅仅是:
- 在/sys/class/目录下,创建一个本class的目录(misc)
- 在本目录下,创建每一个属于该class的设备的符号链接,这样就可以在本class目录下,访问该设备的所有特性(即attribute)
- 另外,device在sysfs的目录下,也会创建一个subsystem的符号链接,链接到本class的目录。
3.2、 class的注册
drivers/base/class.c
struct class * __must_check class_create(const char *name);
drivers/base/class.c
/**
* class_create - create a struct class structure
* @name: pointer to a string for the name of this class.
*
* This is used to create a struct class pointer that can then be used
* in calls to device_create().
*
* Returns &struct class pointer on success, or ERR_PTR() on error.
*
* Note, the pointer created here is to be destroyed when finished by
* making a call to class_destroy().
*/
struct class *class_create(const char *name)
{
struct class *cls;
int retval;
cls = kzalloc(sizeof(*cls), GFP_KERNEL);
if (!cls) {
retval = -ENOMEM;
goto error;
}
cls->name = name;
cls->class_release = class_create_release;
retval = class_register(cls);
if (retval)
goto error;
return cls;
error:
kfree(cls);
return ERR_PTR(retval);
}
EXPORT_SYMBOL_GPL(class_create);
drivers/base/class.c
int class_register(const struct class *cls)
{
struct subsys_private *cp;
struct lock_class_key *key;
int error;
pr_debug("device class '%s': registering\n", cls->name);
cp = kzalloc(sizeof(*cp), GFP_KERNEL); /* 和bus一样,private可以管理下面所有的东西 */
if (!cp)
return -ENOMEM;
/* 初始化private */
klist_init(&cp->klist_devices, klist_class_dev_get, klist_class_dev_put);
INIT_LIST_HEAD(&cp->interfaces);
kset_init(&cp->glue_dirs);
key = &cp->lock_key;
lockdep_register_key(key);
__mutex_init(&cp->mutex, "subsys mutex", key);
error = kobject_set_name(&cp->subsys.kobj, "%s", cls->name);
if (error) {
kfree(cp);
return error;
}
cp->subsys.kobj.kset = class_kset;
cp->subsys.kobj.ktype = &class_ktype;
cp->class = cls;
error = kset_register(&cp->subsys);/* 在/sys/class/下面创建一个目录,同时发送一个uenent时间给上层 */
if (error)
goto err_out;
error = sysfs_create_groups(&cp->subsys.kobj, cls->class_groups);
if (error) {
kobject_del(&cp->subsys.kobj);
kfree_const(cp->subsys.kobj.name);
goto err_out;
}
return 0;
err_out:
lockdep_unregister_key(key);
kfree(cp);
return error;
}
EXPORT_SYMBOL_GPL(class_register);
class的注册,是由class_register接口实现的,它的处理逻辑和bus的注册类似,主要包括:
- 为class结构中的struct subsys_private类型的指针(cp)分配空间,并初始化其中的字段,包括cp->subsys.kobj.kset、cp->subsys.kobj.ktype等等。
- 调用kset_register,注册该class(一个class就是一个子系统,因此注册class也是注册子系统)。该过程结束后,在/sys/class/目录下,就会创建对应该class(子系统)的目录。
- 调用add_class_attrs接口,将class结构中class_attrs指针所指向的attribute,添加到内核中。执行完后,在/sys/class/xxx_class/目录下,就会看到这些attribute对应的文件。
3.3、 device注册时,和class有关的动作
上篇有讲过struct device和struct device_driver这两个数据结构,其中struct device结构会包含一个struct class指针(这从侧面说明了class是device的集合,甚至,class可以是device的driver)。当某个class driver向内核注册了一个class后,需要使用该class的device,通过把自身的class指针指向该class即可,剩下的事情,就由内核在注册device时处理了。
device的注册最终是由device_add接口(drivers/base/core.c)实现了,该接口中和class有关的动作包括:
- 调用device_add_class_symlinks接口,创建3.1小节描述的各种符号链接,即:在对应class的目录下,创建指向device的符号链接;在device的目录下,创建名称为subsystem、指向对应class目录的符号链接。
- 调用device_add_attrs,添加由class指定的attributes(class->dev_attrs)。
- 如果存在对应该class的add_dev回调函数,调用该回调函数。
上面说了很多class的具体做了哪些事,但真正有效只有一件就算是对设备分类,即以字符设备为例,一个类代表一类字符设备即代表一个主设备号,即输入类设备有共同的主设备号,led类有共同的主设备号,framebuffer类有共同的主设备号,杂散类有共同的主设备号。类和总线是同不用角度来管理设备的。
四、总结
创建和注册 class
通常在设备驱动程序中,首先需要创建一个类,并将该类与具体的设备对象关联。可以通过 class_create 函数来创建一个类:
struct class *class_create(struct module *owner, const char *name);
该函数将创建一个新的 class,并将其与指定的模块和名称关联起来。如果成功,它将返回一个指向 struct class 的指针。
例如,创建一个 example_class 类:
struct class *my_class;
my_class = class_create(THIS_MODULE, "example_class");
if (IS_ERR(my_class)) {
pr_err("Failed to create class\n");
return PTR_ERR(my_class);
}
设备与类的关联
一旦类被创建,就可以将设备注册到该类下。设备与类的关联通常通过 device_create 函数来实现,它将设备和类之间建立联系,并且为该设备创建一个设备文件:
struct device *device_create(struct class *class, struct device *parent,
dev_t devt, void *drvdata, const char *fmt, ...);
该函数的参数:
class:设备所属的类。parent:父设备(如果有)。devt:设备号。drvdata:驱动程序私有数据。fmt:设备文件的名称。
设备创建后,将会在 /sys/class/<class_name>/ 目录下生成一个子目录,并且为每个设备生成一个属性文件,用户可以通过这些文件与设备进行交互。
示例:
struct device *my_device;
my_device = device_create(my_class, NULL, MKDEV(0, 0), NULL, "my_device");
if (IS_ERR(my_device)) {
pr_err("Failed to create device\n");
return PTR_ERR(my_device);
}
类属性(class attributes)
每个 class 可以有一些属性(如设备的状态、配置信息等),这些属性通过 class_attribute 来定义。class_attribute 是一种特殊的 attribute,它定义了一个类级别的属性。
struct class_attribute {
struct attribute attr;
ssize_t (*show)(struct class *class, char *buf);
ssize_t (*store)(struct class *class, const char *buf, size_t count);
};
类属性通过 sysfs 暴露给用户空间,用户可以读取或写入这些属性。对于每个属性,内核提供了 show 和 store 回调函数来处理读取和写入操作。
删除设备和类
在不再需要时,可以通过 device_unregister 和 class_destroy 来删除设备和类。
device_unregister:注销设备。class_destroy:销毁类并释放相关资源。
示例代码:创建类和设备
下面是一个简单的示例,展示如何在 Linux 驱动中创建类和设备。
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/device.h>
#include <linux/fs.h>
#include <linux/cdev.h>
static struct class *my_class;
static struct device *my_device;
static dev_t dev_num;
static int __init my_driver_init(void) {
int ret;
// 创建设备类
my_class = class_create(THIS_MODULE, "my_class");
if (IS_ERR(my_class)) {
pr_err("Failed to create class\n");
return PTR_ERR(my_class);
}
// 注册设备号
ret = alloc_chrdev_region(&dev_num, 0, 1, "my_device");
if (ret) {
pr_err("Failed to register device number\n");
class_destroy(my_class);
return ret;
}
// 创建设备
my_device = device_create(my_class, NULL, dev_num, NULL, "my_device");
if (IS_ERR(my_device)) {
pr_err("Failed to create device\n");
unregister_chrdev_region(dev_num, 1);
class_destroy(my_class);
return PTR_ERR(my_device);
}
pr_info("Driver initialized\n");
return 0;
}
static void __exit my_driver_exit(void) {
device_unregister(my_device);
class_destroy(my_class);
unregister_chrdev_region(dev_num, 1);
pr_info("Driver exited\n");
}
module_init(my_driver_init);
module_exit(my_driver_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Example Author");
MODULE_DESCRIPTION("Example Linux Driver");
在这个示例中:
- 创建了一个名为
my_class的设备类。 - 注册了一个设备号,并创建了一个设备
my_device。 - 通过
sysfs可以访问my_class下的设备,并与之交互。
小结
- class 是 Linux 设备模型中用于管理设备的分类和组织的机制。
- 设备类提供了一种将设备按功能分组的方式,简化了设备管理。
- 通过
class_create创建类,device_create将设备与类关联,设备属性则通过sysfs提供给用户空间。
1363

被折叠的 条评论
为什么被折叠?



