0%

linux mmu pte 相关

内存分配

kmalloc, vmalloc malloc

kmalloc

基于 slab 分配器。 slab 缓存在一个连续物理地址的大块内存上。所以其缓存对象也是物理地址连续的。

vmalloc

仅能保证虚拟地址连续

vmalloc 地址范围

VMALLOC_START
VMALLOC_END

1
2
3
4
5
6
7
8
[    0.000000] Virtual kernel memory layout:
[ 0.000000] fixmap : 0xff1bfffffea00000 - 0xff1bffffff000000 (6144 kB)
[ 0.000000] pci io : 0xff1bffffff000000 - 0xff1c000000000000 ( 16 MB)
[ 0.000000] vmemmap : 0xff1c000000000000 - 0xff20000000000000 (1024 TB)
[ 0.000000] vmalloc : 0xff20000000000000 - 0xff60000000000000 (16384 TB) #vmalloc_start - vmalloc_endl
[ 0.000000] modules : 0xffffffff01576000 - 0xffffffff80000000 (2026 MB)
[ 0.000000] lowmem : 0xff60000000000000 - 0xff600000c0000000 (3072 MB)
[ 0.000000] kernel : 0xffffffff80000000 - 0xffffffffffffffff (2047 MB)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
-+ vmalloc(unsigned long size)
\ -+ __vmalloc_node(size, align:1, GFP_KERNEL)
\ -+ __vmalloc_node_range(size, align, VMALLOC_START, VMALLOC_END,...)
\ -+ area = __get_vm_area_node(real_size, align, shift, VM_ALLOC | VM_UNINITIALIZED | vm_flags, start, end, node,
\ - struct vm_struct *area = kzalloc_node(sizeof(*area), gfp_mask & GFP_RECLAIM_MASK, node);
| -+ struct vmap_area *va = alloc_vmap_area(size, align, start, end, node, gfp_mask, 0); "在 vmalloc 整个空间中查找一块合适的没有使用的空间 hole"
"查找的地址从 VMALLOC_START 开始, 首先从 vmap_area_root 红黑数上查找, 该树上存放着正在使用的 vmalloc 区块”
“从 VMALOC_START 开始, 查找每个存在的vmalloc 区块的hole能够容纳目前要分配的大小。 如果找到了合适的hole,
调用 __insert_vmap_area 把这个 hole 注册到该红黑树中, 如果没找到hole, 则从最后一个 vmalloc 区块的结束地址开始一个新的 vmalloc 区块"
| -+ setup_vmalloc_vm(area, va, flags, caller); "将该找到的 va 填充到 vm area 中"
| -+ __vmalloc_area_node(area, gfp_mask, prot, shift, node);
\ - area->pages = kmalloc_node(array_size, nested_gfp, node); "分配 area->pages 二维数组指针"
| - area->nr_pages = vm_area_alloc_pages(gfp_mask | __GFP_NOWARN, node, page_order, nr_small_pages, area->pages);
"使用 alloc_page 分配物理页面, 并使用 area->pages 保存这些 page的指针"
| -+ vmap_pages_range(addr, addr + size, prot, area->pages, page_shift); "建立页面映射"
\ -+ vmap_pages_range_noflush(addr, end, prot, pages, page_shift);
\ -+ __vmap_pages_range_noflush(addr, end, prot, pages, page_shift);
\ -+ for i in npages:
\ -+ vmap_range_noflush(addr, addr + (1UL << page_shift), page_to_phys(pages[i]), prot, page_shift); "为每个 pte 建立映射"
"pgd-> p4d -> pud -> pmd -> pte"
\ - pgd = pgd_offset_k(addr); "从 init_mm 中获取 指向 pgd 页面目录项的基址, 然后通过addr 找到对应的pgd 表项"
| -+ flush_cache_vmap(addr, end); "按地址 刷新tlb"
\ -+ flush_tlb_kernel_range(start, end)
\ -+ __flush_tlb_range(struct mm_struct *mm, unsigned long start, unsigned long size, unsigned long stride)
\ - "sfence.vma ..."

malloc

用户态调用 malloc 分配堆内存, c 库调用 brk 向系统申请内存

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
-+ SYSCALL_DEFINE1(brk, unsigned long, brk)
\ - min_brk = mm->start_brk
| -+ if (brk < min_brk) goto out "mm_struct 中有一个存档数据段的结束地址 start_brk, 如果申请的brk 边界小于该地址, 不正常, 退出"
| -|+ if (brk <= mm->brk) "如果新边界 小于 老边界, 表示要释放内存"
\ - do_vma_munmap(&vmi, brkvma, newbrk, oldbrk, &uf, true) "释放 new_brk 与 old_brk 之间的空间"
|+ else
\ - brkvma = vma_prev_limit(&vmi, mm->start_brk); "查找 start_brk 所在的vma"
| -+ do_brk_flags(&vmi, brkvma, oldbrk, newbrk - oldbrk, 0)
\ - vma = vm_area_alloc(mm); "分配新的 vma, vma的地址空间为 [addr, addr+len], 基于 slab 系统分配内存"
| - vma_set_anonymous(vma); "设置为 匿名页面"
| - vma->vm_start = addr;
| - vma->vm_end = addr + len;
| - ksm_add_vma(vma);
| - mm->map_count++;
| - mm->total_vm += len >> PAGE_SHIFT;
| - mm->brk = brk;
| -|+ if (mm->def_flags & VM_LOCKED) "VM_LOCKED 来自于 mlockall 系统调用, 用户程序很少使用 VM_LOCKED 分配 mask
设置了 VM_LOCKED 后, 会立即为用户态进程分配物理页面, 如果不设置, 则会将分配物理页面的工作延迟到用户进程访问这些虚拟页面时,
发生了缺页中断才会分配物理内存, 并和虚拟地址建立映射关系"
\ -+ mm_populate(oldbrk, newbrk - oldbrk);
\ -+ __mm_populate(addr, len, ignore_errors:1);
\ -+ for (nstart = start; nstart < end; nstart = nend)
\ -+ populate_vma_page_range(vma, nstart, nend, &locked); "为 vma 分配物理内存"
\ -+ __get_user_pages(mm, start, nr_pages, gup_flags, NULL, locked ? locked : &local_locked);
"为进程地址空间分配物理内存并建立映射关系, mm 是进程内存管理的 mm_struct 数据结构"