内存管理
一、虚拟内存布局(用户态进程)
低地址 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);
}
六、页缓存
- 内核用 page cache 缓存磁盘文件
- 读:
filp → page → copy_to_user - 写:writeback 机制(pdflush → writeback)
sync / fsync / fdatasync强制落盘
七、内存回收
1. 页面回收算法
- LRU(Least Recently Used)
- Linux 用双 LRU:
active_list/inactive_list - 每个 zone 维护 LRU
shrink_inactive_list / shrink_active_list
2. OOM(Out of Memory)
- 内存不够时触发 OOM Killer
- 选择
oom_score最高的进程杀掉 - 用
oom_score_adj调整:负值=更安全,正值=更易被杀
八、CMA(Contiguous Memory Allocator)
- 给 DMA 预留大块连续物理内存
- 平时可被普通分配占用
- DMA 时可"挤"出来
九、内存调试
# /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 杀当前
十、嵌入式注意点
- 启动参数
mem=256M限制内存 vmalloc=限制 vmalloc 区- 不要用 swap(闪存会缩短寿命)
- 用 CMA 处理大块 DMA
- 监控内存碎片:碎片化严重时
compact_zone