linux设备模型九(class)

1. 概述

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

举个例子,一些年龄相仿、需要获取的知识相似的人,聚在一起学习,就构成了一个班级(Class)。这个班级可以有自己的名称(如295),但如果离开构成它的学生(device),它就没有任何存在意义。另外,班级存在的最大意义是什么呢?是由老师讲授的每一个课程!因为老师只需要讲一遍,一个班的学生都可以听到。不然的话(例如每个学生都在家学习),就要为每人请一个老师,讲授一遍。而讲的内容,大多是一样的,这就是极大的浪费。

设备模型中的class所提供的功能也一样了,例如一些相似的device(学生),需要向用户空间提供相似的接口(课程),如果每个设备的驱动都实现一遍的话,就会导致内核有大量的冗余代码,这就是极大的浪费。所以,class说了,我帮你们实现吧,你们会用就行了。

这就是设备模型中Class的功能,再结合内核的注释:A class is a higher-level view of a device that abstracts out low-level implementation details(include/linux/device.h line326类是抽象出低级实现细节的设备的更高级视图),就容易理解了。

2. 数据结构描述

2.1 struct class

struct class是class的抽象,它的定义如下


/**
 * struct class - device classes
 * @name:	Name of the class.
 * @owner:	The module owner.
 * @class_attrs: Default attributes of this class.
 * @dev_groups:	Default attributes of the devices that belong to the class.
 * @dev_kobj:	The kobject that represents this class and links it into the hierarchy.
 * @dev_uevent:	Called when a device is added, removed from this class, or a
 *		few other things that generate uevents to add the environment
 *		variables.
 * @devnode:	Callback to provide the devtmpfs.
 * @class_release: Called to release this class.
 * @dev_release: Called to release the device.
 * @suspend:	Used to put the device to sleep mode, usually to a low power
 *		state.
 * @resume:	Used to bring the device from the sleep mode.
 * @shutdown:	Called at shut-down time to quiesce the device.
 * @ns_type:	Callbacks so sysfs can detemine namespaces.
 * @namespace:	Namespace of the device belongs to this class.
 * @pm:		The default device power management operations of this class.
 * @p:		The private data of the driver core, no one other than the
 *		driver core can touch this.
 *
 * A class is a higher-level view of a device that abstracts out low-level
 * implementation details. Drivers may see a SCSI disk or an ATA disk, but,
 * at the class level, they are all simply disks. Classes allow user space
 * to work with devices based on what they do, rather than how they are
 * connected or how they work.
 */
struct class {
	const char		*name;
	struct module		*owner;
 
	struct class_attribute		*class_attrs;
	const struct attribute_group	**dev_groups;
	struct kobject			*dev_kobj;
 
	int (*dev_uevent)(struct device *dev, struct kobj_uevent_env *env);
	char *(*devnode)(struct device *dev, umode_t *mode);
 
	void (*class_release)(struct class *class);
	void (*dev_release)(struct device *dev);
 
	int (*suspend)(struct device *dev, pm_message_t state);
	int (*resume)(struct device *dev);
	int (*shutdown)(struct device *dev);
 
	const struct kobj_ns_type_operations *ns_type;
	const void *(*namespace)(struct device *dev);
 
	const struct dev_pm_ops *pm;
 
	struct subsys_private *p;
};

其实struct class和struct bus很类似,解释如下:

name,class的名称,会在“/sys/class/”目录下体现。

class_atrrs,该class的默认attribute,会在class注册到内核时,自动在“/sys/class/xxx_class”下创建对应的attribute文件。

dev_attrs,该class下每个设备的attribute,会在设备注册到内核时,自动在该设备的sysfs目录下创建对应的attribute文件。

dev_bin_attrs,类似dev_attrs,只不过是二进制类型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,和"linux设备模型八(bus)"中struct bus结构一样,不再说明。

 

 

2.2 struct class_interface

struct class_interface是这样的一个结构:它允许class driver在class下有设备添加或移除的时候,调用预先设置好的回调函数(add_dev和remove_dev)。那调用它们做什么呢?想做什么都行(),由具体的class driver实现。

该结构的定义如下:


struct class_interface {
	struct list_head	node;
	struct class		*class;
 
	int (*add_dev)		(struct device *, struct class_interface *);
	void (*remove_dev)	(struct device *, struct class_interface *);
}

我搜了一下,发下在device_add的最后面有使用到class_interface

可以看到在注册完一个设备后,都要通知所有interface,这个事件,具体的通知函数还是由具体的class来实现。

 

3. 功能及内部逻辑解析

3.1 class的功能

看完上面的东西,class到底提供了什么功能?怎么使用呢?让我们先看一下现有Linux系统中有关class的状况(这里以input class为例):

可以看到class下面的所有设备都是device设备下面的符号链接。class只是一个管理者和分类着。

看上面的例子,发现input class也没做什么实实在在的事儿,它(input class)的功能,仅仅是:

  • 在/sys/class/目录下,创建一个本class的目录(input)
  • 在本目录下,创建每一个属于该class的设备的符号链接,这样就可以在本class目录下,访问该设备的所有特性(即attribute)
  • 另外,device在sysfs的目录下,也会创建一个subsystem的符号链接,链接到本class的目录。

3.2 class的注册


/* This is a #define to keep the compiler from merging different
 * instances of the __key variable */
#define class_create(owner, name)		\
({						\
	static struct lock_class_key __key;	\
	__class_create(owner, name, &__key);	\
})

 

static void class_create_release(struct class *cls)
{
	pr_debug("%s called for %s\n", __func__, cls->name);
	kfree(cls);
}
 
/**
 * class_create - create a struct class structure
 * @owner: pointer to the module that is to "own" this struct class
 * @name: pointer to a string for the name of this class.
 * @key: the lock_class_key for this class; used by mutex lock debugging
 *
 * 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(struct module *owner, const char *name,
			     struct lock_class_key *key)
{
	struct class *cls;
	int retval;
 
	cls = kzalloc(sizeof(*cls), GFP_KERNEL);    /* 申请一个class结构体 */
	if (!cls) {
		retval = -ENOMEM;
		goto error;
	}
 
    /* 初始化 */
	cls->name = name;
	cls->owner = owner;
	cls->class_release = class_create_release;    /* 绑定注销函数 */
 
	retval = __class_register(cls, key);
	if (retval)
		goto error;
 
	return cls;
 
error:
	kfree(cls);
	return ERR_PTR(retval);
}
int __class_register(struct class *cls, struct lock_class_key *key)
{
	struct subsys_private *cp;
	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);
	__mutex_init(&cp->mutex, "subsys mutex", key);
	error = kobject_set_name(&cp->subsys.kobj, "%s", cls->name);
	if (error) {
		kfree(cp);
		return error;
	}
 
	/* set the default /sys/dev directory for devices of this class */
	if (!cls->dev_kobj)
		cls->dev_kobj = sysfs_dev_char_kobj;
 
#if defined(CONFIG_BLOCK)
	/* let the block class directory show up in the root of sysfs */
	if (!sysfs_deprecated || cls != &block_class)
		cp->subsys.kobj.kset = class_kset;
#else
	cp->subsys.kobj.kset = class_kset;
#endif
	cp->subsys.kobj.ktype = &class_ktype;
	cp->class = cls;
	cls->p = cp;
 
    /* 在/sys/class/下面创建一个目录,同时发送一个uenent时间给上层 */
	error = kset_register(&cp->subsys);
	if (error) {
		kfree(cp);
		return error;
	}
    /* 添加属性 */
	error = add_class_attrs(class_get(cls));
	class_put(cls);
	return error;
}

class的注册,是由__class_register接口(它的实现位于"drivers/base/class.c, line 609")实现的,它的处理逻辑和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有关的动作

在 linux设备模型五、六、七中,我们有讲过struct device和struct device_driver这两个数据结构,其中struct device结构会包含一个struct class指针(这从侧面说明了class是device的集合,甚至,class可以是device的driver)。当某个class driver向内核注册了一个class后,需要使用该class的device,通过把自身的class指针指向该class即可,剩下的事情,就由内核在注册device时处理了。

本节,我们讲一下在device注册时,和class有关的动作:

device的注册最终是由device_add接口(drivers/base/core.c)实现了,该接口中和class有关的动作包括:

  • 调用device_add_class_symlinks接口,创建3.1小节描述的各种符号链接,即:在对应class的目录下,创建指向device的符号链接;在device的目录下,创建名称为subsystem、指向对应class目录的符号链接(这个我在device和driver章节举例分析了)
  • 调用device_add_attrs,添加由class指定的attributes(class->dev_attrs)
  • 如果存在对应该class的add_dev回调函数,调用该回调函数

 

上面说了很多class的具体做了哪些事,但真正有效只有一件就算是对设备分类,即以字符设备为例,一个类代表一类字符设备即代表一个主设备号,即输入类设备有共同的主设备号,led类有共同的主设备号,framebuffer类有共同的主设备号,杂散类有共同的主设备号。类和总线是同不用角度来管理设备的。

 

 

参考博客:

http://www.wowotech.net/device_model/class.html

原文链接:加载失败,请重新获取