Linux 内核:设备驱动模型(3)class与device

背景

前面我们知道了设备如何通过总线与驱动匹配,也了解了设备插拔时与用户空间是如何通过uevent基于环境变量进行交互的。

前面看过了设备驱动模型中的bus、device、driver,这3种对象都很好理解,drv与dev通过bus找到对方:

  • bus:总线
  • device:设备和接口
  • driver:驱动

现在,我们继续来说一些比较抽象的概念吧。

实际上,设备驱动模型中,还有一个 抽象概念 叫做类(CLass),准确来说,叫做设备类

所谓设备类,是指提供的用户接口相似的一类设备的集合,常见的设备类的有block、tty、input、usb等等。

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

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

系列:Linux 内核:设备驱动模型 学习总结

参考:

设备类

设备类是一个设备的高层视图,它抽象出了底层的实现细节,从而允许用户空间使用设备所提供的功能,而不用关心设备是如何连接和工作的。

设备类是用来抽象设备的共性而诞生的。

类成员通常由上层代码所控制,而无需驱动的明确支持。但有些情况下驱动也需要直接处理类。

设备类的作用

让我们先看一下现有Linux系统中有关class的状况(这里以input class为例):

root@android:/ # ls /sys/class/input/ -l
lrwxrwxrwx root root 2014-04-23 03:39 event0 -> ../../devices/platform/i2c-gpio.17/i2c-17/17-0066/max77693-muic/input/input0/event0
lrwxrwxrwx root root 2014-04-23 03:39 event1 -> ../../devices/platform/gpio-keys.0/input/input1/event1
...
...
lrwxrwxrwx root root 2014-04-23 03:39 mice -> ../../devices/virtual/input/mice root@android:/ # ls /sys/devices/platform/s3c2440-i2c.3/i2c-3/3-0048/input/input2/event2/ -l
-r--r--r-- root root 4096 2014-04-23 04:08 dev
lrwxrwxrwx root root 2014-04-23 04:08 device -> ../../input2
drwxr-xr-x root root 2014-04-23 04:08 power
lrwxrwxrwx root root 2014-04-23 04:08 subsystem -> ../../../../../../../../class/input
-rw-r--r-- root root 4096 2014-04-23 04:08 uevent root@android:/ # ls /sys/devices/virtual/input/mice/ -l
-r--r--r-- root root 4096 2014-04-23 03:57 dev
drwxr-xr-x root root 2014-04-23 03:57 power
lrwxrwxrwx root root 2014-04-23 03:57 subsystem -> ../../../../class/input

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

1、在/sys/class/目录下,创建一个本class的目录(input)

2、在本目录下,创建每一个属于该class的设备的符号链接,这样就可以在本class目录下,访问该设备的所有特性(即attribute)

如,把“/sys/devices/platform/s3c2440-i2c.3/i2c-3/3-0048/input/input2/event2”设备链接到”/sys/class/input/event2”。

3、device在sysfs的目录下,也会创建一个subsystem的符号链接,链接到本class的目录

算了,我们还是先分析一下Class的核心逻辑都做了哪些事情,至于class到底有什么用处,可以在后续具体的子系统里面(如input子系统),更为细致的探讨。

class原型

其实struct classstruct bus很类似

// include/linux/device.h
/**
* 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.
* @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;
// owner是class所属的模块,虽然class是涉及一类设备,但也是由相应的模块注册的。
// 比如usb类就是由usb模块注册的。
struct module *owner; /* 属性 */
struct class_attribute *class_attrs;
const struct attribute_group **dev_groups;
/* 内部对象 */
struct kobject *dev_kobj; // 设备发出uevent消息时添加环境变量用的
// 还记得在core.c中的dev_uevent()函数,其中就包含对设备所属bus或class中dev_uevent()方法的调用,
// 只是bus结构中定义方法用的函数名是uevent。
int (*dev_uevent)(struct device *dev, struct kobj_uevent_env *env);
// 返回设备节点的相对路径名,在core.c的device_get_devnode()中有调用到。
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); /* 命名空间,略*/
const struct kobj_ns_type_operations *ns_type;
const void *(*namespace)(struct device *dev); /* 电源管理用的函数集合 */
const struct dev_pm_ops *pm; /* 私有数据 */
struct subsys_private *p;
};

大部分的成员通过注释就可以搞懂了,但是有几个需要进行特别说明。

名称name

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

实际使用的是内部kobj包含的动态创建的名称。

内部对象kobject

dev_kobj: 在device注册时,会在/sys/dev下创建名为自己设备号的软链接。

但设备不知道自己属于块设备还是字符设备,所以会请示自己所属的class;class就是用dev_kobj记录本类设备应属于的哪种设备。

属性attribute

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

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

释放方法release

class_release:用于回收这个设备类本身自身的回调函数。

dev_release:用于release class内设备的回调函数。在device_release接口中,会依次检查Device、Device Type以及Device所在的class,是否注册release接口,如果有则调用相应的release接口release设备指针。

具体在drivers/base/core.cdevice_release()函数中调用。

私有数据subsys_private

// drivers/base/base.h
/**
* struct subsys_private - structure to hold the private to the driver core portions of the bus_type/class structure.
*
* @subsys - the struct kset that defines this subsystem
* @devices_kset - the list of devices associated
*
* @drivers_kset - the list of drivers associated
* @klist_devices - the klist to iterate over the @devices_kset
* @klist_drivers - the klist to iterate over the @drivers_kset
* @bus_notifier - the bus notifier list for anything that cares about things
* on this bus.
* @bus - pointer back to the struct bus_type that this structure is associated
* with.
*
* @class_interfaces - list of class_interfaces associated
* @glue_dirs - "glue" directory to put in-between the parent device to
* avoid namespace conflicts
* @mutex - mutex to protect the children, devices, and interfaces lists.
* @class - pointer back to the struct class that this structure is associated
* with.
*
* This structure is the one that is the actual kobject allowing struct
* bus_type/class to be statically allocated safely. Nothing outside of the
* driver core should ever touch these fields.
*/
struct subsys_private {
struct kset subsys; //代表 class 在sysfs中的类型
struct kset *devices_kset; //代表 class目录下的drivers子目录
// ... //代表 class目录下的 devices 子目录
struct kset *drivers_kset;
// class下的设备链表
struct klist klist_devices;
// bus的驱动链表
struct klist klist_drivers;
用于在xx发送变化时调用特定的函数
struct blocking_notifier_head bus_notifier; unsigned int drivers_autoprobe:1; struct bus_type *bus; // 设备类接口稍后会介绍
struct list_head class_interfaces;
// 胶水目录
struct kset glue_dirs;
// 互斥信号量,用于保护class内部的数据结构。
struct mutex mutex;
// 指回struct class的指针
struct class *class;
};

glue_dirs:

它并未实际在sysfs中体现,反而是其下链接了一系列胶水kobject。

记得在core.c中的get_device_parent()函数,好像小蝌蚪找妈妈一样,我们在为新注册的设备寻找sysfs中可以存放的位置。

如果发现dev->class存在,而dev->parent->class不存在,就要建立一个胶水目录,在sysfs中隔离这两个实际上有父子关系的设备。

linux这么做也是为了在sysfs显示时更清晰一些。但如果父设备下有多个属于同一类的设备,它们需要放在同一胶水目录下。

怎么寻找这个胶水目录有没有建立过,就要从这里的glue_dirs下的kobject中找了。

设备类的交界面机制

我不喜欢翻译这个 interface接口,因为class_interface实际上做的是在class driverclass下有设备添加或移除的时候,调用预先设置好的回调函数(add_devremove_dev)。

相当于一个类的设备进出类的表面所触发的获取信息的机制。

class_interface原型

// include/linux/device.h
struct class_interface {
//
struct list_head node;
// 指向所属class的指针
struct class *class; // 有设备添加到所属class时调用的函数
int (*add_dev) (struct device *, struct class_interface *);
// 设备删除时调用。
void (*remove_dev) (struct device *, struct class_interface *);
};

关于add_dev()还有一点要说明,如果class_interface比设备更晚添加到class,也会在添加到class的时候执行。

那调用add_dev/remove_dev做什么呢?想做什么都行(例如修改设备的名称),由具体的class driver实现。

设备类的sysfs属性

class_attribute原型

bus_attribute,到driver_attribute,到device_attribute,当然也少不了这里的class_attribute

// include/linux/device.h
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);
}; #define CLASS_ATTR(_name, _mode, _show, _store) \
struct class_attribute class_attr_##_name = __ATTR(_name, _mode, _show, _store)

struct attribute封装这种东西,既简单又耐用,何乐而不为?不过呢,等到讲到sysfs的时候,我们再来细讲这个。

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_add

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

// drviers/base/core.c
/**
* device_add - add device to device hierarchy.
* @dev: device.
*
* This is part 2 of device_register(), though may be called
* separately _iff_ device_initialize() has been called separately.
*
* This adds @dev to the kobject hierarchy via kobject_add(), adds it
* to the global and sibling lists for the device, then
* adds it to the other relevant subsystems of the driver model.
*
* Do not call this routine or device_register() more than once for
* any device structure. The driver model core is not designed to work
* with devices that get unregistered and then spring back to life.
* (Among other things, it's very hard to guarantee that all references
* to the previous incarnation of @dev have been dropped.) Allocate
* and register a fresh new struct device instead.
*
* NOTE: _Never_ directly free @dev after calling this function, even
* if it returned an error! Always use put_device() to give up your
* reference instead.
*/
int device_add(struct device *dev)
{
struct device *parent = NULL;
struct kobject *kobj;
struct class_interface *class_intf;
int error = -EINVAL; // ... // 创建类符号链接,相互创建dev和class之间的链接文件
error = device_add_class_symlinks(dev);
// 创建sys目录下设备其他属性文件
error = device_add_attrs(dev); // ... // 如果 设备属于某个类,添加到类的设备列表中。
// 如果这个类的interface 指定了 添加类设备 的方法,则执行
if (dev->class) {
mutex_lock(&dev->class->p->mutex);
/* tie the class to the device */
klist_add_tail(&dev->knode_class,
&dev->class->p->klist_devices); /* notify any interfaces that the device is here */
list_for_each_entry(class_intf,
&dev->class->p->interfaces, node)
if (class_intf->add_dev)
class_intf->add_dev(dev, class_intf);
mutex_unlock(&dev->class->p->mutex);
}
done:
put_device(dev);
return error; }
EXPORT_SYMBOL_GPL(device_add);

device_add_class_symlinks

// drivers/base/core.c
static int device_add_class_symlinks(struct device *dev)
{
int error; if (!dev->class)
return 0; error = sysfs_create_link(&dev->kobj,
&dev->class->p->subsys.kobj,
"subsystem"); if (dev->parent && device_is_not_partition(dev)) {
error = sysfs_create_link(&dev->kobj, &dev->parent->kobj,
"device");
if (error)
goto out_subsys;
} #ifdef CONFIG_BLOCK
/* /sys/block has directories and does not need symlinks */
if (sysfs_deprecated && dev->class == &block_class)
return 0;
#endif /* link in the class directory pointing to the device */
error = sysfs_create_link(&dev->class->p->subsys.kobj,
&dev->kobj, dev_name(dev)); return 0;
}

device_add_class_symlinks

class.c解析

class_sysfs_ops

#define to_class_attr(_attr) container_of(_attr, struct class_attribute, attr)

static ssize_t class_attr_show(struct kobject *kobj, struct attribute *attr,
char *buf)
{
struct class_attribute *class_attr = to_class_attr(attr);
struct subsys_private *cp = to_subsys_private(kobj);
ssize_t ret = -EIO; if (class_attr->show)
ret = class_attr->show(cp->class, class_attr, buf);
return ret;
} static ssize_t class_attr_store(struct kobject *kobj, struct attribute *attr,
const char *buf, size_t count)
{
struct class_attribute *class_attr = to_class_attr(attr);
struct subsys_private *cp = to_subsys_private(kobj);
ssize_t ret = -EIO; if (class_attr->store)
ret = class_attr->store(cp->class, class_attr, buf, count);
return ret;
} static const struct sysfs_ops class_sysfs_ops = {
.show = class_attr_show,
.store = class_attr_store,
};

class_sysfs_ops就是class定义的sysfs读写函数集合。

class_ktype

static void class_release(struct kobject *kobj)
{
struct subsys_private *cp = to_subsys_private(kobj);
struct class *class = cp->class; pr_debug("class '%s': release.\n", class->name); if (class->class_release)
class->class_release(class);
else
pr_debug("class '%s' does not have a release() function, "
"be careful\n", class->name); kfree(cp);
} static struct kobj_type class_ktype = {
.sysfs_ops = &class_sysfs_ops,
.release = class_release,
.child_ns_type = class_child_ns_type,
};

class_release()是在class引用计数降为零时调用的释放函数。因为class在结构中提供了class_release的函数指针,所以可以由具体的class调用相应的处理方法。

class_ktype是为class对应的kobject(也可以说kset)定义的kobj_type。

class_kset与初始化

/* Hotplug events for classes go to the class subsys */
static struct kset *class_kset; int __init classes_init(void)
{
class_kset = kset_create_and_add("class", NULL, NULL);
if (!class_kset)
return -ENOMEM;
return 0;
} ////////////////////////////////////////////////////////////
// drivers/base/init.c
void __init driver_init(void)
{
/* These are the core pieces */
devtmpfs_init();
devices_init();
buses_init();
classes_init(); // 初始化 class
firmware_init();
hypervisor_init(); /* These are also core pieces, but must come after the
* core core pieces.
*/
platform_bus_init();
cpu_dev_init();
memory_dev_init();
container_dev_init();
}

class_kset代表了/sys/class对应的kset,在classes_init()中创建。

classes_init()的作用,和之前见到的buses_init()devices_init()作用相似,都是构建/sys下的主要目录结构。

创建/删除class的属性

// include/linux/device.h
static inline int __must_check class_create_file(struct class *class,
const struct class_attribute *attr)
{
return class_create_file_ns(class, attr, NULL);
} static inline void class_remove_file(struct class *class,
const struct class_attribute *attr)
{
return class_remove_file_ns(class, attr, NULL);
} ////////////////////////////////////////////////////////////
int class_create_file_ns(struct class *cls, const struct class_attribute *attr,
const void *ns)
{
int error;
if (cls)
error = sysfs_create_file_ns(&cls->p->subsys.kobj,
&attr->attr, ns);
else
error = -EINVAL;
return error;
} void class_remove_file_ns(struct class *cls, const struct class_attribute *attr,
const void *ns)
{
if (cls)
sysfs_remove_file_ns(&cls->p->subsys.kobj, &attr->attr, ns);
}

class_create_file():创建class的属性文件。

class_remove_files():删除class的属性文件。

对kobj的封装

static struct class *class_get(struct class *cls)
{
if (cls)
kset_get(&cls->p->subsys);
return cls;
} static void class_put(struct class *cls)
{
if (cls)
kset_put(&cls->p->subsys);
}

class_get()增加对cls的引用计数;class_put()减少对cls的引用计数,并在计数降为零时调用相应的释放函数,也就是之前见过的class_release函数。

class的引用计数是由subsys_private结构中的kset来管的,kset又是由其内部kobject来管的,kobject又是调用其结构中的kref来管的。这是一种嵌套的封装技术。

static int add_class_attrs(struct class *cls)
{
int i;
int error = 0; if (cls->class_attrs) {
for (i = 0; cls->class_attrs[i].attr.name; i++) {
error = class_create_file(cls, &cls->class_attrs[i]);
if (error)
goto error;
}
}
done:
return error;
error:
while (--i >= 0)
class_remove_file(cls, &cls->class_attrs[i]);
goto done;
} static void remove_class_attrs(struct class *cls)
{
int i; if (cls->class_attrs) {
for (i = 0; cls->class_attrs[i].attr.name; i++)
class_remove_file(cls, &cls->class_attrs[i]);
}
}

add_class_attrs()把cls->class_attrs中的属性加入sysfs。

remove_class_attrs()把cls->class_attrs中的属性删除。

到了class这个级别,就和bus一样,除了自己,没有其它结构能为自己添加属性。

节点对应设备的引用操作

static void klist_class_dev_get(struct klist_node *n)
{
struct device *dev = container_of(n, struct device, knode_class); get_device(dev);
} static void klist_class_dev_put(struct klist_node *n)
{
struct device *dev = container_of(n, struct device, knode_class); put_device(dev);
}

klist_class_dev_get()增加节点对应设备的引用计数,klist_class_dev_put()减少节点对应设备的引用计数。

这是class的设备链表,在节点添加和删除时调用的。相似的klist链表,还有驱动的设备链表,不过由于linux对驱动不太信任,所以没有让驱动占用设备的引用计数。还有总线的设备链表,在添加释放节点时分别调用klist_devices_get()和list_devices_put(),是在bus.c中定义的。还有设备的子设备链表,在添加释放节点时分别调用klist_children_get()和klist_children_put(),是在device.c中定义的。看来klist中的get()/put()函数,是在初始化klist时设定的,也由创建方负责实现。

注册class

// include/linux/device.h
/* This is a #define to keep the compiler from merging different
* instances of the __key variable */
#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)
{
struct subsys_private *cp;
int error; pr_debug("device class '%s': registering\n", cls->name); cp = kzalloc(sizeof(*cp), GFP_KERNEL);
if (!cp)
return -ENOMEM;
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; error = kset_register(&cp->subsys);
if (error) {
kfree(cp);
return error;
}
error = add_class_attrs(class_get(cls));
class_put(cls);
return error;
}
EXPORT_SYMBOL_GPL(__class_register);

class_register()将class注册到系统中。

之所以把class_register()写成宏定义的形式,似乎是为了__key的不同实例合并,在__class_register()中确实使用了__key,但是是为了调试class中使用的mutex用的。__

key的类型lock_class_key是只有使用LOCKDEP定义时才会有内容,写成这样也许是为了在lock_class_key定义为空时减少一些不必要的空间消耗。总之这类trick的做法,是不会影响我们理解代码逻辑的。

__class_register()中进行实际的class注册工作:

先是分配和初始化class_private结构。

可以看到对cp->glue_dirs,只是调用kset_init()定义,并未实际注册到sysfs中。

调用kobject_set_name()创建kobj中实际的类名。

cls->dev_kobj如果未设置,这里会被设为sysfs_dev_char_kobj。

调用kset_register()将class注册到sysfs中,所属kset为class_kset,使用类型为class_ktype。因为没有设置parent,会以/sys/class为父目录。

最后调用add_class_attrs()添加相关的属性文件。

在bus、device、driver、class中,最简单的注册过程就是class的注册,因为它不仅和bus一样属于一种顶层结构,而且连通用的属性文件都不需要,所有的操作就围绕在class_private的创建初始化与添加到sysfs上面。

void class_unregister(struct class *cls)
{
pr_debug("device class '%s': unregistering\n", cls->name);
remove_class_attrs(cls);
kset_unregister(&cls->p->subsys);
}

class_unregister()取消class的注册。它的操作也简单到了极点。

只是这里的class注销也太懒了些。无论是class_unregister(),还是在计数完全释放时调用的class_release(),都找不到释放class_private结构的地方。这是bug吗?

我怀着敬畏的心情,查看了linux-3.0.4中的drivers/base/class.c,发现其中的class_release()函数最后添加了释放class_private结构的代码。看来linux-2.6.32还是有较为明显的缺陷。奈何木已成舟,只能先把这个bug在现有代码里改正,至少以后自己编译内核时不会再这个问题上出错。

不过说起来,像bus_unregister()、class_unregister()这种函数,估计只有在关机时才可能调用得到,实在是无关紧要。

// include/linux/device.h
/* 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); \
})
//////////////////////////////////////////////////////////// /**
* 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);
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);
}
EXPORT_SYMBOL_GPL(__class_create);

class_create()是提供给外界快速创建class的API。应该说,class中可以提供的一系列函数,这里都没有提供,或许可以在创建后再加上。

相似的函数是在core.c中的device_create(),那是提供一种快速创建device的API。

static void class_create_release(struct class *cls)
{
pr_debug("%s called for %s\n", __func__, cls->name);
kfree(cls);
} /**
* class_destroy - destroys a struct class structure
* @cls: pointer to the struct class that is to be destroyed
*
* Note, the pointer to be destroyed must have been created with a call
* to class_create().
*/
void class_destroy(struct class *cls)
{
if ((cls == NULL) || (IS_ERR(cls)))
return; class_unregister(cls);
}

class_destroy()是与class_create()相对的删除class的函数。

虽然在class_destroy()中没有看到释放class内存的代码,但这是在class_create_release()中做的。class_create_release()之前已经在class_create()中被作为class结构中定义的class_release()函数,会在class引用计数降为零时被调用。

在class中,class结构和class_private结构都是在class引用计数降为零时才释放的。这保证了即使class已经被注销,仍然不会影响其下设备的正常使用。但在bus中,bus_private结构是在bus_unregister()中就被释放的。没有了bus_private,bus下面的device和driver想必都无法正常工作了吧。这或许和bus对应与实际总线有关。总线都没了,下面的设备自然没人用了。

遍历

class为了遍历设备链表,特意定义了专门的结构和遍历函数,实现如下。

struct class_dev_iter {
struct klist_iter ki;
const struct device_type *type;
}; /**
* class_dev_iter_init - initialize class device iterator
* @iter: class iterator to initialize
* @class: the class we wanna iterate over
* @start: the device to start iterating from, if any
* @type: device_type of the devices to iterate over, NULL for all
*
* Initialize class iterator @iter such that it iterates over devices
* of @class. If @start is set, the list iteration will start there,
* otherwise if it is NULL, the iteration starts at the beginning of
* the list.
*/
void class_dev_iter_init(struct class_dev_iter *iter, struct class *class,
struct device *start, const struct device_type *type)
{
struct klist_node *start_knode = NULL; if (start)
start_knode = &start->knode_class;
klist_iter_init_node(&class->p->class_devices, &iter->ki, start_knode);
iter->type = type;
} struct device *class_dev_iter_next(struct class_dev_iter *iter)
{
struct klist_node *knode;
struct device *dev; while (1) {
knode = klist_next(&iter->ki);
if (!knode)
return NULL;
dev = container_of(knode, struct device, knode_class);
if (!iter->type || iter->type == dev->type)
return dev;
}
} void class_dev_iter_exit(struct class_dev_iter *iter)
{
klist_iter_exit(&iter->ki);
}

之所以要如此费一番周折,在klist_iter外面加上这一层封装,完全是为了对链表进行选择性遍历。选择的条件就是device_type。device_type是在device结构中使用的类型,其中定义了相似设备使用的一些处理操作,可以说比class的划分还要小一层。class对设备链表如此遍历,也是用心良苦啊。

int class_for_each_device(struct class *class, struct device *start,
void *data, int (*fn)(struct device *, void *))
{
struct class_dev_iter iter;
struct device *dev;
int error = 0; if (!class)
return -EINVAL;
if (!class->p) {
WARN(1, "%s called for class '%s' before it was initialized",
__func__, class->name);
return -EINVAL;
} class_dev_iter_init(&iter, class, start, NULL);
while ((dev = class_dev_iter_next(&iter))) {
error = fn(dev, data);
if (error)
break;
}
class_dev_iter_exit(&iter); return error;
}
struct device *class_find_device(struct class *class, struct device *start,
void *data,
int (*match)(struct device *, void *))
{
struct class_dev_iter iter;
struct device *dev; if (!class)
return NULL;
if (!class->p) {
WARN(1, "%s called for class '%s' before it was initialized",
__func__, class->name);
return NULL;
} class_dev_iter_init(&iter, class, start, NULL);
while ((dev = class_dev_iter_next(&iter))) {
if (match(dev, data)) {
get_device(dev);
break;
}
}
class_dev_iter_exit(&iter); return dev;
}

class_for_each_device()是对class的设备链表上的每个设备调用指定的函数。

class_find_device()查找class设备链表上的某个设备,使用指定的匹配函数。

注册class_interface

int class_interface_register(struct class_interface *class_intf)
{
struct class *parent;
struct class_dev_iter iter;
struct device *dev; if (!class_intf || !class_intf->class)
return -ENODEV; parent = class_get(class_intf->class);
if (!parent)
return -EINVAL; mutex_lock(&parent->p->mutex);
list_add_tail(&class_intf->node, &parent->p->interfaces);
if (class_intf->add_dev) {
class_dev_iter_init(&iter, parent, NULL, NULL);
while ((dev = class_dev_iter_next(&iter)))
class_intf->add_dev(dev, class_intf);
class_dev_iter_exit(&iter);
}
mutex_unlock(&parent->p->mutex); return 0;
}

class_interface_register()把class_interface添加到指定的class上。

调用class_get()获取class的引用计数。

使用class->mutex进行保护。

将classs_intf添加到class的接口列表中。

对已经添加到class上的设备补上add_dev()操作。

这里使用的class->mutex是用来保护class的类接口链表。对于简单的list_head来说,这种mutex保护是应该的。但对于武装到牙齿的klist来说,就完全不必要了,因为klist内置了spinlock来完成互斥的操作。所以之前其它的klist链表操作都没有mutex保护。

比较spinlock和mutex的话,spinlock操作要比mutex快很多,因为对mutex的操作本身就需要spinlock来保护。但mutex的好处是它可以阻塞。使用spinlock时间太长的话,一是浪费cpu时间,二是禁止了任务抢占。klist是使用spinlock来保护的,这适合大部分情况,但在klist遍历时也可能调用一些未知的操作,它们可能很耗时,甚至可能阻塞,这时最好能使用mutex加以替换。

void class_interface_unregister(struct class_interface *class_intf)
{
struct class *parent = class_intf->class;
struct class_dev_iter iter;
struct device *dev; if (!parent)
return; mutex_lock(&parent->p->mutex);
list_del_init(&class_intf->node);
if (class_intf->remove_dev) {
class_dev_iter_init(&iter, parent, NULL, NULL);
while ((dev = class_dev_iter_next(&iter)))
class_intf->remove_dev(dev, class_intf);
class_dev_iter_exit(&iter);
}
mutex_unlock(&parent->p->mutex); class_put(parent);
}

class_interface_register()把class_interface添加到指定的class上。

调用class_get()获取class的引用计数。

使用class->mutex进行保护。

将classs_intf添加到class的接口列表中。

对已经添加到class上的设备补上add_dev()操作。

这里使用的class->mutex是用来保护class的类接口链表。对于简单的list_head来说,这种mutex保护是应该的。但对于武装到牙齿的klist来说,就完全不必要了,因为klist内置了spinlock来完成互斥的操作。所以之前其它的klist链表操作都没有mutex保护。

比较spinlock和mutex的话,spinlock操作要比mutex快很多,因为对mutex的操作本身就需要spinlock来保护。但mutex的好处是它可以阻塞。使用spinlock时间太长的话,一是浪费cpu时间,二是禁止了任务抢占。klist是使用spinlock来保护的,这适合大部分情况,但在klist遍历时也可能调用一些未知的操作,它们可能很耗时,甚至可能阻塞,这时最好能使用mutex加以替换。

class_interface_unregister()从class中去除指定的class_interface。对于这些class_interface来说,自己注销和设备注销效果是一样的,都会调用相应的remove_dev()。

class_compat

struct class_compat {
struct kobject *kobj;
}; /**
* class_compat_register - register a compatibility class
* @name: the name of the class
*
* Compatibility class are meant as a temporary user-space compatibility
* workaround when converting a family of class devices to a bus devices.
*/
struct class_compat *class_compat_register(const char *name)
{
struct class_compat *cls; cls = kmalloc(sizeof(struct class_compat), GFP_KERNEL);
if (!cls)
return NULL;
cls->kobj = kobject_create_and_add(name, &class_kset->kobj);
if (!cls->kobj) {
kfree(cls);
return NULL;
}
return cls;
}
EXPORT_SYMBOL_GPL(class_compat_register); /**
* class_compat_unregister - unregister a compatibility class
* @cls: the class to unregister
*/
void class_compat_unregister(struct class_compat *cls)
{
kobject_put(cls->kobj);
kfree(cls);
}
EXPORT_SYMBOL_GPL(class_compat_unregister);

在/sys/class下面,除了class类型的,还有表现起来和class相同的class_compat类型。

其实class_compat就是单单为了显示一个目录,不会定义对应的属性或者函数。

/**
* class_compat_create_link - create a compatibility class device link to
* a bus device
* @cls: the compatibility class
* @dev: the target bus device
* @device_link: an optional device to which a "device" link should be created
*/
int class_compat_create_link(struct class_compat *cls, struct device *dev,
struct device *device_link)
{
int error; error = sysfs_create_link(cls->kobj, &dev->kobj, dev_name(dev));
if (error)
return error; /*
* Optionally add a "device" link (typically to the parent), as a
* class device would have one and we want to provide as much
* backwards compatibility as possible.
*/
if (device_link) {
error = sysfs_create_link(&dev->kobj, &device_link->kobj,
"device");
if (error)
sysfs_remove_link(cls->kobj, dev_name(dev));
} return error;
} void class_compat_remove_link(struct class_compat *cls, struct device *dev,
struct device *device_link)
{
if (device_link)
sysfs_remove_link(&dev->kobj, "device");
sysfs_remove_link(cls->kobj, dev_name(dev));
}

class_compat_create_link()的目的是在class_compat目录下建立类似于class目录下的,对设备的软链接。这个不是在标准的设备注册时调用的。

总结

本节我们分析完了设备驱动模型中的class,对设备驱动模型的分析也告一段落。现在,我相信大家对于设备驱动模型的框架也有了一个基础的认识。

若是要加深对它的认识,就要在此基础上不断充实细节,用具体的设备驱动来理解。

Linux 内核:设备驱动模型(3)class与device的更多相关文章

  1. Linux 字符设备驱动模型

    一.使用字符设备驱动程序 1. 编译/安装驱动 在Linux系统中,驱动程序通常采用内核模块的程序结构来进行编码.因此,编译/安装一个驱动程序,其实质就是编译/安装一个内核模块 2. 创建设备文件 通 ...

  2. Linux总线设备驱动模型

    1. Linux2.6内核引入总线.设备.驱动模型来描述各种总线(PCI.USB.I2C.SPI)与外围设备及其驱动之间的关系. 2. 在Linux内核中,总线用bus_type结构来描述,定义于文件 ...

  3. Linux 内核设备驱动

    设备模型跟踪所有对系统已知的驱动. 这个跟踪的主要原因是使驱动核心能匹配驱动和新 设备. 一旦驱动在系统中是已知的对象, 但是, 许多其他的事情变得有可能. 设备驱动可 输出和任何特定设备无关的信息和 ...

  4. Linux内核驱动学习(四)Platform设备驱动模型

    Linux platform设备驱动模型 文章目录 Linux platform设备驱动模型 前言 框架 设备与驱动的分离 设备(device) 驱动(driver) 匹配(match) 参考 前言 ...

  5. Linux中总线设备驱动模型及平台设备驱动实例

    本文将简要地介绍Linux总线设备驱动模型及其实现方式,并不会过多地涉及其在内核中的具体实现,最后,本文将会以平台总线为例介绍设备和驱动程序的实现过程. 目录: 一.总线设备驱动模型总体介绍及其实现方 ...

  6. Linux的总线设备驱动模型

    裸机编写驱动比较自由,按照手册实现其功能即可,每个人写出来都有很大不同: 而Linux中还需要按照Linux的驱动模型来编写,也就是需要按照"模板"来写,写出来的驱动就比较统一. ...

  7. Linux混杂设备驱动

    1. Linux混杂设备驱动模型 ① 在Linux系统中,存在一类字符设备,它们拥有相同的主设备号(10),但次设备号不同,我们称这类设备为混杂设备(miscdevice).所有混杂设备形成一个链表, ...

  8. linux设备驱动模型

    尽管LDD3中说对多数程序员掌握设备驱动模型不是必要的,但对于嵌入式Linux的底层程序员而言,对设备驱动模型的学习非常重要. Linux设备模型的目的:为内核建立一个统一的设备模型,从而又一个对系统 ...

  9. linux设备驱动模型(kobject与kset)

    Linux设备模型的目的:为内核建立一个统一的设备模型,从而又一个对系统结构的一般性抽象描述.换句话说,Linux设备模型提取了设备操作的共同属性,进行抽象,并将这部分共同的属性在内核中实现,而为需要 ...

  10. Linux块设备驱动(一) _驱动模型

    块设备是Linux三大设备之一,其驱动模型主要针对磁盘,Flash等存储类设备,本文以3.14为蓝本,探讨内核中的块设备驱动模型 框架 下图是Linux中的块设备模型示意图,应用层程序有两种方式访问一 ...

随机推荐

  1. k8s网页访问实战

    流程: 客户端访问--->ingress七层代理---->service四层代理---->deployment 详细情况:https://www.cnblogs.com/yangme ...

  2. openpyxl模块操作Excel

    1.openpyxl模块了解 1.excel版本问题 03版本之前的excel文件后缀名xls 03版本之后的excel文件后缀名xlsx 2.python操作excel表格的模块 openpyxl ...

  3. 第一章 Jenkins安装配置

    Jenkins官网 # 官网: https://www.jenkins.iohttps://www.jenkins.io/zh/ # docker安装: https://www.jenkins.io/ ...

  4. Lock、Monitor线程锁

    Lock.Monitor线程锁 官网使用 https://learn.microsoft.com/zh-cn/dotnet/api/system.threading.monitor?view=net- ...

  5. 记录一次fs通话无声的问题

    概述 freeswitch是一款简单好用的VOIP开源软交换平台. fs的实际应用中,由于网络.配置等问题,经常会产生通话无声的问题. 环境 CentOS 7.9 freeswitch 1.10.7 ...

  6. 2023年的Clion内建立多个子项目(保姆级教程)

    目录 下载插件C/C++ Single File Execution 项目操作 其他操作 下载插件C/C++ Single File Execution 项目操作 1.新建项目-->如图所示操作 ...

  7. video2blog 视频转图文AI小工具正式开源啦

    前言 最近对一些小细节做了很多处理,但是其实还是有非常多的问题,没办法时间毕竟时间有限.为什么在这个时候开源,因为主要功能可以全部跑通了,分支暂时没开发的功能也可以通过其他的工具来替代. 这个工具开发 ...

  8. 【C# mvc5】使用mvc5 +bootstrap+EF6搭建一个权限管理系统的心得体会

    使用mvc5的体会,是 业务代码都可以独立分层,比如搭配多层架构,通过controller控制器传递需要渲染的列表,按钮.接受前端返回的实体模型等.总之我觉得要在前端渲染的数据可以写在controll ...

  9. Flutter(一):MAC的Flutter安装指南

    官网地址 官网: https://flutter.dev Github: https://github.com/flutter/flutter Git的核心分支包括master.dev.stable. ...

  10. 申请并部署免费的 SSL/TLS 证书

    对于囊中羞涩的我们来说,只要能白嫖,就绝不乱花钱.惯常申请免费 SSL/TLS 证书的途径有: 各大云服务平台限量提供.比如阿里云会给每个账号每年 20 个证书的申请额度.缺点是不支持泛域名,一年后须 ...