3. of_node数据结构深度剖析:struct device_node、struct property、struct resource的内存布局

设备树在内核中的核心数据结构是 struct device_node,它代表一个设备树节点。围绕它,struct property 描述节点属性,struct resource 描述硬件资源。理解这三者的内存布局与关系,是优化设备树解析性能的基础。

3.1 struct device_node:设备树节点的内核化身

每个设备树节点(如 uart@fe001000)在解析后都会生成一个 struct device_node 实例。该结构体不仅存储节点名称、类型、路径,还维护了父子兄弟链表以及属性链表。

字段类型说明
nameconst char *节点名称(不含 unit-address)
typeconst char *device_type 属性值,如 "serial"
full_nameconst char *完整路径名,如 "/soc/uart@fe001000"
phandlephandle节点的 phandle 值(唯一标识)
parentstruct device_node *指向父节点
childrenstruct device_node *指向第一个子节点
siblingstruct device_node *指向下一个兄弟节点
propertiesstruct property *属性链表头
dataunsigned long平台私有数据指针
性能要点: full_name 字段在匹配时频繁使用,其字符串比较是解析路径中的主要开销。内核 5.10+ 引入 of_node_name_eq() 仅比较节点名,避免全路径比较。
内存布局观察: 在 64 位系统上,struct device_node 大小约为 120~160 字节(取决于内核配置)。每个节点都包含多个指针字段,导致缓存行利用率较低。大量节点(如 500+)会占用 60KB~80KB 内核内存。
struct device_node 内存布局与链表关系 struct device_node name: "uart" type: "serial" full_name: "/soc/uart@..." phandle: 0x1234 parent ──→ 父节点 children ──→ 子节点 sibling ──→ 兄弟节点 properties → 属性链表 data: 私有数据 父节点 parent 子节点 children 兄弟节点 sibling 属性链表 struct property name: "compatible" value: "snps,dw-apb-uart" struct property name: "reg" value: 0xfe001000, 0x1000 💡 链表遍历是设备树解析的主要开销:每次 of_find_node_by_name() 都需要遍历 sibling 链表,O(n) 复杂度。 优化方向:引入哈希表加速节点查找(内核 5.15+ 已部分实现)。

3.2 struct property:属性存储与值访问

每个设备树属性(如 compatible、reg、interrupts)对应一个 struct property。该结构体通过 next 指针串联成单链表,挂载在 device_node->properties 上。

字段类型说明
namechar *属性名称字符串
lengthint属性值长度(字节)
valuevoid *指向属性值数据的指针
nextstruct property *指向下一个属性
常见陷阱: value 指针指向的数据可能位于 FDT 原始 blob 的映射区域(只读),也可能在 unflatten 时被复制到分配的内存中。直接修改 value 内容可能导致内核崩溃。应使用 of_property_write() 系列函数。

属性查找的性能关键点在于字符串比较。内核提供了 of_find_property() 函数,其内部遍历属性链表并对 name 字段进行 strcmp。对于属性数量较多的节点(如 /chosen 或复杂外设),遍历开销不可忽视。

struct property 链表遍历与查找 device_node properties ↓ 链表头 property #1 name: "compatible" length: 24 next → property #2 name: "reg" length: 8 next → property #3 name: "interrupts" length: 12 next → NULL 🔍 of_find_property(node, "interrupts") 查找路径: ① 从 properties 链表头开始 → ② strcmp("compatible","interrupts") ≠ 0 → ③ next ④ strcmp("reg","interrupts") ≠ 0 → ⑤ next → ⑥ strcmp("interrupts","interrupts") = 0 → 找到!

3.3 struct resource:从 reg 属性到硬件资源

struct resource 是内核描述硬件资源(内存地址范围、中断号、DMA 通道等)的标准结构体。设备树解析过程中,of_address_to_resource() 和 of_irq_to_resource() 将 reg 和 interrupts 属性转换为 struct resource。

字段类型说明
startresource_size_t资源起始地址/中断号
endresource_size_t资源结束地址
nameconst char *资源名称(通常为设备名)
flagsunsigned long资源类型标志(IORESOURCE_MEM, IORESOURCE_IRQ 等)
parentstruct resource *父资源(用于资源树管理)
siblingstruct resource *兄弟资源
childstruct resource *子资源
性能关键路径: of_address_to_resource() 内部需要解析 reg 属性中的地址单元和大小单元,涉及 of_bus_address_map() 和地址翻译。对于有 ranges 属性的复杂总线,翻译过程可能递归遍历父节点的地址映射,这是设备树解析中的主要性能瓶颈之一。

3.4 三者的内存布局关系图

以下 SVG 展示了从 FDT blob 到 device_node、property、resource 的完整数据流与内存布局关系。

从 FDT 到 resource:完整数据流与内存布局 FDT Blob (dtb) 内存映射区域 只读,原始数据 包含所有节点/属性 unflatten struct device_node name, type, full_name phandle, parent, children sibling, properties → data (私有数据) 内存: kmalloc 分配 struct property name: 属性名 length: 值长度 value: 指向 FDT 或复制数据 next: 链表指针 内存: kmalloc + 可能复制 value struct resource start: 基地址/中断号 end: 结束地址 flags: IORESOURCE_MEM/IRQ 内存: 栈或 kmalloc of_find_property() of_address_to_resource() 📊 内存布局关键点 1. FDT blob 是只读映射 2. device_node 全部 kmalloc 3. property.value 可能指向 FDT 区域(节省内存) 4. resource 通常分配在栈上 5. 每个节点平均 5~15 个属性 6. 属性链表遍历 O(n) 性能优化方向: • 属性缓存(property cache) • 哈希索引替代链表遍历 • 预解析常用属性 • 避免重复 of_get_property() 💡 典型嵌入式系统(500 节点) device_node: ~80KB property: ~60KB 总计: ~140KB 内核内存

3.5 性能优化实践:减少属性查找开销

基于上述内存布局分析,以下是实际项目中可用的优化策略:

  1. 缓存常用属性指针:在 probe 函数中一次性获取所有需要的属性,避免多次遍历链表。
  2. 使用 of_property_read_u32() 系列函数:这些函数内部直接解析属性值,比先 of_find_property() 再手动转换更高效。
  3. 避免在热路径中调用 of_get_property():中断处理或高频轮询代码中不应包含设备树属性查找。
  4. 利用 of_match_node() 的匹配表缓存:驱动匹配时,of_match_table 的匹配结果会被缓存,避免重复解析。
实战案例: 某网络驱动在数据包收发路径中调用 of_get_mac_address(),导致每次收包都遍历属性链表。优化后仅在 probe 时读取 MAC 地址并缓存,性能提升 15%。
/* 优化前:每次调用都遍历属性链表 */
static int get_mac_from_dt(struct device *dev, u8 *mac)
{
    const void *prop;
    prop = of_get_property(dev->of_node, "mac-address", NULL);
    if (prop)
        memcpy(mac, prop, ETH_ALEN);
    return prop ? 0 : -ENODEV;
}

/* 优化后:probe 时缓存,运行时直接使用 */
struct priv_data {
    u8 mac_addr[ETH_ALEN];
    bool mac_valid;
};

static int probe(struct platform_device *pdev)
{
    struct priv_data *priv;
    const void *prop;
    
    priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
    prop = of_get_property(pdev->dev.of_node, "mac-address", NULL);
    if (prop) {
        memcpy(priv->mac_addr, prop, ETH_ALEN);
        priv->mac_valid = true;
    }
    // ... 后续运行时直接使用 priv->mac_addr
}

3.6 本章小结

数据结构核心字段内存分配性能关键点
struct device_nodename, parent, children, propertieskmalloc,每个节点 ~120-160B链表遍历 O(n),full_name 字符串比较
struct propertyname, length, value, nextkmalloc,value 可能指向 FDT属性查找 strcmp,链表遍历
struct resourcestart, end, flags栈或 kmalloc地址翻译(ranges 解析)