Linux设备模型之Class
在设备模型中,Bus
、Device
、Device driver
等等,都比较好理解,因为它们对应了实实在在的东西,所有的逻辑都是围绕着这些实体展开的。而本文所要描述的Class
就有些不同了,因为它是虚拟出来的,只是为了抽象设备的共性。
举个例子,一些年龄相仿、需要获取的知识相似的人,聚在一起学习,就构成了一个班级(Class
)。这个班级可以有自己的名称(如295
),但如果离开构成它的学生(device
),它就没有任何存在意义。另外,班级存在的最大意义是什么呢?是由老师讲授的每一个课程!因为老师只需要讲一遍,一个班的学生都可以听到。不然的话(例如每个学生都在家学习),就要为每人请一个老师,讲授一遍。而讲的内容,大多是一样的,这就是极大的浪费。
设备模型中的Class
所提供的功能也一样了,例如一些相似的device
(学生),需要向用户空间提供相似的接口(课程),如果每个设备的驱动都实现一遍的话,就会导致内核有大量的冗余代码,这就是极大的浪费。所以,Class
说了,我帮你们实现吧,你们会用就行了。
这就是设备模型中Class的功能,再结合内核的注释:
/*./include/linux/device/class.h*/
* 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.
就容易理解了。
1. 数据结构描述
1.2 struct class
struct class
是class
的抽象,它的定义如下:
/*./include/linux/device/class.h*/
/**
* struct class - device classes
* @name: Name of the class.
* @owner: The module owner.
* @class_groups: 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.
* @shutdown_pre: Called at shut-down time before driver shutdown.
* @ns_type: Callbacks so sysfs can detemine namespaces.
* @namespace: Namespace of the device belongs to this class.
* @get_ownership: Allows class to specify uid/gid of the sysfs directories
* for the devices belonging to the class. Usually tied to
* device's namespace.
* @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;
const struct attribute_group **class_groups;
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 (*shutdown_pre)(struct device *dev);
const struct kobj_ns_type_operations *ns_type;
const void *(*namespace)(struct device *dev);
void (*get_ownership)(struct device *dev, kuid_t *uid, kgid_t *gid);
const struct dev_pm_ops *pm;
struct subsys_private *p;
};
其实struct class
和struct bus
很类似,解释如下:
name
,class
的名称,会在/sys/class/
目录下体现。class_groups
, 该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
,和“Linux设备模型之Bus”中struct bus
结构一样,不再说明。
1.3 struct 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;
struct class *class;
int (*add_dev) (struct device *, struct class_interface *);
void (*remove_dev) (struct device *, struct class_interface *);
};
2. 功能及内部逻辑解析
2.1 class的功能
先看一下现有Linux系统中有关class
的状况(这里以input class
为例):
uos@uos-PC:~$ ls /sys/class/input/ -l
总用量 0
lrwxrwxrwx 1 root root 0 7月 13 22:19 event0 -> ../../devices/pci0000:00/0000:00:07.0/sound/card0/input0/event0
lrwxrwxrwx 1 root root 0 7月 13 22:19 event1 -> ../../devices/pci0000:00/0000:00:07.0/sound/card0/input1/event1
lrwxrwxrwx 1 root root 0 7月 13 22:19 event2 -> ../../devices/pci0000:00/0000:00:07.0/sound/card0/input2/event2
lrwxrwxrwx 1 root root 0 7月 13 22:19 event3 -> ../../devices/pci0000:00/0000:00:07.0/sound/card0/input3/event3
lrwxrwxrwx 1 root root 0 7月 13 22:19 event4 -> ../../devices/pci0000:00/0000:00:07.0/sound/card0/input4/event4
lrwxrwxrwx 1 root root 0 7月 13 22:19 event5 -> ../../devices/pci0000:00/0000:00:07.0/sound/card0/input5/event5
lrwxrwxrwx 1 root root 0 7月 13 14:19 event6 -> ../../devices/LNXSYSTM:00/LNXPWRBN:00/input/input6/event6
lrwxrwxrwx 1 root root 0 7月 13 14:19 event7 -> ../../devices/pci0000:00/0000:00:0f.0/0000:04:00.1/sound/card1/input7/event7
lrwxrwxrwx 1 root root 0 7月 13 22:19 input0 -> ../../devices/pci0000:00/0000:00:07.0/sound/card0/input0
lrwxrwxrwx 1 root root 0 7月 13 22:19 input1 -> ../../devices/pci0000:00/0000:00:07.0/sound/card0/input1
lrwxrwxrwx 1 root root 0 7月 13 22:19 input2 -> ../../devices/pci0000:00/0000:00:07.0/sound/card0/input2
lrwxrwxrwx 1 root root 0 7月 13 22:19 input3 -> ../../devices/pci0000:00/0000:00:07.0/sound/card0/input3
lrwxrwxrwx 1 root root 0 7月 13 22:19 input4 -> ../../devices/pci0000:00/0000:00:07.0/sound/card0/input4
lrwxrwxrwx 1 root root 0 7月 13 22:19 input5 -> ../../devices/pci0000:00/0000:00:07.0/sound/card0/input5
lrwxrwxrwx 1 root root 0 7月 13 14:19 input6 -> ../../devices/LNXSYSTM:00/LNXPWRBN:00/input/input6
lrwxrwxrwx 1 root root 0 7月 13 14:19 input7 -> ../../devices/pci0000:00/0000:00:0f.0/0000:04:00.1/sound/card1/input7
lrwxrwxrwx 1 root root 0 7月 13 22:19 mice -> ../../devices/virtual/input/mice
uos@uos-PC:~$ ls /sys/devices/LNXSYSTM:00/LNXPWRBN:00/input/input6/event6 -l
总用量 0
-r--r--r-- 1 root root 16384 7月 13 14:19 dev
lrwxrwxrwx 1 root root 0 7月 13 14:19 device -> ../../input6
drwxr-xr-x 2 root root 0 7月 19 20:28 power
lrwxrwxrwx 1 root root 0 7月 13 14:19 subsystem -> ../../../../../../class/input
-rw-r--r-- 1 root root 16384 7月 13 14:19 uevent
uos@uos-PC:~$ ls /sys/devices/virtual/input/mice -l
总用量 0
-r--r--r-- 1 root root 16384 7月 13 14:19 dev
drwxr-xr-x 2 root root 0 7月 19 20:29 power
lrwxrwxrwx 1 root root 0 7月 13 22:19 subsystem -> ../../../../class/input
-rw-r--r-- 1 root root 16384 7月 13 22:19 uevent
看上面的例子,发现input class
也没做什么实实在在的事儿,它(input class
)的功能,仅仅是:
- 在
/sys/class/
目录下,创建一个本class
的目录(input
) - 在本目录下,创建每一个属于该
class
的设备的符号链接(如,把/sys/devices/LNXSYSTM:00/LNXPWRBN:00/input/input6/event6
设备链接到/sys/class/input/event6
),这样就可以在本class
目录下,访问该设备的所有特性(即attribute
) - 另外,
device
在sysfs
的目录下,也会创建一个subsystem
的符号链接,链接到本class
的目录
继续分析一下Class
的核心逻辑都做了哪些事情。
2.2 class的注册
class的注册,是由__class_register接口实现,
/*drivers/base/class.c*/
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 = class_add_groups(class_get(cls), cls->class_groups);
class_put(cls);
return error;
}
EXPORT_SYMBOL_GPL(__class_register);
它的处理逻辑和bus的注册类似,主要包括:
- 为
class
结构中的truct subsys_private
类型的指针(cp
)分配空间,并初始化其中的字段,包括cp->subsys.kobj.kset
、cp->subsys.kobj.ktype
等等 - 调用
kset_register
,注册该class
(回忆“Linux设备模型之Bus”中的描述,一个class
就是一个子系统,因此注册class
也是注册子系统)。该过程结束后,在/sys/class/
目录下,就会创建对应该class
(子系统)的目录 - 调用
add_class_attrs
接口,将class
结构中class_attrs
指针所指向的attribute
,添加到内核中。执行完后,在/sys/class/xxx_class/
目录下,就会看到这些attribute
对应的文件
2.3 device注册时,和class有关的动作
在"Linux设备模型之device和device driver”中,有讲过struct device
和struct device_driver
这两个数据结构,其中struct device
结构会包含一个struct class
指针(这从侧面说明了class
是device
的集合,甚至,class
可以是device
的drive
r)。当某个class driver
向内核注册了一个class
后,需要使用该class
的device
,通过把自身的class
指针指向该class
即可,剩下的事情,就由内核在注册device
时处理了。
这里讲一下在device
注册时,和class
有关的动作:
device
的注册最终是由device_add
接口(drivers/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.
*
* Rule of thumb is: if device_add() succeeds, you should call
* device_del() when you want to get rid of it. If device_add() has
* *not* succeeded, use *only* put_device() to drop the reference
* count.
*/
int device_add(struct device *dev)
{
struct device *parent;
struct kobject *kobj;
struct class_interface *class_intf;
int error = -EINVAL;
struct kobject *glue_dir = NULL;
dev = get_device(dev);
if (!dev)
goto done;
if (!dev->p) {
error = device_private_init(dev);
if (error)
goto done;
}
/*
* for statically allocated devices, which should all be converted
* some day, we need to initialize the name. We prevent reading back
* the name, and force the use of dev_name()
*/
if (dev->init_name) {
dev_set_name(dev, "%s", dev->init_name);
dev->init_name = NULL;
}
/* subsystems can specify simple device enumeration */
if (!dev_name(dev) && dev->bus && dev->bus->dev_name)
dev_set_name(dev, "%s%u", dev->bus->dev_name, dev->id);
if (!dev_name(dev)) {
error = -EINVAL;
goto name_error;
}
pr_debug("device: '%s': %s\n", dev_name(dev), __func__);
parent = get_device(dev->parent);
kobj = get_device_parent(dev, parent);
if (IS_ERR(kobj)) {
error = PTR_ERR(kobj);
goto parent_error;
}
if (kobj)
dev->kobj.parent = kobj;
/* use parent numa_node */
if (parent && (dev_to_node(dev) == NUMA_NO_NODE))
set_dev_node(dev, dev_to_node(parent));
/* first, register with generic layer. */
/* we require the name to be set before, and pass NULL */
error = kobject_add(&dev->kobj, dev->kobj.parent, NULL);
if (error) {
glue_dir = get_glue_dir(dev);
goto Error;
}
/* notify platform of device entry */
device_platform_notify(dev);
error = device_create_file(dev, &dev_attr_uevent);
if (error)
goto attrError;
error = device_add_class_symlinks(dev);
if (error)
goto SymlinkError;
error = device_add_attrs(dev);
if (error)
goto AttrsError;
error = bus_add_device(dev);
if (error)
goto BusError;
error = dpm_sysfs_add(dev);
if (error)
goto DPMError;
device_pm_add(dev);
if (MAJOR(dev->devt)) {
error = device_create_file(dev, &dev_attr_dev);
if (error)
goto DevAttrError;
error = device_create_sys_dev_entry(dev);
if (error)
goto SysEntryError;
devtmpfs_create_node(dev);
}
/* Notify clients of device addition. This call must come
* after dpm_sysfs_add() and before kobject_uevent().
*/
if (dev->bus)
blocking_notifier_call_chain(&dev->bus->p->bus_notifier,
BUS_NOTIFY_ADD_DEVICE, dev);
kobject_uevent(&dev->kobj, KOBJ_ADD);
/*
* Check if any of the other devices (consumers) have been waiting for
* this device (supplier) to be added so that they can create a device
* link to it.
*
* This needs to happen after device_pm_add() because device_link_add()
* requires the supplier be registered before it's called.
*
* But this also needs to happen before bus_probe_device() to make sure
* waiting consumers can link to it before the driver is bound to the
* device and the driver sync_state callback is called for this device.
*/
if (dev->fwnode && !dev->fwnode->dev) {
dev->fwnode->dev = dev;
fw_devlink_link_device(dev);
}
bus_probe_device(dev);
/*
* If all driver registration is done and a newly added device doesn't
* match with any driver, don't block its consumers from probing in
* case the consumer device is able to operate without this supplier.
*/
if (dev->fwnode && fw_devlink_drv_reg_done && !dev->can_match)
fw_devlink_unblock_consumers(dev);
if (parent)
klist_add_tail(&dev->p->knode_parent,
&parent->p->klist_children);
if (dev->class) {
mutex_lock(&dev->class->p->mutex);
/* tie the class to the device */
klist_add_tail(&dev->p->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;
SysEntryError:
if (MAJOR(dev->devt))
device_remove_file(dev, &dev_attr_dev);
DevAttrError:
device_pm_remove(dev);
dpm_sysfs_remove(dev);
DPMError:
bus_remove_device(dev);
BusError:
device_remove_attrs(dev);
AttrsError:
device_remove_class_symlinks(dev);
SymlinkError:
device_remove_file(dev, &dev_attr_uevent);
attrError:
device_platform_notify_remove(dev);
kobject_uevent(&dev->kobj, KOBJ_REMOVE);
glue_dir = get_glue_dir(dev);
kobject_del(&dev->kobj);
Error:
cleanup_glue_dir(dev, glue_dir);
parent_error:
put_device(parent);
name_error:
kfree(dev->p);
dev->p = NULL;
goto done;
}
EXPORT_SYMBOL_GPL(device_add);
该接口中和class
有关的动作包括:
- 调用
device_add_class_symlinks
接口,创建"class的功能"小节描述的各种符号链接,即:在对应class的目录下,创建指向device
的符号链接;在device
的目录下,创建名称为subsystem
、指向对应class
目录的符号链接 - 调用
device_add_attrs
,添加由class指定的attributes
(class->dev_attrs
) - 如果存在对应该
class
的add_dev
回调函数,调用该回调函数
refer to
Linux设备模型(7)_Class