物理虚拟内存管理理论
在 mmu 开启之后, 存在了两种内存
物理内存
虚拟内存
就需要对 这两种内存 进行管理
我们用内存的时候,需要
1. 物理内存
2. 虚拟内存
对物理内存的管理方法有4种(根据内存模型不同而不同)
CONFIG_FLATMEM
CONFIG_DISCONTIGMEM
CONFIG_SPARSEMEM_VMEMMAP
CONFIG_SPARSEMEM
对虚拟内存的管理方法有4种,我们关注以下过程
直接映射区
vmalloc 动态映射区
持久映射区 kmap
临时映射区 fixmap (kmap_atomic 临时内核映射)
注意 : 在运行时,
物理内存的管理方法只有一种,配置哪种就是哪种
虚拟内存的管理方法有4种
对于内存管理,我们关注
level 1 物理内存的管理
1. 初始化
配置了 CONFIG_FLAT_NODE_MEM_MAP 之后 pglist_data 结构体 中 才会有 node_mem_map 成员
pg_data_t *pgdat = NODE_DATA(nid);
struct page * map = memblock_alloc_node(size, SMP_CACHE_BYTES, pgdat->node_id);
pgdat->node_mem_map = map + offset;
reserve_bootmem_region
__free_memory_core
2. 申请
get_page_from_freelist
3. 释放
add_to_free_list
level 1 虚拟内存的管理
1.初始化
map_lowmem
2.申请/获取
page_to_virt(page)
page_address(page);
3.释放
不需要释放
特点
1.线性映射关系,管理简单
2.唯一的lowmemory 映射的区域
3.映射一旦完成,系统运行期间不会改变.其他的4种都会改变
应用场景
TODO
1. 初始化
vmalloc_init
2. 申请
__alloc_vmap_area(size, align, vstart, vend);
3. 释放
va = __find_vmap_area((unsigned long)addr);
free_unmap_vmap_area(va);
特点
1.最灵活,其他区不能满足的需求,都由该区满足
2.返回的虚拟地址空间是连续的(4K 4K的连续)
3.地址范围固定,VMALLOC_START - VMALLOC_END
应用场景
TODO
1. 初始化
pkmap_page_table = early_pte_alloc(pmd_off_k(PKMAP_BASE), PKMAP_BASE, _PAGE_KERNEL_TABLE);
2. 申请
vaddr = map_new_virtual(page);
vaddr = PKMAP_ADDR(last_pkmap_nr);
set_pte_at(&init_mm, vaddr, &(pkmap_page_table[last_pkmap_nr]), mk_pte(page, kmap_prot));
3. 释放
vaddr = (unsigned long)page_address(page);
nr = PKMAP_NR(vaddr);
特点
1.一次只能映射一页
2.可能会引起睡眠
应用场景
TODO
- 第四种 临时映射区 fixmap (kmap_atomic)
1. 初始化
early_pte_alloc(pmd_off_k(FIXADDR_START), FIXADDR_START, _PAGE_KERNEL_TABLE);
2. 申请
idx = arch_kmap_local_map_idx(kmap_local_idx_push(), pfn);
vaddr = __fix_to_virt(FIX_KMAP_BEGIN + idx);
pteval = pfn_pte(pfn, prot);
arch_kmap_local_set_pte(&init_mm, vaddr, kmap_pte - idx, pteval);
3. 释放
idx = arch_kmap_local_unmap_idx(kmap_local_idx(), addr);
pte_clear(&init_mm, addr, kmap_pte - idx);
特点
1. 返回的虚拟地址是固定的,FIXADDR_TOP_START - FIXADDR_TOP 中 偏移量 为 FIX_KMAP_BEGIN - FIX_KMAP_END 的 区间
2. 每个cpu都有属于自己的临时映射区,大小为 KM_TYPE_NR * 4KB
3. 临时映射区的区间很小,管理简单
4. 寻找到空闲的虚拟空间的速度快
5. 不会睡眠
6. 临时映射区的使用是有限制的,映射使用完成后必须马上去除映射.
应用场景
TODO
1. 初始化
2. 申请
addr = get_unmapped_area(file, addr, len, pgoff, flags);
3. 释放
vma = find_vma(mm, start);
remove_vma_list(mm, vma);
特点
1.只有这一种能用于 进程用户空间虚拟地址的管理
2.地址范围在 0x0000 0000 - 0xC000 0000
应用场景
TODO
level2 内存管理的实现
在处理 用户需求(申请内存)时,要处理
1. 物理内存的申请
2. 虚拟内存的申请
实现有 buddy vmalloc , 这些实现 是 对 level1虚拟内存的管理 level1物理内存的管理 的封装
在内核配置一定的基础上, buddy管理了 运行时的物理内存管理
配置 管理了 物理内存
buddy 针对这些配置 实现了 不同的 物理内存管理,但在运行时,只有一种 物理内存管理,即 配置确定后的buddy
buddy很奇怪,封装了如下,但并不是简单的封装 level1
第一种 物理内存的管理
没有封装 虚拟内存的管理
buddy 提供的api中
alloc_pages/alloc_page 封装了物理内存的管理,没有封装虚拟内存的管理
__get_free_pages/__get_free_page 封装了物理内存的管理,并封装了虚拟内存的管理
可以这么说:
buddy 实现了对物理内存的管理,并没有实现对虚拟内存的管理
但在buddy输出的api里面,有些api封装了对虚拟内存的管理
但是我们一般说 buddy 管理的仅仅是物理内存,而不包括虚拟内存
vmalloc 封装了
第一种 物理内存的管理
第二种 虚拟内存的管理
slab 是 很特殊的,不是 这几种合成的产物
物理内存的管理
第一种虚拟内存的管理
kmalloc 基于 slab
其他
buddy 属于 物理内存管理还是虚拟内存管理
物理内存管理
物理内存是什么 :
内存中所有的页框
物理内存怎么管理
1个页框 对应 一个 struct page 结构体 , 存储 在 mem_map 数组中.
物理地址 在 struct page 中的 体现
体现在 struct page 的 地址上 : page_to_phys(page)
虚拟地址 在 struct page 中的 体现
如果是低端内存,体现在 struct page 的 地址上 : page_to_virt(page)
如果是高端内存,体现在 在 持久内核映射kmap机制 建立的 数据结构 中查找
如果之前做了映射(pam->page = page),返回 pam->virtual
如果之前没做映射,返回 NULL.
虚拟内存是什么
0G-3G 用户空间
每个用户进程 有一个
映射是怎么做的?
映射关系是怎么保存的?
3G-4G 内核空间
内核/用户进程内核态/内核线程 共用一个
分类:
低端内存
虚拟地址 C000 0000 - EF80 0000 线性映射 物理地址 (0-760MB) 0000 0000 - 2F80 0000
映射在页表中,映射关系保存在哪里
高端内存
虚拟地址 EF80 0000 - FFFF FFFF (264MB)动态映射 物理地址 (760M-4G)2F80 0000 - FFFF FFFF
映射在表中(三种机制建立的),映射关系保存在哪里?
需要在 建立页表的过程中,需要追代码来看映射关系.
1. 持久内核映射 : page_address_htable
2. 临时内核映射 : 每个cpu有几个?
3. 非连续内存分配 :
虚拟内存怎么管理
内存的管理
物理内存管理原理
物理内存管理实现
1. buddy
物理地址 在 struct page 中的 体现
体现在 struct page 的 地址上 : page_to_phys(page)
2. slab
slab 没有 对物理内存的管理,完全是 基于 buddy
3. vmalloc
vmalloc 没有 对物理内存的管理,完全是 基于 buddy
虚拟内存管理原理
虚拟内存:低端内存实现
1. buddy
虚拟地址 在 struct page 中的 体现
如果是低端内存,体现在 struct page 的 地址上 : page_to_virt(page)
2. slab
slab 没有对虚拟内存的管理,完全是基于 buddy
3. vmalloc
在这个维度不存在
虚拟内存:高端内存实现
1. buddy
如果是高端内存,在 持久内核映射kmap机制 建立的 数据结构 page_address_htable 中查找 到的 pam->virtual 变量 体现
如果之前做了映射(pam->page = page),返回 pam->virtual
如果之前没做映射,返回 NULL.
2. slab
slab 没有对虚拟内存的管理,完全是基于 buddy
3. vmalloc
虚拟地址在 struct vmap_area 结构体 中的 va_start 成员 体现
struct vmap_area 结构体被挂载到 vmap_area_root和vmap_area_list
内存模型
https:
#define page_to_pfn __page_to_pfn
#define page_to_phys(page) (__pfn_to_phys(page_to_pfn(page)))
不同的 memory_model 下 函数关系 不同 , 存在有4个模型,选一个即可
CONFIG_FLATMEM
#define __page_to_pfn(page) ((unsigned long)((page) - mem_map) + \
ARCH_PFN_OFFSET)
CONFIG_DISCONTIGMEM
#define __page_to_pfn(pg) \
({ const struct page *__pg = (pg); \
struct pglist_data *__pgdat = NODE_DATA(page_to_nid(__pg)); \
(unsigned long)(__pg - __pgdat->node_mem_map) + \
__pgdat->node_start_pfn; \
})
CONFIG_SPARSEMEM_VMEMMAP
#define __page_to_pfn(page) (unsigned long)((page) - vmemmap)
CONFIG_SPARSEMEM
#define __page_to_pfn(pg) \
({ const struct page *__pg = (pg); \
int __sec = page_to_section(__pg); \
(unsigned long)(__pg - __section_mem_map_addr(__nr_to_section(__sec))); \
})
物理虚拟内存管理实现1:buddy的实现
能直接用 用 alloc_pages 从 ZONE_DMA/ZONE_NORMAL 返回 struct page
并能通过 page_to_phys(page) 得到 虚拟地址
能直接 用 alloc_pages 从 ZONE_HIGHMEM 返回 struct page
不能通过 page_to_phys(page) 得到 虚拟地址
因为 page_to_phys(page) 只支持 低端内存(虚拟内存概念) 对应的 物理页
通过 查找 几种方法(A) 建立的映射关系
如果 查找了映射关系,则返回虚拟地址
如果 没查到映射关系,则返回NULL
此时可以有 几种方法(A) 建立映射
1. 持久内核映射 : kmap/kunmap : 利用了与 fixmap 同时建立的 pkmap_page_table
2. 临时内核映射 : kmap_atomic/kunmap_atomic : 利用了 fixmap
3. 非连续内存分配 : vmap/vunmap : 是 vmalloc 相关的函数
__pte_alloc_kernel
pte_alloc_one_kernel
__pte_alloc_one_kernel
__get_free_page(GFP_PGTABLE_KERNEL);
pmd_populate_kernel
__pmd_populate
pmdp[0] = __pmd(pmdval);
pmdp[1] = __pmd(pmdval + 256 * sizeof(pte_t));
虚拟内存管理机制的区别
虚拟内存管理机制分为两大类
1. linux运行时用的内存管理机制
vmalloc
原型:void *vmalloc(unsigned long size);
虚拟地址 : VMALLOC_START - VMALLOC_END
典型地址 : vmalloc : 0xf0000000 - 0xff000000 ( 240 MB)
虚拟地址范围 : 0M-1G 范围内,典型为几百M
特点 :
1.可能睡眠
2.不需要马上释放
3.申请前不需要做准备
4.可以申请大于4K的空间,能达到100M
5. 慢(需要做三件事)
6.返回地址 在 vmalloc 区内
7.得到的物理地址4K间不连续,4K内连续
应用场景:
1.场景必须满足:非中断上下文
2.场景必须满足:物理地址不必要连续
kmap
原型 : void *kmap(struct page *page);
虚拟地址 : PKMAP_BASE 开始
典型地址 : pkmap : 0xbfe00000 - 0xc0000000 ( 2 MB)
典型地址 : arch/x86/include/asm/pgtable_32_areas.h:42:# define VMALLOC_END (PKMAP_BASE - 2 * PAGE_SIZE)
虚拟地址范围 : arm32为 512 个 4KB
特点 :
1.可能睡眠
2.不需要马上释放
3.申请前需要准备page
4.只能申请4K的空间
5.快(需要做一件事)
6.返回地址 在 pkmap 区内
应用场景:
1.场景必须满足: 非中断上下文
2.场景必须满足: 已经有一个page了,而现在要访问这个page
kmap_atomic
原型 : void *kmap_atomic(struct page *page);
虚拟地址 : fixmap 中的 FIX_KMAP_BEGIN
典型地址 : fixmap : 0xffc00000 - 0xfff00000 (3072 kB) 中的 FIX_KMAP_BEGIN
虚拟地址范围 : arm32为 (13 * cpu_number) 个 4KB
特点 :
1.不会睡眠
2.需要马上释放(因为申请的api带加锁,所以需要尽快用kunmap_atomic释放锁)
3.申请前需要准备page
4.只能申请4K的空间
5. 更快(需要做一件事)
6.返回地址 在 fixmap 区的 FIX_KMAP_BEGIN 内
应用场景:
1.场景必须满足: 可以在中断上下文使用,也可以在进程上下文中使用
2.场景必须满足: 已经有一个page了,而现在要访问这个page
3.场景必须满足: 使用一般是访问一次,访问一次后 就释放
2. linux启动时用的内存管理机制
1. fixmap 永久
原型1 : void __iomem * __init earlycon_map(resource_size_t paddr, size_t size);
原型2 :
虚拟地址 : fixmap 中的 FIX_EARLYCON_MEM_BASE
典型地址 : fixmap : 0xffc00000 - 0xfff00000 (3072 kB) 中的 FIX_EARLYCON_MEM_BASE
虚拟地址范围 : 对于console来说,1个4KB ; 对于 dtb 来说,?个4KB
特点 :
1.不会睡眠(TODO)
2.不需要释放
3.申请前需要准备物理地址
4.能申请大于4K的空间,但还是比较小
5. 快(需要做一件事)
6.对于earlycon来说,返回地址 在 fixmap 区的 FIX_EARLYCON_MEM_BASE 内
应用场景:
1.场景必须满足: 在启动时
2.场景必须满足: 必须是 earlycon 或者 fdt 或者 ... 中 的 一种
2. fixmap 临时
原型 : void __init __iomem * early_ioremap(resource_size_t phys_addr, unsigned long size)
虚拟地址 : fixmap 中的 FIX_BTMAPS_SLOTS
典型地址 : fixmap : 0xffc00000 - 0xfff00000 (3072 kB) 中的 FIX_BTMAPS_SLOTS
虚拟地址范围 : ?个4KB
特点 :
1.不会睡眠(TODO)
2.使用完可尽快主动释放,就算不释放也会被early_fixmap_shutdown 释放
3.申请前需要准备物理地址
4.能申请大于4K的空间,但还是比较小
5. 快(需要做一件事)
6.返回地址 在 fixmap 区的 FIX_BTMAPS_SLOTS 内
应用场景:
1.场景必须满足: 在启动时
2.场景必须满足: 没有必要 是 earlycon 或者 fdt 或者 ... 中 的 一种,但是 earlycon 也可以使用这种内存申请方式
3.场景必须满足: 做好映射页表被clear的准备,在内核运行时不会再去访问映射得到的虚拟地址
- fixed_addresses & 三种 虚拟内存管理机制
enum fixed_addresses {
FIX_EARLYCON_MEM_BASE,
__end_of_permanent_fixed_addresses,
FIX_KMAP_BEGIN = __end_of_permanent_fixed_addresses,
FIX_KMAP_END = FIX_KMAP_BEGIN + (KM_MAX_IDX * NR_CPUS) - 1,
FIX_TEXT_POKE0,
FIX_TEXT_POKE1,
__end_of_fixmap_region,
#define NR_FIX_BTMAPS 32
#define FIX_BTMAPS_SLOTS 7
#define TOTAL_FIX_BTMAPS (NR_FIX_BTMAPS * FIX_BTMAPS_SLOTS)
FIX_BTMAP_END = __end_of_permanent_fixed_addresses,
FIX_BTMAP_BEGIN = FIX_BTMAP_END + TOTAL_FIX_BTMAPS - 1,
__end_of_early_ioremap_region
};
fixmap 地址从上到下
fixmap 永久 : FIX_EARLYCON_MEM_BASE
kmap_atomic : FIX_KMAP_BEGIN - FIX_KMAP_END
fixmap 临时 : FIX_BTMAP_BEGIN - FIX_BTMAP_END
可见 , "fixmap 永久" kmap_atomic "fixmap 临时" 都是基于 fixmap 的
------------------
fixed_addresses 中的成员 为 index , 我们要将 index 转换为 虚拟地址,可通过
#define __fix_to_virt(x) (FIXADDR_TOP - ((x) << PAGE_SHIFT))
- vmalloc机制与内核高端内存映射机制 与 fixmap(固定映射) 的关系与区别
vmalloc
vmalloc机制可用于高端内存的临时映射,这里的临时是说,32位系统下,虚拟内存的数量比物理内存的数量小。
vmalloc:什么都没有,可以用vmalloc申请,申请的可以大于2M,甚至可以100M。 特点:慢,需要做物理内存和虚拟内存申请和映射三件事;地址空间大,返回的地址在那VMALLOC(一般大于100M)空间内
以上两者:kmap过程中可能会睡眠,vmalloc也可能会
kmap
kmap可用于高端内存的永久映射,永久是相对于kmap_atomic来说的。
kmap有512个页表,一个映射4k字节,也就是512*4K,2M的映射空间
kmap_atomic有13*cpu_number个页表,能映射的空间一般比kmap小
kmap映射的空间,用的页表,是不能被别人占用的。自己将其解除映射后,别人才能申请到。
kmap_atomic是这么设计的, A申请了一个页表a,A还没释放,B也可以申请a。对于一个页表a来说,A和B不管他是否被占用,直接把自己的内容放进去。但这样会造成静态。所以AB申请a的时候自带对a的加锁,因为所以A申请到a后,得尽快释放a
kmap_atomic用的的FIX_KMAP_BEGIN,基于fixmap
kmap用的是PKMAP_BASE空间,和fixmap没关系
kmap:以page为输入,可以用kmap申请,申请的为4K,不可更大。
特点:快,需要做映射一件事。地址空间小,返回的地址就在那2M内。
以上两者:kmap过程中可能会睡眠,vmalloc也可能会
kmap_atomic,不会睡眠,以page为输入。申请的为4K,特点,快,只需要映射,比kmap还快。地址空间小,比kmap还小
fixmap
是用来在内存没有完全建立的时候做映射用的
做完映射的一部分(临时)被清理,一部分保留
被清理的是公共使用的,被保留的都是私有模块在用的
私有模块有几种
dtb
earlycon
公共的提供接口early_ioremap
fixmap 的应用时机是启动时(内存机制还没完全建立),虚拟地址是固定(编译时固定)的。空间比较小,物理地址是使用模块(消费者)确定的,映射是使用过程中建立的。分为临时(会被系统启动过程中的shutdown关闭),永久(可以系统启动完成后继续使用)
fixmap(固定映射)
临时的 : 在系统启动过程early_fixmap_shutdown中,创建的映射关系(pte)被会清0
temporary-fix-map
temporary fixed address
在 fixed_addresses 枚举体 中 0 - __end_of_permanent_fixed_addresses
例如 FIX_EARLYCON_MEM_BASE 对应的 earlycon / early_ioremap
永久的 : 创建好的不会被清0
permanent-fix-map
permanent fixed address
在 fixed_addresses 枚举体 中 __end_of_permanent_fixed_addresses - __end_of_fixed_addresses(__end_of_fixmap_region/或__end_of_early_ioremap_region 哪个大是哪个)
例如 FIX_KMAP_BEGIN/FIX_KMAP_END
内核高端内存映射机制
临时的 :
kmap_atomic 不会阻塞
临时映射,一个cpu core只能建立16个映射,一般建立后用完会马上清除,因为空间不够
基于 fixmap 的 FIX_KMAP_BEGIN/FIX_KMAP_END
返回地址 : FIXADDR_START - FIXADDR_TOP
有人把这种方式 成为 固定映射(fixmap)
固定映射和临时映射不能等同,严格来说临时映射只是固定映射的一段FIX_KMAP_BEGIN到FIX_KMAP_END区间
永久/持久的 :
persisent-kernel-mapping
kmap 会阻塞,不能用在中断处理函数和可延迟函数
长期映射,不会因为空间不够而清0 原来建立的,可以建立很多映射
基于 与fixmap 几乎同时创建的 pkmap_page_table
返回地址: PKMAP_BASE - FIXADDR_START