Linux设备模型9(基于Linux6.6)---bus介绍
一、 bus概述
在Linux设备模型中,bus(总线)是一类特殊的设备,它是连接处理器和其它设备之间的通道(channel)。为了方便设备模型的实现,内核规定,系统中的每个设备都要连接在一个Bus上,这个Bus可以是一个内部Bus、虚拟Bus或者Platform Bus。
内核通过struct bus_type结构,抽象bus,它是在include/linux/device.h中定义的。本文会围绕该结构,描述Linux内核中bus的功能,以及相关的实现逻辑。最后,会简单的介绍一些标准的bus(如platform),介绍它们的用途、它们的使用场景。
在 Linux 内核中,bus(总线)是设备模型中的一个关键概念,用于表示和管理硬件设备之间的通信和数据传输机制。总线不仅是硬件组件之间的连接通道,也是内核用来组织和访问硬件设备的重要工具。通过总线,设备可以在不同的硬件架构、驱动程序和设备之间建立连接。Linux 内核支持多种类型的总线,常见的如 PCI 总线、USB 总线、I2C 总线等。
1. Linux 内核中的总线模型
在 Linux 内核中,设备的管理遵循设备模型(Device Model),并将设备分为不同的层次结构,其中总线是介于设备和驱动程序之间的重要部分。总线不仅提供了设备之间的物理连接,也决定了设备的寻址、数据交换和驱动程序的加载。
每个总线在内核中由一个 struct bus_type 结构体表示,bus_type 定义了与总线相关的操作、属性和设备管理机制。
总线的关键作用
- 设备组织:总线将具有相同硬件连接特性或功能的设备组织在一起,形成一个设备层级结构。
- 设备发现与管理:总线管理设备的注册、注销以及设备之间的通信。它还定义了如何查找和控制连接到该总线的设备。
- 驱动程序绑定:总线负责将合适的设备与驱动程序进行匹配和绑定。当一个设备被添加到总线上时,内核会搜索适合该设备的驱动程序并加载。
2. bus_type 结构
bus_type 是内核用来表示总线类型的结构体。每种硬件总线类型(如 PCI、USB、I2C)都会有一个与之对应的 bus_type 结构体。在 Linux 内核中,总线类型有很多种,每个总线类型都可以支持多个设备。
3. 总线的类型
Linux 内核支持多种类型的总线,每种总线都有不同的设备和驱动程序模型。以下是一些常见的总线类型:
3.1 PCI 总线
PCI(Peripheral Component Interconnect)总线是连接计算机中各种硬件设备(如网卡、显卡、存储控制器等)的一种标准总线。Linux 内核通过 pci_bus_type 结构来管理 PCI 总线及其设备。
- 总线管理:PCI 总线管理 PCI 设备的注册、驱动程序绑定以及与设备的通信。
- 设备管理:通过
pci_device和pci_driver,Linux 内核能够控制 PCI 设备的操作。
3.2 USB 总线
USB(Universal Serial Bus)总线是用于连接外部设备(如键盘、鼠标、打印机、存储设备等)的常见标准。Linux 内核通过 usb_bus_type 结构管理 USB 设备。
- 设备热插拔:USB 总线的一个重要特点是支持设备的动态插拔。内核会监视 USB 总线,自动发现连接的 USB 设备并加载相应的驱动程序。
3.3 I2C 总线
I2C(Inter-Integrated Circuit)是一种串行总线,用于连接嵌入式系统中的低速设备(如传感器、时钟、EEPROM 等)。Linux 内核通过 i2c_bus_type 结构来管理 I2C 总线及其设备。
- 多主机支持:I2C 总线可以支持多个主设备和从设备的通信。
- 简化的硬件连接:I2C 总线通常使用两根线(数据线 SDA 和时钟线 SCL)进行设备连接。
3.4 SPI 总线
SPI(Serial Peripheral Interface)是一种同步串行通信协议,常用于嵌入式系统中连接高速度设备(如 Flash 存储器、ADC、DAC 等)。与 I2C 不同,SPI 是全双工的。
- 设备连接:SPI 总线通过主设备和从设备进行通信。
- 速度与带宽:SPI 提供更高的数据传输速率和带宽,适合用于高速设备。
4. 设备与总线的关系
设备与总线之间的关系是层次化的。设备通常会附加到一个特定的总线上,而驱动程序则通过该总线与设备进行通信。总线类型决定了设备和驱动程序的匹配方式。
- 设备注册:当一个设备通过某种总线(如 PCI 总线)被发现时,设备会被注册到总线上。
- 驱动程序绑定:总线负责将合适的驱动程序与设备进行匹配。如果设备需要某种特定的驱动程序,内核会通过总线匹配机制自动加载该驱动程序。
5. 总线与驱动程序的匹配
在 Linux 中,总线负责设备与驱动程序的绑定。每个总线类型都有一个 match 函数,用于根据设备的类型和属性来选择合适的驱动程序。例如:
- 对于 PCI 总线,当新的 PCI 设备被发现时,
pci_driver的probe函数会被调用,允许驱动程序初始化该设备。 - 对于 USB 总线,
usb_driver的probe函数也会在新设备连接时被触发。
总线提供了与驱动程序匹配的机制,通常通过设备 ID、设备属性或总线类型来进行。
6. 热插拔支持
一些总线(如 USB、PCI)支持热插拔,这意味着设备可以在系统运行时动态地添加或移除。总线会监视设备状态并通知驱动程序进行相应的处理。这种功能对服务器和嵌入式系统尤为重要,能提高系统的灵活性和可维护性。
二、bus功能说明
2.1、bus_type
描述功能前,先介绍一下该模块的一些核心数据结构,对bus模块而言,核心数据结构就是struct bus_type,另外,还有一个sub system相关的结构,会一并说明。
include/linux/device/bus.h
struct bus_type {
const char *name;
const char *dev_name;
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)(const struct device *dev, struct kobj_uevent_env *env);
int (*probe)(struct device *dev);
void (*sync_state)(struct device *dev);
void (*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);
int (*dma_configure)(struct device *dev);
void (*dma_cleanup)(struct device *dev);
const struct dev_pm_ops *pm;
const struct iommu_ops *iommu_ops;
bool need_parent_lock;
};
说明:
name,该bus的名称,会在sysfs中以目录的形式存在,如platform bus在sysfs中表现为"/sys/bus/platform”。
dev_name,该名称和下面讲到的struct device结构中的init_name有关。对有些设备而言,允许将设备的名字留空。这样当设备注册到内核后,设备模型的核心逻辑就会用"bus->dev_name+device ID”的形式,为这样的设备生成一个名称。
dev_attrs,被下边的groups取代
bus_groups、dev_groups、drv_groups,一些默认的attribute,可以在bus、device或者device_driver添加到内核时,自动为它们添加相应的attribute。
dev_root,dev_root设备为bus的默认父设备(Default device to use as the parent),但在内核实际实现中,和一个叫sub system的功能有关。
match,一个由具体的bus driver实现的回调函数。当任何属于该Bus的device或者device_driver添加到内核时,内核都会调用该接口,如果新加的device或device_driver匹配上了彼此的话,该接口要返回非零值,此时Bus模块的核心逻辑就会执行后续的处理。
uevent,一个由具体的bus driver实现的回调函数。当任何属于该Bus的device,发生添加、移除或者其它动作时,Bus模块的核心逻辑就会调用该接口,以便bus driver能够修改环境变量。
probe、remove,这两个回调函数,和device_driver中的非常类似,但它们的存在是非常有意义的。可以想象一下,如果需要probe(其实就是初始化)指定的device话,需要保证该device所在的bus是被初始化过、确保能正确工作的。这就要就在执行device_driver的probe前,先执行它的bus的probe。remove的过程相反。
并不是所有的bus都需要probe和remove接口的,因为对有些bus来说(例如platform bus),它本身就是一个虚拟的总线,无所谓初始化,直接就能使用,因此这些bus的driver就可以将这两个回调函数留空。
shutdown、suspend、resume,和probe、remove的原理类似,电源管理相关的实现。
online、offline,和属于这个总线设备的online属性相关。
pm,电源管理相关的逻辑。
iommu_ops,总线的IOMMU相关操作,IOMMU与MMU功能类似,可以给设备一个内核空间的地址(或叫总线地址),而不限于可以直接访问的常规内存区域。
2.2、subsys_private
什么是子系统?无论是bus,还是class,还是一些虚拟的子系统,它都构成了一个“子系统(sub-system)”,该子系统会包含形形色色的device或device_driver,就像一个独立的王国一样,存在于内核中。而这些子系统的表现形式,就是/sys/bus(或/sys/class,或其它)目录下面的子目录,每一个子目录,都是一个子系统(如/sys/bus/spi/)。从子系统的角度看bus和后面的class很类似,它们都用subsys_private表示子系统。
drivers/base/base.h
struct subsys_private {
struct kset subsys;
struct kset *devices_kset;
struct list_head interfaces;
struct mutex mutex;
struct kset *drivers_kset;
struct klist klist_devices;
struct klist klist_drivers;
struct blocking_notifier_head bus_notifier;
unsigned int drivers_autoprobe:1;
const struct bus_type *bus;
struct device *dev_root;
struct kset glue_dirs;
const struct class *class;
struct lock_class_key lock_key;
};
subsys、devices_kset、drivers_kset是三个kset。其中subsys,代表了本bus(如/sys/bus/spi),它下面可以包含其它的kset或者其它的kobject;devices_kset和drivers_kset则是bus下面的两个kset(如/sys/bus/spi/devices和/sys/bus/spi/drivers),分别包括本bus下所有的device和device_driver。bus_type与kobject的关系通过subsys成员体现。通过宏to_subsys_private(obj)可以看出。
interface,用于保存该bus下所有的interface。interface抽象了此类子系统的专有功能。
klist_devices、klist_drivers,分别保存了本bus下所有的device和device_driver的指针,以方便查找。
drivers_autoprobe,用于控制该bus下的drivers或者device是否自动probe。
bus、class,分别保存上层的bus或者class指针。
subsys_private 是一种用于特定子系统的私有数据结构或字段,它通常用来存储与该子系统特定实例相关的数据或状态。具体来说,它通常作为一个内部字段出现在内核的某些结构体中,用来关联或存储与该子系统相关的私有数据。这个字段的名字和用途可能会根据具体的子系统或实现而有所不同,但其核心作用是为每个子系统或每个子系统的实例提供一个私有的存储空间。
在内核中,很多子系统(如文件系统、网络子系统、设备驱动等)都需要保存一些私有的上下文数据。为了避免不同子系统之间的数据冲突,这些私有数据往往是与每个子系统实例关联的,并且通常不会被外部直接访问。
subsys_private 就是用于存储这些私有数据的一种方式。它通常出现在某些结构体中,如 struct subsystem 或其他特定子系统的结构体,作为一个指针或指向私有数据的字段。
无论是bus,还是class,它都构成了一个“子系统(sub-system)”,该子系统会包含形形色色的device或device_driver,就像一个独立的王国一样,存在于内核中。而这些子系统的表现形式,就是/sys/bus(或/sys/class,或其它)目录下面的子目录,每一个子目录,都是一个子系统(如/sys/bus/spi/)。

drivers/base/bus.c
/**
* bus_register - register a driver-core subsystem
* @bus: bus to register
*
* Once we have that, we register the bus with the kobject
* infrastructure, then register the children subsystems it has:
* the devices and drivers that belong to the subsystem.
*/
int bus_register(const struct bus_type *bus)
{
int retval;
struct subsys_private *priv;
struct kobject *bus_kobj;
struct lock_class_key *key;
/* 分配子系统 */
priv = kzalloc(sizeof(struct subsys_private), GFP_KERNEL);
if (!priv)
return -ENOMEM;
priv->bus = bus;
bus->p = priv;
BLOCKING_INIT_NOTIFIER_HEAD(&priv->bus_notifier);
retval = kobject_set_name(&priv->subsys.kobj, "%s", bus->name);
if (retval)
goto out;
priv->subsys.kobj.kset = bus_kset; /* 绑定内核再初始化时就位所有的bus统一申请的kset */
priv->subsys.kobj.ktype = &bus_ktype; /* 绑定ktype */
priv->drivers_autoprobe = 1;
retval = kset_register(&priv->subsys); /* 注册kset,即在bus目录下就出现了对应目录 */
if (retval)
goto out;
retval = bus_create_file(bus, &bus_attr_uevent); /** /
if (retval)
goto bus_uevent_fail;
/* bus目录下创建devices和drivers目录,用来将来存放绑定该总线的驱动 */
priv->devices_kset = kset_create_and_add("devices", NULL,
&priv->subsys.kobj);
if (!priv->devices_kset) {
retval = -ENOMEM;
goto bus_devices_fail;
}
priv->drivers_kset = kset_create_and_add("drivers", NULL,
&priv->subsys.kobj);
if (!priv->drivers_kset) {
retval = -ENOMEM;
goto bus_drivers_fail;
}
/* 初始化 */
INIT_LIST_HEAD(&priv->interfaces);
__mutex_init(&priv->mutex, "subsys mutex", key);
klist_init(&priv->klist_devices, klist_devices_get, klist_devices_put);
klist_init(&priv->klist_drivers, NULL, NULL);
/* 总线的最重要的目的是匹配device和driver,用的就是probe机制 */
retval = add_probe_files(bus);
if (retval)
goto bus_probe_files_fail;
/* 总线可以属于其它总线的组里面 */
retval = bus_add_groups(bus, bus->bus_groups);
if (retval)
goto bus_groups_fail;
pr_debug("bus: '%s': registered\n", bus->name);
return 0;
bus_groups_fail:
remove_probe_files(bus);
bus_probe_files_fail:
kset_unregister(priv->drivers_kset);
bus_drivers_fail:
kset_unregister(priv->devices_kset);
bus_devices_fail:
bus_remove_file(bus, &bus_attr_uevent);
bus_uevent_fail:
kset_unregister(&priv->subsys);
out:
kfree(priv);
return retval;
}
EXPORT_SYMBOL_GPL(bus_register);
总线最主要的功能是匹配绑定到它下面的device和driver,上面的其它都是创建sys文件系统的关联性的东西。
2.3、bus_type,device和device_driver与kobject的关系
从device和device_driver的角度看:

device和device_driver直接继承kobject,bus_type通过subsys_private与kobject发生联系。图中kset和kobject的关系是从kset与包含在它里边的kobject的角度看的,与前面kset与kobjcet的关系角度不同。由此可见,总线、设备和驱动是建立在kobject基础上的,借由kobject和kset建立层次关系。
2.4、bus_type,device、device_driver的关系

一个device_driver可以支持多个device。bus_type通过subsys_private与device_driver和device发生关联。
2.5、bus_type、device、device_driver与sysfs的关系
总线,设备和设备驱动与sysfs的对应关系相对简单直观。
|
核心数据结构或成员 |
sysfs对应表现形式 |
|
bus_type.name |
/sys/bus下的目录名 |
|
bus_type.dev_name |
设备委托给总线给其一个名字,如果设备没有起名,与device_id一起作为/sys/devices下的目录名 |
|
device.name |
/sys/devices下的目录名 |
|
driver.name |
/sys/devices下的目录名 |
三、实例分析
3.1、 Linux 中的 Bus 实例分析
在 Linux 内核中,每个总线通过 bus_type 结构体来进行注册,并管理通过该总线连接的设备。接下来我们通过几个具体的例子来分析常见的总线类型。
PCI 总线
PCI 总线是一种非常常见的总线类型,它用于连接计算机中的多种硬件设备,如显卡、网卡、声卡等。在 Linux 中,PCI 总线由 pci_bus_type 结构体表示。
pci_bus_type 结构体
extern struct bus_type pci_bus_type;
pci_bus_type 结构体是描述 PCI 总线的核心数据结构。它定义了与 PCI 总线相关的操作和事件,例如设备的探测、驱动绑定等。
PCI 总线的设备注册
static int pci_device_probe(struct device *dev)
{
struct pci_dev *pdev = to_pci_dev(dev);
// 初始化和设置 PCI 设备
return 0;
}
static const struct dev_pm_ops pci_dev_pm_ops = {
.suspend = pci_device_suspend,
.resume = pci_device_resume,
};
struct pci_driver my_pci_driver = {
.name = "my_pci_driver",
.id_table = my_pci_id_table,
.probe = pci_device_probe,
.driver = {
.pm = &pci_dev_pm_ops,
},
};
static int __init my_pci_driver_init(void)
{
return pci_register_driver(&my_pci_driver);
}
在上述代码中,pci_register_driver 函数用于将 PCI 设备驱动注册到内核中。驱动的 probe 函数负责对设备进行初始化和配置。
USB 总线
USB 总线是另一种广泛使用的总线类型,它连接了各种 USB 外设(如鼠标、键盘、U 盘、摄像头等)。在 Linux 中,USB 总线由 usb_bus_type 结构体表示。
usb_bus_type 结构体
extern struct bus_type usb_bus_type;
usb_bus_type 结构体描述了与 USB 设备交互的接口,包括 USB 设备的探测、驱动程序绑定等。
USB 总线设备注册
static int my_usb_probe(struct usb_interface *interface, const struct usb_device_id *id)
{
// 对 USB 设备进行初始化
return 0;
}
static struct usb_device_id my_usb_table[] = {
{ USB_DEVICE(0x1234, 0x5678) },
{ }
};
static struct usb_driver my_usb_driver = {
.name = "my_usb_driver",
.id_table = my_usb_table,
.probe = my_usb_probe,
};
static int __init my_usb_driver_init(void)
{
return usb_register(&my_usb_driver);
}
在这段代码中,usb_register 函数将 USB 设备驱动注册到内核中。my_usb_probe 函数负责初始化连接的 USB 设备。
I2C 总线
I2C 总线是一种串行总线,常用于低速设备(如传感器、EEPROM 等)的连接。Linux 内核提供了对 I2C 总线的支持,其中总线的管理由 i2c_bus_type 结构体完成。
i2c_bus_type 结构体
extern struct bus_type i2c_bus_type;
通过 i2c_bus_type,I2C 总线管理器可以注册、删除 I2C 设备,并对设备进行操作。
I2C 总线设备注册
static int my_i2c_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
// 初始化 I2C 设备
return 0;
}
static const struct i2c_device_id my_i2c_id_table[] = {
{ "my_i2c_device", 0 },
{ }
};
static struct i2c_driver my_i2c_driver = {
.driver = {
.name = "my_i2c_driver",
},
.probe = my_i2c_probe,
.id_table = my_i2c_id_table,
};
static int __init my_i2c_driver_init(void)
{
return i2c_add_driver(&my_i2c_driver);
}
3.2、总线的生命周期
总线的生命周期包括以下几个步骤:
- 总线类型注册:通过
bus_type结构体,内核注册一个新的总线类型。 - 设备注册:设备通过
device_register注册到总线。 - 驱动注册:设备驱动通过
driver_register注册到内核。 - 设备和驱动匹配:内核根据设备与驱动的匹配表自动将设备和驱动绑定。
- 设备的操作:驱动程序通过内核提供的接口与设备进行交互。
727

被折叠的 条评论
为什么被折叠?



