Linux设备模型2

Linux设备模型2(基于Linux6.6)---Kobject介绍

 


一、Kobject概述

在 Linux 内核中,kobject 是一个非常重要的概念,它用于表示内核对象,并在内核中提供统一的对象管理机制。kobject 提供了对内核对象的抽象,它支持对象的生命周期管理、属性管理、事件通知等功能。kobject 是 Linux 设备模型的核心组成部分,并且它在设备模型中扮演着非常重要的角色。

什么是 kobject?

kobject(Kernel Object)是 Linux 内核中一个用于表示内核对象的结构体,它为对象提供了标准化的生命周期管理、属性存取和事件通知机制。可以将 kobject 视为一个基础设施,帮助内核管理各种对象(例如设备、驱动、模块等),并与用户空间或者其他内核子系统进行交互。

在 Linux 内核中,几乎所有的内核对象(包括硬件设备、驱动程序、文件系统对象、模块等)都可以通过 kobject 来进行管理。

二、kobject 基本概念

Linux设备模型的核心是使用Bus、Class、Device、Driver四个核心数据结构,将大量的、不同功能的硬件设备(以及驱动该硬件设备的方法),以树状结构的形式,进行归纳、抽象,从而方便Kernel的统一管理。

而硬件设备的数量、种类是非常多的,这就决定了Kernel中将会有大量的有关设备模型的数据结构。这些数据结构一定有一些共同的功能,需要抽象出来统一实现,否则就会不可避免的产生冗余代码。这就是Kobject诞生的背景。

  1. 通过parent指针,可以将所有Kobject以层次结构的形式组合起来。
  2. 使用一个引用计数(reference count),来记录Kobject被引用的次数,并在引用次数变为0时把它释放(这是Kobject诞生时的唯一功能)。
  3. 和sysfs虚拟文件系统配合,将每一个Kobject及其特性,以文件的形式,开放到用户空间。

在Linux中,Kobject几乎不会单独存在。它的主要功能,就是内嵌在一个大型的数据结构中,为这个数据结构提供一些底层的功能实现。 
Linux driver开发者,很少会直接使用Kobject以及它提供的接口,而是使用构建在Kobject之上的设备模型接口。

三、 kobject 的结构

3.1、 在Linux Kernel source code中的位置

在Kernel源代码中,Kobject由如下两个文件实现:

  • include/linux/kobject.h
  • lib/kobject.c

其中kobject.h为Kobject的头文件,包含所有的数据结构定义和接口声明。kobject.c为核心功能的实现。

3.2、 主要的数据结构

Kobject, Kset和Ktype这三个概念。

kobject 是一个结构体,用于表示内核中的一个对象,提供了一个统一的机制来管理对象的生命周期、属性和事件通知。几乎所有内核中的对象(如设备、模块、文件系统对象等)都可以通过 kobject 来表示和管理。
kset 是一组相关的 kobject 的集合。它是 kobject 组织的一种方式,通常用来将多个相关的 kobject 聚集在一起,并且提供统一的管理方式。一个 kset 可以包含多个 kobject,并且可以通过 kset 来处理一组对象的生命周期管理和统一行为。
ktype 用于定义 kobject 的行为。它是一个结构体,包含了一些操作方法(如初始化、销毁、属性读写等),这些方法定义了一个 kobject 对象的行为。每个 kobject 都可以指定一个 ktype,从而决定该对象的行为。
关系与作用:
    kobject 是 Linux 内核中表示对象的基本单位,几乎所有内核中的对象(如设备、驱动程序等)都被视为 kobject。
    kset 是 kobject 的集合,一个 kset 可以包含多个相关的 kobject,并为这些对象提供统一的管理。例如,所有的设备 kobject 可能属于一个设备 kset,从而可以统一管理设备。
    ktype 是定义 kobject 行为的结构体,它描述了一个 kobject 的具体操作,例如它如何与 sysfs 交互,如何初始化和销毁等。每个 kobject 都可以指定一个 ktype,来决定它的行为。

下面就是它们三者在内核中的低位和关系结构图。

Kobject的原型: include/linux/kobject.h

struct kobject {
	const char		*name;
	struct list_head	entry;
	struct kobject		*parent;
	struct kset		*kset;
	const struct kobj_type	*ktype;
	struct kernfs_node	*sd; /* sysfs directory entry */
	struct kref		kref;

	unsigned int state_initialized:1;
	unsigned int state_in_sysfs:1;
	unsigned int state_add_uevent_sent:1;
	unsigned int state_remove_uevent_sent:1;
	unsigned int uevent_suppress:1;

#ifdef CONFIG_DEBUG_KOBJECT_RELEASE
	struct delayed_work	release;
#endif
};

下面是该结构体的详细分析:

1. const char *name

  • namekobject 的名称,通常是对象在内核中的唯一标识符。
  • 例如,对于设备来说,name 可能是设备的名称(如 "eth0""sda")。

2. struct list_head entry

  • entry 是一个双向链表的链表头,表示该 kobject 在某些链表中的位置。
  • kobject 可能会被添加到多个链表中,便于管理和遍历。

3. struct kobject *parent

  • parent 是指向父 kobject 的指针,表示该对象的父对象。
  • 在设备模型中,设备通常会有一个父对象,像是总线、控制器等。

4. struct kset *kset

  • kset 是指向包含该 kobject 的集合(kset)的指针。
  • 一个 kset 是一组相关 kobject 的集合,通常用于将相同类型的对象组织在一起。每个 kobject 都可以归属于一个 kset,便于统一管理。

5. const struct kobj_type *ktype

  • ktype 是指向 kobj_type 结构的指针,定义了该 kobject 的类型及行为。
  • kobj_type 包含了对 kobject 的操作函数,如初始化、销毁、属性操作等。

6. struct kernfs_node *sd

  • sd 是指向 kernfs_node 的指针,表示该 kobjectsysfs 文件系统中的条目。
  • kernfs_node 代表 kobjectsysfs 中的目录项,它连接了内核对象与用户空间的 sysfs 文件系统。

7. struct kref kref

  • kref 用于管理 kobject 的引用计数,以便内核可以跟踪对象的引用情况并在引用计数归零时销毁对象。
  • kref 是内核中常见的引用计数机制,可以防止对象被提前销毁或泄漏。

8. 状态标志字段

这些标志位表示 kobject 的不同状态,通常是布尔值(01):

  • state_initialized: 标识 kobject 是否已经初始化。
  • state_in_sysfs: 标识 kobject 是否已经添加到 sysfs 文件系统中。
  • state_add_uevent_sent: 表示是否已经发送了添加事件(uevent),即设备已添加到系统中。
  • state_remove_uevent_sent: 表示是否已经发送了移除事件(uevent),即设备已从系统中移除。
  • uevent_suppress: 表示是否禁止发送事件。通常在某些情况下,可能会禁止 uevent 的发送,避免触发不必要的事件。

9. #ifdef CONFIG_DEBUG_KOBJECT_RELEASE

  • 如果启用了 CONFIG_DEBUG_KOBJECT_RELEASE 配置项,kobject 会有一个 release 字段,它是一个 delayed_work 结构。
  • delayed_work 用于推迟执行某些操作,在 kobject 被销毁时,进行延迟处理。这个机制通常用于调试和资源释放时进行延时清理。

 Kset的原型为: include/linux/kobject.h

struct kset {
    struct list_head list;                     // 双向链表,用于将所有 kset 链接在一起
    spinlock_t list_lock;                       // 用于保护 list 的锁
    struct kobject kobj;                        // kset 本身作为一个 kobject
    const struct kset_uevent_ops *uevent_ops;   // 事件操作函数指针
} __randomize_layout;

下面是该结构体的详细分析:

1. struct list_head list

  • list 是一个双向链表头(list_head)。它用于将多个 kset 对象链接在一起。通过 list_head,你可以把多个 kset 挂载到一个链表中,以便管理。
  • 该链表用于内核中各种 kset 的全局管理,允许在内核中查找、遍历和操作这些 kset

2. spinlock_t list_lock

  • list_lock 是一个自旋锁,用于保护链表操作(如遍历、添加或删除 kset)。由于 kset 可能在多个上下文中并发访问,使用 spinlock_t 可以确保在访问或修改链表时不会发生竞争条件。
  • 通过 spinlock_t,内核可以同步对链表的访问,确保数据的一致性。

3. struct kobject kobj

  • kobj 是一个 kobject 结构,表示 kset 作为一个对象的存在。每个 kset 都是一个 kobject,它可以通过 sysfs 文件系统与用户空间交互。这个 kobject 充当了 kset 的代表,可以被视为 kset 的基础对象。
  • kobj 允许 kset 作为一个内核对象,具备基本的生命周期管理、属性管理和事件通知功能。

4. const struct kset_uevent_ops *uevent_ops

  • uevent_ops 是指向 kset_uevent_ops 结构体的指针,定义了与 kset 相关的事件操作。kset_uevent_ops 包含了处理设备或对象事件的函数,例如发送 uevent(即用户空间事件)。
  • 该字段允许 kset 控制事件的生成和管理。事件(如设备的添加、移除等)可以通过 uevent_ops 进行定制。

5. __randomize_layout

  • __randomize_layout 是一个内核的宏,用于随机化结构体成员的布局。这样做的目的是为了增强内存布局的随机性,提升系统的安全性(例如防止内存布局泄漏导致的攻击)。
  • 它通常是为了增加内存安全性,防止一些攻击者通过固定的结构布局进行内存操作。

 Ktype的原型为:include/linux/kobject.h

struct kobj_type {
	void (*release)(struct kobject *kobj);
	const struct sysfs_ops *sysfs_ops;
	const struct attribute_group **default_groups;
	const struct kobj_ns_type_operations *(*child_ns_type)(const struct kobject *kobj);
	const void *(*namespace)(const struct kobject *kobj);
	void (*get_ownership)(const struct kobject *kobj, kuid_t *uid, kgid_t *gid);
};

下面是该结构体的详细分析:

1. release

  • release 是一个指向函数的指针,用于释放 kobject 资源。在 kobject 的生命周期结束时,这个函数会被调用,允许开发者自定义 kobject 相关资源的释放方式。默认情况下,这个函数通常用于释放分配给 kobject 的内存或清理其他资源。
  • 例如,如果你有一个设备对象,这个回调会在设备被删除时调用,允许你释放设备相关的内存。

2. sysfs_ops

  • sysfs_ops 是一个指向 sysfs_ops 结构的指针,定义了如何通过 sysfs 文件系统与 kobject 交互。sysfs 是内核与用户空间交互的主要方式之一,sysfs_ops 通过函数指针提供了操作 kobject 的具体方法。
  • sysfs_ops 中通常会包含读写操作、创建和删除属性等操作。

3. default_groups

  • default_groups 是一个指向 attribute_group 数组的指针,定义了 kobject 的默认属性组。每个 attribute_group 可以包含一组属性,这些属性通常是通过 sysfs 提供的接口暴露给用户空间的。
  • 默认属性组使得 kobject 通过 sysfs 自动暴露一组预定义的属性。例如,一个设备对象可以有默认的属性组来显示设备的状态、型号、驱动信息等。

4. child_ns_type

  • child_ns_type 是一个指向函数的指针,返回与 kobject 相关的子命名空间操作。命名空间是 Linux 中的一种机制,它可以为一组进程提供隔离环境,kobject 的命名空间操作用于确定 kobject 对象如何在命名空间之间共享或隔离。
  • 该函数返回一个 kobj_ns_type_operations 结构体指针,定义了命名空间操作的具体行为,通常用于处理像网络命名空间、用户命名空间等场景。

5. namespace

  • namespace 是一个指向函数的指针,用于获取与 kobject 相关的命名空间。这通常用于 kobject 是否属于特定命名空间的情况,比如获取某个设备对象所属的命名空间。
  • 它允许你获取该 kobject 的命名空间对象,可以与子系统的隔离、资源分配等机制结合使用。

6. get_ownership

  • get_ownership 是一个指向函数的指针,用于获取与 kobject 相关的所有者信息。它会返回一个用户 ID (uid) 和一个组 ID (gid),表示该 kobject 对应的资源或对象的所有者。
  • 该函数通常用于获取设备或其他对象的所有权信息,以便系统或用户进行权限控制和访问。

kobject、kset和kobj_type的关系

                      +------------------+
                      |    kobj_type     |
                      +------------------+
                             ^
                             |
                             | Defines behavior
                             v
                      +------------------+
                      |     kobject      |
                      +------------------+
                             ^
                             |
                             | Belongs to
                             v
                      +------------------+
                      |      kset        |
                      +------------------+
                             ^
                             |
                             | Contained in
                             v
                +-------------------------+
                |  List of kobjects (list) |
                +-------------------------+

关系说明

  1. kobj_type

    • kobj_type 是一个结构体,它定义了与 kobject 相关的操作行为。每个 kobject 都可以有一个 kobj_type,它告诉内核如何处理该对象的生命周期、sysfs 文件操作、属性等。
    • kobj_type 包括如 release(释放资源的回调)、sysfs_ops(定义如何处理 sysfs 操作)等字段,负责对 kobject 对象进行自定义操作。
  2. kobject

    • kobject 是 Linux 内核中对象的基本单位。它表示一个内核对象,可能是设备、驱动、文件系统、虚拟文件等。每个 kobject 都会有一个 kobj_type 来定义如何管理该对象。
    • kobject 通过 kobj_type 来决定如何释放资源、如何暴露属性到 sysfs 等。
    • kobject 还可以通过 kset 进行组织和分类。kobject 可以属于多个 kset,每个 kset 是一组相关 kobject 的集合。
  3. kset

    • ksetkobject 的集合,它用于组织和管理一组相关的 kobject。例如,所有的设备对象可以被组织到一个 kset 中,这样可以便于统一管理。
    • kset 本身是一个结构体,它包含了多个 kobject,这些 kobject 通过 list 链接在一起,形成一个链表。
    • kset 还负责管理 kobject 的创建、删除等操作。每个 kset 可以管理一类 kobject,并定义一些全局操作。

详细关系:

  1. kobjectkobj_type 的关系

    • kobject 对象由 kobj_type 结构定义它的行为。例如,它的 release 操作、如何通过 sysfs 暴露属性等。每个 kobject 都需要关联一个 kobj_type,这个类型定义了它如何被管理。
  2. kobjectkset 的关系

    • kset 是一个 kobject 的集合。多个相关的 kobject 可以通过一个 kset 进行组织。每个 kset 都有一个 kset_type,而 kobject 则被放置在特定的 kset 中。 kset 允许内核通过它来操作一组 kobject,比如创建、销毁、列举等。
  3. kset 的操作与管理

    • 每个 kset 负责管理一组 kobject 的生命周期,包括 kobject 的创建和销毁等。kset 可以通过 kobject 进行 kobject 的排序、增删操作等,实际上就是一个 kobject 的管理容器。

小结:

  • kobject:内核对象的基本单位,代表一个具体的对象(例如设备、驱动等),包含了很多与该对象相关的功能和属性。
  • kobj_type:定义了如何管理 kobject 的操作,如资源释放、sysfs 操作等。每个 kobject 都有一个关联的 kobj_type,它决定了 kobject 的具体行为。
  • kset:是 kobject 的集合,用于组织、管理一组相关的 kobject,提供统一的管理接口。

这三者通过各自的职责协同工作,共同完成内核对象的管理和操作。

总结:Ktype以及整个Kobject机制的理解
Kobject的核心功能是:保持一个引用计数,当该计数减为0时,自动释放(由本文所讲的kobject模块负责) Kobject所占用的meomry空间。这就决定了Kobject必须是动态分配的(只有这样才能动态释放)。 

而Kobject大多数的使用场景,是内嵌在大型的数据结构中(如Kset、device_driver等),因此这些大型的数据结构,也必须是动态分配、动态释放的。那么释放的时机是什么呢?是内嵌的Kobject释放时。但是Kobject的释放是由Kobject模块自动完成的(在引用计数为0时),那么怎么一并释放包含自己的大型数据结构呢? 

这时Ktype就派上用场了。我们知道,Ktype中的release回调函数负责释放Kobject(甚至是包含Kobject的数据结构)的内存空间,那么Ktype及其内部函数,是由谁实现呢?是由上层数据结构所在的模块!因为只有它,才清楚Kobject嵌在哪个数据结构中,并通过Kobject指针以及自身的数据结构类型,找到需要释放的上层数据结构的指针,然后释放它。 

讲到这里,就清晰多了。所以,每一个内嵌Kobject的数据结构,例如kset、device、device_driver等等,都要实现一个Ktype,并定义其中的回调函数。同理,sysfs相关的操作也一样,必须经过ktype的中转,因为sysfs看到的是Kobject,而真正的文件操作的主体,是内嵌Kobject的上层数据结构。

3.3、 功能分析

3.3.1、 Kobject使用流程

Kobject大多数情况下会嵌在其它数据结构中使用,其使用流程如下:

  1. 初始化 kobject:分配内存并初始化 kobject
  2. 设置 kobj_type:为 kobject 配置相关操作(如 sysfs 操作、资源释放等)。
  3. kobject 加入 kset:如果需要,可以将 kobject 添加到 kset 中进行管理。
  4. 暴露属性到 sysfs:如果需要暴露属性,可以通过 sysfs 进行配置。
  5. 使用 kobject:执行操作,如属性访问、事件处理等。
  6. 销毁 kobject:在不再需要时,正确销毁 kobject,释放资源。

上面有提过,有一种例外,Kobject不再嵌在其它数据结构中,可以单独使用,这个例外就是:开发者只需要在sysfs中创建一个目录,而不需要其它的kset、ktype的操作。这时可以直接调用kobject_create_and_add接口,分配一个kobject结构并把它添加到kernel中。

3.3.2、 Kobject的分配和释放

前面讲过,Kobject必须动态分配,而不能静态定义或者位于堆栈之上,它的分配方法有两种。

1. 通过kmalloc自行分配(一般是跟随上层数据结构分配),并在初始化后添加到kernel。这种方法涉及如下接口:include/linux/kobject.h

void kobject_init(struct kobject *kobj, const struct kobj_type *ktype);
__printf(3, 4) __must_check int kobject_add(struct kobject *kobj,
					    struct kobject *parent,
					    const char *fmt, ...);
__printf(4, 5) __must_check int kobject_init_and_add(struct kobject *kobj,
						     const struct kobj_type *ktype,
						     struct kobject *parent,
						     const char *fmt, ...);

void kobject_del(struct kobject *kobj);

kobject_init,初始化通过kmalloc等内存分配函数获得的struct kobject指针。lib/kobject.c


void kobject_init(struct kobject *kobj, struct kobj_type *ktype)
{
	char *err_str;
 
	if (!kobj) {        /* 必须有效才能初始化 */
		err_str = "invalid kobject pointer!";
		goto error;
	}
	if (!ktype) {         /* 必须有效才能绑定 */
		err_str = "must have a ktype to be initialized properly!\n";
		goto error;
	}
	if (kobj->state_initialized) {    /* 用这个函数初始化,所以正常都是没初始化过的 */
		/* do not error out as sometimes we can recover */
		printk(KERN_ERR "kobject (%p): tried to init an initialized "
		       "object, something is seriously wrong.\n", kobj);
		dump_stack_lvl(KERN_ERR);
	}
 
	kobject_init_internal(kobj);    /* 初始化kobj */
	kobj->ktype = ktype;            /* 绑定ktype到该kobj */
	return;
 
error:
	pr_err("kobject (%p): %s\n", kobj, err_str);
	dump_stack_lvl(KERN_ERR);
}

主要执行逻辑为:

  • 确认kobj和ktype不为空。
  • 如果该指针已经初始化过(判断kobj->state_initialized),打印错误提示及堆栈信息(但不是致命错误,所以还可以继续)。
  • 初始化kobj内部的参数,包括引用计数、list、各种标志等。
  • 根据输入参数,将ktype指针赋予kobj->ktype。

lib/kobject.c 

 static void kobject_init_internal(struct kobject *kobj)
{
	if (!kobj)
		return;
	kref_init(&kobj->kref);               /* 原子操作,对kref引用计数加1 */
	INIT_LIST_HEAD(&kobj->entry);
	kobj->state_in_sysfs = 0;             /* 标记还没呈现到sysfs */
	kobj->state_add_uevent_sent = 0;      /* 还未户空间发送ADD uevent */
	kobj->state_remove_uevent_sent = 0;   /* 没有发送remove uevent */
	kobj->state_initialized = 1;          /* 标记被初始化过 */
}

kobject_add,将初始化完成的kobject添加到kernel中,参数包括需要添加的kobject、该kobject的parent(用于形成层次结构,可以为空)、用于提供kobject name的格式化字符串。主要执行逻辑为:lib/kobject.c

int kobject_add(struct kobject *kobj, struct kobject *parent,
		const char *fmt, ...)
{
	va_list args;
	int retval;
 
	if (!kobj)
		return -EINVAL;
 
	if (!kobj->state_initialized) {    /* 只有初始化过的才能add */
		printk(KERN_ERR "kobject '%s' (%p): tried to add an "
		       "uninitialized object, something is seriously wrong.\n",
		       kobject_name(kobj), kobj);
		dump_stack_lvl(KERN_ERR);
		return -EINVAL;
	}
	va_start(args, fmt);
	retval = kobject_add_varg(kobj, parent, fmt, args);
	va_end(args);
 
	return retval;
}
  • 确认kobj不为空,确认kobj已经初始化,否则错误退出
  • 调用内部接口kobject_add_varg,完成添加操作

kobject_add_varg,解析格式化字符串,将结果赋予kobj->name,之后调用kobject_add_internal接口,完成真正的添加操作。lib/kobject.c

static __printf(3, 0) int kobject_add_varg(struct kobject *kobj,
					   struct kobject *parent,
					   const char *fmt, va_list vargs)
{
	int retval;

	retval = kobject_set_name_vargs(kobj, fmt, vargs);
	if (retval) {
		pr_err("can not set name properly!\n");
		return retval;
	}
	kobj->parent = parent;
	return kobject_add_internal(kobj);
}

kobject_add_internal,将kobject添加到kernel。lib/kobject.c

  
static int kobject_add_internal(struct kobject *kobj)
{
	int error = 0;
	struct kobject *parent;
 
	if (!kobj)            /* 必须有效 */
		return -ENOENT;
 
	if (!kobj->name || !kobj->name[0]) {    /* 有具体名字,不然没法显示文件夹 */
		WARN(1, "kobject: (%p): attempted to be registered with empty "
			 "name!\n", kobj);
		return -EINVAL;
	}
 
	parent = kobject_get(kobj->parent);    /* 如果有父节点的话,父节的点引用计数加1 */
 
	/* join kset if set, use it as parent if we do not already have one */
	if (kobj->kset) {        /* kset是kobj的集合 */
		if (!parent)         /* 如果该kobj的父节点不存在,就让kset里面的kobj做它的父节点 */
			parent = kobject_get(&kobj->kset->kobj);
		kobj_kset_join(kobj);        /* 把kobj加入到kset list的链表中去 */
		kobj->parent = parent;       /* 给kobj添加父节点 */
	}
 
	pr_debug("kobject: '%s' (%p): %s: parent: '%s', set: '%s'\n",
		 kobject_name(kobj), kobj, __func__,
		 parent ? kobject_name(parent) : "<NULL>",
		 kobj->kset ? kobject_name(&kobj->kset->kobj) : "<NULL>");
 
	error = create_dir(kobj);        /* 调用sysfs文件系统接口,创建一个名为kobj->name的文件夹 ,目录和parent有直接关系*/
	if (error) {
		kobj_kset_leave(kobj);
		kobject_put(parent);
		kobj->parent = NULL;
 
		/* be noisy on error issues */
		if (error == -EEXIST)
			printk(KERN_ERR "%s failed for %s with "
			       "-EEXIST, don't try to register things with "
			       "the same name in the same directory.\n",
			       __func__, kobject_name(kobj));
		else
			printk(KERN_ERR "%s failed for %s (%d)\n",
			       __func__, kobject_name(kobj), error);
		dump_stack();
	} else
		kobj->state_in_sysfs = 1;
 
	return error;
}

主要执行逻辑为:

  • 校验kobj以及kobj->name的合法性,若不合法打印错误信息并退出。
  • 调用kobject_get增加该kobject的parent的引用计数(如果存在parent的话)。
  • 如果存在kset(即kobj->kset不为空),则调用kobj_kset_join接口加入kset。同时,如果该kobject没有parent,却存在kset,则将它的parent设为kset(kset是一个特殊的kobject),并增加kset的引用计数。
  • 通过create_dir接口,调用sysfs的相关接口,在sysfs下创建该kobject对应的目录。
  • 如果创建失败,执行后续的回滚操作,否则将kobj->state_in_sysfs置为1。

kobj_kset_join,负责将kobj加入到对应kset的链表中。lib/kobject.c

 /* add the kobject to its kset's list */
static void kobj_kset_join(struct kobject *kobj)
{
	if (!kobj->kset)
		return;
 
	kset_get(kobj->kset);
	spin_lock(&kobj->kset->list_lock);
	list_add_tail(&kobj->entry, &kobj->kset->list);   
 /* 将kobj的entey加入到kset链表中去 */
	spin_unlock(&kobj->kset->list_lock);
}

这种方式分配的kobject,会在引用计数变为0时,由kobject_put调用其ktype的release接口,释放内存空间。

2. 使用kobject_create创建

Kobject模块可以使用kobject_create自行分配空间,并内置了一个ktype(dynamic_kobj_ktype),用于在计数为0是释放空间。代码如下:include/linux/kobject.h

struct kobject * __must_check kobject_create(void)
struct kobject * __must_check kobject_create_and_add(const char *name, struct kobject *parent);

每个kobj在其分配的时候就已经为其准备好了释放函数,绑定在它的kobj_type的回调函数release中。lib/kobject.c

  struct kobject *kobject_create(void)
{
	struct kobject *kobj;
 
	kobj = kzalloc(sizeof(*kobj), GFP_KERNEL);    /* 创建一个kobj */
	if (!kobj)
		return NULL;
 
	kobject_init(kobj, &dynamic_kobj_ktype);      /* 初始化并绑定ktype,注销函数 */
	return kobj;
}



static void dynamic_kobj_release(struct kobject *kobj)
{
	pr_debug("kobject: (%p): %s\n", kobj, __func__);
	kfree(kobj);
}
 
static struct kobj_type dynamic_kobj_ktype = {
	.release	= dynamic_kobj_release,
	.sysfs_ops	= &kobj_sysfs_ops,
};



kobject_create,该接口为kobj分配内存空间,并以dynamic_kobj_ktype为参数,调用kobject_init接口,完成后续的初始化操作。

kobject_create_and_add,是kobject_create和kobject_add的组合,不再说明。

dynamic_kobj_release,直接调用kfree释放kobj的空间。

3.3.4、 Kset的初始化、注册

Kset是一个特殊的kobject,因此其初始化、注册等操作也会调用kobject的相关接口,除此之外,会有它特有的部分。另外,和Kobject一样,kset的内存分配,可以由上层软件通过kmalloc自行分配,也可以由Kobject模块负责分配,具体如下。include/linux/kobject.h

void kset_init(struct kset *kset);
int __must_check kset_register(struct kset *kset);
void kset_unregister(struct kset *kset);
struct kset * __must_check kset_create_and_add(const char *name, const struct kset_uevent_ops *u,
					       struct kobject *parent_kobj);

lib/kobject.c

 /**
 * kset_init - initialize a kset for use
 * @k: kset
 */
void kset_init(struct kset *k)
{
	kobject_init_internal(&k->kobj);         /* 初始化里面的kobj */
	INIT_LIST_HEAD(&k->list);                /* 初始化kset用于组织下面kobj的链表 */
	spin_lock_init(&k->list_lock);
}

kset_init,该接口用于初始化已分配的kset,主要包括调用kobject_init_internal初始化其kobject,然后初始化kset的链表。需要注意的时,如果使用此接口,上层软件必须提供该kset中的kobject的ktype。lib/kobject.c

 /**
 * kset_register - initialize and add a kset.
 * @k: kset.
 */
int kset_register(struct kset *k)
{
	int err;
 
	if (!k)
		return -EINVAL;
 
	kset_init(k);                            /* 初始化kset */
	err = kobject_add_internal(&k->kobj);    /* 把kset的kobj加入到内核(创建一个文件夹) */
	if (err) {
		kfree_const(k->kobj.name);
		/* Set it to NULL to avoid accessing bad pointer in callers. */
		k->kobj.name = NULL;
		return err;
	}
	kobject_uevent(&k->kobj, KOBJ_ADD);
	return 0;
}

kset_register,先调用kset_init,然后调用kobject_add_internal将其kobject添加到kernel。

 /**
 * kset_unregister - remove a kset.
 * @k: kset.
 */
void kset_unregister(struct kset *k)
{
	if (!k)
		return;
	kobject_del(&k->kobj);  /* 删除这个kset */      
	kobject_put(&k->kobj);  /* 引用计数减1,调用release */
}
EXPORT_SYMBOL(kset_unregister);

kset_unregister,直接调用kobject_put释放其kobject。当其kobject的引用计数为0时,即调用ktype的release接口释放kset占用的空间。lib/kobject.c

struct kset *kset_create_and_add(const char *name,
				 const struct kset_uevent_ops *uevent_ops,
				 struct kobject *parent_kobj)
{
	struct kset *kset;
	int error;
 
	kset = kset_create(name, uevent_ops, parent_kobj);
	if (!kset)
		return NULL;
	error = kset_register(kset);
	if (error) {
		kfree(kset);
		return NULL;
	}
	return kset;
}
EXPORT_SYMBOL_GPL(kset_create_and_add);

kset_create_and_add,会调用内部接口kset_create动态创建一个kset,并调用kset_register将其注册到kernel。

lib/kobject.c

 static void kset_release(struct kobject *kobj)
{
	struct kset *kset = container_of(kobj, struct kset, kobj);
	pr_debug("kobject: '%s' (%p): %s\n",
		 kobject_name(kobj), kobj, __func__);
	kfree(kset);        /* 释放kset本身 */
}
 
static const struct kobj_type kset_ktype = {
	.sysfs_ops	= &kobj_sysfs_ops,
	.release	= kset_release,
	.get_ownership	= kset_get_ownership,
};
 
 

static struct kset *kset_create(const char *name,
				const struct kset_uevent_ops *uevent_ops,
				struct kobject *parent_kobj)
{
	struct kset *kset;
	int retval;
 
	kset = kzalloc(sizeof(*kset), GFP_KERNEL);    /* 申请一个kset */
	if (!kset)
		return NULL;
	retval = kobject_set_name(&kset->kobj, "%s", name);    
	if (retval) {
		kfree(kset);
		return NULL;
	}
	kset->uevent_ops = uevent_ops;
	kset->kobj.parent = parent_kobj;
 
	/*
	 * The kobject of this kset will have a type of kset_ktype and belong to
	 * no kset itself.  That way we can properly free it when it is
	 * finished being used.
	 */
	kset->kobj.ktype = &kset_ktype;    /* 给它里面的kobj里面的ktype绑定一个release函数 */
	kset->kobj.kset = NULL;            /* kobj本就属于kset,这里不需要连接 */
 
	return kset;
}

kset_create,该接口使用kzalloc分配一个kset空间,并定义一个kset_ktype类型的ktype,用于释放所有由它分配的kset空间。lib/kobject.c

 static void kset_release(struct kobject *kobj)
{
	struct kset *kset = container_of(kobj, struct kset, kobj);
	pr_debug("kobject: '%s' (%p): %s\n",
		 kobject_name(kobj), kobj, __func__);
	kfree(kset);
}
 
static const struct kobj_type kset_ktype = {
	.sysfs_ops	= &kobj_sysfs_ops,
	.release	= kset_release,
	.get_ownership	= kset_get_ownership,
};

释放kset的方法。lib/kobject.c

 /**  该函数的使用通常和kset_register配对
 * kset_unregister - remove a kset.
 * @k: kset.
 */
void kset_unregister(struct kset *k)
{
	if (!k)
		return;
        /* 此函数只是将kobject从sysfs和kset中去掉(初始化kobject的list_head),并不是删除kobject,删除kobject得靠kobject->ktype->release才行。kobject_del在把kobject从kset中去掉后,会把kset的引用值减1。*/
        kobject_del(&k->kobj);   
        /* 此函数为kobject的引用值减1,若引用值减为0,则销毁此kobject(调用kobject_release调用kobject_cleanup,最终调用kobject_del、ktype的release),最后一步释放的name是在kobject_add中调用的kobject_set_name_vargs中分配的。 */   
	kobject_put(&k->kobj);       
}

对比上下两个可以发现在kset_unregister里面其实调用两次konject_put函数,一次是其parent的,一次是自己的。lib/kobject.c

 /**
 * kobject_del - unlink kobject from hierarchy.
 * @kobj: object.
 */
void kobject_del(struct kobject *kobj)
{
	struct kernfs_node *sd;
 
	if (!kobj)
		return;
 
	parent = kobj->parent;
	__kobject_del(kobj);
	kobject_put(parent);
}

lib/kobject.c

/**
 * kref_put - decrement refcount for object.
 * @kref: object.
 * @release: pointer to the function that will clean up the object when the
 *	     last reference to the object is released.
 *	     This pointer is required, and it is not acceptable to pass kfree
 *	     in as this function.
 *
 * Decrement the refcount, and if 0, call release().
 * Return 1 if the object was removed, otherwise return 0.  Beware, if this
 * function returns 0, you still can not count on the kref from remaining in
 * memory.  Only use the return value if you want to see if the kref is now
 * gone, not present.
 */
static inline int kref_put(struct kref *kref, void (*release)(struct kref *kref))
{
	if (refcount_dec_and_test(&kref->refcount)) {
		release(kref);
		return 1;
	}
	return 0;
}

void kobject_put(struct kobject *kobj)
{
	if (kobj) {
		if (!kobj->state_initialized)
			WARN(1, KERN_WARNING "kobject: '%s' (%p): is not "
			       "initialized, yet kobject_put() is being "
			       "called.\n", kobject_name(kobj), kobj);
		kref_put(&kobj->kref, kobject_release);    /* 如果引用计数为0,则调用kobject_release清理 */
	}
}
 
 
/* remove the kobject from its kset's list */
static void kobj_kset_leave(struct kobject *kobj)
{
	if (!kobj->kset)
		return;
 
	spin_lock(&kobj->kset->list_lock);
	list_del_init(&kobj->entry);            /* 删除kobj在kset中的节点 */
	spin_unlock(&kobj->kset->list_lock);
	kset_put(kobj->kset);                   /* kobj的kset引用计数减1,减为0则调用它上一级的kset为其绑定的kobj_type里面的release释放kset本身 */
}
 
 

lib/kobject.c

 /**
 * kobject_put - decrement refcount for object.
 * @kobj: object.
 *
 * Decrement the refcount, and if 0, call kobject_cleanup().
 */
void kobject_put(struct kobject *kobj)
{
	if (kobj) {
		if (!kobj->state_initialized)    /* 0表示已经释放过了 */
			WARN(1, KERN_WARNING "kobject: '%s' (%p): is not "
			       "initialized, yet kobject_put() is being "
			       "called.\n", kobject_name(kobj), kobj);
		kref_put(&kobj->kref, kobject_release);    /* 这个函数在好多地方都时候用了,好好分析一下 */
	}
}
EXPORT_SYMBOL(kobject_put);

其中kobject_release是kobject.c文件定义的。

 static void kobject_release(struct kref *kref)
{
	struct kobject *kobj = container_of(kref, struct kobject, kref);
#ifdef CONFIG_DEBUG_KOBJECT_RELEASE
	unsigned long delay = HZ + HZ * get_random_u32_below(4);
	pr_info("'%s' (%p): %s, parent %p (delayed %ld)\n",
		kobject_name(kobj), kobj, __func__, kobj->parent, delay);
	INIT_DELAYED_WORK(&kobj->release, kobject_delayed_cleanup);

	schedule_delayed_work(&kobj->release, delay);
#else
    /* 先根据kref找到kobject的地址,再把这个地址传给kobject_cleanup */
	kobject_cleanup(container_of(kref, struct kobject, kref));
#endif
}

lib/kobject.c

  
/*
 * kobject_cleanup - free kobject resources.
 * @kobj: object to cleanup
 */
static void kobject_cleanup(struct kobject *kobj)
{
	struct kobject *parent = kobj->parent;
	struct kobj_type *t = get_ktype(kobj);    /* 得到kobj里面的ktype指针 */
	const char *name = kobj->name;

	pr_debug("'%s' (%p): %s, parent %p\n",
		 kobject_name(kobj), kobj, __func__, kobj->parent);
 
	if (t && !t->release)        /* 如果ktype存在,但ktype里的release不存在,则ktype没法释放,打印出告警信息 */
		pr_debug("'%s' (%p): does not have a release() function, it is broken and must be fixed. See Documentation/core-api/kobject.rst.\n",
			 kobject_name(kobj), kobj);
 
	/* remove from sysfs if the caller did not do it */
	if (kobj->state_in_sysfs) { /* 如果换处在sysfs中,则要先调用kobjcat_del,删除文件夹操作等 */
		pr_debug("'%s' (%p): auto cleanup kobject_del\n",
			 kobject_name(kobj), kobj);
		__kobject_del(kobj);
	} else {
		/* avoid dropping the parent reference unnecessarily */
		parent = NULL;
	}
	if (t && t->release) {    /* kobj_type和其释放函数都在的话,则调用release释放内存 */
        pr_debug("'%s' (%p): calling ktype release\n",
			 kobject_name(kobj), kobj);
		t->release(kobj);
	}
 
	/* free name if we allocated it */
	if (name) {
		pr_debug("'%s': free name\n", name);
		kfree_const(name);            /* 释放name占用的字节数 */
	}
	kobject_put(parent);
}

总结后我们发现:

void kset_init(struct kset *kset);
int __must_check kset_register(struct kset *kset);
void kset_unregister(struct kset *kset);
struct kset * __must_check kset_create_and_add(const char *name, const struct kset_uevent_ops *u,
					       struct kobject *parent_kobj);

如果使用kset_create_and_add函数在kset_creat的时候,它里面kobject里面的kobj_type已经被绑定,它里面的kobject里面的kset则没有,即没有而直接包含在某个设备中的kset,则直接调用kset_register注册,注册前要自己初始化好里面kobject的kobj_type和kset指针,这样会让使用变得更加灵活。

kset和kobject的关系:

每一个 kobj 对应 文件系统 /sys 里的一个 目录,那么每一个 kset 都包含了一个 kobj,那样的话,kset也对应于 /sys 里的一个 目录。
    简单来说,kset 与 kobj 都是目录,既然是目录,那么在就是一个树状结构,每一个目录都将有一个 父节点:
在kset中使用kset.kobj->parent 指定 。 在kboject中使用  kobj->parent 指定。
    显然,整个树状目录结构,都是通过kobj来构建的,只不过有些kobj嵌在Kset里,分析目录结构时把kset当成一个普通的kobj会好理解很多。

父目录kset和其子目录的konject和kset之间,子目录的kobject(或kset里面的kobject)使用里面的parent指向父目录kset里面的kobject,kset用其里的list链表作为head负责连接这个文件夹里面的所有的kobject,用其里面kobject的lsit指针作为更高一层目录kset链表的node接入上层。

而kobject因为里面只有list,只能作为node计入同一层目录下的链表中。

那么kset 有何特殊之处呢?
     kset 结构中有一个链表,它目录下的 一些相同类型的kobj会在创建时链入链表,也就是说Kset是一些kobj的集合。看看 /sys 目录底下的这些东西,哪些是kset 哪些是kobj 结构又是怎样的。
    看过上面代码的应该知道,想要在/sys 目录下创建 目录,那就要 构造 kset 或者 kobj 设置并注册。

可以简单点这样理解,kset是以文件夹形式组织内核中的树形结构,它通过list链表连接下面所有的kobject或kset。而下面的kobject和kset则用他们里面的kobject里面的parent指向kset里面的kobject,同时所有kobject里面的kset指针也会指向kset结构体(主要实现引用计数,只有在kset文件夹下面的所有kobject和kset都没有的话,它的下面的最后一个kobject注销的时候,会对它的kset引用计数减1,发现该kset的引用计数为0,才可以调用kset自身绑定的ktype里面的release释放kset,否则下面还有文件肯定不能释放)。除了sysfs的根目录,其它目录下的kset最掌管下面的kobject和kset,对上通过里面的kobject作为更上一层的子目录。这样就能通过kset的多级连接以及kobject实现内核sysfs中的树形目录的基础。

LDDM层次结构在底层的实现及与sysfs的联系:

内核使用kobject族将各个对象连接起来组成一个分层的体系结构,也就是LDDM的分层体系结构。由于程序员很少直接操作kobject族,而是通过将kobject嵌入到数据结构中发挥作用,所以,更准确的说是更高层的数据结构(bus_type,device,device_driver,class等)通过kobject族形成LDDM所设计的层次结构。如下图所示:

前面讲过,sysfs展示设备驱动模型个组件的层次关系,所以,sysfs的层次结构和kobject族的层次关系是一一对应的,或者说kobject族是sysfs的底层实现。kobjcet族与sysfs的对应关系如下表所示:

kobject族或其成员

sysfs对应表现形式

kset

sysfs中的一个目录(一定出现在sysfs中)

kobject

sysfs中的一个目录(不一定出现在sysfs中,kset的kobject链表中的kobject一定出现在sysfs中)

kobject.name

sysfs中的目录名

kobj_type.defauts_attr

sysfs中的一个文件

kobj_type.defauts_attr.name

sysfs中的文件名

通过上述图和表可以推测出sysfs的底层表现形式,例如:

/sys/bus对应kset

/sys/bus下的子目录,如amba,是有代表/sys/bus的kset中的链表链接起来的kobject

/sys/bus/amba还包含子目录,如amba包含子目录devices,上述kobject又被包含在代表本级目录的kset中,形成了子目录结构。

 用kobject族核心类表示这个关系,如下图所示:

上图为树形目录的实现原理。

kobject是隐藏在sysfs虚拟文件系统后的机制,对于sysfs中的每一个目录,内核中都会存在一个对应的kobject。每一个kobject都输出一个或者多个属性,它们在kobject的sysfs目录中表现为文件,其中的内容由内核生成。

kobject在sysfs中始终是一个目录,这个目录包含一个或者多个属性。

分配给kobject的名字,是sysfs中的目录名字。

sysfs的入口目录的位置对应于kobject的parent指针。调用kobject_add的时候,如果parent为NULL,它将被设置为嵌入到心得kobject的kset中的kobject,这样,sysfs 分层结构通常与kset创建的内部结构相匹配。如果parent和kset都是null,则会在最高层创建目录。

上图中:

对kobject
kobject_init_and_add
kobject_create_and_add

对kset
kset_create_and_add

这3个函数中增加了prink打印语句,打印内核创建的每一个 kobj 或者 kset 的名字,以及父节点的名字,甚至它指向的kset的kobj的名字。

 int kobject_init_and_add(struct kobject *kobj, struct kobj_type *ktype,
             struct kobject *parent, const char *fmt, ...) 
{
    va_list args;
    int retval;
 
    kobject_init(kobj, ktype);
 
    va_start(args, fmt);
    retval = kobject_add_varg(kobj, parent, fmt, args);
    va_end(args);
    /*********************************************************/
    printk(KERN_INFO"the kobject is %s\n" ,kobj->name);
    if(parent)  
        printk(KERN_INFO"the kobject parent is %s\n", kobj->parent->name);
    else 
        printk(KERN_INFO"the kobject no parent\n");
    
    if(kobj->kset)  
        printk(KERN_INFO"the kobject kset is %s\n", kobj->kset->kobj.name);
    else 
        printk(KERN_INFO"the kobject no kset\n");
    /*********************************************************/
    
    return retval;
}

struct kobject *kobject_create_and_add(const char *name, struct kobject *parent)
{
	struct kobject *kobj;
	int retval;

	kobj = kobject_create();
	if (!kobj)
		return NULL;
 
    retval = kobject_add(kobj, parent, "%s", name);
    if (retval) {
        printk(KERN_WARNING "%s: kobject_add error: %d\n",
               __func__, retval);
        kobject_put(kobj);
        kobj = NULL;
    }
 
    /********************************************************/
    printk(KERN_ERR"the kobject is %s\n",kobj->name);
    if(parent)
        printk(KERN_INFO"the kobject parent is %s\n", kobj->parent->name);
    else
        printk(KERN_INFO"the kobject no parent\n");
 
    if(kobj->kset)
        printk(KERN_INFO"the kobject kset is %s\n" ,kobj->kset->kobj.name);
    else
        printk(KERN_INFO"the kobject no kset\n");
    /********************************************************/
 
    return kobj;
}

 struct kset *kset_create_and_add(const char *name,
                 const struct kset_uevent_ops *uevent_ops,
                 struct kobject *parent_kobj)
{
    struct kset *kset;
    int error;
 
    kset = kset_create(name, uevent_ops, parent_kobj);
    if (!kset)
        return NULL;
    error = kset_register(kset);
    if (error) {
        kfree(kset);
        return NULL;
    }
    /*************************************************/
    printk(KERN_ERR"the kset is %s\n", kset->kobj.name);
    if(kset->kobj.parent)
        printk(KERN_INFO"the kset parent is %s\n",kset->kobj.parent->name );
    else
        printk(KERN_INFO"the kset no parent\n");
 
    if(kset->kobj.kset)
        printk(KERN_INFO"the kset of kset is %s\n", kset->kobj.kset->kobj.name);
    else
        printk(KERN_INFO"the kset no kset\n");
    /**************************************************/
 
    return kset;
}

开机过程中找到几个重要的目录进行分析kset和kobject的在目录中的组织。

 

可以发现:

1.顶层的一些目录也可以是kobject,如fs,dev目录等。

2.kobject下面可以放kobject   。

3.kset下面可以放kset

 

4.kset下可以放kobject

5.kobject下可以放kset

kobject里面可以放kobject(2),但上层的kobject不能联系下层,只能下层联系上层。

kset下可以放kset(3),上层可以通过kset里面的list链表连接下层kset里kobject里的list连接

kset下可以放kobj(4),上层可以通过kset里面的list链表连接下层kobject里的list连接

kobject里的kset,但上层的kobject不能联系下层,只能下层联系上层。


总结:从下层到上层很简单,只要当前kobject(或kset里面的kobject)的parent指针操作就可以

 1、sys 目录下的层次结构依赖于 kobject.parent ,未指定parent时,默认使用 kobject.kset.kobject 作为 parent,如果都没有,就出现在 /sys 目录下。

 2、该 kobject 目录下的属性文件依赖于 kobject.ktype。

3.kset之间的下级到上级(子目录到父目录)关系是依靠,kset里面kobject里面的kset指针(只能子指向父)。而同一目录下的则是用kset里面kobject里面的list指针,和父节点彼此组成双向链表。(这样想来是不是由子目录返回上一级目录直接一个指针操作就可以,而父目录进子目录需要链表遍历然后对比名字才能进去。

4.父目录kset和其子目录的konject和kset之间,子目录的kobject(或kset里面的kobject)使用里面的parent指向父目录kset里面的kobject,kset用其里的list链表负责连接这个文件夹里面的所有的kobject。

5.父子关系必须是同类型的,所以文件夹之间的父子是用,子文件夹kset里面的kobject里的kset指针指向父kset。文件和文件夹的父子关系是用文件kobject里的parent指针指向文件夹kset里的kobject。

6.用ABC分表描述三个等级的文件夹,其中A是根目录,B是A的子文件夹,C是B的子文件夹或里面的文件。B文件夹连接下层C文件或文件夹是使用kset里的list链表(此时B中kset的list是作为链表head),而B被A连接使用它里面的kobject里的list(此时B中kobject里的list是作为链表node)。

同时在驱动层次注册device或driver的时候会为其kobj(因为kobject单独存在无意义,通常都是和dev结合的)绑定kobj_type(大多时候都只是show和store接口)。大多数情况下kobject结构体都是作为基类被放置在某个device或driver里面的,所有释放device或driver的时候会整体释放。只有在单独调用下面两个函数,创建文件夹的时候,才需要调前面分析的创建文件夹时绑定的动态release函数。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值