linux内核驱动中预留内存的使用方法

[TOC]

❓ 问题描述:

linux内核驱动中预留内存一般分为下面几种情况:
1.通用性CMA(当设备驱动程序不使用时,系统可以调度给其他程序使用,当设备驱动使用时,系统再根据必定策略分配给设备驱动)
2.专用性CMA(只给专用的设备驱动使用,系统不可以给其他程序使用)
3.早期预留内存(预留一部分内存,和专用型CMA基本一样)
4.普通预留内存(可以用户自己使用,使用完成后可以再放到系统中,让系统给其他程序使用)
5./memreserve/的使用
6.uboot中传递参数,给物理内存进行分段

日志

添加打印日志信息

分析步骤

第1步:
第2步:
...

代码片段

  1. 通用性CMA
定义cma区域有2种方式:第1种在设备树中定义;第2种在.config中定义:例如定义48M的cma区域CONFIG_CMA_SIZE_MBYTES=48:其中设备树中定义的会覆盖.config定义的信息

设备树信息:
    reserved-memory {
        #address-cells = <1>;
        #size-cells = <1>;
        ranges;
        reserved: cma@70000000 {
            compatible = "shared-dma-pool";
            reusable;
            reg = <0x70000000 0x6400000>; //定义cma区域的地址和大小
            //size = <0x6400000>; //也可以只定义cma区域的大小,不定义起始地址,系统会自己根据实际情况去选择
            alignment = <0x2000>;
            linux,cma-default;
        };
    };

    reserved-driver@0 {
        compatible = "linux,virtual-free";
        memory-region = <&reserved>;
    };
    

linux内核驱动中预留内存的使用方法

linux内核驱动中预留内存的使用方法

#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/dma-mapping.h>
#include <linux/io.h>
#include <linux/slab.h>

// 定义内存大小 (10MB 和 50MB)
#define MEM_SIZE_10M  (10 * 1024 * 1024)  // 10MB
#define MEM_SIZE_50M  (50 * 1024 * 1024)  // 50MB

// 内存块结构体
struct dma_memory_block {
    void *virt_addr;       // 内核虚拟地址
    dma_addr_t dma_addr;   // DMA总线地址
    size_t size;           // 内存大小
    char *name;            // 内存块名称
};

// 设备私有数据
struct dma_dev_priv {
    struct dma_memory_block block1;  // 10MB内存块
    struct dma_memory_block block2;  // 50MB内存块
};

// 测试数据写入和读取函数
static void test_memory_access(struct dma_memory_block *block)
{
    u32 test_value = 0x12345678;
    u32 read_back;
    size_t test_offset = 0x100;  // 测试偏移位置

    // 简单测试:写入一个32位值
    *(u32 *)(block->virt_addr + test_offset) = test_value;
    read_back = *(u32 *)(block->virt_addr + test_offset);
    
    dev_info(block->name ? NULL : NULL, 
             "Memory block '%s' test:
"
             "  Wrote value: 0x%x at offset 0x%zx
"
             "  Read value: 0x%x
",
             block->name, test_value, test_offset, read_back);

    // 块测试:写入一段数据
    memset(block->virt_addr, 0xAA, 1024);  // 写入前1KB
    if (*(u8 *)block->virt_addr == 0xAA && 
        *(u8 *)(block->virt_addr + 1023) == 0xAA) {
        dev_info(block->name ? NULL : NULL, 
                 "  Block write test passed for first 1KB
");
    } else {
        dev_err(block->name ? NULL : NULL, 
                "  Block write test failed!
");
    }
}

static int dma_mem_probe(struct platform_device *pdev)
{
    struct device *dev = &pdev->dev;
    struct dma_dev_priv *priv;
    int ret = 0;

    // 分配设备私有数据
    priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
    if (!priv)
        return -ENOMEM;

    // 初始化内存块信息
    priv->block1.size = MEM_SIZE_10M;
    priv->block1.name = "10MB Block";
    priv->block2.size = MEM_SIZE_50M;
    priv->block2.name = "50MB Block";

    // 分配第一块内存 (10MB)
    priv->block1.virt_addr = dma_alloc_coherent(dev,
                                               priv->block1.size,
                                               &priv->block1.dma_addr,
                                               GFP_KERNEL);
    if (!priv->block1.virt_addr) {
        dev_err(dev, "Failed to allocate 10MB DMA memory
");
        return -ENOMEM;
    }

    // 分配第二块内存 (50MB)
    priv->block2.virt_addr = dma_alloc_coherent(dev,
                                               priv->block2.size,
                                               &priv->block2.dma_addr,
                                               GFP_KERNEL);
    if (!priv->block2.virt_addr) {
        dev_err(dev, "Failed to allocate 50MB DMA memory
");
        ret = -ENOMEM;
        goto free_block1;
    }

    // 打印第一块内存信息
    dev_info(dev, "
Allocated %s:
", priv->block1.name);
    dev_info(dev, "  Size: %zu bytes (%zu MB)
",
             priv->block1.size, priv->block1.size / (1024 * 1024));
    dev_info(dev, "  Kernel virtual address: %p
", priv->block1.virt_addr);
    dev_info(dev, "  DMA bus address: %pad
", &priv->block1.dma_addr);

    // 打印第二块内存信息
    dev_info(dev, "
Allocated %s:
", priv->block2.name);
    dev_info(dev, "  Size: %zu bytes (%zu MB)
",
             priv->block2.size, priv->block2.size / (1024 * 1024));
    dev_info(dev, "  Kernel virtual address: %p
", priv->block2.virt_addr);
    dev_info(dev, "  DMA bus address: %pad
", &priv->block2.dma_addr);

    // 测试第一块内存的读写操作
    dev_info(dev, "
Testing %s access...
", priv->block1.name);
    test_memory_access(&priv->block1);

    // 测试第二块内存的读写操作
    dev_info(dev, "
Testing %s access...
", priv->block2.name);
    test_memory_access(&priv->block2);

    platform_set_drvdata(pdev, priv);
    return 0;

free_block1:
    dma_free_coherent(dev, priv->block1.size,
                     priv->block1.virt_addr, priv->block1.dma_addr);
    return ret;
}

static int dma_mem_remove(struct platform_device *pdev)
{
    struct dma_dev_priv *priv = platform_get_drvdata(pdev);
    struct device *dev = &pdev->dev;

    // 释放第二块内存
    if (priv->block2.virt_addr) {
        dma_free_coherent(dev, priv->block2.size,
                         priv->block2.virt_addr, priv->block2.dma_addr);
        dev_info(dev, "Freed %s
", priv->block2.name);
    }

    // 释放第一块内存
    if (priv->block1.virt_addr) {
        dma_free_coherent(dev, priv->block1.size,
                         priv->block1.virt_addr, priv->block1.dma_addr);
        dev_info(dev, "Freed %s
", priv->block1.name);
    }

    return 0;
}

static const struct of_device_id dma_mem_of_match[] = {
    { .compatible = "linux,virtual-free" },
    { /* Sentinel */ }
};
MODULE_DEVICE_TABLE(of, dma_mem_of_match);

static struct platform_driver dma_mem_driver = {
    .probe = dma_mem_probe,
    .remove = dma_mem_remove,
    .driver = {
        .name = "dma-two-blocks-driver",
        .of_match_table = dma_mem_of_match,
    },
};
module_platform_driver(dma_mem_driver);

MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("DMA Driver allocating two memory blocks (10MB and 50MB)");
MODULE_AUTHOR("Your Name");    

内核输出信息(一般通过2种方式查看内存物理地址或者虚拟地址信息:1.内核启动信息 2.通过proc和sysfs文件系统方式)

可以看到分配的地址从0x70000000开始,大小为100M
Reserved memory: created CMA memory pool at 0x70000000, size 100 MiB
Reserved memory: initialized node cma@70000000, compatible id shared-dma-pool

//当设置为reusable时,可以看到物理内存区域没有变化(实际物理内存大小512M),同时被统计到了cma-reserved中
Memory: 409040K/524288K available (5461K kernel code, 170K rwdata, 1688K rodata, 296K init, 156K bss, 12848K reserved, 102400K cma-reserved)

//下面实际对应的是虚拟地址,而非物理地址,如果查看各个函数的虚拟地址,也可以查看cat /proc/kallsyms
Virtual kernel memory layout:
    vector  : 0xffff0000 - 0xffff1000   (   4 kB)
    fixmap  : 0xffc00000 - 0xfff00000   (3072 kB)
    vmalloc : 0xa0800000 - 0xff800000   (1520 MB)
    lowmem  : 0x80000000 - 0xa0000000   ( 512 MB)
    modules : 0x7f000000 - 0x80000000   (  16 MB)
      .text : 0x80008000 - 0x80703a00   (7151 kB)
      .init : 0x80704000 - 0x8074e000   ( 296 kB) //将来会进行释放
      .data : 0x8074e000 - 0x80778aa0   ( 171 kB)
       .bss : 0x8077b000 - 0x807a2038   ( 157 kB)
SLUB: HWalign=64, Order=0-3, MinObjects=0, CPUs=4, Nodes=1

可以看到内核在启动后把init内存释放了
Freeing unused kernel memory: 296K

总的物理内存(524288K) 
= 可用内存(409040K) 
+ 普通预留内存(12848K,包含内核常驻内存(kernel code + rwdata + rodata + bss)和 其他预留内存) 
+ CMA预留内存(102400K) 

linux内核驱动中预留内存的使用方法

加载驱动输出信息:

可以看到分配的物理地址为0x70300000和0x70d00000,在上面定义的范围内
dma-two-blocks-driver reserved-driver@0:
Allocated 10MB Block:
dma-two-blocks-driver reserved-driver@0:   Size: 10485760 bytes (10 MB)
dma-two-blocks-driver reserved-driver@0:   Kernel virtual address: 90300000
dma-two-blocks-driver reserved-driver@0:   DMA bus address: 0x70300000
dma-two-blocks-driver reserved-driver@0:
Allocated 50MB Block:
dma-two-blocks-driver reserved-driver@0:   Size: 52428800 bytes (50 MB)
dma-two-blocks-driver reserved-driver@0:   Kernel virtual address: 90d00000
dma-two-blocks-driver reserved-driver@0:   DMA bus address: 0x70d00000
dma-two-blocks-driver reserved-driver@0:
Testing 10MB Block access...
(NULL device *): Memory block '10MB Block' test:
  Wrote value: 0x12345678 at offset 0x100
  Read value: 0x12345678
(NULL device *):   Block write test passed for first 1KB
dma-two-blocks-driver reserved-driver@0:
Testing 50MB Block access...
(NULL device *): Memory block '50MB Block' test:
  Wrote value: 0x12345678 at offset 0x100
  Read value: 0x12345678
(NULL device *):   Block write test passed for first 1KB

/ # cat /proc/meminfo
MemTotal:         511736 kB
MemFree:          497376 kB
MemAvailable:     496420 kB
Buffers:             232 kB
Cached:             1352 kB
SwapCached:            0 kB
Active:             2012 kB
Inactive:            128 kB
Active(anon):        576 kB
Inactive(anon):       12 kB
Active(file):       1436 kB
Inactive(file):      116 kB
Unevictable:           0 kB
Mlocked:               0 kB
SwapTotal:             0 kB
SwapFree:              0 kB
Dirty:                 0 kB
Writeback:             0 kB
AnonPages:           556 kB
Mapped:              984 kB
Shmem:                32 kB
Slab:               7632 kB
SReclaimable:       4200 kB
SUnreclaim:         3432 kB
KernelStack:         416 kB
PageTables:           60 kB
NFS_Unstable:          0 kB
Bounce:                0 kB
WritebackTmp:          0 kB
CommitLimit:      255868 kB
Committed_AS:       1312 kB
VmallocTotal:    1556480 kB
VmallocUsed:           0 kB
VmallocChunk:          0 kB
CmaTotal:         102400 kB //可以看到cma的总大小为100M
CmaFree:          100608 kB

/ # free
              total        used        free      shared  buff/cache   available
Mem:         511736        8484      497464          32        5788      496512
Swap:             0           0           0

linux内核驱动中预留内存的使用方法

/ # cat /proc/iomem
60000000-7fffffff : System RAM
  60008000-607039ff : Kernel code
  6074e000-607a2037 : Kernel data

linux内核驱动中预留内存的使用方法

/ # cat /sys/kernel/debug/memblock/reserved
   0: 0x60004000..0x60007fff
   1: 0x60008280..0x607a2037
   2: 0x68000000..0x6800ccbd
   3: 0x70000000..0x763fffff
   4: 0x7fb20000..0x7fbb7fff
   5: 0x7fbb8440..0x7fbba43f
   6: 0x7fbba474..0x7fffefff
   7: 0x7ffffc00..0x7ffffc3b
   8: 0x7ffffc40..0x7ffffc7b
   9: 0x7ffffc80..0x7ffffcf7
  10: 0x7ffffd00..0x7ffffd0f
  11: 0x7ffffd40..0x7ffffd4f
  12: 0x7ffffd80..0x7ffffd83
  13: 0x7ffffdc0..0x7ffffdf1
  14: 0x7ffffe00..0x7ffffe31
  15: 0x7ffffe40..0x7ffffe71
  16: 0x7ffffe80..0x7ffffe83
  17: 0x7ffffea0..0x7ffffeea
  18: 0x7ffffeec..0x7fffff06
  19: 0x7fffff08..0x7fffff22
  20: 0x7fffff24..0x7fffff3e
  21: 0x7fffff40..0x7fffff9b
  22: 0x7fffffb0..0x7fffffff

linux内核驱动中预留内存的使用方法

什么叫做常规内存分配分不到保留的区域?

linux内核驱动中预留内存的使用方法

linux内核驱动中预留内存的使用方法

linux内核驱动中预留内存的使用方法

linux内核驱动中预留内存的使用方法

kernel code 段:存放内核的指令,只读,不会用来放栈或动态数据;
kernel data 段:存放内核的全局 / 静态变量,编译时确定大小;
内核栈、task_struct、动态分配的内核对象:都是在运行时从物理内存的常规区通过伙伴系统 + slab 等分配器获取的。

2.专用型CMA

设备树:
/*
    标记no-mao属性后,它的作用是如下:
        1.内核不会为该区域创建页表映射,因此内核无法通过常规虚拟地址访问该物理内存;即使通过 phys_to_virt() 计算虚拟地址,也会因缺少页表映射而导致访问失败(触发页错误 Oops)
        2.该区域会被标记为 MEMBLOCK_NOMAP,从内核的 “可访问内存池” 中彻底排除;
    
    没有标记linux,cma-default会有以下影响:
    则该区域不会被默认分配给通用型CMA,只有标记了linux,cma-default;属性,
    才会被默认分配给通用型CMA.
*/
reserved-memory {
        #address-cells = <1>;
        #size-cells = <1>;
        ranges;
        reserved: cma@70000000 {
            compatible = "shared-dma-pool";
            no-map; //相比通用型cma,使用no-map属性替代reusable
            reg = <0x70000000 0x6400000>; 
            alignment = <0x2000>;
        };
    };

    reserved-driver@0 {
        compatible = "linux,virtual-free";
        memory-region = <&reserved>;
    };
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/dma-mapping.h>
#include <linux/io.h>
#include <linux/slab.h>
#include <linux/of_reserved_mem.h>

// 定义内存大小 (10MB 和 20MB)
#define MEM_SIZE_10M  (10 * 1024 * 1024)  // 10MB
#define MEM_SIZE_20M  (20 * 1024 * 1024)  // 20MB

// 内存块结构体
struct dma_memory_block {
    void *virt_addr;       // 内核虚拟地址
    dma_addr_t dma_addr;   // DMA总线地址
    size_t size;           // 内存大小
    char *name;            // 内存块名称
};

// 设备私有数据
struct dma_dev_priv {
    struct dma_memory_block block1;  // 10MB内存块
    struct dma_memory_block block2;  // 20MB内存块
};

// 测试数据写入和读取函数
static void test_memory_access(struct dma_memory_block *block)
{
    u32 test_value = 0x12345678;
    u32 read_back;
    size_t test_offset = 0x100;  // 测试偏移位置

    // 简单测试:写入一个32位值
    *(u32 *)(block->virt_addr + test_offset) = test_value;
    read_back = *(u32 *)(block->virt_addr + test_offset);
    
    dev_info(block->name ? NULL : NULL, 
             "Memory block '%s' test:
"
             "  Wrote value: 0x%x at offset 0x%zx
"
             "  Read value: 0x%x
",
             block->name, test_value, test_offset, read_back);

    // 块测试:写入一段数据
    memset(block->virt_addr, 0xAA, 1024);  // 写入前1KB
    if (*(u8 *)block->virt_addr == 0xAA && 
        *(u8 *)(block->virt_addr + 1023) == 0xAA) {
        dev_info(block->name ? NULL : NULL, 
                 "  Block write test passed for first 1KB
");
    } else {
        dev_err(block->name ? NULL : NULL, 
                "  Block write test failed!
");
    }
}

static int dma_mem_probe(struct platform_device *pdev)
{
    struct device *dev = &pdev->dev;
    struct dma_dev_priv *priv;
    int ret = 0;

    // 分配设备私有数据
    priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
    if (!priv)
        return -ENOMEM;

    // 初始化内存块信息
    priv->block1.size = MEM_SIZE_10M;
    priv->block1.name = "10MB Block";
    priv->block2.size = MEM_SIZE_20M;
    priv->block2.name = "20MB Block";

    // 1. 绑定预留内存到设备(关键步骤),如果不调用则会使用默认的CMA区域
    ret = of_reserved_mem_device_init(dev);
    if (ret) {
        dev_err(dev, "of_reserved_mem_device_init failed: %d
", ret);
        return ret;
    }
    // 分配第一块内存 (10MB)
    priv->block1.virt_addr = dma_alloc_coherent(dev,
                                               priv->block1.size,
                                               &priv->block1.dma_addr,
                                               GFP_KERNEL);
    if (!priv->block1.virt_addr) {
        dev_err(dev, "Failed to allocate 10MB DMA memory
");
        return -ENOMEM;
    }

    // 分配第二块内存 (20MB)
    priv->block2.virt_addr = dma_alloc_coherent(dev,
                                               priv->block2.size,
                                               &priv->block2.dma_addr,
                                               GFP_KERNEL);
    if (!priv->block2.virt_addr) {
        dev_err(dev, "Failed to allocate 20MB DMA memory
");
        ret = -ENOMEM;
        goto free_block1;
    }

    // 打印第一块内存信息
    dev_info(dev, "
Allocated %s:
", priv->block1.name);
    dev_info(dev, "  Size: %zu bytes (%zu MB)
",
             priv->block1.size, priv->block1.size / (1024 * 1024));
    dev_info(dev, "  Kernel virtual address: %p
", priv->block1.virt_addr);
    dev_info(dev, "  DMA bus address: %pad
", &priv->block1.dma_addr);

    // 打印第二块内存信息
    dev_info(dev, "
Allocated %s:
", priv->block2.name);
    dev_info(dev, "  Size: %zu bytes (%zu MB)
",
             priv->block2.size, priv->block2.size / (1024 * 1024));
    dev_info(dev, "  Kernel virtual address: %p
", priv->block2.virt_addr);
    dev_info(dev, "  DMA bus address: %pad
", &priv->block2.dma_addr);

    // 测试第一块内存的读写操作
    dev_info(dev, "
Testing %s access...
", priv->block1.name);
    test_memory_access(&priv->block1);

    // 测试第二块内存的读写操作
    dev_info(dev, "
Testing %s access...
", priv->block2.name);
    test_memory_access(&priv->block2);

    platform_set_drvdata(pdev, priv);
    return 0;

free_block1:
    dma_free_coherent(dev, priv->block1.size,
                     priv->block1.virt_addr, priv->block1.dma_addr);
    return ret;
}

static int dma_mem_remove(struct platform_device *pdev)
{
    struct dma_dev_priv *priv = platform_get_drvdata(pdev);
    struct device *dev = &pdev->dev;

    // 释放第二块内存
    if (priv->block2.virt_addr) {
        dma_free_coherent(dev, priv->block2.size,
                         priv->block2.virt_addr, priv->block2.dma_addr);
        dev_info(dev, "Freed %s
", priv->block2.name);
    }

    // 释放第一块内存
    if (priv->block1.virt_addr) {
        dma_free_coherent(dev, priv->block1.size,
                         priv->block1.virt_addr, priv->block1.dma_addr);
        dev_info(dev, "Freed %s
", priv->block1.name);
    }

    return 0;
}

static const struct of_device_id dma_mem_of_match[] = {
    { .compatible = "linux,virtual-free" },
    { /* Sentinel */ }
};
MODULE_DEVICE_TABLE(of, dma_mem_of_match);

static struct platform_driver dma_mem_driver = {
    .probe = dma_mem_probe,
    .remove = dma_mem_remove,
    .driver = {
        .name = "dma-two-blocks-driver",
        .of_match_table = dma_mem_of_match,
    },
};
module_platform_driver(dma_mem_driver);

MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("DMA Driver allocating two memory blocks (10MB and 20MB)");
MODULE_AUTHOR("Your Name");
    
内核输出信息: 可以内核分配了2个保留区域
1.设备树中配置的区域
Reserved memory: created DMA memory pool at 0x70000000, size 100 MiB
Reserved memory: initialized node cma@70000000, compatible id shared-dma-pool
2. .config文件中配置了48M的默认CMA区域
cma: Reserved 48 MiB at 0x7d000000

//可以看到当使用no-mao属性时,整体物理内存区域减小了100M,同时都没有被统计到整个区域中
Memory: 360792K/421888K available (5461K kernel code, 170K rwdata, 1688K rodata, 296K init, 156K bss, 11944K reserved, 49152K cma-reserved)

驱动输出信息如下: 如果不调用of_reserved_mem_device_init函数,可以看到分配的内存地址使用的默认CMA区域
dma-two-blocks-driver reserved-driver@0:
Allocated 10MB Block:
dma-two-blocks-driver reserved-driver@0:   Size: 10485760 bytes (10 MB)
dma-two-blocks-driver reserved-driver@0:   Kernel virtual address: 9d300000
dma-two-blocks-driver reserved-driver@0:   DMA bus address: 0x7d300000
dma-two-blocks-driver reserved-driver@0:
Allocated 20MB Block:
dma-two-blocks-driver reserved-driver@0:   Size: 20971520 bytes (20 MB)
dma-two-blocks-driver reserved-driver@0:   Kernel virtual address: 9dd00000
dma-two-blocks-driver reserved-driver@0:   DMA bus address: 0x7dd00000
dma-two-blocks-driver reserved-driver@0:
Testing 10MB Block access...
(NULL device *): Memory block '10MB Block' test:
  Wrote value: 0x12345678 at offset 0x100
  Read value: 0x12345678
(NULL device *):   Block write test passed for first 1KB
dma-two-blocks-driver reserved-driver@0:
Testing 20MB Block access...
(NULL device *): Memory block '20MB Block' test:
  Wrote value: 0x12345678 at offset 0x100
  Read value: 0x12345678
(NULL device *):   Block write test passed for first 1KB

驱动输出信息如下: 如果调用of_reserved_mem_device_init函数,可以看到分配的内存地址使用的是设备树中配置的区域
dma-two-blocks-driver reserved-driver@0: assigned reserved memory node cma@70000000
dma-two-blocks-driver reserved-driver@0:
Allocated 10MB Block:
dma-two-blocks-driver reserved-driver@0:   Size: 10485760 bytes (10 MB)
dma-two-blocks-driver reserved-driver@0:   Kernel virtual address: ab300000
dma-two-blocks-driver reserved-driver@0:   DMA bus address: 0x70000000
dma-two-blocks-driver reserved-driver@0:
Allocated 20MB Block:
dma-two-blocks-driver reserved-driver@0:   Size: 20971520 bytes (20 MB)
dma-two-blocks-driver reserved-driver@0:   Kernel virtual address: ad300000
dma-two-blocks-driver reserved-driver@0:   DMA bus address: 0x72000000
dma-two-blocks-driver reserved-driver@0:
Testing 10MB Block access...
(NULL device *): Memory block '10MB Block' test:
  Wrote value: 0x12345678 at offset 0x100
  Read value: 0x12345678
(NULL device *):   Block write test passed for first 1KB
dma-two-blocks-driver reserved-driver@0:
Testing 20MB Block access...
(NULL device *): Memory block '20MB Block' test:
  Wrote value: 0x12345678 at offset 0x100
  Read value: 0x12345678
(NULL device *):   Block write test passed for first 1KB

//可以看到配置了no-mao属性,整体内存变小
/ # free
              total        used        free      shared  buff/cache   available
Mem:         410240        8448      396036          32        5756      395300
Swap:             0           0           0

//no-map属性指示内核不要为该区域创建线性映射,该区域被标记为MEMBLOCK_NOMAP,内核不会将其视为常规的可预留内存区域进行展示
cat /sys/kernel/debug/memblock/reserved
   0: 0x60004000..0x60007fff
   1: 0x60008280..0x607a2037
   2: 0x68000000..0x6800cc7d
   3: 0x7cb3a000..0x7cbd1fff
   4: 0x7cbd2480..0x7cbd447f
   5: 0x7cbd44a8..0x7cde1fff
   6: 0x7ceaa000..0x7cffefff
   7: 0x7cfffbc0..0x7cfffbfb
   8: 0x7cfffc00..0x7cfffc3b
   9: 0x7cfffc40..0x7cfffcb7
  10: 0x7cfffcc0..0x7cfffccf
  11: 0x7cfffd00..0x7cfffd0f
  12: 0x7cfffd40..0x7cfffd43
  13: 0x7cfffd80..0x7cfffd83
  14: 0x7cfffdc0..0x7cfffdf1
  15: 0x7cfffe00..0x7cfffe31
  16: 0x7cfffe40..0x7cfffe71
  17: 0x7cfffe7c..0x7cfffec6
  18: 0x7cfffec8..0x7cfffee2
  19: 0x7cfffee4..0x7cfffefe
  20: 0x7cffff00..0x7cffff1b
  21: 0x7cffff24..0x7cffff3e
  22: 0x7cffff40..0x7cffff9b
  23: 0x7cffffb0..0x7fffffff

3.早期使用的预留内存

设备树定义:
reserved-memory {
        #address-cells = <1>;
        #size-cells = <1>;
        ranges;
        reserved: cma@70000000 {
            no-map;
            reg = <0x70000000 0x6400000>;
            alignment = <0x2000>;
        };
    };

    reserved-driver@0 {
            compatible = "linux,virtual-free";
            memory-region = <&reserved>;
    };
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/io.h>
#include <linux/of_reserved_mem.h>
#include <linux/slab.h>

/* 设备私有数据结构 */
struct reserved_dev_data {
    void __iomem *vaddr;       // 虚拟地址(devm_memremap返回)
    phys_addr_t paddr;         // 物理地址
    size_t size;               // 内存大小
    struct device *dev;        // 设备指针
};

/* 探测函数:设备匹配时调用 */
static int reserved_mem_probe(struct platform_device *pdev)
{
    struct device *dev = &pdev->dev;
    struct reserved_dev_data *dev_data;
    struct device_node *rmem_node;
    struct resource res;
    u32 read_val;
    int ret;
    u32 test_val = 0x12345678;

    /* 1. 分配并初始化设备私有数据 */
    dev_data = devm_kzalloc(dev, sizeof(*dev_data), GFP_KERNEL);
    if (!dev_data) {
        dev_err(dev, "Failed to allocate device data
");
        return -ENOMEM;
    }
    dev_data->dev = dev;

    /* 2. 从设备树获取内存区域节点 */
    rmem_node = of_parse_phandle(dev->of_node, "memory-region", 0);
    if (!rmem_node) {
        dev_err(dev, "Device tree has no 'memory-region' property
");
        return -ENODEV;
    }

    /* 3. 解析物理地址和大小 */
    ret = of_address_to_resource(rmem_node, 0, &res);
    of_node_put(rmem_node);  // 释放节点引用
    if (ret) {
        dev_err(dev, "Failed to parse memory resource: %d
", ret);
        return ret;
    }

    dev_data->paddr = res.start;
    dev_data->size = resource_size(&res);
    dev_info(dev, "Found reserved memory:
");
    dev_info(dev, "  Physical address: 0x%pa
", &dev_data->paddr);
    dev_info(dev, "  Size: %zu bytes (%zu MB)
", 
             dev_data->size, dev_data->size / (1024 * 1024));

    /* 4. 使用devm_memremap映射内存(自动管理生命周期) */
    dev_data->vaddr = devm_memremap(dev, dev_data->paddr, 
                                   dev_data->size, MEMREMAP_WB);
    if (IS_ERR(dev_data->vaddr)) {
         dev_err(dev, "devm_memremap failed: %d
", ret);
        return ret;
    }

    /* 5. 内存读写测试 */
    *(u32 *)(dev_data->vaddr) = test_val;
    read_val = *(u32 *)(dev_data->vaddr);
    if (read_val == test_val) {
        dev_info(dev, "Memory test passed: 0x%x == 0x%x
", test_val, read_val);
    } else {
        dev_err(dev, "Memory test failed: 0x%x != 0x%x
", test_val, read_val);
        return -EIO;
    }

    /* 6. 保存私有数据 */
    platform_set_drvdata(pdev, dev_data);
    dev_info(dev, "Probe successful
");
    return 0;
}

/* 移除函数:设备卸载时调用 */
static int reserved_mem_remove(struct platform_device *pdev)
{
    struct reserved_dev_data *dev_data = platform_get_drvdata(pdev);
    dev_info(dev_data->dev, "Device removed (devm will auto-release memory)
");
    return 0;
}

/* 设备树匹配表 */
static const struct of_device_id reserved_mem_of_match[] = {
    { .compatible = "linux,virtual-free" },
    { /* Sentinel */ }
};
MODULE_DEVICE_TABLE(of, reserved_mem_of_match);

/* 平台驱动结构体 */
static struct platform_driver reserved_mem_driver = {
    .probe  = reserved_mem_probe,
    .remove = reserved_mem_remove,
    .driver = {
        .name           = "reserved-mem-driver",
        .of_match_table = reserved_mem_of_match,
    },
};
module_platform_driver(reserved_mem_driver);

MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Reserved Memory Driver with devm_memremap (supports reusable)");
MODULE_AUTHOR("Your Name");
内核输出信息: 只有.config中定义的cma区域,而没有dts中定义的区域
cma: Reserved 48 MiB at 0x7d000000

//同样定义了no-map区域,但是没有被统计到内存中
Memory: 360792K/421888K available (5461K kernel code, 170K rwdata, 1688K rodata, 296K init, 156K bss, 11944K reserved, 49152K cma-reserved)

//可以看到整体内存变小了
/ # free
              total        used        free      shared  buff/cache   available
Mem:         410240        8760      395732          32        5748      394992
Swap:             0           0           0

//no-map属性指示内核不要为该区域创建线性映射,该区域被标记为MEMBLOCK_NOMAP,内核不会将其视为常规的可预留内存区域进行展示
cat /sys/kernel/debug/memblock/reserved
   0: 0x60004000..0x60007fff
   1: 0x60008280..0x607a2037
   2: 0x68000000..0x6800cc45
   3: 0x7cb3a000..0x7cbd1fff
   4: 0x7cbd24c0..0x7cbd44bf
   5: 0x7cbd44dc..0x7cde1fff
   6: 0x7ceaa000..0x7cffefff
   7: 0x7cfffbc0..0x7cfffbfb
   8: 0x7cfffc00..0x7cfffc3b
   9: 0x7cfffc40..0x7cfffcb7
  10: 0x7cfffcc0..0x7cfffccf
  11: 0x7cfffd00..0x7cfffd0f
  12: 0x7cfffd40..0x7cfffd43
  13: 0x7cfffd80..0x7cfffd83
  14: 0x7cfffdc0..0x7cfffdf1
  15: 0x7cfffe00..0x7cfffe31
  16: 0x7cfffe40..0x7cfffe71
  17: 0x7cfffe7c..0x7cfffec6
  18: 0x7cfffec8..0x7cfffee2
  19: 0x7cfffee4..0x7cfffefe
  20: 0x7cffff00..0x7cffff1b
  21: 0x7cffff24..0x7cffff3e
  22: 0x7cffff40..0x7cffff9b
  23: 0x7cffffb0..0x7fffffff

驱动输出信息:
reserved-mem-driver reserved-driver@0: Found reserved memory:
reserved-mem-driver reserved-driver@0:   Physical address: 0x0x70000000
reserved-mem-driver reserved-driver@0:   Size: 104857600 bytes (100 MB)
reserved-mem-driver reserved-driver@0: Memory test passed: 0x12345678 == 0x12345678
reserved-mem-driver reserved-driver@0: Probe successful
  1. 普通预留内存 //相比早期预留内存,缺少了no-map属性
设备树信息如下:
    reserved-memory {
        #address-cells = <1>;
        #size-cells = <1>;
        ranges;
        reserved: cma@70000000 {
            reg = <0x70000000 0x6400000>;
            alignment = <0x2000>;
        };
    };

    reserved-driver@0 {
            compatible = "linux,virtual-free";
            memory-region = <&reserved>;
    };
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/io.h>
#include <linux/of_reserved_mem.h>
#include <linux/memblock.h>

/* 设备私有数据结构 */
struct reserved_dev {
    void __iomem *vaddr;        /* 映射后的虚拟地址 */
    phys_addr_t paddr;          /* 物理地址 */
    size_t size;                /* 内存大小 */
};

/* 设备探测函数 */
static int reserved_probe(struct platform_device *pdev)
{
    struct device *dev = &pdev->dev;
    struct reserved_dev *dev_data;
    struct device_node *rmem_node;
    struct resource res;
    int ret;
    u32 test_val, read_val;
    size_t i;

    /* 分配设备私有数据 */
    dev_data = devm_kzalloc(dev, sizeof(*dev_data), GFP_KERNEL);
    if (!dev_data)
        return -ENOMEM;

    /* 解析设备树中的内存区域 */
    rmem_node = of_parse_phandle(dev->of_node, "memory-region", 0);
    if (!rmem_node) {
        dev_err(dev, "Failed to get memory-region from device tree
");
        return -ENODEV;
    }

    /* 获取物理地址和大小 */
    ret = of_address_to_resource(rmem_node, 0, &res);
    of_node_put(rmem_node);
    if (ret) {
        dev_err(dev, "Failed to parse memory resource: %d
", ret);
        return ret;
    }

    dev_data->paddr = res.start;
    dev_data->size = resource_size(&res);
    
    /* 打印物理地址信息 */
    dev_info(dev, "=== 物理地址信息 ===");
    dev_info(dev, "起始物理地址: 0x%pa", &dev_data->paddr);
    dev_info(dev, "结束物理地址: 0x%pa", &(phys_addr_t){dev_data->paddr + dev_data->size - 1});
    dev_info(dev, "内存大小: %zu 字节", dev_data->size);

   // dev_info(dev, "memremap前通过phys_to_virt得到的虚拟地址: 0x%p", phys_to_virt(dev_data->paddr));

    /* 映射内存 */
    dev_data->vaddr = memremap(dev_data->paddr, dev_data->size, MEMREMAP_WB);
    if (!dev_data->vaddr) {
        dev_err(dev, "Failed to memremap reserved memory
");
        ret = -ENOMEM;
        goto release_mem;
    }

    /* 打印虚拟地址信息 */
    dev_info(dev, "=== 虚拟地址信息 ===");
    dev_info(dev, "起始虚拟地址: 0x%p", dev_data->vaddr);
    dev_info(dev, "结束虚拟地址: 0x%p", dev_data->vaddr + dev_data->size - 1);

    /* 写入测试数据 */
    test_val = 0xAA55AA55;
    dev_info(dev, "开始写入测试数据 (起始值: 0x%x)", test_val);
    
    /* 填充整个内存区域 */
    for (i = 0; i < dev_data->size; i += sizeof(u32)) {
        iowrite32(test_val, dev_data->vaddr + i);
        test_val += 0x11111111;
    }

    /* 读取验证 */
    test_val = 0xAA55AA55;
    dev_info(dev, "开始验证内存数据...");
    
    for (i = 0; i < dev_data->size; i += sizeof(u32)) {
        read_val = ioread32(dev_data->vaddr + i);
        if (read_val != test_val) {
            dev_err(dev, "地址偏移 0x%zu 验证失败: 预期 0x%x, 实际 0x%x",
                    i, test_val, read_val);
            goto unmap_mem;
        }
        test_val += 0x11111111;
    }

    dev_info(dev, "内存读写测试成功完成");
    platform_set_drvdata(pdev, dev_data);

    free_reserved_area( dev_data->vaddr, dev_data->vaddr+dev_data->size,-1,NULL);

    return 0;

unmap_mem:
    memunmap(dev_data->vaddr);
release_mem:
    of_reserved_mem_device_release(dev);
    return ret;
}

/* 设备移除函数 */
static int reserved_remove(struct platform_device *pdev)
{
    struct device *dev = &pdev->dev;
    struct reserved_dev *dev_data = platform_get_drvdata(pdev);
    
    /* 手动释放内存映射 */
    if (dev_data && dev_data->vaddr) {
        dev_info(dev, "释放虚拟地址映射: 0x%p", dev_data->vaddr);
        memunmap(dev_data->vaddr);
        dev_data->vaddr = NULL;
    }
    
    /* 释放预留内存绑定 */
    of_reserved_mem_device_release(dev);
    dev_info(dev, "预留内存已释放 (物理地址: 0x%pa)", &dev_data->paddr);
    return 0;
}

/* 设备树匹配表 */
static const struct of_device_id reserved_of_match[] = {
    { .compatible = "linux,virtual-free" },
    { /* Sentinel */ }
};
MODULE_DEVICE_TABLE(of, reserved_of_match);

/* 平台驱动结构体 */
static struct platform_driver reserved_driver = {
    .probe = reserved_probe,
    .remove = reserved_remove,
    .driver = {
        .name = "reserved-memory-driver",
        .of_match_table = reserved_of_match,
    },
};
module_platform_driver(reserved_driver);

MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Reserved Memory Driver with Address Printing");
MODULE_AUTHOR("Your Name");
内核输出信息如下:
//可以看到只有默认的cma区域
cma: Reserved 48 MiB at 0x7d000000

//可以看到整体内存并没有减少,但是可用区域是减少到,并且100M的预留内存存放到了reserved区域中
Memory: 359992K/524288K available (5461K kernel code, 170K rwdata, 1688K rodata, 296K init, 156K bss, 115144K reserved, 49152K cma-reserved)

加载驱动前,可以看到整体total内存是409440k
/ # free
              total        used        free      shared  buff/cache   available
Mem:         409440        7884      395792          32        5764      395068
Swap:             0           0           0

加载驱动后,可以看到整体total内存是511840,整体可以内存提高了,由于在驱动中释放了内存
free
              total        used        free      shared  buff/cache   available
Mem:         511840        7916      497916          32        6008      497304
Swap:             0           0           0

//可以看到这里也能看到预留的内存
/ # cat /sys/kernel/debug/memblock/reserved
   0: 0x60004000..0x60007fff
   1: 0x60008280..0x607a2037
   2: 0x68000000..0x6800cc1f
   3: 0x70000000..0x763fffff // 100M的预留内存
   4: 0x7cb3a000..0x7cbd1fff
   5: 0x7cbd2500..0x7cbd44ff
   6: 0x7cbd4510..0x7cffefff
   7: 0x7cfffc00..0x7cfffc3b
   8: 0x7cfffc40..0x7cfffc7b
   9: 0x7cfffc80..0x7cfffcf7
  10: 0x7cfffd00..0x7cfffd0f
  11: 0x7cfffd40..0x7cfffd4f
  12: 0x7cfffd80..0x7cfffd83
  13: 0x7cfffdc0..0x7cfffdf1
  14: 0x7cfffe00..0x7cfffe31
  15: 0x7cfffe40..0x7cfffe71
  16: 0x7cfffe80..0x7cfffe83
  17: 0x7cfffea0..0x7cfffeea
  18: 0x7cfffeec..0x7cffff06
  19: 0x7cffff08..0x7cffff22
  20: 0x7cffff24..0x7cffff3e
  21: 0x7cffff40..0x7cffff9b
  22: 0x7cffffb0..0x7fffffff

reserved-memory-driver reserved-driver@0: === 物理地址信息 ===
reserved-memory-driver reserved-driver@0: 起始物理地址: 0x0x70000000
reserved-memory-driver reserved-driver@0: 结束物理地址: 0x0x763fffff
reserved-memory-driver reserved-driver@0: 内存大小: 104857600 字节
reserved-memory-driver reserved-driver@0: === 虚拟地址信息 ===
reserved-memory-driver reserved-driver@0: 起始虚拟地址: 0x90000000
reserved-memory-driver reserved-driver@0: 结束虚拟地址: 0x963fffff
reserved-memory-driver reserved-driver@0: 开始写入测试数据 (起始值: 0xaa55aa55)
reserved-memory-driver reserved-driver@0: 开始验证内存数据...
reserved-memory-driver reserved-driver@0: 内存读写测试成功完成

5./memreserve/节点的使用 这种方式简单,如果没有回收的要求,可以使用这种方式

设备树信息:
 /memreserve/ 0x70000000 0x6400000;
 
可以看到物理整体大小没有变化,但是可用内存减少了,由于预留的内存被放到了reserved区域中
Memory: 359992K/524288K available (5461K kernel code, 170K rwdata, 1688K rodata, 296K init, 156K bss, 115144K reserved, 49152K cma-reserved)

//可以看到可用内存减少了
/ # free
              total        used        free      shared  buff/cache   available
Mem:         409440        8060      395628          32        5752      394896
Swap:             0           0           0

//可以看到预留的内存被放到了reserved区域中
/ # cat /sys/kernel/debug/memblock/reserved
   0: 0x60004000..0x60007fff
   1: 0x60008280..0x607a2037
   2: 0x68000000..0x6800ca53
   3: 0x70000000..0x763fffff
   4: 0x7cb3a000..0x7cbd1fff
   5: 0x7cbd28c0..0x7cbd48bf
   6: 0x7cbd48f4..0x7cffefff
   7: 0x7cfffc00..0x7cfffc3b
   8: 0x7cfffc40..0x7cfffc7b
   9: 0x7cfffc80..0x7cfffcf7
  10: 0x7cfffd00..0x7cfffd0f
  11: 0x7cfffd40..0x7cfffd4f
  12: 0x7cfffd80..0x7cfffd83
  13: 0x7cfffdc0..0x7cfffdf1
  14: 0x7cfffe00..0x7cfffe31
  15: 0x7cfffe40..0x7cfffe71
  16: 0x7cfffe80..0x7cfffe83
  17: 0x7cfffea0..0x7cfffeea
  18: 0x7cfffeec..0x7cffff06
  19: 0x7cffff08..0x7cffff22
  20: 0x7cffff24..0x7cffff3e
  21: 0x7cffff40..0x7cffff9b
  22: 0x7cffffb0..0x7fffffff

6.将一块内存分为多个物理段


//mem格式是size@addr
uboot中bootargs设置为:console=ttyAMA0,115200 rootwait root=/dev/mmcblk0 mem=256M@0x60000000 mem=255M@0x70100000

内核信息如下:
Kernel command line: console=ttyAMA0,115200 rootwait root=/dev/mmcblk0 mem=256M@0x60000000 mem=255M@0x70100000

可以看到整体物理内存减小了1M
Memory: 461368K/523264K available (5461K kernel code, 170K rwdata, 1688K rodata, 296K init, 156K bss, 12744K reserved, 49152K cma-reserved)

//输出表明系统存在两个独立的可用物理内存段,可以看到输出了2段物理内存区域
/ # cat /sys/kernel/debug/memblock/memory
   0: 0x60000000..0x6fffffff
   1: 0x70100000..0x7fffffff

/ # cat /sys/kernel/debug/memblock/reserved
   0: 0x60004000..0x60007fff
   1: 0x60008280..0x607a2037
   2: 0x68000000..0x6800ca33
   3: 0x7cb3a000..0x7cbd1fff
   4: 0x7cbd28c0..0x7cbd48bf
   5: 0x7cbd48f4..0x7cffefff
   6: 0x7cfffb00..0x7cfffb3b
   7: 0x7cfffb40..0x7cfffb7b
   8: 0x7cfffb80..0x7cfffbf7
   9: 0x7cfffc00..0x7cfffc0f
  10: 0x7cfffc40..0x7cfffc4f
  11: 0x7cfffc80..0x7cfffc83
  12: 0x7cfffcc0..0x7cfffcc3
  13: 0x7cfffd00..0x7cfffd59
  14: 0x7cfffd80..0x7cfffdd9
  15: 0x7cfffe00..0x7cfffe59
  16: 0x7cfffe7c..0x7cfffec6
  17: 0x7cfffec8..0x7cfffee2
  18: 0x7cfffee4..0x7cfffefe
  19: 0x7cffff00..0x7cffff1b
  20: 0x7cffff24..0x7cffff3e
  21: 0x7cffff40..0x7cffff9b
  22: 0x7cffffb0..0x7fffffff
总结: 
    1.通过内核日志先看整体物理内存有多少,然后再通过free命令看可用内存有多少
    2.通过cat /sys/kernel/debug/memblock/reserved 命令看预留的内存的位置

图片

✅ 结论

输出结论

待查资料问题

  • ❓ 问题 1:?
  • ❓ 问题 2:?

参考链接

  • 官方文档
© 版权声明
THE END
如果内容对您有所帮助,就支持一下吧!
点赞0 分享
emmmmmmm咩的头像 - 宋马
评论 抢沙发

请登录后发表评论

    暂无评论内容