第八十章 设备模型基本框架-kobject 和 kset
80.1 什么是设备模型
设备模型使Linux内核处理复杂设备更高效。
字符设备驱动适用于简单设备,但对于电源管理和热插拔,不够灵活。
设备模型允许开发人员以高级方式描述硬件及关系,提供API处理设备注册、热插拔和电源管理。这样,开发人员可依赖内核处理底层功能,减少重复工作和错误。
内核已提供USB、I2C等平台设备的模型,开发人员可基于此编写驱动,快速实现功能,并利用内核的电源和热插拔管理。
80.2 设备模型的好处
设备模型在内核驱动中至关重要,它统一描述硬件及关系,带来以下优势:
- 代码复用:多个设备可共用同一驱动,减少冗余,提升复用性和维护性。
cpp
// 示例:多个设备使用同一驱动
struct device_driver my_driver;
register_driver_for_devices(&my_driver, device_list);
- 资源动态管理:设备模型支持资源(如内存、中断)的动态申请和释放。
cpp
// 示例:动态申请资源
device_resource = request_resource(device, RESOURCE_TYPE, ...);
// 释放资源
release_resource(device_resource);
- 简化驱动编写:提供通用API,使驱动编写更简化和模块化。
cpp
// 示例:使用通用API注册设备
register_device(my_device);
// 处理设备事件
device_event_handler(my_device, EVENT_TYPE, ...);
- 热插拔支持:设备模型支持运行时动态添加或移除设备,并生成事件。
cpp
// 示例:监听热插拔事件
register_hotplug_event_listener(my_device, hotplug_event_callback);
- 面向对象设计:设备被视为对象,具有属性和方法,支持通过设备模型的机制来继承和扩展。
cpp
// 示例:设备对象及其方法
struct device {
void (*init)(struct device*);
void (*release)(struct device*);
// 其他属性和方法
};
struct my_device {
struct device base;
// 特定属性和方法
};
void my_device_init(struct my_device* dev) {
// 初始化设备
dev->base.init(&dev->base);
// 其他初始化操作
}
80.3 kobject 和 kset 基本概念
kobject(内核对象):
是内核中的通用对象模型,表示各种实体。
kobject是一个结构体,其中包含了一些描述该对象的属性和方法。
每个 kobject 在 /sys 下对应一个目录。
每一个kobject都会对应/sys/下一个目录
比如查看平台总线要进入/sys/bus 目录下,bus 目录下的文件都是和总线相关的目录,比如 amba 总线,CPU 总线,platform 总线。
kobject存在树状关系
KObject存在树状关系
一个 kobject 可以有一个父 kobject 和多个子 kobject,通过 parent 指针可以将它们连接起来形成一个层次化的结构,类似于目录结构。
cpp
struct kobject {
const char *name; // 对象的名称
struct list_head entry; // 链接到父对象的子对象列表中的项
struct kobject *parent; // 指向父对象的指针
struct kset *kset; // 指向包含该对象的kset的指针
struct kobj_type *ktype; // 指向定义对象类型的kobj_type结构体的指针
struct kernfs_node *sd; // 指向sysfs目录中对应的kernfs_node的指针
struct kref kref; // 引用计数,用于管理对象的生命周期
// 仅在CONFIG_DEBUG_KOBJECT_RELEASE配置启用时存在
#ifdef CONFIG_DEBUG_KOBJECT_RELEASE
struct delayed_work release; // 延迟释放工作项,用于调试
#endif
// 状态标志位
unsigned int state_initialized:1; // 是否已初始化
unsigned int state_in_sysfs:1; // 是否已在sysfs中注册
unsigned int state_add_uevent_sent:1; // 是否已发送添加事件通知
unsigned int state_remove_uevent_sent:1;// 是否已发送移除事件通知
unsigned int uevent_suppress:1; // 是否抑制uevent发送
// Android内核ABI保留字段
ANDROID_KABI_RESERVE(1);
ANDROID_KABI_RESERVE(2);
ANDROID_KABI_RESERVE(3);
ANDROID_KABI_RESERVE(4);
};
cpp
/*用于描述kobj对象的结构体*/
struct kobj_type {
void (*release)(struct kobject *kobj); //release函数
const struct sysfs_ops *sysfs_ops; //sysfs操作
struct attribute **default_attrs; //obj对象的属性成员,包括名称、文件模式
};
cpp
/*sysfs文件操作*/
struct sysfs_ops {
/*读操作*/
ssize_t (*show)(struct kobject *kobj, struct attribute *attr, char *buf);
/*写操作*/
ssize_t (*store)(struct kobject *kobj, struct attribute *attr,
const char *buf, size_t count);
};
cpp
/*属性结构体*/
struct attribute {
const char *name; //属性名称,会生成对应文件夹
umode_t mode; //文件模式
};
kset(内核对象集合):
用于组织和管理一组相关的 kobject。
包含 kobject 作为成员,自身也是一个 kobject。
提供全局链表和锁,确保线程安全的管理。
可定义 uevent 操作,处理相关事件。
cpp
struct kset {
struct list_head list; // 链接到全局kset列表的项
spinlock_t list_lock; // 保护list的自旋锁
struct kobject kobj; // 作为kset自身的kobject
const struct kset_uevent_ops *uevent_ops; // 指向处理uevent操作的函数指针集
// Android内核ABI保留字段
ANDROID_KABI_RESERVE(1);
ANDROID_KABI_RESERVE(2);
ANDROID_KABI_RESERVE(3);
ANDROID_KABI_RESERVE(4);
} __randomize_layout;
80.4 kset 和 kobject 的关系
在 Linux 内核中,kset 和 kobject 是相关联的两个概念,它们之间存在一种层次化的关系,
kset和 kobject的关系
一个 kset 可以包含多个 kobject,而一个 kobject只能属于一个 kset。
kset 提供了对 kobject 的集合管理和操作接口,用于组织和管理具有相似特性或关系的 kobject。这种关系使得内核能够以一种统一的方式管理和操作不同类型的内核对象。
80.5 创建 kobject
两种创建 kobject 的方法,
一种是使用 kobject_create_and_add() 函数创建 kobject对象,
cpp
/*创建kobject对象*/
struct kobject *kobject_create_and_add(const char *name, //kobject对象名称
struct kobject *parent);//父kobject对象
另一种是使用 kzalloc() 和 kobject_init_and_add() 函数创建 kobject对象。
cpp
void *kzalloc(size_t size, //内存大小
gfp_t flags);//内存分配标志
//GFP_KERNEL(可以在睡眠的情况下分配内存,用于内核的正常操作)
//GFP_ATOMIC(不能睡眠,用于中断上下文或紧急情况下).
int kobject_init_and_add(struct kobject *kobj, //kobject指针
const struct kobj_type *ktype, //kobject_type指针,
//定义了与 kobject 相关的方法
//如 release 函数用于释放资源,
//sysfs_ops 用于处理 sysfs 文件系统操作等。
struct kobject *parent, //父kobject指针
const char *name); //kobject名称
cpp
// 定义了三个 kobject 指针变量:mykobject01、mykobject02、mykobject03
struct kobject *mykobject01;
struct kobject *mykobject02;
struct kobject *mykobject03;
// 定义了一个 kobj_type 结构体变量 mytype,用于描述 kobject 的类型。
struct kobj_type mytype;
// 模块的初始化函数
static int mykobj_init(void)
{
int ret;
// 创建 kobject 的第一种方法
// 创建并添加了名为"mykobject01"的 kobject 对象,父 kobject 为 NULL
mykobject01 = kobject_create_and_add("mykobject01", NULL);
// 创建并添加了名为"mykobject02"的 kobject 对象,父 kobject 为 mykobject01。
mykobject02 = kobject_create_and_add("mykobject02", mykobject01);
// 创建 kobject 的第二种方法
// 1 使用 kzalloc 函数分配了一个 kobject 对象的内存
mykobject03 = kzalloc(sizeof(struct kobject), GFP_KERNEL);
// 2 初始化并添加到内核中,名为"mykobject03"。
ret = kobject_init_and_add(mykobject03, &mytype, NULL, "%s", "mykobject03");
return 0;
}
// 模块退出函数
static void mykobj_exit(void)
{
// 释放了之前创建的 kobject 对象
kobject_put(mykobject01);
kobject_put(mykobject02);
kobject_put(mykobject03);
}
kobject_get()可用于增加 kobject的引用计数。
kobject_put()用于减少 kobject的引用计数。
cpp
/*增加kobject的引用计数*/
struct kobject *kobject_get(struct kobject *kobj);
/*减少kobject的引用计数*/
void kobject_put(struct kobject *kobj);
/*当kobject的引用计数降为0,会调用kobject_type的release方法释放*/
驱动编译、装载后,发现 kobject01,kobject03 创建在系统根目录/sys 目录下,kobject02 的父节点是 kobject01,所以被创建在 mykobject02 目录下。
现在我们成功验证了创建 kobject 就是在系统根目录/sys 目录下创建一个文件夹,他们是一一对应的关系。
80.6 创建 kset
kset_create_and_add()用于创建 kset对象。
cpp
/*创建kset并添加相关操作*/
struct kset *kset_create_and_add(const char *name,
const struct kset_uevent_ops *uevent_ops,
struct kobject *parent_kobj);
cpp
/*kset事件操作函数集,用于处理与kset相关的热拔插事件*/
struct kset_uevent_ops {
/*决定是否将某个事件传递到用户空间。
当上报热插拔事件时,kset 会通过 filter 函数来过滤事件,
如果 filter 返回 0,则阻止该事件上报到用户空间。*/
int (*const filter)(struct kset *kset, struct kobject *kobj);
/*返回 kset 或其包含的 kobject 的名称*/
const char *(*const name)(struct kset *kset, struct kobject *kobj);
/*当需要向用户空间发送热插拔事件时,uevent 函数会被调用,
它可以将用户空间需要的参数添加到环境变量中。*/
int (*const uevent)(struct kset *kset,
struct kobject *kobj,
struct kobj_uevent_env *env);
};
cpp
struct kobj_uevent_env {
char *argv[3]; // 用于存储传递给事件的参数,通常表示事件的命令行参数
char *envp[UEVENT_NUM_ENVP]; // 包含环境变量指针的数组,用于存储传递给事件的环境变量
int envp_idx; // 用于跟踪环境变量数组中的当前索引
char buf[UEVENT_BUFFER_SIZE]; // 用于存储事件的文本数据,事件通常以文本形式表示
int buflen; // 用于跟踪事件数据缓冲区中的当前有效数据长度
};
cpp
// 定义 kobject 结构体指针,用于表示第一个自定义内核对象
struct kobject *mykobject01;
// 定义 kobject 结构体指针,用于表示第二个自定义内核对象
struct kobject *mykobject02;
// 定义 kset 结构体指针,用于表示自定义内核对象的集合
struct kset *mykset;
// 定义 kobj_type 结构体,用于定义自定义内核对象的类型
struct kobj_type mytype;
// 模块的初始化函数
static int mykobj_init(void)
{
int ret;
// 创建并添加 kset,名称为"mykset",父 kobject 为 NULL,属性为 NULL
mykset = kset_create_and_add("mykset", NULL, NULL);
// 为 mykobject01 分配内存空间,大小为 struct kobject 的大小,标志为 GFP_KERNEL
mykobject01 = kzalloc(sizeof(struct kobject), GFP_KERNEL);
/* 将 mykset 设置为 mykobject01 的 kset 属性 */
mykobject01->kset = mykset;
// 初始化并添加 mykobject01,类型为 mytype,父 kobject 为 NULL,名称为"mykobject01"
ret = kobject_init_and_add(mykobject01, &mytype, NULL, "%s", "mykobject01");
// 为 mykobject02 分配内存空间,大小为 struct kobject 的大小,标志为 GFP_KERNEL
mykobject02 = kzalloc(sizeof(struct kobject), GFP_KERNEL);
/* 将 mykset 设置为 mykobject02 的 kset 属性*/
mykobject02->kset = mykset;
// 初始化并添加 mykobject02,类型为 mytype,父 kobject 为 NULL,名称为"mykobject02"
ret = kobject_init_and_add(mykobject02, &mytype, NULL, "%s", "mykobject02");
return 0;
}
// 模块退出函数
static void mykobj_exit(void)
{
// 释放 mykobject01 的引用计数
kobject_put(mykobject01);
// 释放 mykobject02 的引用计数
kobject_put(mykobject02);
}
第八十一章 为什么要引入设备模型
81.1 设备模型简介
设备模型在内核驱动中至关重要,它统一描述和管理设备,简化了驱动开发,提高了代码复用性和可维护性,并支持热插拔和动态资源管理。
设备模型包含以下核心概念:
总线(Bus):连接设备的通信通道,可以是物理的(如PCI、USB)或虚拟的。
设备(Device):系统中的硬件设备,如网卡、显示器等,每个设备有唯一标识符。
驱动(Driver) :控制和管理设备操作的软件,与操作系统交互,发送命令、接收事件等。
类(Class) :逻辑组织单元,对相似功能和特性的设备进行分类管理。 88.2 相关结构体
81.2 相关结构体
在Linux设备模型中,存在一条名为"platform"的虚拟总线,它专门用于管理那些直接与CPU相连、不符合常见总线标准(如PCI、USB)的设备控制器。
Platform总线为这些设备控制器提供了一个统一的注册和管理机制。
设备控制器通常在设备树中定义,并通过设备树与相应的设备驱动程序进行匹配。
设备驱动程序通过注册到Platform总线,能够与对应的设备控制器进行绑定和通信,从而访问控制器的寄存器、配置设备、处理中断等。
总线
尽管在芯片原厂提供的 BSP 中已经实现了设备模型,但是了解这些概念可以好地理解设备的工作原理,驱动程序的编写和设备的管理。
81.2.1 struct bus_type
bus_type 结构体是 Linux 内核中用于描述总线的数据结构。
以下是结构体包含的成员和其作用的简要说明
name: 总线类型名称
dev_name: 总线设备名称(格式化字符串,总线设备名称前缀)
dev_root: 总线设备的根设备
bus_groups: 总线类型的属性组
dev_groups: 设备属性组
drv_groups: 驱动程序属性组
以下是一些回调函数成员
match: 设备和驱动程序之间的匹配函数
uevent: 设备的事件处理函数
probe: 设备的探测函数
sync_state: 设备状态同步函数
remove: 设备的移除函数
online: 设备上线函数
offline: 设备离线函数
suspend: 设备的挂起函数
resume: 设备的恢复函数
num_vf: 设备的虚拟功能数目函数
dma_configure:设备的 DMA 配置函数
以下是一些其他成员:
pm: 设备的电源管理操作。
iommu_ops: 设备的 IOMMU 操作。
p: 子系统私有数据。
lock_key: 用于锁机制的锁类别键。
need_parent_lock:是否需要父级锁。
cpp
//定义在 include/linux/device.h头文件中。
/*总线类型结构体*/
struct bus_type {
// 总线的名称,用于唯一标识该总线类型
const char *name;
// 设备名称的格式字符串,用于生成连接到该总线上的设备的默认名称
const char *dev_name;
// 指向该总线上所有设备的根节点的指针,作为所有设备的父节点
struct device *dev_root;
// 指向总线属性组的指针数组,用于定义和导出总线的属性
const struct attribute_group **bus_groups;
// 指向设备属性组的指针数组,用于定义和导出连接到该总线上的设备的属性
const struct attribute_group **dev_groups;
// 指向驱动属性组的指针数组,用于定义和导出与该总线相关的驱动的属性
const struct attribute_group **drv_groups;
// 匹配函数,用于判断设备和驱动是否匹配
int (*match)(struct device *dev, struct device_driver *drv);
// uevent函数,用于生成并发送设备事件到用户空间
int (*uevent)(struct device *dev, struct kobj_uevent_env *env);
// 探测函数,当设备被添加到系统时调用,用于初始化设备
int (*probe)(struct device *dev);
// 同步状态函数,用于同步设备的状态
void (*sync_state)(struct device *dev);
// 移除函数,当设备从系统中移除时调用
int (*remove)(struct device *dev);
// 关机函数,在系统关机时调用
void (*shutdown)(struct device *dev);
// 上线函数,用于将设备设置为在线状态
int (*online)(struct device *dev);
// 下线函数,用于将设备设置为离线状态
int (*offline)(struct device *dev);
// 挂起函数,用于将设备挂起到指定的电源状态
int (*suspend)(struct device *dev, pm_message_t state);
// 恢复函数,用于从挂起状态恢复设备
int (*resume)(struct device *dev);
// 获取设备支持的虚拟功能数量的函数
int (*num_vf)(struct device *dev);
// DMA配置函数,用于配置设备的DMA能力
int (*dma_configure)(struct device *dev);
// 指向电源管理操作结构体的指针,定义了设备的电源管理行为
const struct dev_pm_ops *pm;
// 指向IOMMU操作结构体的指针,定义了IOMMU相关的操作
const struct iommu_ops *iommu_ops;
// 指向子系统私有数据的指针,用于存储子系统的内部状态和信息
struct subsys_private *p;
// 锁类关键字,用于调试和性能分析,确保锁的唯一性和正确性
struct lock_class_key lock_key;
// 布尔值,指示是否需要对父设备进行加锁操作
bool need_parent_lock;
// 保留字段,用于Android内核ABI的兼容性保留
ANDROID_KABI_RESERVE(1);
ANDROID_KABI_RESERVE(2);
ANDROID_KABI_RESERVE(3);
ANDROID_KABI_RESERVE(4);
};
81.2.2 struct device
device 结构体是 Linux 内核中用于描述设备的数据结构。
cpp
//定义在 include/linux/device.h 头文件中
/*设备结构体*/
struct device {
// 设备的父设备指针,如果设备是顶级设备,则此指针为NULL
struct device *parent;
// 指向设备私有数据的指针,通常用于存储设备的内部状态和信息
struct device_private *p;
// 与设备相关联的内核对象,提供了对象管理和属性导出等功能
struct kobject kobj;
// 设备初始化时指定的名称,可能用于设备的唯一标识或调试
const char *init_name;
// 指向设备类型的指针,定义了设备的行为和特性
const struct device_type *type;
// 设备所属的总线类型指针,用于将设备与总线相关联
struct bus_type *bus;
// 指向设备驱动的指针,如果设备已绑定到驱动,则此指针非空
struct device_driver *driver;
// 设备所属的类指针,用于将设备分组到特定的设备类中
struct class *class;
// 指向设备属性组的指针数组,用于定义和导出设备的属性
// 这些属性可以在用户空间通过sysfs文件系统访问
const struct attribute_group **groups; /* optional groups */
// 省略了其他成员...
};
81.2.3 struct device_driver
device_driver结构体是Linux内核中描述设备驱动程序的数据结构。
cpp
//定义在include/linux/device.h 头文件中。
/*设备驱动结构体*/
struct device_driver {
// 驱动的名称,通常用于日志输出、调试和匹配设备
const char *name;
// 指向设备总线类型的指针,表示该驱动支持的总线
struct bus_type *bus;
// 指向驱动所属模块的指针,用于模块加载和卸载时的引用计数
struct module *owner;
// 内置模块的名称,当驱动是内置到内核中时,此字段用于标识模块
const char *mod_name; /* used for built-in modules */
// 是否禁止通过sysfs文件系统绑定或解绑设备
bool suppress_bind_attrs; /* disables bind/unbind via sysfs */
// 探测类型,指定驱动是如何被探测和加载的
enum probe_type probe_type;
// 指向设备树(Device Tree)匹配表的指针,用于基于设备树信息的设备匹配
const struct of_device_id *of_match_table;
// 指向ACPI匹配表的指针,用于基于ACPI信息的设备匹配
const struct acpi_device_id *acpi_match_table;
// 当设备被探测到时调用的回调函数,用于初始化设备
int (*probe) (struct device *dev);
// 同步设备状态的回调函数,用于更新设备的状态信息
void (*sync_state)(struct device *dev);
// 当设备被移除时调用的回调函数,用于清理设备资源
int (*remove) (struct device *dev);
// 当系统关机时调用的回调函数,用于执行驱动级的关机操作
void (*shutdown) (struct device *dev);
// 当设备进入挂起状态时调用的回调函数,用于保存设备状态
int (*suspend) (struct device *dev, pm_message_t state);
// 当设备从挂起状态恢复时调用的回调函数,用于恢复设备状态
int (*resume) (struct device *dev);
// 指向属性组的指针数组,用于定义和导出驱动的属性
const struct attribute_group **groups;
// 指向电源管理操作的指针,包含了挂起、恢复等电源管理相关的回调函数
const struct dev_pm_ops *pm;
// 当设备需要生成coredump时调用的回调函数
void (*coredump) (struct device *dev);
// 指向驱动私有数据的指针,通常用于存储驱动的内部状态和信息
struct driver_private *p;
// 保留字段,用于Android内核ABI(Application Binary Interface)的兼容性保留
// 这些字段在当前版本中未使用,但为未来扩展保留
ANDROID_KABI_RESERVE(1);
ANDROID_KABI_RESERVE(2);
ANDROID_KABI_RESERVE(3);
ANDROID_KABI_RESERVE(4);
};
81.2.4 struct class
class结构体是 Linux 内核中描述设备类的数据结构。
cpp
//定义在 include/linux/device.h 头文件中。
/*设备类*/
struct class {
// 类的名称,通常用于日志输出、调试和sysfs文件系统中的目录名
const char *name;
// 指向类所属模块的指针,用于模块加载和卸载时的引用计数
struct module *owner;
// 指向类属性组的指针数组,用于定义和导出类的属性
// 这些属性可以在用户空间通过sysfs文件系统访问
const struct attribute_group **class_groups;
// 指向设备属性组的指针数组,这些属性是应用于该类下所有设备的
// 与class_groups不同,这些属性更具体于设备实例
const struct attribute_group **dev_groups;
// 指向类设备对象的指针,是类在sysfs中的顶级目录对象
struct kobject *dev_kobj;
// 当设备发生事件时(如添加、移除等),此回调函数用于生成uevent消息
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);
// 获取设备所有权的回调函数,用于设置设备的用户ID和组ID
void (*get_ownership)(struct device *dev, kuid_t *uid, kgid_t *gid);
// 指向电源管理操作的指针,包含了挂起、恢复等电源管理相关的回调函数
const struct dev_pm_ops *pm;
// 指向类私有数据的指针,通常用于存储类的内部状态和信息
struct subsys_private *p;
// 保留字段,用于Android内核ABI(Application Binary Interface)的兼容性保留
// 这些字段在当前版本中未使用,但为未来扩展保留
ANDROID_KABI_RESERVE(1);
ANDROID_KABI_RESERVE(2);
ANDROID_KABI_RESERVE(3);
ANDROID_KABI_RESERVE(4);
};
第八十二章 进一步探究设备模型
为什么当创建 kobj 的时候,父节点为 NULL,会在系统根目录/sys 目录下创建呢。
82.1 什么是 sysfs 文件系统
sysfs是Linux内核的一个虚拟文件系统,它在/sys目录下,以层次结构展示设备和内核对象的信息。用户空间程序可以通过文件系统接口访问这些信息,从而浏览、获取和配置内核中的设备、总线、驱动程序等对象。
82.2 设备模型的基本框架
kobject和 kset构成了Linux设备模型的基本框架,因为它们为内核对象提供了统一的表示和管理机制。
kobject是内核对象的基础,它包含了对象的基本属性和与设备模型集成的接口。
kset则是kobject的集合,提供了对一组相似对象的统一管理。
在Linux设备模型中,许多重要的结构体都嵌入了kobject,
如cdev(字符设备)和platform_device(平台设备)等。
这样做是为了将这些高级对象接入到设备模型中,使它们能够利用 kobject提供的机制进行管理和访问。
例如,cdev结构体中嵌入了kobject,使其能够作为设备模型中的一个对象被管理和访问。
同样,platform_device结构体中包含了device结构体,而device结构体又嵌入了kobject。
这样,platform_device就能够通过 device和 kobject与设备模型进行集成。
所以我们也可以把总线,设备,驱动看作是 kobject 的派生类。因为他们都是设备模型中的实体,通过继承或扩展 kobject 来实现与设备模型的集成。
在 Linux 内核中,kobject 是一个通用的基础结构,用于构建设备模型。每个 kobject 实例对应于 sys 目录下的一个目录,这个目录包含了该 kobject 相关的属性,操作和状态信息。
每个kobjedt实例对应于 /sys/目录下的一个目录
因此,可以说 kobject 是设备模型的基石,通过创建对应的目录结构和属性文件, 它提供了一个统一的接口和框架,用于管理和操作设备模型中的各个实体。
82.3 代码层面分析
在系统启动的时候会在/sys 目录下创建以下目录,
系统启动时在/sys下自动创建的目录
当 kobject_create_and_add() 的 parent 参数为 NULL 时,新创建的 kobject 会在 /sys 根目录下创建相应的目录。
这是因为在 kobject_add_internal() 和 sysfs_create_dir_ns() 中,如果 kobj->parent 为 NULL,则使用 sysfs_root_kn(即 /sys 根目录的节点)作为父节点。
sysfs_root_kn 是在系统启动时通过 sysfs_init() 初始化的。
sysfs_init(),在系统启动时初始化 sysfs,创建 sysfs_root 和 sysfs_root_kn,分别代表 sysfs 的根和根节点。
sysfs_root_kn 是 sysfs 根目录的节点,代表 /sys。
第八十三章 虚拟文件系统 sysfs 目录层次分析
83.1 sys目录对应设备模型的层次结构
我们进入到 Linux 系统的/sys 目录下,可以看到如下文件夹
/sys目录
和设备模型有关的文件夹为 bus,class,devices。
cpp
/sys/bus
/sys/class
/sys/devices
/sys/devices:该目录包含了系统中所有设备的子目录。
每个设备子目录代表一个具体的设备。
每个设备子目录中包含了设备的属性、状态和其他相关信息。
/sys/devices下包含系统所有设备的子目录。设备子目录代表具体的设备
/sys/bus:该目录包含了总线类型的子目录。
每个子目录代表一个特定类型的总线,例如 PCI、USB 等。
每个总线子目录中包含与该总线相关的设备和驱动程序的信息。
/sys/bus下包含系统所有总线的子目录。总线子目录代表总线相关的设备和驱动信息
/sys/class:该目录包含了设备类别的子目录。
每个子目录代表一个设备类别,例如磁盘、网络接口等。
每个设备类别子目录中包含了属于该类别的设备的信息。
/sys/class下包含设备类别的子目录,每个子目录代表一个设备类别,含属于该类别的设备的信息
使用`class`进行归类可以提供一种扩展性和可移植性的机制。
当引入新的设备类型时,可将其归类到现有的类别中,无需修改现有设备管理和驱动程序。
比如应用现在要设置 gpio,
如果使用类可以直接使用以下命令:
cpp
echo 1 > /sys/class/gpio/gpio157/value
如果不使用类,使用以下命令:
cpp
echo 1 > /sys/devices/platform/fe770000.gpio/gpiochip4/gpio/gpio157/value
83.2 sys 目录层次图解
Sys 目录层次结构如下图所示,
sys目录层次结构
第八十四章 引用计数器
84.1 引用计数器介绍
引用计数器管理内存,通过计数引用数量决定何时释放资源。
对象创建时计数为1,引用增加时计数上升,引用失效时计数下降,计数为0时释放资源。
84.2 引用计数器 kref 介绍
kref 是 Linux 内核中提供的一种引用计数器实现。
它是一种轻量级的引用计数技术,用于管理内核中的对象的引用计数。
cpp
// 定义一个结构体 kref,用于表示具有引用计数的对象
struct kref {
// refcount 是一个 refcount_t 类型的成员,用于存储引用计数
refcount_t refcount;
};
// 使用 typedef 定义一个名为 refcount_t 的新类型,它是一个结构体
typedef struct {
atomic_t refs; // refs 是一个 atomic_t 类型的成员,确保引用计数的原子性操作
} refcount_t;
typedef struct {
int counter; // 在实际应用中,不应直接操作这个成员,而应使用原子操作函数
} atomic_t;
kobject结构体中就有 kref结构体成员 ,以实现引用计数的管理。
这样可以通过对 struct kref 的操作来对 struct kobject 进行引用计数的管理,并在引用计数减少到 0 时释放相关资源。
device_node结构体中有 kobject结构体成员,简介完成内核对象的实现和引用计数的管理。
84.3 常用 api 函数
kref_init()用于初始化 kref结构体对象,设置引用计数为 1。
kref_get()结构体 kref 的引用计数加1。
kref_put()结构体 kref 的引用计数减1,并在计数为零时调用释放函数release()。
refcount_set()设置 refcount_t 的计数值。
kref_init()用于初始化 kref结构体对象,设置引用计数为 1。
cpp
/*初始化 kerf结构体,引用计数值给1*/
static inline void kref_init(struct kref *kref) {
refcount_set(&kref->refcount, 1);
}
kref_get()结构体 kref 的引用计数加1。
cpp
/*kerf引用计数值加一*/
static inline void kref_get(struct kref *kref) {
refcount_inc(&kref->refcount);
}
kref_put()结构体 kref 的引用计数减1,并在计数为零时调用释放函数release()。
cpp
/*kerf引用计数减一*/
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; // 引用计数未为零,资源未释放
}
refcount_set()设置 refcount_t 的计数值。
cpp
/*设置计数值*/
static inline void refcount_set(refcount_t *r, int n) {
atomic_set(&r->refs, n);
}
84.4 引用计数器实验
cpp
// 定义了三个 kobject 指针变量:mykobject01、mykobject02、mykobject03
struct kobject *mykobject01;
struct kobject *mykobject02;
struct kobject *mykobject03;
// 定义了一个 kobj_type 结构体变量 mytype,用于描述 kobject 的类型。
struct kobj_type mytype;
// 模块的初始化函数
static int mykobj_init(void)
{
int ret;
// 创建 kobject 的第一种方法
// 创建并添加了名为"mykobject01"的 kobject 对象,父 kobject 为 NULL
mykobject01 = kobject_create_and_add("mykobject01", NULL);
printk("mykobject01 kref is %d\n", mykobject01->kref.refcount.refs.counter);
// 创建并添加了名为"mykobject02"的 kobject 对象,父 kobject 为 mykobject01。
mykobject02 = kobject_create_and_add("mykobject02", mykobject01);
printk("mykobject01 kref is %d\n", mykobject01->kref.refcount.refs.counter);
printk("mykobject02 kref is %d\n", mykobject02->kref.refcount.refs.counter);
// 创建 kobject 的第二种方法
// 1 使用 kzalloc 函数分配了一个 kobject 对象的内存
mykobject03 = kzalloc(sizeof(struct kobject), GFP_KERNEL);
// 2 初始化并添加到内核中,名为"mykobject03"。
ret = kobject_init_and_add(mykobject03, &mytype, NULL, "%s", "mykobject03");
printk("mykobject03 kref is %d\n", mykobject03->kref.refcount.refs.counter);
return 0;
}
// 模块退出函数
static void mykobj_exit(void)
{
printk("mykobject01 kref is %d\n", mykobject01->kref.refcount.refs.counter);
printk("mykobject02 kref is %d\n", mykobject02->kref.refcount.refs.counter);
printk("mykobject03 kref is %d\n", mykobject03->kref.refcount.refs.counter);
// 释放了之前创建的 kobject 对象
kobject_put(mykobject01);
printk("mykobject01 kref is %d\n", mykobject01->kref.refcount.refs.counter);
printk("mykobject02 kref is %d\n", mykobject02->kref.refcount.refs.counter);
printk("mykobject03 kref is %d\n", mykobject03->kref.refcount.refs.counter);
kobject_put(mykobject02);
printk("mykobject01 kref is %d\n", mykobject01->kref.refcount.refs.counter);
printk("mykobject02 kref is %d\n", mykobject02->kref.refcount.refs.counter);
printk("mykobject03 kref is %d\n", mykobject03->kref.refcount.refs.counter);
kobject_put(mykobject03);
printk("mykobject01 kref is %d\n", mykobject01->kref.refcount.refs.counter);
printk("mykobject02 kref is %d\n", mykobject02->kref.refcount.refs.counter);
printk("mykobject03 kref is %d\n", mykobject03->kref.refcount.refs.counter);
}
引用计数图解
如上图 III 所示,如果在 objectA 下面创建俩个 object,objectA 的计数器值为 3。
如上图所示 IV,如果在 objectA 下面创建俩个 object,那么 objectA 的计数器值为 3,在 objectB 下创建 object,那么 objectB 的计数器值为 2,objectC 的计数器值为 1。
第八十五 kobject 释放实例分析
当引用计数器的值变为 0后,会自动调用自定义的释放函数release()去执行释放的操作。
kobject的创建
kobject_create_and_add()函数创建 kobj对象:
分配内存(kzalloc)。
初始化kobject(kobject_init),设置默认ktype。
将kobject添加到系统中(kobject_add),创建sysfs文件系统条目。
cpp
struct kobject * kobject_create_and_add(const char *name, //kobject名称
struct kobject *parent, //父kobject
const struct kobj_type *ktype) //kobj_type类型
kobject_init_and_add()函数创建 kobj对象:
手动分配内存。
初始化kobject(kobject_init),需要手动实现ktype结构体。
将kobject添加到系统中(kobject_add),创建sysfs文件系统条目。
cpp
/*手动创建*/
int kobject_init_and_add(struct kobject *kobj, //kobject指针
const char *name, //kobject名称
struct kobject *parent, //父kobject
const struct kobj_type *ktype) //kobj_type类型
kobject的释放
kobject_put(),减少kobject的引用计数,并在计数为0时调用release()释放资源。
kobject_release()是默认释放函数,底层调用kobject_cleanup()进行资源清理。
cpp
/*kobject释放函数*/
static void kobject_release(struct kref *kref)
{
struct kobject *kobj = container_of(kref, struct kobject, kref);
kobject_cleanup(kobj);
}
资源清理:
kobject_cleanup()函数处理资源清理。
- 检查
kobj_type
结构体中的release
函数 :- 当kobject的引用计数降为零时,内核会检查该kobject的
kobj_type
结构体中是否定义了release
回调函数。 - 如果定义了
release
函数,内核会在后续步骤中调用它。
- 当kobject的引用计数降为零时,内核会检查该kobject的
- 发送"remove"事件(如果未发送) :
- 在释放kobject之前,内核通常会确保已经向用户空间发送了表示kobject被移除的"remove"事件(如果之前还没有发送的话)。
- 这是通过sysfs文件系统完成的,sysfs会监视kobject的状态变化,并在必要时向用户空间广播事件。
- 从sysfs中删除kobject(如果未删除) :
- 内核会从sysfs文件系统中删除与该kobject对应的目录和文件。
- 这确保了用户空间无法再通过sysfs访问该kobject。
- 调用
kobj_type
中的release
函数(如果存在) :- 如果
kobj_type
结构体中定义了release
回调函数,内核会在从sysfs中删除kobject之后调用它。 release
函数负责执行与kobject相关的任何特定清理工作,比如释放内存、关闭文件描述符等。
- 如果
- 释放动态分配的名称(如果存在) :
- 如果kobject的名称是动态分配的(即,不是在编译时静态确定的),内核会释放这个名称所占用的内存。
- 这是资源管理的一部分,确保不会泄漏任何动态分配的资源。
cpp
/*kobject资源清理*/
static void kobject_cleanup(struct kobject *kobj)
{
struct kobj_type *t = get_ktype(kobj); //获取 kobject的 kobj_type成员
const char *name = kobj->name; //获取 kobj的名称
// 检查并调用kobj_type中的release函数等
if (t && t->release) {
t->release(kobj);
}
if (name) { //如果名称存在
kfree_const(name); //动态释放
}
}
kobject_cleanup() 函数的实现表明,最终调用的释放函数是在 kobj_type 结构体中定义的。这解释了为什么在使用 kobject_init_and_add() 函数时,kobj_type 结构体不能为空的原因。
因为释放函数是在 kobj_type 结构体中定义的,如果不实现释放函数,就无法进行正确的资源释放。
动态 kobject的 ktype:
dynamic_kobj_ktype定义了动态创建的 kobject的类型。
指定了释放函数dynamic_kobj_release()。
cpp
/*动态 kobj_type */
static struct kobj_type dynamic_kobj_ktype = {
.release = dynamic_kobj_release,
//指向struct sysfs_ops结构体的指针,该结构体定义了与sysfs文件系统交互的操作。
.sysfs_ops = &kobj_sysfs_ops,
};
/*动态 kobj的释放函数*/
static void dynamic_kobj_release(struct kobject *kobj)
{
kfree(kobj);
}
第八十六章 引入并完善 kobject_type 结构体
cpp
// 定义了 kobject 指针变量:mykobject03
struct kobject *mykobject03;
// 定义 kobject 的释放函数
static void dynamic_kobj_release(struct kobject *kobj)
{
printk("kobject: (%p): %s\n", kobj, __func__);
kfree(kobj);
}
// 定义了一个 kobj_type 结构体变量 mytype,用于描述 kobject 的类型。
struct kobj_type mytype = {
.release = dynamic_kobj_release,
};
// 模块的初始化函数
static int mykobj_init(void)
{
int ret;
// 创建 kobject 的第二种方法
// 1 使用 kzalloc 函数分配了一个 kobject 对象的内存
mykobject03 = kzalloc(sizeof(struct kobject), GFP_KERNEL);
// 2 初始化并添加到内核中,名为"mykobject03"。
ret = kobject_init_and_add(mykobject03, &mytype, NULL, "%s", "mykobject03");
return 0;
}
// 模块退出函数
static void mykobj_exit(void)
{
kobject_put(mykobject03);//释放kref引用计数
}
第八十七章 创建属性文件并实现读写功能
sysfs虚拟文件系统下,有 bus、class、devices目录,
分别包括 不同总线 bus下的设备,不同 class下的设备,和代表具体设备 device的子目录。
sysfs虚拟文件系统基于 kobject和 kset实现基本的设备管理机制。
在使用 kobject_init_and_add()初始化 kobj指针时,可以手动给定 kobj_type类型,
kobj_type类型有 release()成员、sysfs_ops结构体成员、属性attribute结构体数组成员,
sysfs_ops结构体成员有 show()和 store()成员,
attribute结构体有 属性名称name和 文件模式mode成员。
所谓创建属性文件,
就是完善
kobject结构体的
kobj_type结构体成员的
sysfs_ops结构体成员的
show()和 store()。
cpp
// 自定义的 kobject 结构体,包含一个 kobject 对象和两个整型值
struct mykobj
{
struct kobject kobj;
int value1;
int value2;
};
// 自定义的 kobject 释放函数
static void dynamic_kobj_release(struct kobject *kobj)
{
struct mykobj *mykobject01 = container_of(kobj, struct mykobj, kobj);
printk("kobject: (%p): %s\n", kobj, __func__);
kfree(mykobject01);
}
// 自定义的 attribute 对象 value1 和 value2
struct attribute value1 = {
.name = "value1",
.mode = 0666,
};
struct attribute value2 = {
.name = "value2",
.mode = 0666,
};
// 将 attribute 对象放入数组中
struct attribute *myattr[] = {
&value1,
&value2,
NULL,
};
// 自定义的 show 函数,用于读取属性值
ssize_t myshow(struct kobject *kobj, struct attribute *attr, char *buf)
{
ssize_t count;
struct mykobj *mykobject01 = container_of(kobj, struct mykobj, kobj);
if (strcmp(attr->name, "value1") == 0)
{
count = sprintf(buf, "%d\n", mykobject01->value1);
}
else if (strcmp(attr->name, "value2") == 0)
{
count = sprintf(buf, "%d\n", mykobject01->value2);
}
else
{
count = 0;
}
return count;
}
// 自定义的 store 函数,用于写入属性值
ssize_t mystore(struct kobject *kobj,
struct attribute *attr,
const char *buf,
size_t size)
{
/*获取完整的结构体*/
struct mykobj *mykobject01 = container_of(kobj, struct mykobj, kobj);
/*比较被操作的属性文件的名称*/
if (strcmp(attr->name, "value1") == 0)
{
sscanf(buf, "%d\n", &mykobject01->value1);//写入自定义的结构体的value1
}
else if (strcmp(attr->name, "value2") == 0)
{
sscanf(buf, "%d\n", &mykobject01->value2);写入自定义的结构体的value2
}
return size;
}
// 自定义的 sysfs_ops 结构体,包含 show 和 store 函数指针
struct sysfs_ops myops = {
.show = myshow,
.store = mystore,
};
/*包含释放函数、默认属性和 sysfs_ops*/
static struct kobj_type mytype = {
.release = dynamic_kobj_release, //填充 realease函数
.default_attrs = myattr, //填充 attribute结构体
.sysfs_ops = &myops, //填充sysfs_ops操作集
};
// 定义了 mykobj 结构体指针变量 mykobject01
struct mykobj *mykobject01;
// 模块的初始化函数
static int mykobj_init(void)
{
int ret;
// 分配并初始化 mykobject01
mykobject01 = kzalloc(sizeof(struct mykobj), GFP_KERNEL);
mykobject01->value1 = 1;
mykobject01->value2 = 1;
// 初始化并添加 mykobject01 到内核中,名为"mykobject01"
ret = kobject_init_and_add(&mykobject01->kobj, //kobj指针
&mytype, //kobj_type类型,
NULL, //父object指针
"mykobject01"); //kobj的名称
return 0;
}
// 模块的退出函数
static void mykobj_exit(void)
{
// 释放 mykobject01
kobject_put(&mykobject01->kobj);
}
module_init(mykobj_init); // 指定模块的初始化函数
module_exit(mykobj_exit); // 指定模块的退出函数
驱动加载之后,我们进入/sys/目录下,可以看到创建生成的 myobject01,
因为 每个kobject对象对应于/sys/目录下的一个文件夹。
kobject注册后对应/sys/目录下的文件夹
进到 myobject01 目录下,可以看到创建的属性文件 value1 和 value2。
kobj_type结构体的attribute结构体数组也对应属性文件夹
我们可以使用 echo 和 cat 命令对属性值进行写入和读取,
向属性文件进行写入和读取,其实是调用 kobj的 kobj_type的 show()和store()
第八十八章 优化属性文件读写函数
kobj_type的 attribute原填充方式。
cpp
// 自定义的 attribute 对象 value1 和 value2
struct attribute value1 = {
.name = "value1",
.mode = 0666,
};
struct attribute value2 = {
.name = "value2",
.mode = 0666,
};
// 将 attribute 对象放入数组中
struct attribute *myattr[] = {
&value1,
&value2,
NULL,
};
// 自定义的 sysfs_ops 结构体,包含 show 和 store 函数指针
struct sysfs_ops myops = {
.show = myshow,
.store = mystore,
};
/*包含释放函数、默认属性和 sysfs_ops*/
static struct kobj_type mytype = {
.release = dynamic_kobj_release, //填充 realease函数
.default_attrs = myattr, //填充 attribute结构体
.sysfs_ops = &myops, //填充sysfs_ops操作集
};
使用 __ATTR()宏定义声明并初始化 kobj_attribute对象。
cpp
// 定义 attribute 对象 value1 和 value2 假设已经完成了 自定义的show和store函数
struct kobj_attribute value1 = __ATTR(value1, //对象,声明的同时定义
0664, //权限
show_myvalue1, //show函数
store_myvalue1); //store函数
struct kobj_attribute value2 = __ATTR(value2, 0664, show_myvalue2, store_myvalue2);
// 将 attribute 对象放入数组中
struct attribute *myattr[] = {&value1.attr, &value2.attr, NULL, };
// 自定义的 sysfs_ops 结构体,包含 show 和 store 函数指针
struct sysfs_ops myops = {
.show = myshow,
.store = mystore,}; //!!!注意,这里绑定的函数才是读写属性文件时回调的
// 自定义的 kobj_type 结构体,包含释放函数、默认属性和 sysfs_ops
static struct kobj_type mytype = {
.release = dynamic_kobj_release,
.default_attrs = myattr,
.sysfs_ops = &myops, };
__ATTR宏是用于快速创建并初始化 kobj_attribute结构体的便捷方式。这个宏接受几个参数,包括属性的名称、权限(使用八进制表示法)、以及指向show和store回调函数的指针。
cpp
struct kobj_attribute {
struct attribute attr; //kobj_type的attribute结构体成员
ssize_t (*show)(struct kobject *kobj, struct kobj_attribute *attr, char *buf);
ssize_t (*store)(struct kobject *kobj, struct kobj_attribute *attr,
const char *buf, size_t count);
};
第八十九章 创建多个属性文件的简便方法
前面几章,我们通过为 kobj_type绑定 attribute数组,来创建属性文件。
但如果要创建大量属性文件,这种方法显然低效。
89.1 sysfs_create_group 函数
sysfs_create_group() 函数,用于在 sysfs 中创建一个组(group)。
组是一组相关的属性文件的集合,可将它们放在同一个目录下提供更好的组织性和可读性。
cpp
/*在sysfs文件系统中创建一个组*/
int sysfs_create_group(struct kobject *kobj, //指向包含目标组的 kobject 的指针。
const struct attribute_group *grp); //指向 attribute_group 结构体的指针,
//该结构体定义了组中的属性文件。
cpp
struct attribute_group {
const char *name; // 组名,用于sysfs中的目录名
const struct attribute **attrs; // 指向属性指针数组的指针,数组以NULL结尾
// 可选回调函数,用于判断属性是否可见
mode_t (*is_visible)(struct kobject *kobj, struct attribute *attr, int index);
};
下面展示使用 sysfs_create_group() 创建一个组并添加属性文件,
cpp
// 定义 attribute 对象 value1 和 value2
struct kobj_attribute value1 = __ATTR(value1, 0664, show_myvalue1, store_myvalue1);
struct kobj_attribute value2 = __ATTR(value2, 0664, show_myvalue2, store_myvalue2);
/*将 attribute对象添加进数组*/
struct attribute *attr[] = {
&value1.attr,
&value2.attr,
NULL, };
/*创建一个名为 myattr的组,并将 attribute添加进组中*/
const struct attribute_group my_attr_group = {
.name = "myattr",
.attrs = attr, };
// 模块的初始化函数
static int mykobj_init(void)
{
int ret;
// 创建并添加 kobject "mykobject01"
mykobject01 = kobject_create_and_add("mykobject01", NULL);
//将组添加进 kobj
ret = sysfs_create_group(mykobject01, &my_attr_group);
return ret;
}
// 模块的退出函数
static void mykobj_exit(void)
{
// 释放 kobject "mykobject01"
kobject_put(mykobject01);
}
第九十章 注册一个自己的总线
在 设备模型中,包含 总线、 设备、 驱动和 类四个概念。
90.1 bus_register()函数
我们进入开发板的/sys/bus 目录下,/sys/bus 是 Linux 系统中的一个目录,用于表示总线(bus)子系统的根目录。如果我们自己注册一个总线,会在此目录下显示。
/sys/bus目录 bus_register() 函数用于将一个自定义总线注册到 Linux 内核中。
cpp
/*注册一个总线到内核中*/
int bus_register(struct bus_type *bus);
bus_unregister() 函数用于取消注册一个已经注册的自定义总线。
cpp
/*取消注册一个自定义总线到内核*/
void bus_unregister(struct bus_type *bus);
cpp
/*匹配函数*/
int mybus_match(struct device *dev, struct device_driver *drv)
{
// 检查设备名称和驱动程序名称是否匹配
return (strcmp(dev_name(dev), drv->name) == 0);
}
/*探测函数*/
int mybus_probe(struct device *dev)
{
struct device_driver *drv = dev->driver;
if (drv->probe)
drv->probe(dev);
return 0;
}
struct bus_type mybus = {
.name = "mybus", // 总线的名称
.match = mybus_match, // 设备和驱动程序匹配的回调函数
.probe = mybus_probe, // 设备探测的回调函数
};
// 模块的初始化函数
static int bus_init(void)
{
int ret;
ret = bus_register(&mybus); // 注册总线
return 0;
}
// 模块退出函数
static void bus_exit(void)
{
bus_unregister(&mybus); // 取消注册总线
}
module_init(bus_init); // 指定模块的初始化函数
module_exit(bus_exit); // 指定模块的退出函数
驱动加载之后,我们进入/sys/bus 目录下,可以看到创建生成的总线 mybus,
注册的自定义总线文件
进到 mybus 目录下,可以看到创建的属性文件,
自定义总线的属性文件
第九十一章 在总线目录下创建属性文件
91.1 bus_create_file()函数
bus_create_file() 函数用于在总线的 sysfs 目录下创建一个属性文件。
cpp
int bus_create_file(struct bus_type *bus, //bus_type用来描述总线
struct kobject *kobj, //kobject内核成员
const struct attribute *attr);//属性,成员有name和mode
cpp
struct bus_attribute mybus_attr = {
/*attribute成员*/
.attr = {
.name = "value",
.mode = 0664,
},
.show = mybus_show, //show方法
//省略了store方法
};
/*总线下创建属性文件*/
ret = bus_create_file(&mybus, &mydevice.kobj, &mybus_attr.attr);
// 模块的初始化函数
static int bus_init(void)
{
int ret;
ret = bus_register(&mybus); // 注册总线
ret = bus_create_file(&mybus, &mybus_attr); // 在 sysfs 中创建属性文件
return 0;
}
上述示例代码创建了一个名为 "value" 的属性文件,并指定了访问权限为 0664。在创建属性文件时,还可指定其他属性的回调函数,如 .show、.store 等,实现对属性值的读取和写入操作。
驱动加载之后,我们进入/sys/bus 目录下,可以看到创建生成的总线 mybus,我们进到 mybus 目录下,可以看到创建属性文件 value。
总线下创建属性
使用 cat或者 echo对属性文件进行读写,可触发 show、store函数。
cat+属性文件,触发show函数
第九十二章 总线注册流程分析
92.1 bus_register 函数解析
开发板上电,我们进入到开发板的/sys/bus/mybus 目录下。
为什么在 sys/bus 目录下会生成 mybus 目录以及对应的
devices,drivers,drivers_autoprobe,drivers_probe,uevent 目录和属性呢?
bus_register()函数的主要步骤:
1、分配并初始化subsys_private结构体:
cpp
struct subsys_private {
struct kset subsys;
struct kset *devices_kset;
struct kset *drivers_kset;
// 其他成员...
};
priv = kzalloc(...);
priv->bus = bus; //将priv与当前总线关联
bus->p = priv; //让总线快速访问priv
2、初始化阻塞通知链表:
cpp
BLOCKING_INIT_NOTIFIER_HEAD(&priv->bus_notifier);
3、设置子系统名称:
cpp
kobject_set_name(&priv->subsys.kobj, "%s", bus->name);
4、设置子系统属性:
cpp
priv->subsys.kobj.kset = bus_kset; //设置kset
priv->subsys.kobj.ktype = &bus_ktype; //设置 bus_ktype
5、注册子系统的 kset:
cpp
kset_register(&priv->subsys);
6、创建并添加"devices"和"drivers"子目录的 kset
cpp
/*创建子设备的kset*/
priv->devices_kset = kset_create_and_add("devices", NULL, &priv->subsys.kobj);
/*创建子驱动的kset*/
priv->drivers_kset = kset_create_and_add("drivers", NULL, &priv->subsys.kobj);
7、初始化接口链表、互斥锁和设备/驱动的 klist
cpp
klist_init(&priv->klist_devices, klist_devices_get, klist_devices_put);
klist_init(&priv->klist_drivers, NULL, NULL);
8、添加驱动探测文件和总线属性组
cpp
add_probe_files(bus); //驱动探测文件
bus_add_groups(bus, bus->bus_groups);//总线属性组
第九十三章 platform 总线注册流程实例分析
在内核初始化过程中,platform_bus_init() 函数负责初始化平台总线。
清理早期平台设备:
early_platform_cleanup() 函数清空早期平台设备列表上的所有节点。
注册平台总线设备:
调用 device_register(&platform_bus) 将 platform_bus 结构体注册到设备子系统中。
注册平台总线类型:
调用 bus_register(&platform_bus_type) 将 platform_bus_type 结构体注册到总线子系统中。
platform_bus_type 结构体定义了平台总线的关键属性,包括:
cpp
struct bus_type platform_bus_type = {
.name = "platform",
.dev_attrs = platform_dev_attrs, // 设备属性,可能包含获取sys文件名等
.match = platform_match, // 匹配设备和驱动的函数
.uevent = platform_uevent, // 消息传递函数
.pm = &platform_dev_pm_ops, // 电源管理操作
// ... 可能还有其他成员,具体取决于内核版本和配置
};
电源管理操作(platform_dev_pm_ops):
这个结构体包含了与电源管理相关的操作函数,如挂起、恢复等。
这些函数在系统进入休眠或唤醒状态时会被调用,以管理平台上设备的电源状态。
注意,platform_bus_init()注册的 platform总线和 bus_init()注册的总线,有一些区别。
在Linux内核中,platform总线用于管理与硬件平台紧密相关的设备。
为了注册platform设备,首先需要注册一个platform bus设备作为桥梁。
这个bus设备通过device_register()函数添加到设备层次结构,并与platform总线关联。
这样,platform总线初始化时就能识别并管理platform设备。
第九十四章 在总线下注册设备实验
定义了一个名为 "mybus" 的总线,
并实现了总线的匹配回调函数 mybus_match 和设备探测回调函数 mybus_probe。
同时,还定义了一个名为 "value" 的属性文件,并实现了属性的显示回调函数 mybus_show。
cpp
/*总线模块代码*/
/*设备和驱动匹配函数*/
int mybus_match(struct device *dev, struct device_driver *drv)
{
// 检查设备名称和驱动程序名称是否匹配
return (strcmp(dev_name(dev), drv->name) == 0);
};
/*设备探测回调函数*/
int mybus_probe(struct device *dev)
{
// 获取与设备关联的驱动程序的指针
struct device_driver *drv = dev->driver;
// 检查驱动程序是否有probe函数
if (drv->probe) {
// 调用驱动程序的probe函数,并传递设备指针作为参数
drv->probe(dev);
}
// 返回0表示成功(在Linux内核中,0通常表示成功,非0值表示错误)
return 0;
}
struct bus_type mybus = {
.name = "mybus", // 总线的名称
.match = mybus_match, // 设备和驱动程序匹配的回调函数
.probe = mybus_probe, // 设备探测的回调函数
};
/*EXPORT_SYMBOL_GPL宏用于导出内核符号,使其对其他模块(这些模块也必须遵循GPL协议)可见。*/
EXPORT_SYMBOL_GPL(mybus);
ssize_t mybus_show(struct bus_type *bus, char *buf)
{
// 在 sysfs 中显示总线的值
return sprintf(buf, "%s\n", "mybus_show");
};
struct bus_attribute mybus_attr = {
.attr = {
.name = "value", // 属性的名称
.mode = 0664, // 属性的访问权限
},
.show = mybus_show, // 属性的 show 回调函数
};
// 模块的初始化函数
static int bus_init(void)
{
int ret;
ret = bus_register(&mybus); // 注册总线
ret = bus_create_file(&mybus, &mybus_attr); // 在 sysfs 中创建属性文件
return 0;
}
// 模块退出函数
static void bus_exit(void)
{
bus_remove_file(&mybus, &mybus_attr); // 从 sysfs 中移除属性文件
bus_unregister(&mybus); // 取消注册总线
}
我们编写驱动文件 device.c,在驱动中,Linux 内核中创建一个自定义设备并将其注册到自定义总线上。
cpp
/*设备模块代码*/
extern struct bus_type mybus;
void myrelease(struct device *dev)
{
printk("This is myrelease\n");
};
/*device结构体*/
struct device mydevice = {
.init_name = "mydevice", // 设备的初始化名称
/*指定所属总线,这里让设备属于我们自定义的总线*/
.bus = &mybus,
.release = myrelease, // 设备的释放回调函数
.devt = ((255 << 20 | 0)), // 设备号
};
// 模块的初始化函数
static int device_init(void)
{
int ret;
ret = device_register(&mydevice); // 注册设备
return 0;
}
// 模块退出函数
static void device_exit(void)
{
device_unregister(&mydevice); // 取消注册设备
}
module_init(device_init); // 指定模块的初始化函数
module_exit(device_exit); // 指定模块的退出函数
总线驱模块编译加载、设备模块编译加载
进入/sys/devices 目录下,如下图所示,有注册生成的设备。
第九十五章 设备注册流程分析
95.1 device_register 函数分析
device_register() 函数用于在内核中注册设备,
cpp
/*在内核中注册设备*/
int device_register(struct device *dev)
{
device_initialize(dev); //初始化设备对象
return device_add(dev); //将设备注册到内核
}
EXPORT_SYMBOL_GPL(device_register);//使符号对其他模块可见
95.1.1 device_initialize 函数
device_initialize() 函数用于初始化设备对象。
设置设备对象所属的kset。
初始化kobject,使用特定的ktype。
初始化多个链表头为空。
初始化互斥锁并设置验证类别。
初始化自旋锁和设备资源链表头。
初始化电源管理信息pm。
设置设备节点为未指定。
设置设备连接状态为无驱动程序。
cpp
void device_initialize(struct device *dev)
{
// 设置设备对象所属的kset为devices_kset
dev->kobj.kset = devices_kset;
// 初始化设备对象的kobject,使用device_ktype
kobject_init(&dev->kobj, &device_ktype);
// 初始化设备对象的多个链表头为空链表
INIT_LIST_HEAD(&dev->dma_pools);
#ifdef CONFIG_GENERIC_MSI_IRQ
INIT_LIST_HEAD(&dev->msi_list);
#endif
INIT_LIST_HEAD(&dev->links.consumers);
INIT_LIST_HEAD(&dev->links.suppliers);
INIT_LIST_HEAD(&dev->links.needs_suppliers);
INIT_LIST_HEAD(&dev->links.defer_hook);
// 初始化设备对象的互斥锁
mutex_init(&dev->mutex);
// 设置互斥锁的验证类别为无效
lockdep_set_novalidate_class(&dev->mutex);
// 初始化设备对象的自旋锁
spin_lock_init(&dev->devres_lock);
// 初始化设备资源的链表头为空链表
INIT_LIST_HEAD(&dev->devres_head);
// 初始化设备对象的电源管理相关信息
device_pm_init(dev);
// 设置设备节点的值为-1,表示没有指定设备节点
set_dev_node(dev, -1);
// 设置设备对象的连接状态为没有驱动程序
dev->links.status = DL_DEV_NO_DRIVER;
}
95.1.2 device_add 函数
device_add() 函数负责将设备添加到系统中,并处理相关的初始化和注册工作。
每个步骤都对应着设备添加过程中的一个重要环节,
包括
设备命名、父设备设置、内核对象添加、设备文件和属性创建、总线添加、电源管理添加、设备号处理、事件通知、设备链接处理、总线探测以及将设备添加到父设备和设备类的列表中。
cpp
/*将设备添加到系统*/
int device_add(struct device *dev)
{
struct device *parent;
struct kobject *kobj;
int error;
// 获取设备引用
dev = get_device(dev);
if (!dev)
return -EINVAL; // 或其他错误处理
// 初始化设备私有数据(如果需要)
if (!dev->p) {
error = device_private_init(dev);
if (error)
return error;
}
// 设置设备名称
if (dev->init_name) {
dev_set_name(dev, "%s", dev->init_name);
dev->init_name = NULL;
} else if (dev->bus && dev->bus->dev_name) {
dev_set_name(dev, "%s%u", dev->bus->dev_name, dev->id);
}
if (!dev_name(dev))
return -EINVAL;
// 调试信息
pr_debug("device: '%s': %s\n", dev_name(dev), __func__);
// 获取父设备并设置父kobject
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;
// 设置设备NUMA节点(如果未设置)
if (parent && (dev_to_node(dev) == NUMA_NO_NODE))
set_dev_node(dev, dev_to_node(parent));
// 向内核对象层次结构添加设备kobject
error = kobject_add(&dev->kobj, dev->kobj.parent, NULL);
if (error)
goto Error;
// 平台通知(如果可用)
if (platform_notify)
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);
// 处理设备号(devt)相关操作(如创建sys设备节点等)
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);
}
// 通知设备添加事件
if (dev->bus)
blocking_notifier_call_chain(&dev->bus->p->bus_notifier, BUS_NOTIFY_ADD_DEVICE, dev);
// 发送KOBJ_ADD事件
kobject_uevent(&dev->kobj, KOBJ_ADD);
// 处理设备链接(如fw_devlink_link_device)
// ...(这部分可能涉及更复杂的逻辑,为简化而省略)
// 探测总线中的设备
bus_probe_device(dev);
// 将设备添加到父设备的子设备列表(如果适用)
if (parent)
klist_add_tail(&dev->p->knode_parent, &parent->p->klist_children);
// 将设备添加到设备类的列表(如果适用)
if (dev->class) {
// ...(添加设备到类列表和通知接口的代码,为简化而省略)
}
done:
// 释放设备引用
put_device(dev);
return error;
// 错误处理标签和代码(为简化而省略具体实现)
// ...
// SysEntryError, DevAttrError, DPMError, BusError, AttrsError, SymlinkError, attrError, Error, parent_error
}