1. struct resource 概述
struct resource 是 Linux 内核中用于管理硬件资源的核心数据结构。作为一名嵌入式驱动开发者,我几乎每天都要和它打交道。简单来说,它就是内核用来记录和管理各种硬件资源的"账本"。
想象一下,你的电脑里有网卡、声卡、USB控制器等各种硬件设备,每个设备都需要特定的内存地址、中断号等资源才能正常工作。struct resource 就是用来记录这些资源分配情况的,确保不同的设备不会互相"打架"抢同一个资源。
2. struct resource 的详细结构
2.1 基本定义
让我们先看看这个结构体的完整定义(以 Linux 5.x 内核为例):
c复制struct resource {
resource_size_t start; // 资源起始地址/编号
resource_size_t end; // 资源结束地址/编号
const char *name; // 资源名称
unsigned long flags; // 资源类型标志
struct resource *parent; // 父资源指针
struct resource *sibling; // 兄弟资源指针
struct resource *child; // 子资源指针
};
2.2 关键字段解析
2.2.1 start 和 end 字段
这两个字段定义了资源的范围,具体含义取决于资源类型:
- 对于内存资源(IORESOURCE_MEM):表示物理地址范围
- 对于中断资源(IORESOURCE_IRQ):通常start=end=中断号
- 对于DMA资源(IORESOURCE_DMA):表示DMA通道号
举个例子,一个UART设备的寄存器地址范围是0x12340000-0x12340fff,那么:
c复制.start = 0x12340000,
.end = 0x12340fff
2.2.2 flags 字段
flags字段是最重要的字段之一,它定义了资源的类型和属性。常见标志包括:
| 标志 | 含义 |
|---|---|
| IORESOURCE_MEM | 内存资源 |
| IORESOURCE_IO | I/O端口资源 |
| IORESOURCE_IRQ | 中断资源 |
| IORESOURCE_DMA | DMA通道资源 |
| IORESOURCE_SHAREABLE | 可共享资源 |
在实际使用中,我们经常会组合多个标志,比如:
c复制.flags = IORESOURCE_MEM | IORESOURCE_SHAREABLE
2.2.3 name 字段
name字段用于给资源命名,方便调试。当你在/proc/iomem中查看资源分配情况时,就会看到这些名字。
2.2.4 指针字段
parent、sibling和child指针用于构建资源树,通常由内核管理,驱动开发者很少需要直接操作。
3. 资源类型详解
3.1 内存资源(IORESOURCE_MEM)
内存资源是最常用的资源类型,用于描述设备的寄存器地址空间。
3.1.1 使用流程
- 获取资源:
platform_get_resource(pdev, IORESOURCE_MEM, index) - 申请资源:
devm_request_mem_region() - 映射内存:
devm_ioremap_resource() - 访问硬件:使用
readl()/writel()
3.1.2 实际案例
假设我们要访问一个UART设备的寄存器:
c复制struct resource *res;
void __iomem *base;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res) {
dev_err(&pdev->dev, "Failed to get MEM resource\n");
return -ENODEV;
}
base = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(base)) {
return PTR_ERR(base);
}
// 现在可以通过base指针访问寄存器了
u32 status = readl(base + UART_STATUS_OFFSET);
3.2 中断资源(IORESOURCE_IRQ)
中断资源用于处理设备的异步事件。
3.2.1 使用流程
- 获取中断号:
platform_get_irq(pdev, index) - 申请中断:
devm_request_irq() - 编写中断处理函数
3.2.2 实际案例
c复制int irq, ret;
irq = platform_get_irq(pdev, 0);
if (irq < 0) {
return irq;
}
ret = devm_request_irq(&pdev->dev, irq, my_interrupt_handler,
IRQF_SHARED, dev_name(&pdev->dev), priv);
if (ret) {
dev_err(&pdev->dev, "Failed to request IRQ %d: %d\n", irq, ret);
return ret;
}
3.3 DMA资源(IORESOURCE_DMA)
DMA资源用于直接内存访问通道。
3.3.1 使用流程
- 获取DMA通道:
platform_get_resource(pdev, IORESOURCE_DMA, index) - 申请DMA通道:
dma_request_channel() - 配置DMA传输
4. 设备树与资源管理
在现代Linux内核中,设备树是描述硬件的主要方式。内核会自动将设备树中的资源转换为struct resource。
4.1 设备树资源映射
设备树节点中的资源会被自动转换为struct resource:
| 设备树属性 | 对应资源类型 |
|---|---|
| reg | IORESOURCE_MEM |
| interrupts | IORESOURCE_IRQ |
| dmas | IORESOURCE_DMA |
4.2 设备树示例
dts复制uart1: serial@12340000 {
compatible = "fsl,imx6ul-uart";
reg = <0x12340000 0x1000>;
interrupts = <GIC_SPI 10 IRQ_TYPE_LEVEL_HIGH>;
};
内核会为这个节点创建两个资源:
- 内存资源:0x12340000-0x12340fff
- 中断资源:中断号10
5. 资源管理API
5.1 资源申请API
| API | 功能 |
|---|---|
| request_resource() | 申请资源 |
| release_resource() | 释放资源 |
| devm_request_mem_region() | 设备管理的内存资源申请 |
| devm_request_irq() | 设备管理的中断申请 |
5.2 资源查找API
| API | 功能 |
|---|---|
| platform_get_resource() | 获取平台设备资源 |
| platform_get_irq() | 获取平台设备中断号 |
| of_address_to_resource() | 从设备树节点获取资源 |
6. 调试与问题排查
6.1 调试工具
/proc/iomem:查看内存资源分配/proc/interrupts:查看中断分配/proc/ioports:查看I/O端口分配
6.2 常见问题
-
资源冲突:两个驱动申请同一资源
- 解决方案:检查
/proc/iomem和/proc/interrupts
- 解决方案:检查
-
资源申请失败
- 可能原因:资源已被占用、参数错误
- 解决方案:检查返回值,打印错误信息
-
内存映射失败
- 可能原因:物理地址无效、长度错误
- 解决方案:检查设备树中的reg属性
7. 最佳实践
- 始终检查API返回值:资源申请失败是常见情况,必须处理
- 优先使用devm_ API:自动资源管理减少泄漏风险
- 合理命名资源:方便调试和问题排查
- 共享资源要小心:特别是中断资源,处理函数要高效
- 考虑资源释放:即使使用devm_ API,也要注意卸载顺序
8. 实际开发经验
在我开发一个SPI设备驱动时,曾经遇到过这样的问题:驱动加载后系统会随机崩溃。通过排查发现:
- 设备树中定义了中断号10
- 另一个驱动也使用了中断号10
- 两个驱动都没有设置IRQF_SHARED标志
解决方案是在两个驱动中都添加IRQF_SHARED标志,或者修改设备树使用不同的中断号。
另一个常见问题是内存映射失败。有一次我发现ioremap总是返回NULL,最后发现是设备树中的reg属性长度设置为了0。修正长度后问题解决。
9. 性能考虑
- 中断处理:中断处理函数要尽可能短,把耗时操作放到工作队列
- 内存访问:频繁访问的寄存器可以考虑预取或缓存
- DMA使用:合理使用DMA可以显著提高性能
10. 进阶话题
10.1 自定义资源类型
除了标准资源类型,你还可以定义自己的资源类型:
c复制#define IORESOURCE_MY_TYPE (1 << 10)
10.2 资源树管理
内核维护了一个全局资源树,可以通过以下API操作:
c复制request_resource()
insert_resource()
10.3 平台设备资源
对于传统的平台设备,可以在代码中直接定义资源:
c复制static struct resource my_resources[] = {
{
.start = 0x12340000,
.end = 0x12340fff,
.flags = IORESOURCE_MEM,
.name = "my_device_mem",
},
{
.start = 10,
.end = 10,
.flags = IORESOURCE_IRQ,
.name = "my_device_irq",
},
};
11. 总结
struct resource 是Linux驱动开发的基础知识,理解它的工作原理对于编写稳定可靠的驱动程序至关重要。记住以下几点:
- 每个硬件资源都需要正确的描述和管理
- 设备树是现代Linux驱动获取资源的主要方式
- 资源申请失败必须处理
- 使用devm_ API可以简化资源管理
- 调试工具是排查资源问题的好帮手
在实际开发中,我建议多查看内核源码中的驱动示例,特别是drivers/目录下主流芯片厂商的驱动实现,这些都是很好的学习资料。