4、CPU与内存节点调整:CPU频率、核心数、内存布局、DMA内存区域的配置

设备树里CPU和内存节点的调整,说白了你就是在告诉内核:「我的硬件长这样,你按这个来跑」。调对了,性能蹭蹭涨;调错了,系统可能连起都起不来。

我个人习惯,拿到一块新板子,第一件事就是先看这两块节点。CPU节点决定了你能用几个核、跑多快;内存节点决定了系统怎么用这片RAM。嗯,咱们一个一个来拆。

4.1 CPU节点:频率与核心数的精确控制

CPU节点在设备树里通常长这样:

cpus {
    #address-cells = <1>;
    #size-cells = <0>;

    cpu@0 {
        device_type = "cpu";
        compatible = "arm,cortex-a72";
        reg = <0x0>;
        operating-points = <
            1200000 1100000
            1000000 1000000
            800000  900000
        >;
        clock-latency = <300000>;
    };

    cpu@1 {
        device_type = "cpu";
        compatible = "arm,cortex-a72";
        reg = <0x1>;
        operating-points = <
            1200000 1100000
            1000000 1000000
            800000  900000
        >;
        clock-latency = <300000>;
    };
};

这里有个关键点——operating-points。每一行第一个数字是频率(kHz),第二个是电压(mV)。你想想看,如果你把频率写高了但电压没跟上,CPU就会不稳定。我在项目中遇到过,有人把频率从1.2GHz改到1.5GHz,电压没动,结果跑压力测试时系统随机死机。查了两天才发现是OPP表配错了。

注意: 修改CPU频率时,一定要确认硬件设计支持的电压范围。盲目提频轻则系统不稳定,重则烧芯片。

核心数的控制更直接。如果你只想用4核中的2核,直接把另外两个cpu节点删掉或status设为"disabled"就行:

cpu@2 {
    status = "disabled";
};

我曾经为了省电,在某个IoT项目里只开了2个核,剩下2个核在需要时才通过cpu-hotplug动态唤醒。效果不错,待机功耗降了30%。

4.2 内存节点:布局与大小

内存节点决定了内核能看到多少RAM,以及这些RAM怎么分布。基础写法:

memory@80000000 {
    device_type = "memory";
    reg = <0x0 0x80000000 0x0 0x80000000>;
};

这里reg的前两个数字是起始地址,后两个是大小。上面这个例子表示:从0x80000000开始,共2GB内存。

如果你的板子有多个内存区域,比如DDR和SRAM混用,可以这样写:

memory@80000000 {
    device_type = "memory";
    reg = <0x0 0x80000000 0x0 0x40000000>;  /* 1GB DDR */
};

memory@c0000000 {
    device_type = "memory";
    reg = <0x0 0xc0000000 0x0 0x10000000>;  /* 256MB SRAM */
};
小技巧: 调试阶段可以在内核启动参数里加mem=来临时限制内存大小,不用反复改设备树。比如mem=512M,方便你测试内存不足时的行为。

我有个教训:某次项目里,内存节点地址写错了,导致内核只认到一半的RAM。系统能跑,但跑着跑着就OOM。查了三天,最后发现是reg里的地址偏移算错了。嗯,这种低级错误最坑人。

4.3 DMA内存区域:性能的关键

DMA内存区域,说白了就是给外设直接访问用的内存池。配置好了,数据搬运不经过CPU,性能翻倍;配不好,DMA和CPU抢内存,反而更慢。

设备树里用reserved-memory来预留DMA区域:

reserved-memory {
    #address-cells = <2>;
    #size-cells = <2>;
    ranges;

    dma_pool: dma@a0000000 {
        compatible = "shared-dma-pool";
        reg = <0x0 0xa0000000 0x0 0x10000000>;
        no-map;
    };
};

这里no-map;表示这块内存不映射到内核的页表,完全给DMA用。为什么这么做?因为DMA需要连续的物理内存,而内核的虚拟内存映射会打碎物理连续性。你想想看,如果DMA要搬运一个大块数据,结果物理地址东一块西一块,那还怎么搞?

然后在外设节点里引用这块区域:

ethernet@40000000 {
    compatible = "vendor,eth";
    reg = <0x0 0x40000000 0x0 0x1000>;
    memory-region = <&dma_pool>;
};
核心原则: DMA内存区域要尽量大、尽量连续、尽量靠近外设总线。我一般预留至少16MB,对于高速网卡或视频采集设备,建议64MB起步。

我曾经在一个视频处理项目里,DMA区域只给了4MB,结果4K视频流一进来,DMA缓冲区频繁溢出,画面卡成PPT。后来改成32MB,问题立刻解决。

4.4 知识体系总览

下面这张图把CPU和内存节点的调整逻辑串起来了:

CPU与内存节点调整知识体系 设备树节点调整 CPU节点 内存节点 频率控制 (OPP表) 核心数控制 内存布局 DMA区域 operating-points status = "disabled" reg 地址/大小 reserved-memory 核心原则 CPU:频率与电压匹配,核心数按需启用 内存:布局准确,DMA区域连续且充足

4.5 实战避坑清单

最后,我把自己踩过的坑整理成一张表,你调设备树时对照着检查:

节点类型 常见错误 后果 检查方法
CPU OPP表电压不足 高频死机 跑stress测试,监控电压
CPU 核心数写错 部分核无法使用 cat /proc/cpuinfo
内存 reg地址偏移算错 内存容量不对 free -m 对比硬件规格
DMA 预留区域太小 DMA溢出、丢数据 dmesg | grep dma
DMA 忘了加no-map 内核和DMA冲突 检查/proc/iomem

嗯,CPU和内存节点的调整,说白了就是让内核知道你的硬件到底长什么样。别小看这几行配置,我见过太多系统因为这里写错,性能直接腰斩。你调设备树时,多花10分钟检查这些节点,后面能省下几天的调试时间。