Linux设备模型8

部署运行你感兴趣的模型镜像

Linux设备模型8(基于Linux6.6)---class介绍

 


一、 class概述

在设备模型中,bus、device、device driver等等,都比较好理解,因为它们对应了实实在在的东西,所有的逻辑都是围绕着这些实体展开的。class是虚拟出来的,只是为了抽象设备的共性。

在内核中,class 是由 struct class 结构表示的。它描述了一个设备类别,并负责将属于该类别的设备与用户空间进行交互。每个 class 都有一组与之相关的属性(通过 class_attribute 定义),这些属性通过 sysfs 机制暴露给用户空间,允许用户读取或修改设备的状态。

class 的作用主要体现在以下几个方面:

  1. 设备分类:将功能相似的设备分组。例如,字符设备、块设备、网络设备等可以分别属于不同的类。

  2. 提供统一的接口:每个类下的设备可以通过统一的接口进行操作和管理。例如,/sys/class 目录下的文件可以用来表示设备的状态和属性。

  3. 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 暴露给用户空间,用户可以读取或写入这些属性。对于每个属性,内核提供了 showstore 回调函数来处理读取和写入操作。

删除设备和类

在不再需要时,可以通过 device_unregisterclass_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 提供给用户空间。

您可能感兴趣的与本文相关的镜像

ACE-Step

ACE-Step

音乐合成
ACE-Step

ACE-Step是由中国团队阶跃星辰(StepFun)与ACE Studio联手打造的开源音乐生成模型。 它拥有3.5B参数量,支持快速高质量生成、强可控性和易于拓展的特点。 最厉害的是,它可以生成多种语言的歌曲,包括但不限于中文、英文、日文等19种语言

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值