Linux设备模型1(基于Linux6.6)---简介
一、Linux设备模型概述
Linux 设备模型是 Linux 内核中的一种框架,旨在为设备和驱动程序提供统一的管理接口。它将硬件设备、设备驱动和内核中的其他组件组织成一个统一的树状结构,使得设备的管理、检测和驱动程序加载变得更加高效、清晰和灵活。设备模型能够帮助内核和用户空间程序更好地与硬件交互,简化设备管理流程,并提高系统的可扩展性和可维护性。
1.1 设备模型的基本概念
在 Linux 中,所有硬件设备都通过 设备对象(device
)进行表示,驱动程序通过 驱动对象(driver
)来与设备进行交互。设备和驱动程序被组织成一个 设备树(device tree),设备树的每个节点都表示一个硬件设备。设备模型的核心概念包括以下几个部分:
- 设备对象(
device
):表示一个硬件设备。每个设备对象都与实际的硬件设备相关联,包含有关设备的基本信息、状态和操作方法。 - 驱动程序对象(
driver
):表示一个设备驱动程序。每个驱动程序对象都有与设备交互的接口(例如初始化设备、处理设备的请求等)。 - 总线(
bus
):设备对象和驱动程序对象通过总线(如 PCI、USB、I2C 等)进行关联。总线是连接设备和驱动程序的中介,提供一个机制来发现和管理设备。 - 类(
class
):类是设备对象的一种分组方式,通常用于将具有相似功能的设备归为一类(例如,所有字符设备、块设备等都可以归为类)。 - 设备模型层(
device model layer
):提供管理设备和驱动程序的机制,如设备的注册、驱动程序匹配、设备的生命周期管理等。
1.2 设备对象(device
)
设备对象是 Linux 设备模型的核心,表示系统中的物理设备或虚拟设备。每个设备对象都包含以下几个关键字段:
- 名称:设备的名称,通常是设备的标识符(如
/dev/sda
)。 - 设备类型:设备属于的类别(如块设备、字符设备等)。
- 状态:设备当前的状态,例如设备是否已经初始化,是否可用等。
- 设备操作:与设备交互的操作(如打开、关闭、读写操作)。
- 驱动程序:该设备与哪个驱动程序相对应。
设备对象通常通过 device
结构体来表示,结构体中包含了关于设备的信息,如设备名、父设备、设备驱动等。
1.3 驱动程序对象(driver
)
驱动程序对象描述了如何控制和操作特定类型的设备。每个驱动程序对象通过 驱动程序接口(如 probe()
、remove()
等)来定义设备的行为。这些接口包括:
probe()
:驱动程序与设备匹配时调用。通常在设备插入时被调用,用于初始化设备并为其分配资源。remove()
:当设备从系统中移除时调用,用于清理与设备相关的资源。suspend()
和resume()
:用于设备的暂停和恢复操作,尤其是在设备进入低功耗模式时。shutdown()
:用于设备关闭时的最后清理操作。
1.4 总线(bus
)
总线是设备和驱动程序之间的媒介,用于在内核中查找和管理设备。设备通过总线进行连接,设备和驱动程序的匹配通常基于总线类型和设备类型。例如:
- PCI 总线:用于管理 PCI 设备。
- USB 总线:用于管理 USB 设备。
- I2C 总线:用于管理 I2C 设备。
总线提供了设备和驱动程序之间的桥梁,并管理设备的插拔、枚举和注册。
1.5 设备类别(class
)
设备类别将功能相似的设备归类。例如,所有的字符设备、块设备、网络设备等都可以通过类进行组织。设备类别通常用于提供一个统一的接口,让用户空间程序更容易访问这些设备。
例如:
class_device
:表示一个设备类。通常使用device_create()
来创建设备类的实例,用户空间程序可以通过udev
工具来与这些设备交互。
1.6 设备树(device tree
)
Linux 设备树是一个结构化的数据树,通常用于描述设备的硬件信息。设备树通常包含设备及其连接信息,包括硬件地址、资源、设备类型等。它的作用是为内核提供硬件平台信息,尤其是在嵌入式系统中,设备树非常重要。
二、 Linux设备模型的基本概念
2.1、 Bus, Class, Device和Device Driver的概念
下图是嵌入式系统常见的硬件拓扑的一个示例:
硬件拓扑描述Linux设备模型中四个重要概念中三个:Bus,Class和Device(第四个为Device Driver,后面会说)。
Bus(总线):总线是CPU和一个或多个设备之间信息交互的通道。而为了方便设备模型的抽象,所有的设备都应连接到总线上(无论是CPU内部总线、还是虚拟的总线“platform Bus”)。
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;
};
Class(分类):在Linux设备模型中,Class的概念非常类似面向对象程序设计中的Class(类),它主要是集合具有相似功能或属性的设备,这样就可以抽象出一套可以在多个设备之间共用的数据结构和接口函数。因而从属于相同Class的设备的驱动程序,就不再需要重复定义这些公共资源,直接从Class中继承即可。
include/linux/device/class.h
struct class {
const char *name;
const struct attribute_group **class_groups;
const struct attribute_group **dev_groups;
int (*dev_uevent)(const struct device *dev, struct kobj_uevent_env *env);
char *(*devnode)(const struct device *dev, umode_t *mode);
void (*class_release)(const 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)(const struct device *dev);
void (*get_ownership)(const struct device *dev, kuid_t *uid, kgid_t *gid);
const struct dev_pm_ops *pm;
};
Device(设备):抽象系统中所有的硬件设备,描述它的名字、属性、从属的Bus、从属的Class等信息。
include/linux/device.h
struct device {
struct kobject kobj;
struct device *parent;
struct device_private *p;
const char *init_name; /* initial name of the device */
const struct device_type *type;
const struct bus_type *bus; /* type of bus device is on */
struct device_driver *driver; /* which driver has allocated this
device */
void *platform_data; /* Platform specific data, device
core doesn't touch it */
void *driver_data; /* Driver data, set and get with
dev_set_drvdata/dev_get_drvdata */
struct mutex mutex; /* mutex to synchronize calls to
* its driver.
*/
struct dev_links_info links;
struct dev_pm_info power;
struct dev_pm_domain *pm_domain;
#ifdef CONFIG_ENERGY_MODEL
struct em_perf_domain *em_pd;
#endif
#ifdef CONFIG_PINCTRL
struct dev_pin_info *pins;
#endif
struct dev_msi_info msi;
#ifdef CONFIG_DMA_OPS
const struct dma_map_ops *dma_ops;
#endif
u64 *dma_mask; /* dma mask (if dma'able device) */
u64 coherent_dma_mask;/* Like dma_mask, but for
alloc_coherent mappings as
not all hardware supports
64 bit addresses for consistent
allocations such descriptors. */
u64 bus_dma_limit; /* upstream dma constraint */
const struct bus_dma_region *dma_range_map;
struct device_dma_parameters *dma_parms;
struct list_head dma_pools; /* dma pools (if dma'ble) */
#ifdef CONFIG_DMA_DECLARE_COHERENT
struct dma_coherent_mem *dma_mem; /* internal for coherent mem
override */
#endif
#ifdef CONFIG_DMA_CMA
struct cma *cma_area; /* contiguous memory area for dma
allocations */
#endif
#ifdef CONFIG_SWIOTLB
struct io_tlb_mem *dma_io_tlb_mem;
#endif
#ifdef CONFIG_SWIOTLB_DYNAMIC
struct list_head dma_io_tlb_pools;
spinlock_t dma_io_tlb_lock;
bool dma_uses_io_tlb;
#endif
/* arch specific additions */
struct dev_archdata archdata;
struct device_node *of_node; /* associated device tree node */
struct fwnode_handle *fwnode; /* firmware device node */
#ifdef CONFIG_NUMA
int numa_node; /* NUMA node this device is close to */
#endif
dev_t devt; /* dev_t, creates the sysfs "dev" */
u32 id; /* device instance */
spinlock_t devres_lock;
struct list_head devres_head;
const struct class *class;
const struct attribute_group **groups; /* optional groups */
void (*release)(struct device *dev);
struct iommu_group *iommu_group;
struct dev_iommu *iommu;
struct device_physical_location *physical_location;
enum device_removable removable;
bool offline_disabled:1;
bool offline:1;
bool of_node_reused:1;
bool state_synced:1;
bool can_match:1;
#if defined(CONFIG_ARCH_HAS_SYNC_DMA_FOR_DEVICE) || \
defined(CONFIG_ARCH_HAS_SYNC_DMA_FOR_CPU) || \
defined(CONFIG_ARCH_HAS_SYNC_DMA_FOR_CPU_ALL)
bool dma_coherent:1;
#endif
#ifdef CONFIG_DMA_OPS_BYPASS
bool dma_ops_bypass : 1;
#endif
};
Device Driver(驱动):Linux设备模型用Driver抽象硬件设备的驱动程序,它包含设备初始化、电源管理相关的接口实现。而Linux内核中的驱动开发,基本都围绕该抽象进行(实现所规定的接口函数)。
include/linux/device/driver.h
struct device_driver {
const char *name;
const struct bus_type *bus;
struct module *owner;
const char *mod_name; /* used for built-in modules */
bool suppress_bind_attrs; /* disables bind/unbind via sysfs */
enum probe_type probe_type;
const struct of_device_id *of_match_table;
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 attribute_group **dev_groups;
const struct dev_pm_ops *pm;
void (*coredump) (struct device *dev);
struct driver_private *p;
};
什么是Platform Bus?
Platform Bus 是 Linux 内核中的一种总线类型,通常用于描述嵌入式设备或某些特定硬件平台上的设备。它是一个逻辑总线,不依赖于传统的硬件总线协议(如 PCI、USB 等),而是通过软件定义的方式来组织和管理设备。Platform Bus 主要用于处理那些没有专门硬件总线支持的设备,如某些嵌入式系统中的处理器内设备、内存映射 I/O 设备等。
1. Platform Bus 的作用
Platform Bus 的作用是作为设备与驱动程序之间的桥梁,它可以连接那些不需要特定硬件总线支持的设备。设备通过 Platform Bus 可以被 Linux 内核识别、管理和控制。通常,Platform Bus 用于连接与处理器、内存和系统中其他组件紧密集成的设备。
2. 常见应用场景
-
嵌入式系统:在许多嵌入式系统中,设备通常没有 PCI、USB 或其他标准硬件总线,而是与 CPU 或其他平台组件紧密耦合。此时,Platform Bus 就提供了一个灵活的方式来管理这些设备。
-
内存映射 I/O 设备:一些设备通过内存映射的方式与处理器进行通信,这类设备通常通过 Platform Bus 来管理。
-
ARM 系统:在许多 ARM 架构的嵌入式设备中,Platform Bus 被广泛应用,尤其是在没有标准总线(如 PCI、USB)支持的情况下。
3. Platform Bus 的特点
-
无硬件协议:与 PCI、USB 等总线不同,Platform Bus 不依赖于任何硬件协议或物理总线。它只是作为一种逻辑结构,帮助管理设备与驱动之间的关系。
-
设备通过设备树(Device Tree)注册:在嵌入式系统中,平台设备通常通过设备树(Device Tree)来描述。设备树提供了设备的配置信息(如地址、资源等),并通过 Platform Bus 将设备注册到内核中。
-
支持多种设备类型:Platform Bus 适用于多种设备类型,包括内存映射 I/O 设备、直接连接到 CPU 的设备等。
-
驱动程序与设备的绑定:设备和驱动程序通常通过
platform_driver
来绑定。驱动程序通过probe()
和remove()
函数来管理设备的生命周期。
4. Platform Bus 相关结构体
在 Linux 内核中,Platform Bus 的相关结构体和操作函数主要包括以下几个:
-
platform_device
:表示一个平台设备。它用于描述一个与特定平台相关的设备,通常包括设备的资源(如内存、I/O 地址)和操作函数。 -
platform_driver
:表示一个平台驱动程序。它包含与平台设备交互的操作方法,如probe()
和remove()
函数。 -
platform_bus_type
:表示平台总线的类型。内核会使用该类型来管理所有通过 Platform Bus 注册的设备。
5. 如何使用 Platform Bus
通常,使用 Platform Bus 需要做以下几个步骤:
(1) 定义一个平台设备
平台设备是与 Platform Bus 连接的对象,它描述了硬件设备的基本信息。一个简单的示例:
#include <linux/platform_device.h>
static struct platform_device *pdev;
static int __init platform_device_init(void)
{
pdev = platform_device_register_simple("my_device", -1, NULL, 0);
if (IS_ERR(pdev))
return PTR_ERR(pdev);
return 0;
}
static void __exit platform_device_exit(void)
{
platform_device_unregister(pdev);
}
module_init(platform_device_init);
module_exit(platform_device_exit);
(2) 定义平台驱动
平台驱动程序通常会实现 probe()
和 remove()
方法来处理设备的注册和注销。
#include <linux/platform_device.h>
static int my_probe(struct platform_device *pdev)
{
pr_info("Platform device probed\n");
return 0;
}
static int my_remove(struct platform_device *pdev)
{
pr_info("Platform device removed\n");
return 0;
}
static struct platform_driver my_driver = {
.driver = {
.name = "my_driver",
},
.probe = my_probe,
.remove = my_remove,
};
module_platform_driver(my_driver);
(3) 使用设备树描述平台设备(可选)
在嵌入式系统中,通常使用设备树来描述平台设备的硬件配置。设备树中的节点描述了设备的物理资源和属性,Linux 内核会根据设备树加载相应的设备和驱动程序。
my_device@0 {
compatible = "my_device";
reg = <0x1000 0x1000>;
};
2.2、 设备模型的核心思想
Linux设备模型的核心思想是提供一个统一、灵活和可扩展的框架,来管理和表示操作系统中的所有硬件设备。设备模型的目标是通过一组标准的接口来处理硬件设备、驱动程序和内核之间的交互,简化设备管理和驱动程序的编写,同时为硬件设备的生命周期提供一致的管理机制。
核心思想和基本概念
-
设备与驱动程序的抽象:
- Linux 设备模型通过抽象设备和驱动程序,使得内核可以统一地管理不同类型的硬件设备,而不必依赖硬件平台的具体细节。
- 设备模型将硬件设备抽象为一个个
device
结构体,通过驱动程序来实现对设备的控制。
-
对象模型:
- 设备、驱动、类和总线等都是对象,并且这些对象通过特定的结构体在内核中表示。它们之间可以通过“父子关系”以及“类”来组织和关联。
- 这种对象化管理方式使得设备、驱动和总线的管理变得更加模块化,降低了代码的耦合度。
-
设备树和驱动程序的绑定:
- 设备模型的核心目标之一是让内核能够自动或半自动地为系统中的每个设备选择和绑定合适的驱动程序。
- 在设备注册时,内核会通过设备的描述信息(如设备 ID)去匹配和绑定合适的驱动程序。
- 设备模型引入了 设备驱动模型(Driver Model),该模型支持设备与驱动的注册、绑定、卸载等操作。
-
总线模型(Bus Model):
- Linux 内核通过 总线模型 来管理不同类型的硬件设备之间的关系。总线为设备提供了一种结构化的组织方式,允许设备按总线类型进行分类(如 PCI 总线、USB 总线、Platform 总线等)。
- 设备通过不同类型的总线来连接到内核,内核通过总线模型来处理设备的创建、初始化、通信等功能。
-
设备的生命周期管理:
- 设备模型不仅仅涉及设备的注册和驱动绑定,还涉及设备的生命周期管理。
- 包括设备的创建、初始化、注册、驱动绑定、设备操作和最终的销毁等过程。
-
模块化与可扩展性:
- 设备模型的设计强调模块化和可扩展性。驱动程序、设备类型和总线类型都通过标准接口进行管理,确保系统可以随着硬件的发展和变更进行扩展,而无需改动核心代码。
- 这种模块化的结构使得开发者可以方便地为新的硬件平台或设备类型编写新的驱动,而不必重新设计整个设备管理框架。
设备模型的核心组成部分
Linux 设备模型由多个关键组成部分构成,主要包括设备(Device)、驱动(Driver)、总线(Bus)和类(Class)等。下面是这些组件的详细介绍:
-
设备(Device):
- 设备在设备模型中是一个抽象的对象,表示系统中的硬件或虚拟设备。每个设备都由一个
device
结构体来表示,结构体中包含设备的名称、资源(如内存、I/O 地址等)、驱动等信息。 - 设备的主要职责是提供硬件资源,并将其暴露给驱动程序进行控制。
- 设备在设备模型中是一个抽象的对象,表示系统中的硬件或虚拟设备。每个设备都由一个
-
驱动程序(Driver):
- 驱动程序是设备与操作系统之间的桥梁,它为硬件设备提供具体的操作接口。每个驱动程序都由一个
driver
结构体来表示,驱动结构体包含了设备与驱动程序之间的映射关系。 - 驱动的核心功能是通过实现设备相关的操作函数(如初始化、读写操作等)来控制设备的行为。
- 驱动程序是设备与操作系统之间的桥梁,它为硬件设备提供具体的操作接口。每个驱动程序都由一个
-
总线(Bus):
- 总线是连接设备和内核其他部分的桥梁,设备通过总线连接到内核。Linux 设备模型中,设备可以属于不同类型的总线,如 PCI 总线、USB 总线、Platform 总线等。
- 总线负责管理设备的注册、资源分配、设备发现和驱动绑定等操作。
-
类(Class):
- 类是对设备类型的抽象,用于在内核中管理一类设备。每个类包含一组相同类型设备的属性和操作函数(例如,一组块设备可能共享相同的操作方法)。
- 类提供了对设备的高级抽象,简化了设备的管理和操作。
设备模型的工作流程
-
设备的注册:
- 当一个设备被发现(例如,系统启动时或热插拔时),它会通过
device_register()
函数进行注册。设备注册后,设备将被放入系统的设备模型中,并等待驱动程序来绑定。
- 当一个设备被发现(例如,系统启动时或热插拔时),它会通过
-
驱动的注册与绑定:
- 驱动程序通过
driver_register()
函数注册到内核。注册时,内核会根据设备的标识(如设备 ID)将设备与适合的驱动程序绑定。 - 驱动程序的绑定通常是通过
probe()
函数实现的,probe()
函数在设备和驱动匹配后被调用,驱动程序可以在此函数中完成对设备的初始化和配置。
- 驱动程序通过
-
设备与驱动的解绑:
- 当设备被移除或者驱动程序被卸载时,设备会通过
device_unregister()
函数与内核解绑,驱动程序则通过driver_unregister()
函数卸载。
- 当设备被移除或者驱动程序被卸载时,设备会通过
设备模型的优点
-
简化驱动开发:设备模型为驱动程序提供了一个统一的框架,使得开发者无需关注底层硬件的细节,专注于设备的控制逻辑。
-
硬件抽象:通过设备模型,Linux 内核能够将硬件抽象成统一的设备接口,使得硬件设备的种类变得透明,驱动程序能够跨硬件平台工作。
-
自动化驱动绑定:设备模型通过设备 ID 和驱动程序之间的匹配机制,能够自动选择和绑定适合的驱动,减少了手动配置的复杂性。
-
易于扩展和维护:设备模型的设计采用模块化结构,新的设备类型、驱动程序和总线类型可以通过简单的接口扩展进来,而不需要对现有代码进行大规模修改。