Linux指定物理地址分配内存

在Linux内核中指定物理地址分配内存需要谨慎操作,通常涉及保留内存区域并将其映射到内核虚拟地址空间。以下是具体步骤和示例:

步骤说明

  1. 保留物理内存区域
    在系统启动时,通过内核启动参数或设备树保留指定物理地址的内存,防止内核将其分配给其他用途。

  2. 映射物理地址到内核虚拟地址
    在内核模块中使用 ioremap()memremap() 将保留的物理地址映射到内核的虚拟地址空间,以便访问。

  3. 访问和释放内存
    通过映射后的虚拟地址进行读写操作,使用完成后解除映射并释放资源。


具体实现

1. 保留物理内存

  • 方法一:内核启动参数(x86/通用)
    在GRUB等引导加载器中添加参数,保留指定区域:

    1
    memmap=1M$0x10000000   # 保留从0x10000000开始的1MB内存
  • 方法二:设备树(ARM/嵌入式)
    在设备树中定义保留内存区域:

    1
    2
    3
    4
    5
    6
    7
    reserved-memory {
    my_region: my_region@10000000 {
    compatible = "my-reserved-memory";
    reg = <0x10000000 0x00100000>; // 起始地址0x10000000,大小1MB
    no-map; // 防止内核映射此区域
    };
    };

2. 内核模块映射物理地址

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#include <linux/init.h>
#include <linux/module.h>
#include <linux/io.h>

#define PHYS_ADDR 0x10000000
#define MEM_SIZE 0x00100000 // 1MB

static void __iomem *vaddr;

static int __init my_module_init(void)
{
// 请求内存区域(可选,确保未被占用)
if (!request_mem_region(PHYS_ADDR, MEM_SIZE, "my_mem")) {
pr_err("Failed to reserve memory region\n");
return -EBUSY;
}

// 映射物理地址到内核虚拟地址
vaddr = ioremap(PHYS_ADDR, MEM_SIZE);
if (!vaddr) {
pr_err("ioremap failed\n");
release_mem_region(PHYS_ADDR, MEM_SIZE);
return -ENOMEM;
}

// 示例:写入数据到该内存区域
iowrite32(0x12345678, vaddr);

// 示例:读取数据
pr_info("Data: 0x%x\n", ioread32(vaddr));

return 0;
}

static void __exit my_module_exit(void)
{
if (vaddr) {
iounmap(vaddr); // 解除映射
release_mem_region(PHYS_ADDR, MEM_SIZE); // 释放保留区域
}
}

module_init(my_module_init);
module_exit(my_module_exit);
MODULE_LICENSE("GPL");

注意事项

  • 物理地址合法性
    确保指定的物理地址属于系统可用内存范围,且未被其他硬件(如DMA控制器)占用,否则可能导致系统崩溃。

  • 缓存一致性
    使用 ioremap() 默认映射为无缓存(Uncached),若需访问普通内存(如RAM),可改用 memremap() 并指定 MEMREMAP_WB 缓存模式:

    1
    vaddr = memremap(PHYS_ADDR, MEM_SIZE, MEMREMAP_WB);
  • 内存屏障
    直接操作硬件寄存器或共享内存时,使用内存屏障(如 mb())确保读写顺序。

  • 平台兼容性
    x86架构可能启用物理地址扩展(PAE),需确认物理地址是否在32位可寻址范围内(或使用64位内核)。


用户空间访问物理内存

若需在用户空间访问指定物理地址,可通过 /dev/mem 设备结合 mmap

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <fcntl.h>
#include <sys/mman.h>

#define PHYS_ADDR 0x10000000
#define PAGE_SIZE 4096

int fd = open("/dev/mem", O_RDWR | O_SYNC);
void *vaddr = mmap(NULL, PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, PHYS_ADDR);

// 读写操作
*(uint32_t*)vaddr = 0x12345678;
printf("Value: 0x%x\n", *(uint32_t*)vaddr);

munmap(vaddr, PAGE_SIZE);
close(fd);

注意:需以root权限运行,且内核配置需启用 CONFIG_STRICT_DEVMEM 的宽松设置(不建议生产环境使用)。


通过上述方法,可以在Linux内核或用户空间中安全地分配并访问指定物理地址的内存。