Linux设备模型之Class

在设备模型中,BusDeviceDevice 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 classclass的抽象,它的定义如下:

/*./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 classstruct bus很类似,解释如下:

  • nameclass的名称,会在/sys/class/目录下体现。
  • class_groups, 该class的默认attribute,会在class注册到内核时,自动在/sys/class/xxx_class下创建对应的attribute文件。
  • dev_groups,该class下每个设备的attribute,会在设备注册到内核时,自动在该设备的sysfs目录下创建对应的attribute文件。
  • dev_kobj,表示该class下的设备在/sys/dev/下的目录,现在一般有charblock两个,如果dev_kobjNULL,则默认选择char
  • dev_uevent,当该class下有设备发生变化时,会调用classuevent回调函数。
  • class_release,用于release自身的回调函数。
  • dev_release,用于release class内设备的回调函数。在device_release接口中,会依次检查DeviceDevice Type以及Device所在的class,是否注册release接口,如果有则调用相应的release接口release设备指针。
  • p,和“Linux设备模型之Bus”中struct bus结构一样,不再说明。

1.3 struct class_interface

struct class_interface是这样的一个结构:它允许class driverclass下有设备添加或移除的时候,调用预先设置好的回调函数(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 713 22:19 event0 -> ../../devices/pci0000:00/0000:00:07.0/sound/card0/input0/event0
lrwxrwxrwx 1 root root 0 713 22:19 event1 -> ../../devices/pci0000:00/0000:00:07.0/sound/card0/input1/event1
lrwxrwxrwx 1 root root 0 713 22:19 event2 -> ../../devices/pci0000:00/0000:00:07.0/sound/card0/input2/event2
lrwxrwxrwx 1 root root 0 713 22:19 event3 -> ../../devices/pci0000:00/0000:00:07.0/sound/card0/input3/event3
lrwxrwxrwx 1 root root 0 713 22:19 event4 -> ../../devices/pci0000:00/0000:00:07.0/sound/card0/input4/event4
lrwxrwxrwx 1 root root 0 713 22:19 event5 -> ../../devices/pci0000:00/0000:00:07.0/sound/card0/input5/event5
lrwxrwxrwx 1 root root 0 713 14:19 event6 -> ../../devices/LNXSYSTM:00/LNXPWRBN:00/input/input6/event6
lrwxrwxrwx 1 root root 0 713 14:19 event7 -> ../../devices/pci0000:00/0000:00:0f.0/0000:04:00.1/sound/card1/input7/event7
lrwxrwxrwx 1 root root 0 713 22:19 input0 -> ../../devices/pci0000:00/0000:00:07.0/sound/card0/input0
lrwxrwxrwx 1 root root 0 713 22:19 input1 -> ../../devices/pci0000:00/0000:00:07.0/sound/card0/input1
lrwxrwxrwx 1 root root 0 713 22:19 input2 -> ../../devices/pci0000:00/0000:00:07.0/sound/card0/input2
lrwxrwxrwx 1 root root 0 713 22:19 input3 -> ../../devices/pci0000:00/0000:00:07.0/sound/card0/input3
lrwxrwxrwx 1 root root 0 713 22:19 input4 -> ../../devices/pci0000:00/0000:00:07.0/sound/card0/input4
lrwxrwxrwx 1 root root 0 713 22:19 input5 -> ../../devices/pci0000:00/0000:00:07.0/sound/card0/input5
lrwxrwxrwx 1 root root 0 713 14:19 input6 -> ../../devices/LNXSYSTM:00/LNXPWRBN:00/input/input6
lrwxrwxrwx 1 root root 0 713 14:19 input7 -> ../../devices/pci0000:00/0000:00:0f.0/0000:04:00.1/sound/card1/input7
lrwxrwxrwx 1 root root 0 713 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 713 14:19 dev
lrwxrwxrwx 1 root root     0 713 14:19 device -> ../../input6
drwxr-xr-x 2 root root     0 719 20:28 power
lrwxrwxrwx 1 root root     0 713 14:19 subsystem -> ../../../../../../class/input
-rw-r--r-- 1 root root 16384 713 14:19 uevent
uos@uos-PC:~$ ls /sys/devices/virtual/input/mice -l
总用量 0
-r--r--r-- 1 root root 16384 713 14:19 dev
drwxr-xr-x 2 root root     0 719 20:29 power
lrwxrwxrwx 1 root root     0 713 22:19 subsystem -> ../../../../class/input
-rw-r--r-- 1 root root 16384 713 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
  • 另外,devicesysfs的目录下,也会创建一个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.ksetcp->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 devicestruct device_driver这两个数据结构,其中struct device结构会包含一个struct class指针(这从侧面说明了classdevice的集合,甚至,class可以是devicedriver)。当某个class driver向内核注册了一个class后,需要使用该classdevice,通过把自身的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指定的attributesclass->dev_attrs
  • 如果存在对应该classadd_dev回调函数,调用该回调函数

refer to
Linux设备模型(7)_Class

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值