linux内存三大分配器:引导内存分配器,伙伴分配器,slab分配器
1.引导内存分配器的作用因为内核里面有很多内存结构体,不可能在静态编译阶段就静态初始化所有的这些内存结构体。另外,在系统启动过程中,系统启动后的物理内存分配器本身也需要初始化,如伙伴分配器,那么伙伴分配器如何获取内存来初始化自己呢 ?为了达到这个目标,我们先实现一个满足要求的但是可能效率不高的笨家伙,引导内存分配器。用它来负责系统初始化初期的内存管理, 最重要的, 用它来初始化我们内存的数据结构, 直到我们真正的内存管理器被初始化完成并能投入使用, 我们将旧的内存管理器丢掉。
2.引导内存分配器的原理在Linux内核中使用struct bootmem_data来描述一个引导内存分配,其节点结构下的一个成员,也就是说每一个节点都有一个引导内存分配。 引导内存分配使用struct bootmem_data结构中的node_bootmem_map这个bitmap来呈现memory的状态,一个bit代表一个物理页框,也就是用struct page,如果一个bit为1,表示该page已经被分配了,如果bit是0,则表示该page未被分配。为了能够满足比一个page还小的内存块的分配,引导内存分配器会使用last_pos来记住上次分配所使用的PFN以及上次分配所使用的page内的偏移:last_offset,下次分配的时候结合last_pos和last_offset将细小的内存块分配尽量集中在相同的page中。
3引导内存分配器的缺点尽管引导内存分配器不会造成严重的内存碎片,但是每次分配过程需要线性扫描搜索内存来满足当前的分配。因为是检查bitmap,所以代价比较昂贵,尤其是最先适配(first fit)算法倾向将小块内存放置在物理内存开头,但是这些内存区域在分配大块内存时,也需要扫描,所以该过程十分浪费。所以早期内存分配器在系统启动后就被弃用的原因。
4.bootmem和memblock的比较但是bootmem也有很多问题. 最明显的就是外碎片的问题, 因此内核维护了memblock内存分配器, 同时用memblock实现了一份bootmem相同的兼容API, 即nobootmem, Memblock以前被定义为Logical Memory Block( 逻辑内存块),但根据Yinghai Lu的补丁, 它被重命名为memblock. 并最终替代bootmem成为初始化阶段的内存管理器。 bootmem是通过位图来管理,位图存在地地址段, 而memblock是在高地址管理内存, 维护两个链表, 即memory和reserved。 memory链表维护系统的内存信息(在初始化阶段通过bios获取的), 对于任何内存分配, 先去查找memory链表, 然后在reserve链表上记录(新增一个节点,或者合并) bootmem和memblock都是就近查找可用的内存, bootmem是从低到高找, memblock是从高往低找。 在boot传递给kernel memory bank相关信息后,kernel这边会以memblcok的方式保存这些信息,当伙伴系统没有起来之前,在内核中也是要有一套机制来管理memory的申请和释放。linux内核可以通过宏定义选择nobootmem 或者bootmem 来在伙伴起来之前管理内存。这两种机制对提供的API是一致的,因此对用户是透明的
5.bootmem小分析bootmem结构体位于文件include/linux/bootmem.h:
typedef struct bootmem_data {
unsigned long node_min_pfn;//节点内存的起始物理页号
unsigned long node_low_pfn;//节点内存的结束物理页号
void *node_bootmem_map;//位图指针,每个物理页对应一位,如果物理页被分配则对应位置一。
unsigned long last_end_off;//最后一次分配的页面内的偏移量(字节);如果为0,则使用的页面已满
unsigned long hint_idx;//最后一次分配的物理页,下次优先考虑从这个物理页分配
struct list_head list;//按内存地址排序链表头
} bootmem_data_t;
bootmem接口函数: 1)bootmem分配内存函数:alloc_bootmem 2)bootmem释放内存函数:free_bootmem
#define alloc_bootmem(x) \
__alloc_bootmem(x, SMP_CACHE_BYTES, BOOTMEM_LOW_LIMIT)
void __init free_bootmem(unsigned long physaddr, unsigned long size)
{
unsigned long start, end;
kmemleak_free_part_phys(physaddr, size);//释放映射的内存
start = PFN_UP(physaddr);//查找到起始位置的物理页
end = PFN_DOWN(physaddr + size);//查找到结束为止的物理页
mark_bootmem(start, end, 0, 0);//把释放的物理页对应的位清零
}
6.memblock结构解析memblock结构体位于include/linux/memblock.h文件:
struct memblock {
bool bottom_up;//表示内存分配方式,真:从低地址向上分配,假:从高地址向下分配
phys_addr_t current_limit;//可分配内存的最大物理地址
struct memblock_type memory;//可用物理内存区域(包括已分配和未分配的)
struct memblock_type reserved;//预留物理内存区域(预留起来不可用,例子:设备树)
#ifdef CONFIG_HAVE_MEMBLOCK_PHYS_MAP
struct memblock_type physmem;//所有的物理内存区域
#endif
};
struct memblock_type {
unsigned long cnt;//区域数量
unsigned long max;//分配区域的大小
phys_addr_t total_size;//所有区域的大小
struct memblock_region *regions;//区域数组指向区域数组
char *name;//内存类型符号名
};
struct memblock_region {
phys_addr_t base;//起始物理地址
phys_addr_t size;//长度
enum memblock_flags flags;//内存区域标志属性
#ifdef CONFIG_HAVE_MEMBLOCK_NODE_MAP
int nid;//节点编号
#endif
};
//内存区域标志属性定义
enum memblock_flags {
MEMBLOCK_NONE = 0x0,//表示没有特殊要求区域
MEMBLOCK_HOTPLUG = 0x1,//表示可以热插拔的区域
MEMBLOCK_MIRROR = 0x2,//表示镜像的区域,将内存数据做两份复制,分配放在主内存和镜像内存中
MEMBLOCK_NOMAP = 0x4,//表示不添加到内核直接映射区域,即线性映射区
};
memblock体系的结构:
7.memblock接口函数解析1)memblock添加内存区域函数:
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_add_range将内存区块添加到memblock.memory进行管理
return memblock_add_range(&memblock.memory, base, size, MAX_NUMNODES, 0);
}
我们继续追memblock_add_range:
int __init_memblock memblock_add_range(struct memblock_type *type,
phys_addr_t base, phys_addr_t size,
int nid, enum memblock_flags flags)
{
bool insert = false;
phys_addr_t obase = base;
phys_addr_t end = base + memblock_cap_size(base, &size);
int idx, nr_new;
struct memblock_region *rgn;
if (!size)
return 0;
if (type->regions[0].size == 0) {
WARN_ON(type->cnt != 1 || type->total_size);
type->regions[0].base = base;
type->regions[0].size = size;
type->regions[0].flags = flags;
memblock_set_region_node(&type->regions[0], nid);
type->total_size = size;
return 0;
}
repeat:
/*
* The following is executed twice. Once with %false @insert and
* then with %true. The first counts the number of regions needed
* to accommodate the new area. The second actually inserts them.
*/
base = obase;
nr_new = 0;
//遍历所有内存块,与新的内存块比较
for_each_memblock_type(idx, type, rgn) {
phys_addr_t rbase = rgn->base;
phys_addr_t rend = rbase + rgn->size;
if (rbase >= end)//新加入的内存块的结束地址已经到了则遍历结束
break;
if (rend <= base)//即加入的内存块的起始地址还没到则遍历下一块
continue;
/*
* @rgn overlaps. If it separates the lower part of new
* area, insert that portion.
*/
//如果新加入的内存起始地址已经到了,但是还没到遍历的内存则插入
if (rbase > base) {
#ifdef CONFIG_HAVE_MEMBLOCK_NODE_MAP
WARN_ON(nid != memblock_get_region_node(rgn));
#endif
WARN_ON(flags != rgn->flags);
nr_new++;
if (insert)
//添加内存区域,也就是填充struct memblock_region而已
memblock_insert_region(type, idx++, base,
rbase - base, nid,
flags);
}
/* area below @rend is dealt with, forget about it */
base = min(rend, end);
}
/* insert the remaining portion */
if (base < end) {
nr_new++;
if (insert)
memblock_insert_region(type, idx, base, end - base,
nid, flags);
}
//如果需要加入的内存块个数为0则返回,不需要第二次遍历执行加入操作
if (!nr_new)
return 0;
/*
* If this was the first round, resize array and repeat for actual
* insertions; otherwise, merge and return.
*/
//第一次会进入,判断内存区域块是否达到上限,是则退出,否则回到repeat
//因为insert参数原因,第一次没有真正插入,第二次才会真正的插入
if (!insert) {
while (type->cnt + nr_new > type->max)
if (memblock_double_array(type, obase, size) < 0)
return -ENOMEM;
insert = true;
goto repeat;
} else {
memblock_merge_regions(type);//合并相邻且没有缝隙的内存区域
return 0;
}
}
2)memblock删除内存区域函数:memblock_remove
int __init_memblock memblock_remove(phys_addr_t base, phys_addr_t size)
{
phys_addr_t end = base + size - 1;
memblock_dbg("memblock_remove: [%pa-%pa] %pS\n",
&base, &end, (void *)_RET_IP_);
return memblock_remove_range(&memblock.memory, base, size);
}
memblock_remove_range:
static int __init_memblock memblock_remove_range(struct memblock_type *type,
phys_addr_t base, phys_addr_t size)
{
int start_rgn, end_rgn;
int i, ret;
//要删除的内存区域内存区内的内存块存在重叠部分,把这部分需要独立出来
ret = memblock_isolate_range(type, base, size, &start_rgn, &end_rgn);
if (ret)
return ret;
//根据要删除内存区的索引号,删除内存区块
for (i = end_rgn - 1; i >= start_rgn; i--)
memblock_remove_region(type, i);
return 0;
}
3)memblock分配内存函数: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);
}
phys_addr_t __init memblock_alloc_base(phys_addr_t size, phys_addr_t align, phys_addr_t max_addr)
{
phys_addr_t alloc;
alloc = __memblock_alloc_base(size, align, max_addr);
if (alloc == 0)
panic("ERROR: Failed to allocate %pa bytes below %pa.\n",
&size, &max_addr);
return alloc;
}
phys_addr_t __init __memblock_alloc_base(phys_addr_t size, phys_addr_t align, phys_addr_t max_addr)
{
return memblock_alloc_base_nid(size, align, max_addr, NUMA_NO_NODE,
MEMBLOCK_NONE);
}
phys_addr_t __init memblock_alloc_base_nid(phys_addr_t size,
phys_addr_t align, phys_addr_t max_addr,
int nid, enum memblock_flags flags)
{
return memblock_alloc_range_nid(size, align, 0, max_addr, nid, flags);
}
static phys_addr_t __init memblock_alloc_range_nid(phys_addr_t size,
phys_addr_t align, phys_addr_t start,
phys_addr_t end, int nid,
enum memblock_flags flags)
{
phys_addr_t found;
if (!align)
align = SMP_CACHE_BYTES;
//在给定范围和节点内找一块空区域
found = memblock_find_in_range_node(size, align, start, end, nid,
flags);
//memblock_reserve是把找到的空区域添加到memblock.reserved中,表示已经用了
if (found && !memblock_reserve(found, size)) {
/*
* The min_count is set to 0 so that memblock allocations are
* never reported as leaks.
*/
//一个内存块分配物理内存的通知
kmemleak_alloc_phys(found, size, 0, 0);
return found;
}
return 0;
}
4)memblock释放内存函数:memblock_free
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);
}
static int __init_memblock memblock_remove_range(struct memblock_type *type,
phys_addr_t base, phys_addr_t size)
{
int start_rgn, end_rgn;
int i, ret;
//要删除的内存区域内存区内的内存块存在重叠部分,把这部分需要独立出来
ret = memblock_isolate_range(type, base, size, &start_rgn, &end_rgn);
if (ret)
return ret;
//根据要删除内存区的索引号,删除内存区块
for (i = end_rgn - 1; i >= start_rgn; i--)
memblock_remove_region(type, i);
return 0;
}
7.memblock启动流程 1)解析设备树中的/memory,把所有物理内存添加到memblock 2)在memblock_init中初始化memblock linux启动从init/main.c文件的start_kernel函数开始,然后从文件setup_arch(arch/arm64/kernel/setup.c文件中)函数检测处理器类型,初始化处理器和内存,其中的arm64_memblock_init(arch/arm64/mm/init.c文件中)函数就是arm64架构的memblock初始化流程。
void __init arm64_memblock_init(void)
{
const s64 linear_region_size = -(s64)PAGE_OFFSET;
/* Handle linux,usable-memory-range property */
//解析设备树文件的内存节点
fdt_enforce_memory_region();
/* Remove memory above our supported physical address size */
//删除超出我们支持的物理地址大小的内存
memblock_remove(1ULL << PHYS_MASK_SHIFT, ULLONG_MAX);
/*
* Ensure that the linear region takes up exactly half of the kernel
* virtual address space. This way, we can distinguish a linear address
* from a kernel/module/vmalloc address by testing a single bit.
*/
BUILD_BUG_ON(linear_region_size != BIT(VA_BITS - 1));
/*
* Select a suitable value for the base of physical memory.
*/
//全局变量memstart_addr记录了内存的起始物理地址
memstart_addr = round_down(memblock_start_of_DRAM(),
ARM64_MEMSTART_ALIGN);
/*
* Remove the memory that we will not be able to cover with the
* linear mapping. Take care not to clip the kernel which may be
* high in memory.
*/
//把线性映射区无法覆盖的物理内存范围从memblock中删除
memblock_remove(max_t(u64, memstart_addr + linear_region_size,
__pa_symbol(_end)), ULLONG_MAX);
if (memstart_addr + linear_region_size < memblock_end_of_DRAM()) {
/* ensure that memstart_addr remains sufficiently aligned */
memstart_addr = round_up(memblock_end_of_DRAM() - linear_region_size,
ARM64_MEMSTART_ALIGN);
memblock_remove(0, memstart_addr);
}
/*
* Apply the memory limit if it was set. Since the kernel may be loaded
* high up in memory, add back the kernel region that must be accessible
* via the linear mapping.
*/
//如果设置了内存限制,要根据限制使用内存
if (memory_limit != PHYS_ADDR_MAX) {
memblock_mem_limit_remove_map(memory_limit);//把超出限制的内存移除
memblock_add(__pa_symbol(_text), (u64)(_end - _text));//添加可以使用的内存
}
if (IS_ENABLED(CONFIG_BLK_DEV_INITRD) && initrd_start) {
/*
* Add back the memory we just removed if it results in the
* initrd to become inaccessible via the linear mapping.
* Otherwise, this is a no-op
*/
u64 base = initrd_start & PAGE_MASK;
u64 size = PAGE_ALIGN(initrd_end) - base;
/*
* We can only add back the initrd memory if we don't end up
* with more memory than we can address via the linear mapping.
* It is up to the bootloader to position the kernel and the
* initrd reasonably close to each other (i.e., within 32 GB of
* each other) so that all granule/#levels combinations can
* always access both.
*/
if (WARN(base < memblock_start_of_DRAM() ||
base + size > memblock_start_of_DRAM() +
linear_region_size,
"initrd not fully accessible via the linear mapping -- please check your bootloader ...\n")) {
initrd_start = 0;
} else {
memblock_remove(base, size); /* clear MEMBLOCK_ flags */
memblock_add(base, size);
memblock_reserve(base, size);
}
}
if (IS_ENABLED(CONFIG_RANDOMIZE_BASE)) {
extern u16 memstart_offset_seed;
u64 range = linear_region_size -
(memblock_end_of_DRAM() - memblock_start_of_DRAM());
/*
* If the size of the linear region exceeds, by a sufficient
* margin, the size of the region that the available physical
* memory spans, randomize the linear region as well.
*/
if (memstart_offset_seed > 0 && range >= ARM64_MEMSTART_ALIGN) {
range /= ARM64_MEMSTART_ALIGN;
memstart_addr -= ARM64_MEMSTART_ALIGN *
((range * memstart_offset_seed) >> 16);
}
}
/*
* Register the kernel text, kernel data, initrd, and initial
* pagetables with memblock.
*/
//把内核镜像占用的内存添加到memblock的预留区中,表示预留了不再分配出去
memblock_reserve(__pa_symbol(_text), _end - _text);
#ifdef CONFIG_BLK_DEV_INITRD
if (initrd_start) {
memblock_reserve(initrd_start, initrd_end - initrd_start);
/* the generic initrd code expects virtual addresses */
initrd_start = __phys_to_virt(initrd_start);
initrd_end = __phys_to_virt(initrd_end);
}
#endif
//扫描设备树中的保留内存区域并添加到memblock的预留区域中
early_init_fdt_scan_reserved_mem();
/* 4GB maximum for 32-bit only capable devices */
if (IS_ENABLED(CONFIG_ZONE_DMA32))
arm64_dma_phys_limit = max_zone_dma_phys();
else
arm64_dma_phys_limit = PHYS_MASK + 1;
reserve_crashkernel();
reserve_elfcorehdr();
high_memory = __va(memblock_end_of_DRAM() - 1) + 1;
dma_contiguous_reserve(arm64_dma_phys_limit);
memblock_allow_resize();
}
最后,引导内存分配器退休,会将物理内存填充到伙伴分配器中,移交给伙伴分配器进行管理。
end
本文由哈喽比特于2年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/3utaHTPMJZbTfNUBNRBkgA
京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。
日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。
据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。
今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。
日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。
近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。
据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。
9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...
9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。
据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。
特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。
据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。
近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。
据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。
9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。
《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。
近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。
社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”
2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。
罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。