文章目录
1.什么是BUS?
在 Linux 内核中,总线(Bus)是用于描述系统中各类设备之间连接关系的一个抽象概念。总线不仅仅指物理上的数据传输路径,也可以是逻辑上的连接结构。Linux 内核通过总线来组织和管理系统中的设备,并提供统一的接口和框架,以便设备驱动程序可以与硬件进行交互。
1.1总线的主要概念
- 总线类型(Bus Type):
- 总线类型表示一种特定的总线协议或标准,比如 PCI、I2C、USB、SPI 等。每种总线类型都由内核中的一个
struct bus_type
结构体表示,它定义了该总线类型的操作和行为,如设备注册、探测等。
- 总线类型表示一种特定的总线协议或标准,比如 PCI、I2C、USB、SPI 等。每种总线类型都由内核中的一个
- 设备(Device):
- 设备是连接在总线上的硬件组件,通常表示一个物理设备。Linux 使用
struct device
来表示系统中的每一个设备,该结构体包含了设备的各种信息,比如设备名称、状态、父设备等。
- 设备是连接在总线上的硬件组件,通常表示一个物理设备。Linux 使用
- 驱动程序(Driver):
- 驱动程序是用于控制设备的代码,通常是与某一特定总线上的设备相对应的。Linux 使用
struct device_driver
表示一个驱动程序,它包括了驱动程序与设备交互的各种函数指针,如探测函数(probe)、移除函数(remove)等。
- 驱动程序是用于控制设备的代码,通常是与某一特定总线上的设备相对应的。Linux 使用
- 设备树(Device Tree):
- 设备树是描述硬件布局的一个数据结构,通常用于嵌入式系统中。设备树文件通过描述硬件资源和连接关系,帮助内核在启动时正确地识别和初始化硬件设备。
1.2总线的操作
- 设备注册与匹配: 当一个设备连接到系统时,它会被注册到与之对应的总线类型中。然后,内核会通过设备与驱动程序之间的匹配机制,将设备与相应的驱动程序关联起来。匹配过程基于设备 ID、兼容性字符串或其他总线特定的信息。
- 探测与初始化: 一旦设备与驱动程序匹配成功,总线的探测函数会被调用,驱动程序将尝试初始化设备。如果探测成功,设备就可以被操作了。
- 电源管理: 总线通常还负责设备的电源管理,比如设备的挂起和恢复操作。
1.3总线的实现
在 Linux 内核中,每种总线类型通常都有一个独立的子系统。这个子系统负责实现该总线类型的特定操作,如设备的探测、注册、驱动程序的加载和卸载等。
示例代码:
#include <linux/module.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/kernel.h>
#include <linux/kobject.h>
#include <linux/sysfs.h>
#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);
int (*uevent)(struct device *dev, struct kobj_uevent_env *env);
int (*probe)(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);
const struct dev_pm_ops *pm;
const struct iommu_ops *iommu_ops;
struct subsys_private *p;
struct lock_class_key lock_key;
};
*/
//进行名称匹配
static int my_bus_match (struct device *dev, struct device_driver *drv){
printk("match success\n");
return (strcmp(dev_name(dev),drv->name)==0);
}
static int my_bus_probe(struct device *dev){
printk("new device probe success\n");
struct device_driver *drv = dev->driver;
if(drv->probe){
drv->probe(dev);
}
return 0;
}
//注册总线的关键结构体
struct bus_type my_bus_type = {
.name = "my_bus",
.match = my_bus_match,
.probe = my_bus_probe,
};
static int __init my_bus_init(void)
{
int ret = bus_register(&my_bus_type);
printk("bus_register success!\n");
return ret;
}
static void __exit my_bus_exit(void)
{
bus_unregister(&my_bus_type);
}
module_init(my_bus_init);
module_exit(my_bus_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("A simple example of bus registration");
2.创建总线关键结构体解析
2.1注册总线到系统
使用bus_register
API 来完成注册
函数原型:
extern int __must_check bus_register(struct bus_type *bus);
传入的参数为:struct bus_type *bus
2.2 struct bus_type *bus
解析
结构体构成:
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);
int (*uevent)(struct device *dev, struct kobj_uevent_env *env);
int (*probe)(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);
const struct dev_pm_ops *pm;
const struct iommu_ops *iommu_ops;
struct subsys_private *p;
struct lock_class_key lock_key;
};
初学驱动,只需关注几个关键参数:
- name:总线名称
- match:匹配函数
- probe:探测函数
- remove:移除函数
我们只需实现对应的函数即可
1. my_bus_match
函数
static int my_bus_match(struct device *dev, struct device_driver *drv) {
printk("match success\n");
return (strcmp(dev_name(dev), drv->name) == 0);
}
- 函数功能:
my_bus_match
函数用于判断一个设备(dev
)和一个驱动程序(drv
)是否匹配。如果匹配成功,则返回非零值;否则返回零。 - 参数:
struct device *dev
: 指向设备对象的指针,表示当前总线上的某个设备。struct device_driver *drv
: 指向驱动程序对象的指针,表示当前总线上的某个驱动程序。
- 工作流程:
dev_name(dev)
: 这是一个内核函数,用于获取设备的名称,实际上是设备的dev_name
字段。drv->name
: 这是驱动程序结构体中的name
字段,通常由驱动程序的开发者在定义驱动程序时指定。strcmp(dev_name(dev), drv->name) == 0
: 使用strcmp
函数对设备名称和驱动程序名称进行比较。如果两者相等,strcmp
返回 0,表示匹配成功。- 如果设备名称与驱动程序名称匹配,
my_bus_match
返回 1,否则返回 0。
printk("match success\n");
:- 这是一个内核中的打印函数,用于输出调试信息到内核日志中。当函数执行时,这条消息会被打印出来,表明匹配操作正在进行。
2. my_bus_probe
函数
static int my_bus_probe(struct device *dev) {
printk("new device probe success\n");
struct device_driver *drv = dev->driver;
if (drv->probe) {
drv->probe(dev);
}
return 0;
}
- 函数功能:
my_bus_probe
函数在设备与驱动程序匹配成功后被调用,负责对设备进行初始化。 - 参数:
struct device *dev
: 指向设备对象的指针,表示当前要被初始化的设备。
- 工作流程:
struct device_driver *drv = dev->driver;
: 通过设备对象的driver
字段,获取与该设备关联的驱动程序对象。if (drv->probe)
: 检查驱动程序对象中是否定义了probe
函数。probe
函数通常由驱动程序开发者实现,用于初始化设备的硬件资源。drv->probe(dev);
: 如果probe
函数存在,则调用它,并将当前设备对象传递给它。通过这个步骤,驱动程序可以对设备进行实际的硬件初始化操作。
- 返回值: 该函数总是返回 0,表示探测操作成功。
3.实验结果分析
当加载注册总线模块到系统之后
可以看出在 /sys/bus
下出现了注册的新总线名称为 my_bus
进入my_bus 目录,可以看到自动生成的目录和文件
devices
目录: 列出所有连接到该总线的设备。
drivers
目录: 列出所有注册到该总线的驱动程序。
drivers_autoprobe
文件: 控制自动探测新设备。
drivers_probe
文件: 手动触发设备探测过程。
uevent
文件: 生成和管理发送到用户空间的事件通知。
以下是这些文件和目录的详细分析:
1. devices
目录
-
作用:
- 该目录包含当前已注册在此总线上的所有设备的符号链接。每个符号链接指向
/sys/devices/
中相应设备的目录。 - 这些链接使得用户可以通过
/sys/bus/<bus_name>/devices/
轻松访问挂载在该总线上的所有设备。
- 该目录包含当前已注册在此总线上的所有设备的符号链接。每个符号链接指向
-
结构:
- 对于每个连接到该总线的设备,该目录中会生成一个符号链接,如
<device_name>
,指向/sys/devices/
下的实际设备目录。
- 对于每个连接到该总线的设备,该目录中会生成一个符号链接,如
2. drivers
目录
-
作用:
- 该目录包含所有注册到此总线类型的驱动程序的符号链接。每个符号链接指向
/sys/bus/<bus_name>/drivers/
中相应驱动程序的目录。 - 驱动程序通过这些链接来管理它们控制的设备。
- 该目录包含所有注册到此总线类型的驱动程序的符号链接。每个符号链接指向
-
结构:
- 该目录下的每个子目录或符号链接通常以驱动程序名称命名,代表一个特定的驱动程序。
- 驱动程序目录内可能包含控制和状态文件,例如
bind
、unbind
,用于手动绑定和解绑设备。
3. drivers_autoprobe
文件
-
作用:
- 这是一个与自动探测(autoprobe)相关的开关。它决定了是否自动探测并绑定新连接到总线的设备。
- 当内核检测到新设备时,如果
drivers_autoprobe
处于启用状态(通常为 1),则会自动调用驱动程序的probe
函数对设备进行初始化。
-
操作:
- 可以通过
echo 1 > drivers_autoprobe
启用自动探测。 - 可以通过
echo 0 > drivers_autoprobe
禁用自动探测。
- 可以通过
4. drivers_probe
文件
-
作用:
- 这是一个手动触发设备探测(probe)过程的接口。写入该文件可以手动触发驱动程序对未被探测的设备进行探测。
-
操作:
- 通过向该文件写入设备的名称,系统将尝试为该设备触发驱动程序的
probe
函数。 - 例如,
echo "<device_name>" > drivers_probe
将手动触发对名为<device_name>
的设备的探测。
- 通过向该文件写入设备的名称,系统将尝试为该设备触发驱动程序的
5. uevent
文件
-
作用:
uevent
文件用于向用户空间发送与该总线相关的uevent
,它们通常用于通知udev
或其他用户空间工具进行设备的自动配置。- 这些事件包括设备的添加、移除、绑定、解绑等操作。
-
操作:
- 可以手动向该文件写入特定事件,触发用户空间的响应。
- 常见用法:写入特定字符串以生成自定义
uevent
,例如echo "add" > uevent
,会通知用户空间设备已添加。
4.在总线目录下创建属性文件
与kobject 创建属性文件操作基本一致:传送门—Linux驱动开发—设备模型框架 kobject创建属性文件-CSDN博客
在 Linux 内核中,通过 kobject
和 bus
创建属性文件的方式非常相似。这是因为这两者都基于 sysfs
接口,而 sysfs
是通过 kobject
来管理文件和目录的。
基础结构:
- 无论是
kobject
还是bus
,属性文件的创建最终都是通过内核提供的sysfs
文件系统实现的。sysfs
是一个内核对象模型(Kobject-based)文件系统,用于暴露内核中的信息和配置接口给用户空间。
属性文件的定义:
- 无论使用
kobject
还是bus
,属性文件的定义方式类似,都是通过定义show
和store
函数,然后通过相应的宏(如__ATTR
、BUS_ATTR
)将它们绑定到属性文件上。
创建和移除属性:
sysfs
中的属性文件创建和移除过程本质上都涉及对kobject
及其相关数据结构的操作。在bus
中创建属性文件时,其实是通过底层的kobject
机制来完成的。
示例代码:
#include <linux/module.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/kernel.h>
#include <linux/kobject.h>
#include <linux/sysfs.h>
#include <linux/device.h>
//进行名称匹配
static int my_bus_match (struct device *dev, struct device_driver *drv){
printk("match success\n");
return (strcmp(dev_name(dev),drv->name)==0);
}
static int my_bus_probe(struct device *dev){
printk("new device probe success\n");
struct device_driver *drv = dev->driver;
if(drv->probe){
drv->probe(dev);
}
return 0;
}
//注册总线的关键结构体
struct bus_type my_bus_type = {
.name = "my_bus",
.match = my_bus_match,
.probe = my_bus_probe,
};
// 在总线目录下创建属性文件 与 kobject 创建属性文件 类似
static ssize_t my_bus_show(struct bus_type *bus, char *buf) {
return sprintf(buf, "Hello from the bus attribute!\n");
}
static ssize_t my_bus_store(struct bus_type *bus, const char *buf, size_t count) {
pr_info("Received from userspace: %s\n", buf);
return count;
}
static BUS_ATTR(my_bus_attr, 0664, my_bus_show, my_bus_store);
static int __init my_bus_init(void)
{
int ret;
// 注册总线
ret = bus_register(&my_bus_type);
if (ret)
return ret;
// 添加属性文件到总线
ret = bus_create_file(&my_bus_type, &bus_attr_my_bus_attr);
if (ret)
bus_unregister(&my_bus_type);
return ret;
}
static void __exit my_bus_exit(void)
{
// 移除属性文件
bus_remove_file(&my_bus_type, &bus_attr_my_bus_attr);
bus_unregister(&my_bus_type);
}
module_init(my_bus_init);
module_exit(my_bus_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("A simple example of bus registration");
结果如下: