引导内存分配器原理

发布于:2023-01-04 ⋅ 阅读:(309) ⋅ 点赞:(0)


1.bootmem分配器

Linux boot-time阶段管理物理内存,并提供物理内存分配和回收的分配器。

  1. 源码结构体
/*
 * node_bootmem_map is a map pointer - the bits represent all physical 
 * memory pages (including holes) on the node.
 */
typedef struct bootmem_data {
	unsigned long node_min_pfn;   // 起始物理页号
	unsigned long node_low_pfn;   // 结束物理页号
	void *node_bootmem_map;		 // 指向一个位图,每个物理页对应一位,如果物理页被分配,对应位设置为1
	unsigned long last_end_off;  // 上次分配的内存块结束位置后面一个字节的偏移
	unsigned long hint_idx;    // 上次分配的内存块结束位置后面物理页位置中索引,下次优先考虑从这个物理页开始分配
	struct list_head list;   
} bootmem_data_t;
  1. 每个内存节点都有一个bootmem_data实例
struct bootmem_data;
typedef struct pglist_data {
	struct zone node_zones[MAX_NR_ZONES];  // 内存区域数组
	struct zonelist node_zonelists[MAX_ZONELISTS]; // 备用区域列表
	int nr_zones;  // 该节点包含的内存区域数量
#ifdef CONFIG_FLAT_NODE_MEM_MAP	/* means !SPARSEMEM */
	struct page *node_mem_map;  // 页描述符数组
#ifdef CONFIG_PAGE_EXTENSION
	struct page_ext *node_page_ext;  //页的扩展属性
#endif
#endif
#ifndef CONFIG_NO_BOOTMEM
	struct bootmem_data *bdata;  // 引导bootmem分配器
#endif
    /*----*/
};
  1. bootmem分配器的算法
  • 只把低端内存添加到bootmem分配器,低端内存是可以直接映射到虚拟地址空间的物理内存;
  • 使用一个位图记录哪些物理页被分配,若被分配,则把这个物理页对应的位设置为1;
  • 采用最先适配算法,扫描位图,找到第一个足够大的空闲内存块;
  • 为了支持分配小于一页的内存块,记录上一次分配的内存块结束位置后面一个字节的偏移和后面一页的索引,下次分配时,从上次分配的位置后面开始分配。如果上次分配的最后一个物理页的剩余空间足够,可以直接在这个物理页上分配内存。
  1. bootmem对外提供分配内存函数alloc_bootmem,释放内存的函数是free_bootmem。ARM64架构内核不使用bootmem分配器,其他架构处理器仍使用。

2.memblock分配器

  1. memblock数据结构
struct memblock {
	bool bottom_up;  /* is bottom up direction? */
	//表示内存分配方式,true从低地址向上分配,false从高地址向下分配
	phys_addr_t current_limit; //可分配内存的最大物理地址
	struct memblock_type memory; //内存类型(包括已分配和未分配)
	struct memblock_type reserved; //预留类型(已分配的内存)
#ifdef CONFIG_HAVE_MEMBLOCK_PHYS_MAP 
	struct memblock_type physmem; //物理内存类型
#endif
};

物理内存类型和内存类型的区别:内存类型是物理内存类型的子集,在引导内核时可以使用内核参数mem=nn[KMG], 指定可用内存的大小,导致内核不能看见所有的内存;物理内存类型总是包含所有内存范围,内存类型只包含内核参数“mem=”指定的可用内在范围。

  1. 内存块类型数据结构
struct memblock_type {
	unsigned long cnt;	/* number of regions */
	unsigned long max;	/* size of the allocated array */
	phys_addr_t total_size;	/* size of all regions */
	struct memblock_region *regions; //执行内存区域结构的指针
	char *name; //内存块类型的名称
};
  1. 内存块区域数据结构
/* Definition of memblock flags. */
//memblock分配器标志位定义
enum {
	MEMBLOCK_NONE		= 0x0,	/* No special request */
	// 表示可以热插拔的区域,即在系统运行中可以拔出或插入的物理内存
	MEMBLOCK_HOTPLUG	= 0x1,	/* hotpluggable region */
	// 镜像区域,将内存数据做两个复制,分配在主内存和镜像内存中
	MEMBLOCK_MIRROR		= 0x2,	/* mirrored region */
	//表示不添加到内核直接映射区域(即线性映射区域)
	MEMBLOCK_NOMAP		= 0x4,	/* don't add to kernel direct mapping */
};

struct memblock_region {
	phys_addr_t base;   //起始物理地址
	phys_addr_t size;   
	unsigned long flags; 
#ifdef CONFIG_HAVE_MEMBLOCK_NODE_MAP
	int nid;  //节点编号
#endif
};
  1. memblock memblock _type和memblock _region三个数据结构之间的关系


3.ARM64内核初始化memblock分配器流程

在“mm/memblock.c”定义全局变量memblock,bottom_up初始化为false,表示从高地址向下分配。
image.png

  1. 分配过程
  • 解析设备树二进制文件中的节点memory,把所有物理内存范围添加到memblock;

  • 在函数arm64_memblock_init中初始化memblock;

    image.png
    image.png
    image.png
    image.png
    image.png
    image.png

4. memblock 分配器编程接口

memblock_add:添加新的内存块区域到memblock.memory中;
memblock_remove:删除内存块区域
memblock_alloc:分配内存
memblock_free:释放内存

  1. memblock_add
// 插入一块可用的物理内存 
// base 指向要添加内存块的起始物理地址
// size 指向要添加内存块的大小
int __init_memblock memblock_add(phys_addr_t base, phys_addr_t size)
{
	phys_addr_t end = base + size - 1;

	memblock_dbg("memblock_add: [%pa-%pa] %pF\n",
		     &base, &end, (void *)_RET_IP_);
	// 将内存区块添加到memblock.memory
	return memblock_add_range(&memblock.memory, base, size, MAX_NUMNODES, 0);
}

image.png
image.png
image.png

  1. memblock_remove
// 从可用物理内存区块中删除一块可用物理内存
// base 指向需要删除物理内存的起始物理地址
// size 指向需要删除物理内存的大小
int __init_memblock memblock_remove(phys_addr_t base, phys_addr_t size)
{
	return memblock_remove_range(&memblock.memory, base, size);
}

image.png

  1. memblock_alloc
// 从指定地址之前分配物理内存
phys_addr_t __init memblock_alloc(phys_addr_t size, phys_addr_t align)
{
	return memblock_alloc_base(size, align, MEMBLOCK_ALLOC_ACCESSIBLE);
}

image.png
image.png
image.png
image.png

  1. memblock_free
/*
 * 从预留内存区中删除一块预留的内存区块
 * base 指向要删除预留内存的起始物理地址
 * size 指向要删除预留内存的大小
 */
int __init_memblock memblock_free(phys_addr_t base, phys_addr_t size)
{
	phys_addr_t end = base + size - 1;

	memblock_dbg("   memblock_free: [%pa-%pa] %pF\n",
		     &base, &end, (void *)_RET_IP_);
	// 函数计算出要删除物理内存的终止物理地址
	kmemleak_free_part_phys(base, size);
	return memblock_remove_range(&memblock.reserved, base, size);
}

image.png

5.小结

memblock内存分配器原理,主要维护两种内存:

  1. 系统可用的物理内存,即系统实际含有的物理内存,其值从DTS中进行配置,通过uboot实际探测之后传到内核。
  2. 内核预留给操作系统的内存,这部分内存作为特殊功能使用,不能作为共享内存使用。

参考

推荐一个零声学院免费公开课程,个人觉得老师讲得不错,分享给大家:[Linux,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCP/IP,协程,DPDK等技术内容,点击立即学习:https://course.0voice.com/v1/course/intro?courseId=5&agentId=0

本文含有隐藏内容,请 开通VIP 后查看

网站公告

今日签到

点亮在社区的每一天
去签到