原文:http://www.wowotech.net/device_model/kobject.html

作者:wowo

发布于:2014-3-7 0:25

分类:统一设备模型

前言

Kobject是Linux设备模型的基础,也是设备模型中最难理解的一部分(可参考Documentation/kobject.txt的表述)。因此有必要先把它分析清楚。

设计背景

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

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

目前为止,Kobject主要提供如下功能:

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

在Linux中,Kobject几乎不会单独存在。它的主要功能,就是内嵌在一个大型的数据结构中,为这个数据结构提供一些底层的功能实现。

Linux driver开发者,很少会直接使用Kobject以及它提供的接口,而是使用构建在Kobject之上的设备模型接口。

代码解析

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

  • include/linux/kobject.h :包含Kobject所有的数据结构定义和接口声明
  • lib/kobject.c :核心功能的实现

主要的数据结构

在描述数据结构之前,有必要说明一下Kobject, KsetKtype这三个概念。

Kobject是基本数据类型,每个Kobject都会在"/sys/“文件系统中以目录的形式出现。

Ktype代表Kobject(严格地讲,是包含了Kobject的数据结构)的属性操作集合(由于通用性,多个Kobject可能共用同一个属性操作集,因此把Ktype独立出来了)。

在设备模型中,ktype的命名和解释,都非常抽象,理解起来非常困难,后面会详细说明。

Kset是一个特殊的Kobject(因此它也会在"/sys/“文件系统中以目录的形式出现),它用来集合相似的Kobject(这些Kobject可以是相同属性的,也可以不同属性的)。

Kobject

/* Kobject: include/linux/kobject.h line 60 */
struct kobject {
const char *name;
struct list_head entry;
struct kobject *parent;
struct kset *kset;
struct kobj_type *ktype;
struct sysfs_dirent *sd;
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;
};

name:该Kobject的名称,同时也是sysfs中的目录名称。

由于Kobject添加到Kernel时,需要根据名字注册到sysfs中,之后就不能再直接修改该字段。

如果需要修改Kobject的名字,需要调用kobject_rename接口,该接口会主动处理sysfs的相关事宜。

entry:用于将Kobject加入到Kset中的list_head。

parent:指向parent kobject,以此形成层次结构(在sysfs就表现为目录结构)。

kset:该kobject属于的Kset。可以为NULL。如果存在,且没有指定parent,则会把Kset作为parent(别忘了Kset是一个特殊的Kobject)。

ktype:该Kobject属于的kobj_type。每个Kobject必须有一个ktype,否则Kernel会提示错误。

sd:该Kobject在sysfs中的表示。

kref:"struct kref”类型(在include/linux/kref.h中定义)的变量,为一个可用于原子操作的引用计数。

state_initialized:指示该Kobject是否已经初始化,以便在Kobject的Init,Put,Add等操作时进行异常校验。

state_in_sysfs:指示该Kobject是否已在sysfs中呈现,以便在自动注销时从sysfs中移除。

state_add_uevent_sent/state_remove_uevent_sent:记录是否已经向用户空间发送ADD uevent,如果有,且没有发送remove uevent,则在自动注销时,补发REMOVE uevent,以便让用户空间正确处理。

uevent_suppress:如果该字段为1,则表示忽略所有上报的uevent事件。

注:Uevent提供了“用户空间通知”的功能实现,通过该功能,当内核中有Kobject的增加、删除、修改等动作时,会通知用户空间。

Kset

/* include/linux/kobject.h, line 159 */
struct kset {
struct list_head list;
spinlock_t list_lock;
struct kobject kobj;
const struct kset_uevent_ops *uevent_ops;
};

list/list_lock:用于保存该kset下所有的kobject的链表。

kobj:该kset自己的kobject(kset是一个特殊的kobject,也会在sysfs中以目录的形式体现)。

uevent_ops:该kset的uevent操作函数集。当任何Kobject需要上报uevent时,都要调用它所从属的kset的uevent_ops,添加环境变量,或者过滤event(kset可以决定哪些event可以上报)。因此,如果一个kobject不属于任何kset时,是不允许发送uevent的。

Ktype

/* include/linux/kobject.h, line 108 */
struct kobj_type {
void (*release)(struct kobject *kobj);
const struct sysfs_ops *sysfs_ops;
struct attribute **default_attrs;
const struct kobj_ns_type_operations *(*child_ns_type)(struct kobject *kobj);
const void *(*namespace)(struct kobject *kobj);
};

release:通过该回调函数,可以将包含该种类型kobject的数据结构的内存空间释放掉。

sysfs_ops:该种类型的Kobject的sysfs文件系统接口。

default_attrs:该种类型的Kobject的atrribute列表(所谓attribute,就是sysfs文件系统中的一个文件)。将会在Kobject添加到内核时,一并注册到sysfs中。

child_ns_type/namespace,和文件系统(sysfs)的命名空间有关,这里不再详细说明。

机制

Kobject的核心功能是:保持一个引用计数,当该计数减为0时,自动释放Kobject所占用的meomry空间(如果Kobject是动态分配的时候)。

在字符设备cdev机构体中,cdev的分配分为两种:静态和动态如果静态分配,cdev.kobj也是静态分配。

而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的上层数据结构!

顺便提一下,Kobject是面向对象的思想在Linux kernel中的极致体现,但C语言的优势却不在这里,所以Linux kernel需要用比较巧妙(也很啰嗦)的手段去实现。

从面向对象的角度来看:

  • struct kobj(及其相关结构如kset, ktype等)属于最抽象的基类,代码最简洁,最不具体;
  • struct device(及其相关结构如device_driver,device_type等)是对kobj的封装,是第一层派生类;
  • 再上层的结构(如platform_device等),是在struct device的基础上再封装一次,是第二层派生类。

因此,例如我们创建了一个struct platform_device的实例,使用完毕后要释放它。那么这个过程按道理应该是:

  • 系统内部先调用platform_device的remove函数,它只处理自己层特有的变量;
  • 完毕后,系统调用第一层派生类struct device的release函数,处理了自己这一层的特有变量;
  • 最后,调用基类kobject的release函数,将整个空间释放掉。

整个过程应该会跟C++析构过程比较类似,上述的“系统内部”也应该类似于C++编译器自动生成的代码,因为C++中析构函数的逆向调用是自动进行的,并没有在派生类的析构函数中显示调用。类似地,在此处上层的release中也不会显式调用下层的release,都是由系统内部完成的。

功能分析

Kobject使用流程

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

  • 定义一个struct kset类型的指针,并在初始化时为它分配空间,添加到内核中
  • 根据实际情况,定义自己所需的数据结构原型,该数据结构中包含有Kobject
  • 定义一个适合自己的ktype,并实现其中回调函数
  • 在需要使用到包含Kobject的数据结构时,动态分配该数据结构,并分配Kobject空间,添加到内核中
  • 每一次引用数据结构时,调用kobject_get接口增加引用计数;引用结束时,调用kobject_put接口,减少引用计数
  • 当引用计数减少为0时,Kobject模块调用ktype所提供的release接口,释放上层数据结构以及Kobject的内存空间

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

Kobject的分配与释放

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

通过kmalloc

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

/* include/linux/kobject.h, line 85 */
extern void kobject_init(struct kobject *kobj, struct kobj_type *ktype);
extern __printf(3, 4) __must_check
int kobject_add(struct kobject *kobj, struct kobject *parent,
const char *fmt, ...);
extern __printf(4, 5) __must_check
int kobject_init_and_add(struct kobject *kobj,
struct kobj_type *ktype, struct kobject *parent,
const char *fmt, ...);

kobject_init:初始化通过kmalloc等内存分配函数获得的struct kobject指针。主要执行逻辑为:

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

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

  • 确认kobj不为空,确认kobj已经初始化,否则错误退出
  • 调用内部接口kobject_add_varg,完成添加操作

kobject_init_and_add,是上面两个接口的组合,不再说明。

通过方式1分配的kobject,会在引用计数变为0时,由kobject_put调用其ktype的release接口,释放内存空间,具体可参考后面有关kobject_put的讲解。

内部接口 :

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

    kobject_add_internal,将kobject添加到kernel。主要执行逻辑为:
- 校验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 2、kobj_kset_join,负责将kobj加入到对应kset的链表中。
使用kobject_create

Kobject模块可以使用kobject_create自行分配空间,并内置了一个ktype(dynamic_kobj_ktype),用于在计数为0是释放空间。

/* include/linux/kobject.h, line 96 */
extern struct kobject * __must_check kobject_create(void);
extern struct kobject * __must_check kobject_create_and_add(const char *name,
struct kobject *parent);
/* lib/kobject.c, line 605 */
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的空间。

Kobject引用计数的修改

通过kobject_get和kobject_put可以修改kobject的引用计数,并在计数为0时,调用ktype的release接口,释放占用空间。

/* include/linux/kobject.h, line 103 */
extern struct kobject *kobject_get(struct kobject *kobj);
extern void kobject_put(struct kobject *kobj);

kobject_get:调用kref_get,增加引用计数。

kobject_put:以内部接口kobject_release为参数,调用kref_put。kref模块会在引用计数为零时,调用kobject_release。

内部接口:

kobject_release() 通过kref结构,获取kobject指针,并调用kobject_cleanup接口继续。
kobject_cleanup,负责释放kobject占用的空间,主要执行逻辑如下:
- 检查该kobject是否有ktype,如果没有,打印警告信息
- 如果该kobject向用户空间发送了ADD uevent但没有发送REMOVE uevent,补发REMOVE uevent
- 如果该kobject有在sysfs文件系统注册,调用kobject_del接口,删除它在sysfs中的注册
- 调用该kobject的ktype的release接口,释放内存空间
- 释放该kobject的name所占用的内存空间

Kset的初始化、注册

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

/* include/linux/kobject.h, line 166 */
extern void kset_init(struct kset *kset);
extern int __must_check kset_register(struct kset *kset);
extern void kset_unregister(struct kset *kset);
extern struct kset * __must_check kset_create_and_add(const char *name,
const struct kset_uevent_ops *u,
struct kobject *parent_kobj);

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

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

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

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

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

Linux设备模型:2、基本对象 Kobject、Kset、Ktype的更多相关文章

  1. Linux 设备模型之 (kobject、kset 和 Subsystem)(二)

    问题描写叙述:前文我们知道了/sys是包括内核和驱动的实施信息的,用户能够通过 /sys 这个接口.用户通过这个接口能够一览内核设备的全貌.本文将从Linux内核的角度来看一看这个设备模型是怎样构建的 ...

  2. Linux设备模型之kobject

    阿辉原创,转载请注明出处 参考文档:LDD3-ch14.内核文档Documentation/kobject.txt,本文中使用到的代码均摘自Linux-3.4.75 ----------------- ...

  3. 【原创】linux设备模型之kset/kobj/ktype分析

    背 景 Read the fucking source code! --By 鲁迅 A picture is worth a thousand words. --By 高尔基 说明: Kernel版本 ...

  4. linux设备模型_转

    建议原博文查看,效果更佳. 转自:http://www.cnblogs.com/wwang/category/269350.html Linux设备模型 (1) 随着计算机的周边外设越来越丰富,设备管 ...

  5. Linux设备模型(总线、设备、驱动程序和类)

    Linux设备驱动程序学习(13) -Linux设备模型(总线.设备.驱动程序和类)[转] 文章的例子和实验使用<LDD3>所配的lddbus模块(稍作修改). 提示:在学习这部分内容是一 ...

  6. Linux设备模型 学习总结

    看LDD3中设备模型一章,觉得思维有些混乱.这里从整体的角度来理理思路.本文从四个方面来总结一些内容: 1.底层数据结构:kobject,kset.2.linux设备模型层次关系:bus_type,d ...

  7. Linux设备模型——设备驱动模型和sysfs文件系统解读

    本文将对Linux系统中的sysfs进行简单的分析,要分析sysfs就必须分析内核的driver-model(驱动模型),两者是紧密联系的.在分析过程中,本文将以platform总线和spi主控制器的 ...

  8. linux设备模型:扩展篇

    Linux设备模型组件:总线  一.定义:总线是不同IC器件之间相互通讯的通道;在计算机中,一个总线就是处理器与一个或多个不同外设之间的通讯通道;为了设备模型的目的,所有的设备都通过总线相互连接,甚至 ...

  9. Linux设备模型(总结)

    转:http://www.360doc.com/content/11/1219/16/1299815_173418267.shtml 看了一段时间的驱动编程,从LDD3的hello wrod到后来的字 ...

  10. Linux设备模型 (2)

    上一篇文章<Linux设备模型 (1)>主要介绍了Linux设备模型在用户空间的接口sysfs,用户通过这个接口可以一览内核设备的全貌.本文将从Linux内核的角度来看一看这个设备模型是如 ...

随机推荐

  1. NetCore开发第一步 Log4Net日志引入

    1.新建一个带mvc模板的项目: 2.引入Microsoft.Extensions.Logging.Log4Net.AspNetCore包,不要引入错了. 引入后后包的结果如下: 3.Startup类 ...

  2. 大模型高效微调详解-从Adpter、PrefixTuning到LoRA

    一.背景 目前NLP主流范式是在大量通用数据上进行预训练语言模型训练,然后再针对特定下游任务进行微调,达到领域适应(迁移学习)的目的. 指令微调是预训练语言模型微调的主流范式 其目的是尽量让下游任务的 ...

  3. fastposter v2.7.1 紧急发布 电商海报编辑器

    fastposter v2.7.1 紧急发布 电商海报编辑器 fastposter海报生成器,电商海报编辑器,电商海报设计器,fast快速生成海报 海报制作 海报开发.二维码海报,图片海报,分享海报, ...

  4. 如何使用Mac远程控制Windows电脑?

    如何使用Mac远程控制Windows电脑?在你开始之前,设置您要远程处理的 Windows 计算机. 先安装 Microsoft Remote Desktop. 打开 Microsoft Remote ...

  5. 自研WPF插件系统(沙箱运行及热插拔)

    前言 插件化的需求主要源于对软件架构灵活性的追求,特别是在开发大型.复杂或需要不断更新的软件系统时,插件化可以提高软件系统的可扩展性.可定制性.隔离性.安全性.可维护性.模块化.易于升级和更新以及支持 ...

  6. android studio 安装与配置

    android  studio  下载地址:http://www.android-studio.org/ 找一个存储空间,我在D盘上,建好如下目录 : 找到刚才在载的文件    android-stu ...

  7. Python爬图片(面向对象版)

    import requests from lxml import etree from threading import Thread class Spider(object): def __init ...

  8. 为什么SwiftUI使用struct, 限制使用class

    前言 在学习SwiftUI所有的地方,视图元素都定义一个struct并实现View协议,该协议定义body变量返回View类型. 但是为什么,这里一直是指定的struct, 而不是class呢? 尝试 ...

  9. iOS手工Crash解析

    一.测试导出来一份ips crash文件,现在需要进行手工解析 现在需要下载对应的dsym文件,为了确定下载好的dsym文件和crash log是不是一致的,可以先看下dsym文件中的uuid p.p ...

  10. react表单处理 非受控组件

    没有和state数据源进行关联的表单项,而是借助ref,使用元素DOM方式获取表单元素值 使用步骤 调用 React.createRef() 方法创建ref对象 将创建好的 ref 对象添加到文本框中 ...