对fixmap实现动态内存申请的理解

本文探讨内核启动过程如何运用fixmap进行动态内存分配,通过解析early_pgtable_alloc函数,揭示物理地址分配后,如何申请虚拟地址并建立页表映射,使内存可用。特别聚焦于使用fixmap解决映射循环问题,及其映射限制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

本文谈谈内核初始话过程中是如何利用fixmap来实现动态分配内存的。
关于fixmap的学习,可以参考这两份链接:
http://www.wowotech.net/memory_management/440.html
http://www.wowotech.net/memory_management/fixmap.html
物理地址的分配是由memblock来完成,但这不意味这就可以使用了,还需要申请虚拟地址,同时再建立页表完成映射,这样才能使用分配的内存。而建立页表又需要申请内存并建立映射,如此循环下去怎么完成工作?我提出这个疑问是因为我在阅读paging_init的代码时,对那里重新映射的代码非常疑惑,因为那里需要不断动态申请内存。
以early_pgtable_alloc的代码来分析它是如何完成的:

static phys_addr_t __init early_pgtable_alloc(void)
{
	phys_addr_t phys;
	void *ptr;
	phys = memblock_alloc(PAGE_SIZE, PAGE_SIZE);
	/*
	 * The FIX_{PGD,PUD,PMD} slots may be in active use, but the FIX_PTE
	 * slot will be free, so we can (ab)use the FIX_PTE slot to initialise
	 * any level of table.
	 */
	ptr = pte_set_fixmap(phys);
	memset(ptr, 0, PAGE_SIZE);
	pte_clear_fixmap();
	return phys;
}

对于使用memblock_alloc申请页对齐的一页内存,这里没什么好说的,但是把这页内存初始化为0就比较有意思。
它要使用memset函数来完成初始化为0的操作,但是要使用这个函数,需要传递的是虚拟地址,不能是memblock_alloc申请到的物理地址phys,所以需要通过pte_set_fixmap来获取虚拟地址,同时这个函数也会完成虚拟地址到物理地址的映射,这样后面使用这个虚拟地址时才不会发生page fault。
先来讨论下这个映射该如何完成:
1.物理地址作为输入参数,不需要关注了;
2.虚拟地址是固定的,就是根据FIX_PTE这个idx,通过fix_to_virt(idx)就可以得到;
3.映射的大小就是一个page,那么pgd、pmd和pte页表如何存储?
对于pgd,当然可以使用swapper_pg_dir,它本就支持所有的虚拟地址空间映射,相比于__create_page_tables,由于fixmap的虚拟地址访问离vmalloc的较远,所以它会使用swapper_pg_dir[512]中的另外一个index。
而这里的pmd和pte就不能和__create_page_tables那边的共用,一是因为那边是section map,只有多余的一个page用来存储pmd,二是这个pmd已经被占用了,不能共用。
这里的解决方法就是为fixmap自己静态申请两个page。要注意的是,bm_pmd和bm_pud有添加__maybe_unused,这意味这它会根据实际页表的状态而定,比如我们39bit,三级页表,那就没有bm_pud

static pte_t bm_pte[PTRS_PER_PTE] __page_aligned_bss;
static pmd_t bm_pmd[PTRS_PER_PMD] __page_aligned_bss __maybe_unused;
static pud_t bm_pud[PTRS_PER_PUD] __page_aligned_bss __maybe_unused;

还有个问题,即便已经有了页来存放页表,但还需要建立这三个页表之间的联系,就是swapper_pg_dir[xx]指向bm_pmd,bm_pmd[xxx]指向bm_pte,关于这部分工作,已经在early_fixmap_init中完成,所以这不用关注了。
下面看看具体的代码实现:

// 这里使用的是FIX_PTE这个index
#define pte_set_fixmap(addr)		((pte_t *)set_fixmap_offset(FIX_PTE, addr))
// 映射时使用的flag就是PAGE_KERNEL
#define FIXMAP_PAGE_NORMAL PAGE_KERNEL
#define set_fixmap_offset(idx, phys) \
	__set_fixmap_offset(idx, phys, FIXMAP_PAGE_NORMAL)
// 返回的________addr就是虚拟地址,直接根据fix_to_virt计算就可以得到,重要的不仅仅是返回虚拟地址,还需要建立映射后这个虚拟地址才能被使用,这就由__set_fixmap完成
/* Return a pointer with offset calculated */
#define __set_fixmap_offset(idx, phys, flags)				\
({									\
	unsigned long ________addr;					\
	__set_fixmap(idx, phys, flags);					\
	________addr = fix_to_virt(idx) + ((phys) & (PAGE_SIZE - 1));	\
	________addr;							\
})
// 这里只做一件事,就是把bm_pte[x]指向物理地址,由set_pte来完成。如前所述,其它的已经在early_fixmap_init中完成
void __set_fixmap(enum fixed_addresses idx,
			       phys_addr_t phys, pgprot_t flags)
{
	unsigned long addr = __fix_to_virt(idx);
	pte_t *pte;
	BUG_ON(idx <= FIX_HOLE || idx >= __end_of_fixed_addresses);
	pte = fixmap_pte(addr);
	if (pgprot_val(flags)) {
		set_pte(pte, pfn_pte(phys >> PAGE_SHIFT, flags));
	} else {
		pte_clear(&init_mm, addr, pte);
		flush_tlb_kernel_range(addr, addr+PAGE_SIZE);
	}
}

这里不禁要问,这样的方式能映射的最大空间是多少?就是bm_pte[512]全部映射到物理空间,那就是512*4K=2M,而且这2M还是受限的,比如虚拟地址的跨度只能在2M内等,不过这里是够用的,因为其实只有几个page会使用这里的映射。

下面看ramdump中bm_pte和bm_pmd的状态:
System.map
ffffff8009cfc000 b bm_pte
ffffff8009cfd000 b bm_pmd
对于bm_pmd,它的范围是[ffffff8009cfd000, ffffff8009cfe000),从ramdump中看,只有两个有效index,虚拟地址分别是:
bm_pmd[499] = ffffff8009cfdf98
bm_pmd[500] = ffffff8009cfdfa0
在这里插入图片描述
其中,bm_pmd[500]对应的dtb,我们前面讨论的使用的是bm_pmd[499],它指向bm_pte,bm_pte的物理地址正是11CFC000
在这里插入图片描述

回头来看,fixmap只适用于临时申请一个page,它可以帮你完成映射,允许你操作这个page。但是操作之后就需要释放掉这个映射,之后又会建立其它临时page的映射。如果你还想再访问原来的物理地址,那没办法了,再使用这个虚拟地址访问的已经是新申请的物理地址了。所以,它只是提供了临时读写一个物理页的方法。这对于map时是有用的,比如我为了创建线性映射区的映射,需要不停地申请页表然后填充信息,但是填充完成后,软件上不需要再访问这些申请的页表,那时硬件MMU去做的,对MMU而言,只要这个页表中内容有效就可以,MMU可不需要建立映射再访问。

<think>嗯,用户想了解MessagePack中Fixmap的用法或者解决相关问题。首先,我需要回忆一下MessagePack的基本结构,尤其是Fixmap的部分。MessagePack是一种二进制序列化格式,用来高效地打包数据。它的类型系统里有固定长度的类型,比如Fixmap,用来处理小型的键值对集合。 Fixmap的定义应该是当键值对数量不超过15对时使用的,格式头是1000xxxx,后面的xxxx表示数量,范围是0到15。这样每个Fixmap的头部占用一个字节,后面跟着键值对。例如,一个包含3对键值对的Fixmap会用0x83作为头部,然后是键和值交替排列。 接下来,用户可能遇到的问题可能包括如何正确序列化和反序列化Fixmap,或者在处理时出现的格式错误。比如,如果键的数量超过了15,应该使用map16或map32,这时候如果错误地使用了Fixmap就会出问题。还有可能是在不同语言实现中,库的自动类型选择是否准确,或者手动指定Fixmap时的方法是否正确。 然后,我需要考虑如何组织回答。首先介绍Fixmap的基本概念和结构,然后给出示例,比如在Python中使用msgpack库时,如何生成Fixmap。可能还需要提到不同语言的处理方式差异,以及常见的错误场景,比如键数量过多、非字符串键、顺序问题等,并给出相应的解决方法。 还要参考用户提供的引用内容,虽然大部分是关于其他问题的,但可能需要看看是否有相关的解决思路可以借鉴。比如,引用5提到DockerFile中FROM必须放在第一行,这可能和格式的严格性有关,类比到MessagePack中,Fixmap的格式也必须严格遵守,比如头部正确,键值对数量正确等。 用户可能还会想知道如何调试,比如查看生成的二进制数据是否正确,这时候可能需要用十六进制查看工具,或者打印字节来验证。此外,不同版本的库可能有不同的行为,需要确保使用的库支持Fixmap的显式控制。 最后,生成相关问题,帮助用户进一步学习,比如Fixmap与其他map类型的区别,性能优化,跨语言兼容性问题,调试工具等。</think>### MessagePack中Fixmap的用法与问题解决 #### 1. Fixmap的定义与格式 MessagePack的`Fixmap`用于表示小规模的键值对集合,其特点是**键值对数量不超过15个**。格式规范为: - 头部字节:`1000xxxx`(二进制,`xxxx`表示4位无符号整数,取值范围$0 \leq n \leq 15$) - 数据部分:键和值交替排列,例如$[key1, value1, key2, value2]$ 示例: ```python import msgpack data = {b'id': 1, b'name': b'Alice'} # 自动选择Fixmap(键数量=2) packed = msgpack.packb(data, use_bin_type=True) # 输出字节:82 a2 6964 01 a4 6e616d65 a5 416c696365 ``` #### 2. 常见问题与解决方法 **问题1:键数量超出Fixmap范围** - **现象**:当键数量$>15$时,若强制使用Fixmap会报错 - **解决**:MessagePack会自动升级为`map16`或`map32`类型,无需手动干预 **问题2:键类型限制** - **错误示例**:使用非字符串类型作为键(如数字) ```python # 错误用法(键应为字符串/二进制类型) data = {1: 'a', 2: 'b'} packed = msgpack.packb(data) # 可能触发TypeError ``` - **解决**:将键转换为字符串或二进制类型 **问题3:跨语言兼容性问题** - **场景**:Python生成的Fixmap在C++反序列化失败 - **原因**:不同语言库的类型推断逻辑差异 - **验证方法**:检查二进制头字节 - Fixmap头字节范围:`0x80`~`0x8f` - Map16头字节:`0xde` #### 3. 手动控制Fixmap的示例(Python) ```python from msgpack import ExtType # 自定义编码器 def encode_fixmap(obj): if isinstance(obj, dict) and len(obj) <= 15: items = [] for k, v in obj.items(): items.extend([k, v]) return ExtType(1, msgpack.packb(items)) return obj # 使用自定义编码 packed = msgpack.packb({'age': 25}, default=encode_fixmap) ``` #### 4. 调试技巧 - **二进制分析**:使用`hexdump`查看输出: ```bash $ python -c "import msgpack; print(msgpack.packb({'a':1}).hex())" 81 a1 61 01 ``` - **头字节解析**: - `0x81` = `10000001`(Fixmap,包含1个键值对)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值