日常开发过程可能要预留一段物理内存出来提供特殊场景使用(独占一段内存不被系统所使用)。

本文讲解3种预留内存的方法,以及对预留内存的使用。



一、memblock方式预留内存

1.1 memblock内存管理

mmeblock是内存的一种管理机制,主要管理这两种内存:一种是系统可用部分的物理内存(usable),也就是/proc/meminfo里看到的总内存都是提供给系统使用的;另一种是用户预留部分的内存(reserved),用户自己特殊使用,这部分在系统总内存里看不到,本文主要讲解该部分内存怎么预留。

memblock 结构体维护着上述两种内存, 其中成员 memory 维护着可用物理内存区域;成员 reserved 维护预留的内存区域。

struct memblock {
        bool bottom_up;  /* is bottom up direction? */
        phys_addr_t current_limit;
        struct memblock_type memory;    //维护着可用物理内存区域
        struct memblock_type reserved;  //维护着预留的内存区域
#ifdef CONFIG_HAVE_MEMBLOCK_PHYS_MAP
        struct memblock_type physmem;
#endif
};

如下这篇对memblock原理讲解的很详细

https://biscuitos.github.io/blog/MMU-ARM32-MEMBLOCK-memblock_reserve/

我们在系统下也可以看到这两种内存的分布情况。
1、dmesg的e820里打印了内存分布情况,可以看到usable和 reserved两种标注的内存块。

  • usable:就是系统可用部分的物理内存(System RAM)。
  • reserved: 就是预留部分的内存。
[    0.000000] e820: BIOS-provided physical RAM map:
[    0.000000] BIOS-e820: [mem 0x0000000000000000-0x000000000009ebff] usable
[    0.000000] BIOS-e820: [mem 0x000000000009ec00-0x000000000009ffff] reserved
[    0.000000] BIOS-e820: [mem 0x00000000000dc000-0x00000000000fffff] reserved
[    0.000000] BIOS-e820: [mem 0x0000000000100000-0x00000000bfecffff] usable
[    0.000000] BIOS-e820: [mem 0x00000000bfed0000-0x00000000bfefefff] ACPI data
[    0.000000] BIOS-e820: [mem 0x00000000bfeff000-0x00000000bfefffff] ACPI NVS
[    0.000000] BIOS-e820: [mem 0x00000000bff00000-0x00000000bfffffff] usable
[    0.000000] BIOS-e820: [mem 0x00000000f0000000-0x00000000f7ffffff] reserved
[    0.000000] BIOS-e820: [mem 0x00000000fec00000-0x00000000fec0ffff] reserved
[    0.000000] BIOS-e820: [mem 0x00000000fee00000-0x00000000fee00fff] reserved
[    0.000000] BIOS-e820: [mem 0x00000000fffe0000-0x00000000ffffffff] reserved
[    0.000000] BIOS-e820: [mem 0x0000000100000000-0x000000013fffffff] usable

2、或者在cat /proc/iomem |grep 'System RAM’的 io内存分布里也能看到系统可用部分的物理内存的分布情况。4段System RAM加起来就是总的可用物理内存。


[root@localhost ~]# cat /proc/iomem |grep 'System RAM'
00001000-0009ebff : System RAM
00100000-bfecffff : System RAM
bff00000-bfffffff : System RAM
100000000-13fffffff : System RAM

1.2 memblock 方式预留内存方法

方法很简单,通过memblock_reserve() 将一段物理内存放在预留区里,在setup.c中setup_arch启函数中添加,内核补丁如下(基于:linux3.10.0-123.):

diff -uNrp a/arch/x86/kernel/setup.c b/arch/x86/kernel/setup.c
--- a/arch/x86/kernel/setup.c   2021-01-29 23:09:08.443072526 -0800
+++ b/arch/x86/kernel/setup.c   2021-01-29 23:31:53.521307672 -0800
@@ -907,6 +907,8 @@ static void rh_check_supported(void)

 void __init setup_arch(char **cmdline_p)
 {
+
+        struct memblock_region *reg;
        memblock_reserve(__pa_symbol(_text),
                         (unsigned long)__bss_stop - (unsigned long)_text);

@@ -1035,6 +1037,13 @@ void __init setup_arch(char **cmdline_p)
         * again from within noexec_setup() during parsing early parameters
         * to honor the respective command line option.
         */
+
+         memblock_reserve(0x100000000, 0x2000000);
+        pr_info("Scan equal region:\n");
+        for_each_memblock(reserved, reg)  //遍历打印reserved所有分区
+                pr_info("Region [%llx -- %llx]\n", (u64)(reg->base),
+                        (u64)(reg->base + reg->size));
+
        x86_configure_nx();

        parse_early_param();

memblock_reserve(0x100000000, 0x2000000);函数完成了对ram起始地址0x100000000开始保留32MB(0x2000000)内存。reserved链表管理着预留区域的内存,memblock_reserve会将预留内存段插入链表新节点里(如果出现reserve之间地址相互覆盖的,reserver会将它们合并成一个内存区,即一个节点)。for_each_memblock()可以获取每个预留区内存信息并打印。

该方式是否成功预留出内存,可以在dmesg中查看打印,如果要查看memblock调试打印信息,可以在grub中加入:memblock=debug。

但实际测试,在e820和iomem里并没有显现该区域内存信息。猜测是memblock_reserve()没有把内存块纳入e820管理中,有待研究。

1.3 预留内存访问

通常访问指定物理地址的内存方式有很多种,如:

memremap / ioramp 方式将其映射
phy_to_vir 线性映射虚拟地址
mmap方式将物理地址映射到用户空间

通过实际测试, memblock_reserve保留的内存段,可以采用memremap ,phy_to_vir 方式将物理地址映射出并使用。


二、 限制内存总空间方式预留内存

2.1 预留内存方法

该方法比较简单,直接修改系统grub启动参数,将系统总内存限制在指定大小,使得其余内存不可见。然后对不可见内存进行申请保留。

[root@localhost ~]# cat /proc/cmdline
BOOT_IMAGE=/vmlinuz-3.10.0-123.el7.x86_64 root=UUID=79704805-e306-420b-827a-52849e1376c1 ro vconsole.keymap=us crashkernel=auto vconsole.font=latarcyrheb-sun16 rhgb quiet LANG=en_US.UTF-8 mem=1G

如上mem=2G ,是限制了可用物理内存为2G,那么我们可以对[2-4G]内的内存进行申请,映射和使用。
在iomem和meminfo里可以看到系统可用总内存已经减少2G左右


[root@localhost ~]# cat /proc/iomem |grep RAM
00001000-0009ebff : System RAM
00100000-3fffffff : System RAM
100000000-13fffffff : System RAM

e820里可以显示到全部(包括隐藏)的内存情况,usable是对应的物理内存,比对iomem中的RAM部分,可知隐藏掉的部分(预留)内存为:[3fffffff—bfecffff], [bff00000–bfffffff]。 大约2G左右


[root@localhost ~]# dmesg |grep e820
[    0.000000] e820: BIOS-provided physical RAM map:
[    0.000000] BIOS-e820: [mem 0x0000000000000000-0x000000000009ebff] usable
[    0.000000] BIOS-e820: [mem 0x000000000009ec00-0x000000000009ffff] reserved
[    0.000000] BIOS-e820: [mem 0x00000000000dc000-0x00000000000fffff] reserved
[    0.000000] BIOS-e820: [mem 0x0000000000100000-0x00000000bfecffff] usable
[    0.000000] BIOS-e820: [mem 0x00000000bfed0000-0x00000000bfefefff] ACPI data
[    0.000000] BIOS-e820: [mem 0x00000000bfeff000-0x00000000bfefffff] ACPI NVS
[    0.000000] BIOS-e820: [mem 0x00000000bff00000-0x00000000bfffffff] usable
[    0.000000] BIOS-e820: [mem 0x00000000f0000000-0x00000000f7ffffff] reserved
[    0.000000] BIOS-e820: [mem 0x00000000fec00000-0x00000000fec0ffff] reserved
[    0.000000] BIOS-e820: [mem 0x00000000fee00000-0x00000000fee00fff] reserved
[    0.000000] BIOS-e820: [mem 0x00000000fffe0000-0x00000000ffffffff] reserved
[    0.000000] BIOS-e820: [mem 0x0000000100000000-0x000000013fffffff] usable

2.2 预留内存访问

对预留部分内存访问的驱动代码如下,从0x40000000开始,申请512MB(0x20000000)内存。

static char data[] = "123456\n";
static void* addr;

static int __init ram_reserve_init(void)
{

      if (!request_mem_region(0x40000000, 0x20000000, "reserve test")) //请求不可见的内存段权限(即:检查你申请的资源是否可用,如果可用,则将其标志为被使用。非必须)
      {
           printk("request_mem_region fail\n");
           return - EBUSY;
      }
      addr = memremap(0x40000000, 0x20000000, MEMREMAP_WB); //映射内存空间
	  
	  memcpy(addr, data, sizeof(str));                   //拷贝测试数据 
	  printk( "%s: %s\n " , __func__, ( char * )addr);//读出内存数据
	  return 0;
}

static void __exit ram_reserve_exit(void)
{

	iounmap(addr);
	release_mem_region(RESERVE_PHY, RESERVE_SIZE);
			pr_info(KBUILD_MODNAME ": unloaded.\n");
}

module_init(ram_reserve_init);

module_exit(ram_reserve_exit);

request_mem_region() 检查你申请的资源是否可用,如果可用,则将其标志为被使用,如果该内存区已经被标记,会返回报错,该调用可以忽略,建议加上。加载驱动后,通过/proc/iomem 可以查看到reserve test段的内存分配信息

该方式有个缺点就是预留的内存是ram空间的尾部,边界不确定性,可能无法准确预留出指定大小的内存。

通过实际测试,不可见内存段保留方式,不能通过phy_to_vir 方式进行映射,因为phy_to_vir 只限在normal区的内存,不可见的内存段在系统认为不在normal区中,但可以采用memremap访问 。


三、CMA连续内存分配方式预留内存

cma(contiguous memory allocation)是linux中一种动态分配连续物理内存的方式,可认为是传统kmalloc的升级版,伙伴管理系统中,kmalloc最大只能申请4M内存,开启了cma后可以申请大块的连续内存区域,该方式是先将cma部分内存从系统预留出来,当要使用时单独从cma里面动态申请大小,因此申请时候不能指定其申请地址。cma机制需要修改内核编译参数(或修改设备树)进行部署才能使用,篇幅较长,后续文章中讲解。

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐