1. 面向对象分离硬件资源驱动思想
背景
在早期的嵌入设备和计算机系统中
思想
- 模块化
将硬件相关的代码从系统的其他部分中分离出来: 形成独立的模块, 驱动模块( ) 。 - 抽象层
引入硬件抽象层: HAL( ) 使得上层软件可以不直接操作硬件, 而是通过一层抽象的接口来进行, 。 - 封装
使用面向对象的编程思想: 将硬件的操作封装成类或对象, 这样可以更容易管理和重用代码, 。
技术细节
- 使用类的继承和多态来实现不同硬件的抽象和具体实现
。 - 提供统一的接口来访问硬件资源
降低了对具体硬件的依赖, 。
例子
- 模块化
创建一个独立的LED驱动模块: 而不是将LED操作代码直接嵌入到应用层代码中, 。 - 抽象层
定义一个抽象的LED类: 包含打开, 关闭等方法、 。
// LED抽象类
class LED {
public:
virtual void turnOn() = 0;
virtual void turnOff() = 0;
};
// GPIO LED 实现
class GPIO_LED : public LED {
private:
int pin;
public:
GPIO_LED(int gpioPin) : pin(gpioPin) {}
void turnOn() override {
// GPIO设置为高电平
}
void turnOff() override {
// GPIO设置为低电平
}
};
2. Platform Device 平台总线模型
随着硬件设备的复杂性增加
通过这种模型
思想
- 平台设备
将硬件设备抽象为平台设备: Platform Device( ) 这些设备通过平台总线, Platform Bus( 连接到系统中) 。 - 平台驱动
相应地: 驱动程序也被抽象为平台驱动, Platform Driver( ) 它们通过平台总线匹配到相应的设备, 。
技术细节
- 在Linux内核中
, struct platform_device
和struct platform_driver
是关键数据结构。 - 设备和驱动通过设备树或者是硬编码的方式注册到平台总线上
。 - 使用总线驱动框架
如I2C( SPI等、 来进一步抽象硬件接口) 。
// 定义平台设备的结构
struct platform_device {
// 设备的名称<span class="bd-box"><h-char class="bd bd-beg"><h-inner>,</h-inner></h-char></span>用于匹配驱动
const char *name;
int id;
bool id_auto;
struct device dev;
u32 num_resources;
// 指向资源数组的指针<span class="bd-box"><h-char class="bd bd-beg"><h-inner>,</h-inner></h-char></span>资源包括内存区域<span class="bd-box"><h-char class="bd bd-beg"><h-inner>、</h-inner></h-char></span>IRQ等
struct resource *resource;
/* 其他字段省略 */
};
// 定义平台驱动的结构
struct platform_driver {
// 探测函数<span class="bd-box"><h-char class="bd bd-beg"><h-inner>,</h-inner></h-char></span>当设备被发现时调用<span class="bd-box"><h-char class="bd bd-beg"><h-inner>,</h-inner></h-char></span>负责初始化设备
int (*probe)(struct platform_device *);
// 移除设备时调用<span class="bd-box"><h-char class="bd bd-beg"><h-inner>,</h-inner></h-char></span>用于清理资源
int (*remove)(struct platform_device *);
void (*shutdown)(struct platform_device *);
int (*suspend)(struct platform_device *, pm_message_t state);
int (*resume)(struct platform_device *);
// 内嵌的通用设备驱动结构<span class="bd-box"><h-char class="bd bd-beg"><h-inner>,</h-inner></h-char></span>包含了驱动信息如驱动名称<span class="bd-box"><h-char class="bd bd-beg"><h-inner>、</h-inner></h-char></span>所有者等
struct device_driver driver;
const struct platform_device_id *id_table;
/* 其他字段省略 */
};
例子
- 平台设备
定义一个平台设备来代表这个LED: 。
static struct platform_device led_device = {
.name = "led",
.id = -1,
.dev = {
.platform_data = &gpio_pin_number, // 假设这个GPIO引脚号作为平台数据
},
};
- 平台驱动
编写一个平台驱动来操作这个LED: 。
static struct platform_driver led_driver = {
.probe = led_probe,
.remove = led_remove,
.driver = {
.name = "led",
.owner = THIS_MODULE,
},
};
static int led_probe(struct platform_device *pdev)
{
// 获取设备的GPIO引脚信息并初始化LED
return 0;
}
static int led_remove(struct platform_device *pdev)
{
// 清理LED相关的资源
return 0;
}
技术细节
- 通过平台设备和平台驱动
系统可以自动匹配设备和驱动, 。 led_probe
函数在匹配成功后初始化LED, led_remove
函数在设备卸载时清理资源。
匹配过程
平台设备与驱动的匹配过程大致如下
设备注册
: - 当内核启动或模块加载时
平台设备, ( struct platform_device
被注册到平台总线) ( platform_bus
上) 。
- 当内核启动或模块加载时
驱动注册
: - 平台驱动
( struct platform_driver
也需要注册到平台总线上) 。
- 平台驱动
设备树解析
: - 如果使用设备树
内核会解析设备树文件, .dts或.dtb( ) 从中获取设备信息, 并创建相应的平台设备实例, 。
- 如果使用设备树
匹配尝试
: - 内核尝试将注册的驱动与平台总线上的设备进行匹配
。 - 匹配过程主要遵循以下顺序
: - 设备树匹配
优先尝试使用设备树匹配规则: 检查设备的, compatible
属性与驱动的of_match_table
是否匹配。 - ID匹配
如果设备树匹配失败: 尝试使用设备ID匹配, 。 - 名称匹配
最后: 尝试使用驱动和设备的名称匹配, 。
- 设备树匹配
- 内核尝试将注册的驱动与平台总线上的设备进行匹配
成功匹配
: - 一旦找到匹配的设备和驱动
内核会调用驱动的, probe
方法。 probe
方法将接收到匹配的struct device
实际上是一个( struct platform_device
) 驱动可以在这个方法中初始化设备, 。
- 一旦找到匹配的设备和驱动
资源分配
: - 在
probe
方法中 驱动可以访问设备的资源, 如IO内存( IRQ等、 ) 这些资源通常是通过设备树或平台设备注册时提供的, 。
- 在
设备绑定
: - 如果
probe
方法成功 设备将绑定到驱动, 设备现在可以正常工作, 。
- 如果
失败处理
: - 如果匹配失败或
probe
方法失败 内核会记录错误信息, 驱动将不会绑定到设,
- 如果匹配失败或
3. 设备树( Device Tree)
背景
随着SoC
思想
- 解耦硬件描述
将硬件的描述从内核代码中分离出来: 形成独立的设备树描述文件, .dts, .dtsi( ) 。 - 动态配置
设备树提供了一种动态的: 可配置的方式来描述系统硬件、 。
技术细节
- 设备树使用一种类似JSON的格式来描述硬件结构
包括节点, nodes( 表示设备) 属性, properties( 表示设备的特性) 。 - 内核中的设备树编译器
dtc( 将设备树源文件编译成二进制格式) DTB( ) 内核启动时加载DTB来配置硬件, 。 - 驱动程序通过设备树中的信息来获取设备的地址
IRQ等信息、 实现即插即用, 。
例子
- 设备树描述
:
/ {
leds {
compatible = "gpio-led";
gpios = <&gpio 17 GPIO_ACTIVE_HIGH>; // 假设LED连接到GPIO17
label = "led";
};
};
- 驱动代码
现在驱动可以从设备树中获取LED的配置信息: 。
static int led_probe(struct platform_device *pdev)
{
struct device_node *node = pdev->dev.of_node;
int gpio;
if (of_property_read_u32(node, "gpios", &gpio))
return -ENODEV;
// 使用GPIO进行LED操作
return 0;
}
设备树描述了硬件配置
进化意义
- 从面向对象到平台总线模型
再到设备树的演变过程中, 硬件资源的管理变得越来越抽象和灵活, 每个阶段都使得驱动开发和维护更加高效。 减少了对具体硬件的依赖, 增强了系统的可移植性和可维护性, 通过LED点灯这个简单的例子。 我们可以看到这种进化带来的实际好处, 从最初的硬编码: 到设备树的动态配置, 使得系统对硬件变化的适应性大大增强, 。 - 从面向对象到平台总线模型
再到设备树, 硬件资源的管理和配置变得越来越抽象和通用, 减少了对具体硬件的依赖, 提升了系统的可移植性和可维护性, 。 - 设备树的引入大大简化了内核的硬件描述方式
使得硬件支持的添加和修改变得更加灵活和高效, 。
这个过程展示了操作系统在面对越来越复杂的硬件环境时