内存管理

一、虚拟内存布局(用户态进程)

低地址 0x0000_0000
┌────────────────┐
│       0         │  NULL(陷阱)
├────────────────┤
│     .text      │  代码
├────────────────┤
│    .rodata     │  常量
├────────────────┤
│     .data      │
├────────────────┤
│     .bss       │
├────────────────┤
│   heap(↑)     │  malloc
│       ...      │
├────────────────┤
│  内存映射段(↑)│  mmap、共享库
├────────────────┤
│  栈(↓)        │
│       ...      │
├────────────────┤
│ 环境/命令行     │
├────────────────┤
│ 内核随机栈      │
└────────────────┘ 高地址 0xFFFF_FFFF

二、内核态内存布局(ARM)

低地址 0xC000_0000
┌────────────────┐
│    .text      │  内核代码
├────────────────┤
│   .rodata     │
├────────────────┤
│   .data       │
├────────────────┤
│   .bss        │
├────────────────┤
│   vmalloc     │  vmalloc 区
├────────────────┤
│   vmemmap     │  struct page 数组
├────────────────┤
│   固定映射      │
├────────────────┤
│   modules     │  模块
├────────────────┤
│   ...         │
├────────────────┤
│  直接映射(lowmem)
│  物理 RAM 1:1   │
├────────────────┤
│   ...         │
├────────────────┤
│  高端内存       │  仅 32 位
└────────────────┘ 高地址 0xFFFF_FFFF

64 位 ARM 没有高端内存,所有物理 RAM 都能直接映射。

三、页表

1. 三级页表(ARMv7,4KB 页)

PGD → PUD → PMD → PTE
每级 4 字节,4096 项
2级 → 1GB
3级 → 2MB
4级 → 4KB

2. PTE 位

Bit 0    PRESENT
Bit 1    R/W
Bit 2    U/S    用户/超级
Bit 3    PWT
Bit 4    PCD
Bit 5    A      Accessed
Bit 6    D      Dirty
Bit 7    PAT
Bit 12-51 物理页框号

3. ARMv8 页表(4 级)

L0 → L1 → L2 → L3
每级 9 bit index

四、内存分配 API

1. 页级

struct page *p = alloc_pages(GFP_KERNEL, order);  // 2^order 页
void *addr = page_address(p);                       // 转为虚拟地址
__free_pages(p, order);

get_free_page(GFP_KERNEL);    // 1 页
free_page(addr);

2. slab(kmalloc)

void *p = kmalloc(size, GFP_KERNEL);
void *p = kzalloc(size, GFP_KERNEL);       // 自动清零
void *p = kvmalloc(size, GFP_KERNEL);      // 大块走 vmalloc
void *p = krealloc(p, new_size, GFP_KERNEL);
kfree(p);

常用 GFP 标志:
- GFP_KERNEL:进程上下文,可睡眠
- GFP_ATOMIC:不能睡眠(中断、持锁)
- GFP_DMA:DMA 内存
- GFP_USER:用户页
- GFP_HIGHUSER:高端内存用户页

3. vmalloc

void *p = vmalloc(size);            // 虚拟地址连续,物理不连续
vfree(p);

4. 内存池(mempool)

mempool_t *pool = mempool_create(min_nr, alloc_fn, free_fn, pool_data);
void *p = mempool_alloc(pool, GFP_KERNEL);
mempool_free(p, pool);
mempool_destroy(pool);

五、用户态内存映射

// 简单方式
unsigned long vaddr = vm_mmap(file, 0, len, prot, flags, off);
// 推荐方式
long vaddr = do_mmap(file, off, len, prot, flags, off);

驱动中常用 remap_pfn_range

static int mmap(struct file *filp, struct vm_area_struct *vma)
{
    unsigned long pfn = virt_to_phys(buf) >> PAGE_SHIFT;
    return remap_pfn_range(vma, vma->vm_start, pfn, vma->vm_end - vma->vm_start,
                           vma->vm_page_prot);
}

六、页缓存

七、内存回收

1. 页面回收算法

2. OOM(Out of Memory)

八、CMA(Contiguous Memory Allocator)

九、内存调试

# /proc/meminfo
cat /proc/meminfo
slabtop
cat /proc/slabinfo
cat /proc/buddyinfo       # 伙伴系统空闲块
cat /proc/pagetypeinfo
cat /proc/vmstat
cat /proc/zoneinfo

# 跟踪
echo 1 > /proc/sys/vm/drop_caches     # 释放页缓存
echo 1 > /proc/sys/vm/oom_kill_allocating_task  # OOM 杀当前

十、嵌入式注意点