本文共 9704 字,大约阅读时间需要 32 分钟。
在 mmu 开启之后, 存在了两种内存 物理内存 虚拟内存就需要对 这两种内存 进行管理我们用内存的时候,需要 1. 物理内存 2. 虚拟内存 // 与 物理内存对应的虚拟内存对物理内存的管理方法有4种(根据内存模型不同而不同) CONFIG_FLATMEM CONFIG_DISCONTIGMEM CONFIG_SPARSEMEM_VMEMMAP CONFIG_SPARSEMEM对虚拟内存的管理方法有4种,我们关注以下过程 直接映射区 vmalloc 动态映射区 持久映射区 kmap 临时映射区 fixmap (kmap_atomic 临时内核映射)注意 : 在运行时, 物理内存的管理方法只有一种,配置哪种就是哪种 虚拟内存的管理方法有4种对于内存管理,我们关注 // 1. 存放 // 2. 申请 // 3. 释放
1. 初始化 配置了 CONFIG_FLAT_NODE_MEM_MAP 之后 pglist_data 结构体 中 才会有 node_mem_map 成员 // 为所有的物理内存创建 struct page (每4KB创建一个) pg_data_t *pgdat = NODE_DATA(nid); // &contig_page_data struct page * map = memblock_alloc_node(size, SMP_CACHE_BYTES, pgdat->node_id); pgdat->node_mem_map = map + offset; reserve_bootmem_region // 设置 struct page 的 flags 成员 __free_memory_core // 将 struct page 挂到 free_list 成员中 // 此时 struct page 也在 node_mem_map 中2. 申请 get_page_from_freelist3. 释放 add_to_free_list
1.初始化 map_lowmem2.申请/获取 page_to_virt(page) page_address(page);3.释放 不需要释放
特点 1.线性映射关系,管理简单 2.唯一的lowmemory 映射的区域 3.映射一旦完成,系统运行期间不会改变.其他的4种都会改变
应用场景 TODO
1. 初始化 vmalloc_init2. 申请 __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
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. 临时映射区的使用是有限制的,映射使用完成后必须马上去除映射.// 因为映射中调用 preempt_disable 关闭了内核抢占
应用场景 TODO
1. 初始化 2. 申请 addr = get_unmapped_area(file, addr, len, pgoff, flags);3. 释放 vma = find_vma(mm, start); // 类似于 vmalloc 的 va = __find_vmap_area((unsigned long)addr); // unmap_region(mm, vma, prev, start, end); remove_vma_list(mm, vma); // 类似于 vmalloc 的 free_unmap_vmap_area(va);
特点 1.只有这一种能用于 进程用户空间虚拟地址的管理 2.地址范围在 0x0000 0000 - 0xC000 0000
应用场景 TODO
在处理 用户需求(申请内存)时,要处理 1. 物理内存的申请 2. 虚拟内存的申请实现有 buddy vmalloc , 这些实现 是 对 level1虚拟内存的管理 level1物理内存的管理 的封装在内核配置一定的基础上, buddy管理了 运行时的物理内存管理 配置 管理了 物理内存 // 设置了管理内存的方式 buddy 针对这些配置 实现了 不同的 物理内存管理,但在运行时,只有一种 物理内存管理,即 配置确定后的buddy
buddy很奇怪,封装了如下,但并不是简单的封装 level1 第一种 物理内存的管理 // 管理的物理内存来自于 buddy 管理的 所有的ZONE的 struct page 没有封装 虚拟内存的管理buddy 提供的api中 alloc_pages/alloc_page 封装了物理内存的管理,没有封装虚拟内存的管理 __get_free_pages/__get_free_page 封装了物理内存的管理,并封装了虚拟内存的管理 // 通过 page_address(page);封装了 虚拟内存的管理 // __get_free_page() 分配的是物理地址,而返回的则是虚拟地址(虽然这听上去有些别扭)可以这么说: buddy 实现了对物理内存的管理,并没有实现对虚拟内存的管理 但在buddy输出的api里面,有些api封装了对虚拟内存的管理 但是我们一般说 buddy 管理的仅仅是物理内存,而不包括虚拟内存
vmalloc 封装了 第一种 物理内存的管理 // 管理的物理内存来自于 buddy 管理的 ZONE_HIGHMEM 中的 struct page // 每次只申请一个 page ,所以 申请出来的多块内存不连续 第二种 虚拟内存的管理
slab 是 很特殊的,不是 这几种合成的产物 物理内存的管理 // 在 buddy(不带封装虚拟内存的buddy)的基础上,将struct page 分成几块 来管理 // kmalloc会根据申请的大小来选择基于slub分配器或者基于Buddy System来申请连续的物理内存 // 基于 slub 分配的话, 一个 对象 小于 4KB , 所以在 4KB 内 物理内存是连续的 // 基于 buddy 分配的化, 由于一次申请多个 4KB块, 所以如果申请成功,则 多个4KB块内 是连续的 第一种虚拟内存的管理 // 通过 page_address(page);封装了 虚拟内存的管理 ??? 没有找到代码kmalloc 基于 slab
buddy 属于 物理内存管理还是虚拟内存管理 物理内存管理物理内存是什么 : 内存中所有的页框 物理内存怎么管理 1个页框 对应 一个 struct page 结构体 , 存储 在 mem_map 数组中. 物理地址 在 struct page 中的 体现 体现在 struct page 的 地址上 : page_to_phys(page) // 虽然这里说的是物理内存的管理,但是alloc返回的却是虚拟地址. // 查找 struct page 对应的 虚拟地址 由 page_address 提供实现 虚拟地址 在 struct page 中的 体现 如果是低端内存,体现在 struct page 的 地址上 : page_to_virt(page) // 高端内存 建立映射的方法有三种,为什么只在 一种(kmap) 中 找 虚拟地址 如果是高端内存,体现在 在 持久内核映射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. 非连续内存分配 : 虚拟内存怎么管理
内存的管理 物理内存管理原理 // 物理内存 的关系(phy_addr与page)由 固定 一次函数关系 来描述 // 不同(可选的有四种)的 模型 , 有不同的 函数关系 // 在 CONFIG_FLATMEM 模型下,函数关系如下 // phy_addr = (addr_of(page) - addr_of(mem_map) + ARCH_PFN_OFFSET) << PAGE_SHIFT // mem_map 为 一个 固定值 // ARCH_PFN_OFFSET = __pv_phys_pfn_offset 物理内存管理实现 1. buddy 物理地址 在 struct page 中的 体现 体现在 struct page 的 地址上 : page_to_phys(page) 2. slab slab 没有 对物理内存的管理,完全是 基于 buddy // TODO,基于buddy的物理内存管理,将物理内存管理粒度变的更小 3. vmalloc vmalloc 没有 对物理内存的管理,完全是 基于 buddy 虚拟内存管理原理 // 虚拟内存 由 以下四种管理 // 直接映射区 低端内存 // vmalloc 动态映射区 内存连续 // 持久映射区 kmap 进程空间 // 临时映射区 基于fixmap (kmap_atomic) 不会睡眠/快速` 虚拟内存:低端内存实现 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 // vmalloc 动态映射区 虚拟地址在 struct vmap_area 结构体 中的 va_start 成员 体现 struct vmap_area 结构体被挂载到 vmap_area_root和vmap_area_list
内存模型 https://zhuanlan.zhihu.com/p/220068494#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))); \ })
能直接用 用 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 // page_address_htable // https://blog.csdn.net/u011011827/article/details/116916526 // 一级页表 在 0x50007xxx // 二级页表 在 &(pkmap_page_table[last_pkmap_nr]) // // pkmap_page_table = early_pte_alloc(pmd_off_k(PKMAP_BASE) // 虚拟地址 在 memory layout 的 PKMAP 区域 , PKMAP 区域 在 配置了 CONFIG_HIGHMEM 之后 才会存在 ,低于0xc000 0000 // 虚拟地址范围 PKMAP_BASE - PKMAP_BASE+ LAST_PKMAP*4K 2. 临时内核映射 : kmap_atomic/kunmap_atomic : 利用了 fixmap // 一级页表 在 0x50007xxx // 二级页表 在 kmap_pte - idx // pte_t *kmap_pte = kmap_get_pte(); // virt_to_kpte(__fix_to_virt(FIX_KMAP_BEGIN)); // 虚拟地址 在 memory layout 的 fixmap 区域 // 虚拟地址范围 FIXADDR_TOP_START - FIXADDR_TOP 3. 非连续内存分配 : vmap/vunmap : 是 vmalloc 相关的函数 // 一级页表 在 0x50007xxx // 二级页表 在 pte = pte_alloc_kernel_track(pmd, addr, mask); // 虚拟地址 在 memory layout 的 vmalloc 区域 // 虚拟地址范围 VMALLOC_START - VMALLOC_END // map_kernel_range -> map_kernel_range_noflush -> vmap_p4d_range -> vmap_pud_range -> vmap_pmd_range // vmap_pmd_range // pmd_alloc_track // // 申请写入的 一级页表的地址 (位于 0x50007xxx) // vmap_pte_range // // 处理二级页表 // vmap_pte_range // pte_alloc_kernel_track // // 申请写入的 二级页表的地址 // // 写入一级页表 // set_pte_at // // 写入二级页表 // 二级页表的申请 // pte_alloc_kernel_track __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));
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 // 有人说 FIX_EARLYCON_MEM_BASE 属于 permanent fixed address // 但是 实验结果是 earlycon 做的映射 在 early_fixmap_shutdown 中被 清0 了 永久的 : 创建好的不会被清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