Linux设备框架:kset与kobject源码分析

发布于:2025-06-20 ⋅ 阅读:(16) ⋅ 点赞:(0)

系列文章目录
Linux设备框架:kset与kobject基本介绍 [link]
Linux设备框架:kset与kobject源码分析 [link]



一、创建 kset

以 devices_kset 为例,分析Linux 内核(提示:本文内容基于Linux 5.4.231)是如何处理kset对象的。

Linux 内核中定义了全局的 devices_kset 指针,并由函数 kset_create_and_add() 创建 kset 实例,devices_kset 名称为 “devices”,实例化过程分为创建和注册两步。
请添加图片描述

1.1 创建

devices_kset 对象由函数 kset_create() 创建。
请添加图片描述
其中,① 函数 kobject_set_name() 设置 devices_kset 对象的名称,即 kobj->name = “devices”,该名称也是后续在 sysfs 文件系统下创建 kset 级别目录(集合级目录,目录名为 devices,即 /sys/devices)的目录名。② kset 对象的父对象为其他 kset,由于 devices_kset 为顶层实例对象,其没有父对象,故kobj->parent 为空(入参 parent_kobj 为空)。③ devices_kset 对象默认设置 kset_ktype;同时,devices_kset 是实例化的 kset 对象,名为 “devices”,自成一个集合,不属于其他集合,故 kobj->kset 为空。

1.2 注册

devices_kset 对象由函数 kset_register() 注册,包括初始化和添加两个过程。
请添加图片描述

1.2.1 初始化 kset

函数 kset_init() 是负责初始化 devices_kset 对象,对象 kset 的初始化包括 list 链表、链表锁(list_lock)和 kobj 的初始化。

其中,对于 kobj 的初始化包括 kref 引用、链表节点(entry)和一些状态标志。这里重点关注 k->list 和 k->kobj->entry,该两项是组织 kset 和 device 的链表指针。在后续的分析中可以知道,这两项在设备驱动模型中有何用处。
请添加图片描述

1.2.2 添加 kset

函数 kobject_add_internal() 负责添加 devices_kset 对象,实质是将 kset 的成员 kobj (k->kobj)添加到内核中,由函数 kobject_add_internal() 完成。这是公共的 kobj 添加函数,设备的 kobject 对象添加也由该函数完成,下面一起分析。

二、创建 kobject

kobject 是 struct device 的成员,创建设备的过程就是创建 kobject 的过程。下面以平台总线设备、平台设备的创建过程为例,看看 kobject 是如何创建的。

Linux 内核中,平台总线设备、平台设备的本质都是设备,均由 struct device 描述,对于kobject 的处理过程也是一样。 kobject 是 device 的结构体成员, 其随 device 一同被创建。

2.1 创建平台总线设备的 kobject

2.1.1 创建

Linux 内核中定义了全局的 platform_bus 总线设备,设备名称为 “platform”,类型为 struct device 结构,作为成员的 kobject 一同被创建。

平台总线设备由函数 device_register() 注册,注册过程分为 设备初始化设备添加。如下所示:
请添加图片描述

2.1.2 初始化和添加

函数 device_initialize() 负责初始化平台总线设备,函数 device_add() 负责将平台总线设备添加到内核,内部分别包含了平台总线设备的 kobject 的初始化和添加。这两个函数是平台总线设备、平台设备公共的初始化和添加函数,后续具体分析。

2.2 创建平台设备的 kobject

平台设备的有两种创建方式:模块方式创建平台设备设备树方式创建平台设备。前者是通过编写内核模块的方式实现;后者无需编写代码,内核自动根据设备树信息创建平台设备。

2.2.1 模块方式创建平台设备

2.2.1.1 创建平台设备的 kobject

函数 platform_device_register() 是通用的平台设备注册函数,通常用于编写内核模块的方式注册平台设备(非绝对)。该函数的入参 pdev 正是外部定义的平台设备 struct platform_device,内部包含 struct device 结构体,成员 kobject 一同被创建。函数 platform_device_register() 实现如下:
请添加图片描述

2.2.1.2 初始化平台设备的 kobject

平台设备的初始化一部分由函数 device_initialize() 负责,另一部分由函数 platform_device_add() 完成。二者内部包含了平台设备的 kobject 的初始化,前者是公共的初始化函数,后续具体分析。

函数 platform_device_add() 的任务一是初始化设备,代码 ① 所示,设置平台设备的父设备为平台总线设备,平台设备所属的总线为平台总线。

函数 platform_device_add() 的任务二是初始化平台设备的 kobject,旨在设置平台设备 kobject 的名称,代码 ② 所示。根据输入的平台设备 pdev 的 name 和 id 设置 kobject 的名称,函数 dev_set_name() 实质是 dev->kobj->name = “xxx”,名称 “xxx” 也是后续在 sysfs 文件系统下创建 kobj 级别目录(设备级目录,目录名为 “xxx”,即 /sys/devices/platform/xxx)的目录名。
请添加图片描述

2.2.1.3 添加平台设备的 kobject

函数 platform_device_add() 的任务三是将平台设备添加到内核中,代码 ③ 所示。函数 device_add() 包含了平台设备的 kobject 的添加,该函数为公共函数,后续具体分析。

2.2.2 设备树方式创建平台设备

从《设备节点转化为平台设备》可知,函数 of_platform_device_create_pdata() 根据设备树信息注册平台设备。该函数注册平台设备的过程也是分两步:创建平台设备添加平台设备

2.2.2.1 创建平台设备的 kobject

函数 of_device_alloc() 负责创建和初始化平台设备。其中,代码 ① 是由 platform_device_alloc() 函数负责创建平台设备和初始化;代码 ② 设置平台设备的父设备为平台总线设备;代码 ③ 由函数 of_device_make_bus_id() 负责设置平台设备的名称。
请添加图片描述
函数 platform_device_alloc() 中,首先为平台设备申请 struct platform_device 类型的内存,内部包含 struct device 结构体,成员 kobject 一同被创建。该函数中同步初始化了平台设备,如下所示:
请添加图片描述

2.2.2.2 初始化平台设备的 kobject

平台设备的初始化一部分由函数 device_initialize() 负责,另一部分由函数 of_device_make_bus_id() 负责,二者内部包含了平台设备的 kobject 的初始化,前者是公共的初始化函数,后续具体分析。

函数 of_device_make_bus_id() 与上述函数 platform_device_add() 的任务二一致,也是初始化平台设备的 kobject,旨在设置平台设备 kobject 的名称。
请添加图片描述

设备树方式创建平台设备时,其名称是根据输入的设备节点信息 dev->of_node 的 reg 属性和节点名 node-name 设置 kobject 的名称。

kobject 的名称有两种方式,① 方式包含节点的 reg 属性中的地址信息和节点名;若节点没有 reg 属性,则使用 ② 方式,采用完整的节点名(full_name)作为 kobject 的名称。

以上两种方式均使用函数 dev_set_name() 完成,其实质是 dev->kobj->name = “yyy”,名称 “yyy” 也是后续在 sysfs 文件系统下创建 kobj 级别目录(设备级目录,目录名为 “yyy”,即 /sys/devices/platform/yyy)的目录名。

补充:%pOFn 不是标准C库定义的格式说明符,而是Linux内核特有的扩展格式,用于打印设备树中节点的名称。

%pOF :输出节点的全路径(如/soc/uart0)
%pOFf :输出节点的全路径(含@地址)
%pOFn :仅输出节点名称(如uart0)

从上述代码可见,函数 of_device_make_bus_id() 与函数 platform_device_add() 在设置平台设备名称的格式存在差异,对应sysfs文件系统中的目录名也会不同。红色框中点分格式的目录名由设备树创建的平台设备,蓝色框中的目录名可能由内核模块方式创建的平台设备,如下图所示:
请添加图片描述

1、红色框中点分格式的目录名中,前面数字为 reg 属性中的地址值,后面为设备节点名。以目录15100000.ethernet 为例,对应设备树信息如下:请添加图片描述
2、红色箭头标注的目录名也表示由设备树创建的平台设备,如:trng@1020f000 和 usb-phy@11e10000目录,以设备节点的完整节点名命名,对应设备树信息如下:请添加图片描述
注意:kset 级目录 devices 与设备级目录之间存在一级 “platform” 目录,该目录正式由上面平台总线设备创建,后续会详细讲到。

2.2.2.3 添加平台设备的 kobject

添加平台设备由函数 of_device_add() 完成,同样是使用函数 device_add() 将平台设备添加到内核。函数 device_add() 包含了对平台设备的 kobject 的添加,为公共函数,后续具体分析。
请添加图片描述

2.3 设备 kobject 的(公共)初始化

上述平台总线设备的初始化完全由函数 device_initialize() 完成。而平台设备在通过两种不同的方式创建时,已经完成了一部分初始化,另一部分初始化工作也由函数 device_initialize() 完成。函数 device_initialize() 包含了对设备 kset 的初始化 和 kobj 的初始化。

其中, kset 的初始化是将 dev->kobj.kset 设置为 devices_kset 实例,说明所有的设备都属于 devices_kset 集合。
请添加图片描述
kobj 的初始化由函数 kobject_init() 负责,除了dev->kobj->ktype被设置为 device_ktype 外(devices_kset中,kset->kobj->ktype = kset_ktype),dev->kobj 的其他初始化与前面 devices_kset 的初始化一致,都是由函数 kobject_init_internal() 完成。
请添加图片描述

2.4 设备 kobject 的(公共)添加

上述平台总线设备和平台设备的创建方式虽有不同,但最终都由共同的函数 device_add() 将其添加到内核。添加设备的过程实际是操作设备的 kobject 成员,实现将设备注册到内核中。函数 device_add() 的实现如下图所示:
请添加图片描述
其中,① 是设置设备的 kobject 名称;② 是设置设备 kobject 的父;③ 是调用函数 kobject_add() 添加 kobject 对象。

2.4.1 kobject 的名字(设备名称)

kobject 的名字由设备名字和设备信息产生,间接反映了设备的属性。其中,函数 device_add() 图片中的代码 ① 是设置设备的 kobject 名称。根据输入设备 dev 的 init_name、id 和所属总线的总线名设置 kobject 的名称,函数 dev_set_name() 实质是 dev->kobj->name = “zzz”,名称 “zzz” 也是后续在 sysfs 文件系统下创建 kobj 级别目录(设备级目录,目录名为 “zzz”)的目录名。

对于平台总线设备 dev->init_name = “platform”,因此平台总线设备的 kobject 名称被设置为 “platform”,即dev->kobj->name = “platform”。后续在 sysfs 文件系统下创建 kobj 级别目录(总线设备级目录)的目录名为 platform,即 /sys/devices/platform。

对于平台设备,从上述两种创建方式中分析可知,都已经设置了 kobject 名称,故 dev_name(dev) 返回非空;同时,平台设备 dev->init_name 为空。因此,此处不会再次设置平台设备的 kobject 名称。

无论是 kset_create() 函数中使用 kobject_set_name() 函数为 devices_kset 对象设置名字,还是为平台总线设备、平台设备设置名字的 dev_set_name() 函数,都是设置其成员 kobj->name。后续在 sysfs 文件系统中创建的目录正是以此命名。

2.4.2 kobject 的父(设备的父)

kobject 的父是根据设备的父设置,其实质就是设备的父(父设备)。函数 device_add() 图片中的代码 ② 是设置设备 kobject 的父。首先,通过函数 get_device() 获取父设备;然后,使用函数 get_device_parent() 获取父设备的 kobj;最后,将父设备的 kobj 设置到当前设备的 kobj.parent。从平台设备的注册过程可知,所有平台设备的父设备均为平台总线设备(platform_bus),故所有的平台设备的 kobj.parent 都指向平台总线设备的 kobj。

2.4.3 添加 kobject

函数 device_add() 图片中的代码 ③ 是调用函数 kobject_add() 添加设备。函数 kobject_add() 的入参是当前设备的 kobj 和父设备的 kobj,可见添加设备的本质是添加 kobject。函数 kobject_add() 最终是调用函数 kobject_add_internal(),与添加 kset 一样,实际都是将 kobj 添加到内核中。
请添加图片描述
上图代码中,kobj->parent = parent 将父设备的 kobj 赋值给当前设备的 kobj->parent,再次将父子 kobj 关联一次。

三、添加 kset 和 kobject

前面分析了添加 kset 和添加 device 的本质都是添加 kobject,由函数 kobject_add_internal() 完成。
请添加图片描述
其中,① 对于没有名字的 kobj 不予以添加;② 是关联 kset 和 kobj;③ 根据 kobj 在 sysfs 中创建目录和文件。

3.1 关联 kset 和 kobject

由于 kset 和 device 的情况不同,这里针对上面代码 ② 分情况详细分析。
请添加图片描述
首先是 kset,对于本文举例的 devices_kset 对象,“创建 kset” 章节已经分析,它是顶层实例对象,其没有父对象,且代表一个集合,也不属于其他集合,故 parent 和 kobj->kset 为空,不执行 ② 部分代码。

然后是平台总线设备,从平台总线设备、平台设备的初始化函数 device_initialize() 中可知,它们的 kobj->kset 都指向 devices_kset。平台总线设备没有父设备,故通过 kobject_get(kobj->parent) 获取的parent 为空。第二次获取 parent 时, kobj->kset 即 devices_kset,kobject_get(&kobj->kset->kobj) 获取的 parent 是 devices_kset 对象的 kobj,并将其赋值给当前设备的 kobj->parent。因此,平台总线设备的父为 devices_kset。

最后是平台设备,平台设备的 kobj->kset 都指向 devices_kset,父设备是平台总线设备。因此, parent 和 kobj->kset 都非空,parent 依然指向平台总线设备的 kobj,下面代码中 kobj->parent = parent 仅仅是再次赋值,并没有修改父设备。

函数 kobj_kset_join()将 kobject 添加到链表。其中,list_add_tail() 将 kobj 从尾部添加到以 kobj->kset->list 为头的链表中。而平台总线设备和所有平台设备的 kobj->kset 都指向 devices_kset,可见它们都通过 kobj 被 devices_kset 中的 list 链表管理。
请添加图片描述
同时,每往 devices_kset 中的链表添加一个成员,通过函数 kset_get(kobj->kset) 对 devices_kset 的 kobj->kref 加一;每往 devices_kset 中的链表删除一个成员,通过函数 kset_put() 对其 kobj->kref 减一。
请添加图片描述

3.2 创建 kset 和 设备目录

无论是 kset 还是设备(包括总线设备和普通设备)都会在 sysfs 文件系统中,以目录和文件的形式展示给用户空间。sysfs 目录结构的创建由函数 create_dir() 负责实现,其主要任务包括创建目录和创建文件,分别由对应函数 sysfs_create_dir_ns() 和函数 populate_dir() 完成。
请添加图片描述

四、创建 sysfs 目录结构

前面讲到 kset 和设备在 sysfs 文件系统中的目录结构由函数 create_dir() 负责创建,创建目录由函数 sysfs_create_dir_ns() 完成,创建文件由函数 populate_dir() 完成。

4.1 创建目录

函数 sysfs_create_dir_ns() 代码如下。其中,① 是选择 parent,即上级目录;② 是基于上级目录,创建当前设备对应的目录;③ 是将创建好的目录 entry 索引到本设备中。
请添加图片描述
函数 sysfs_create_dir_ns() 是 kset 和设备的公共函数,由于二者的情况不同,分情况详细分析。

4.1.1 创建 kset 级目录

对于 kset,本文举例的 devices_kset 对象,前面分析可知,其没有父对象,故代码 ① 中的 parent 直接是全局变量 sysfs_root_kn 。从下面代码可知,sysfs_root_kn 在函数 sysfs_init() 中初始化,是 sysfs 文件系统的顶层目录 entry。同时,devices_kset 的 kobj->name = “devices” 。因此,代码 ② 中 kernfs_create_dir_ns() 函数会在 sysfs 文件系统中创建 devices 目录,即 /sys/devices。
请添加图片描述
在代码 ② 创建完 devices_kset 的目录 /sys/devices 后,代码 ③ 将 /sys/devices 目录的 entry 索引到 devices_kset 的 kobj->sd。

4.1.2 创建总线设备级目录

对于平台总线设备,从 “关联 kset 和 kobject” 章节可知,其父为 devices_kset,kobj->parent 指向 devices_kset 的 kobj,故 ① 中 parent 是 /sys/devices 目录的 entry。同时,从 " kobject 的名字" 章节可知,平台总线设备的 kobj->name = “platform”。 因此,代码 ② 中 kernfs_create_dir_ns() 函数会在 sysfs 文件系统中创建 platform 目录,即 /sys/devices/platform。

同样,在代码 ② 创建完平台总线设备的目录 /sys/devices/platform 后,代码 ③ 将 /sys/devices/platform 目录的 entry 索引到平台总线设备的 kobj->sd。

4.1.3 创建设备级目录

对于平台设备,其父为平台设备,kobj->parent 指向平台设备的 kobj,故 ① 中 parent 是 /sys/devices/platform 目录的 entry。同时,从 " kobject 的名字" 章节可知,平台设备的 kobj->name 与设备属性相关,各不相同,如:xxx。 因此,代码 ② 中 kernfs_create_dir_ns() 函数会在 sysfs 文件系统中创建 xxx 目录,即 /sys/devices/platform/xxx。

4.2 创建文件

函数 populate_dir() 代码如下,其核心任务是根据 kset 和设备中 kobj->ktype 的默认属性 default_attrs,在对应级别的目录下创建文件,由函数 sysfs_create_file() 完成。
请添加图片描述
上面创建目录时,kobj->sd 已经索引到对应的目录 entry,函数 sysfs_create_file() 内部就是基于该 entry 创建文件。

注意:集合 devices_kset 和设备的 kset_ktype 和 device_ktype 中都属性 default_attrs 都为空。

五、总结

1、内核以集合为单位管理设备,属于同一集合的所有设备通过链表管理。以集合+链表形式管理设备的方法是通过 kset 和 kobject 实现的。

2、一个设备集合对应一个 kset 实例和众多设备实例,kset 实例和设备实例都包含了 kobject。本文已经知道 Linux 内核中有一个 devices_kset,类似的还有 bus_kset、class_kset。

3、创建 kset 和注册设备就是实例化集合和设备的过程。

  • 创建 kset 主要完成两件事:第一件事是实例化一种设备的集合,和创建管理设备的链表。第二件事是在 sysfs 中创建 kset 级别的目录,目录以 kset 的名字(kset->kobj.name)命名。
  • 注册设备主要也是完成两件事:第一件事是实例化设备,并通过 kobj 将设备添加到所属集合的链表中。第二件事是在 sysfs 中创建(总线)设备级别目录,目录以设备的名字(device->kobj.name)命名。

4、通过 kobj 管理设备的操作被封装在设备的注册函数中,设备在注册时,自动被添加到对应 kset 实例的链表中,对设备的驱动开发透明。

5、集合、总线设备和设备之间数据结构关系如下,以 devices_kset 为例展示一个集合的管理架构。
请添加图片描述
Linux内核中 devices_kset(对应/sys/devices目录)位于顶层,无父节点,故其 parent 为空。对于非顶层 kset,其 parent 指向父 kset,比如:system_kset(对应/sys/devices/system目录)的父 kset 为 devices_kset,它的 parent 指向 devices_kset。

6、在 sysfs 文件系统中,集合和设备以如下目录结构展示给用户空间。
请添加图片描述


网站公告

今日签到

点亮在社区的每一天
去签到