【linux内核专栏 04】内存管理

发布时间:2026/10/9 5:33:29
【linux内核专栏 04】内存管理 本篇定位:你最大的认知跳变——从一个物理堆跳到每进程虚拟地址空间 page cache 多层分配器。本篇把这些在 Linux 里怎么用讲透:mm_struct/buddy/slab/缺页/mmap/page cache/kmalloc vs vmalloc/DMA 映射。读完能选对 kmalloc/vmalloc、能调 OOM、能配 DMA buffer、能讲清 page cache 为什么让 Linux 又快又省。已有的内存知识是 Linux 内存的子集,核心增量是虚拟化 多层 缓存FreeRTOS:一个 heap,直接物理地址,立即分配。Linux 三大增量:虚拟化:每进程独立虚拟地址空间(靠你学的 Sv39 页表 page fault)多层分配器:buddy(页) slab(对象) kmalloc/vmalloc(内核) mmap(用户),各管一档Page Cache:文件内容缓存,内存富裕就多缓存,自动加速理解这三层增量,就理解 Linux 内存管理。你 Sv39/page fault 知识直接迁移,新增的是分配器层次和 page cache。目录一、地址空间:物理 vs 虚拟1.1 三种地址1.2 用户态 vs 内核态地址空间1.3 对照 FreeRTOS / RISC-V二、mm_struct:进程地址空间2.1 mm_struct 核心字段2.2 vm_area_struct(VMA):地址区间2.3 看进程地址空间三、Buddy 系统:物理页分配3.1 为什么需要 buddy3.2 buddy 怎么工作3.3 API3.4 GFP 标志(Get Free Pages)3.5 对照 FreeRTOS heap四、Slab/Slub/Slob:对象分配器4.1 为什么需要 slab4.2 三种实现4.3 API4.4 kmalloc:通用 slab4.5 vmalloc:虚拟连续即可4.6 kmalloc vs vmalloc 决策五、缺页异常(Demand Paging)⭐5.1 缺页是什么5.2 三种缺页5.3 do_page_fault 流程5.4 对照你 RISC-V trap5.5 看缺页六、mmap:内存映射6.1 mmap 是什么6.2 两种映射6.3 MAP_SHARED vs MAP_PRIVATE6.4 设备 mmap(驱动用)七、Page Cache(文件缓存)⭐7.1 Page Cache 是什么7.2 读路径7.3 写路径(buffered write)7.4 direct IO(绕过 cache)7.5 O_SYNC / fsync7.6 看缓存7.7 回收(reclaim)八、DMA 内存映射(驱动必懂)8.1 三种 DMA 映射8.2 一致性 DMA8.3 流式 DMA8.4 对照你 RISC-V cache 一致性九、OOM(Out of Memory)9.1 OOM 何时触发9.2 OOM Killer 选谁9.3 调 OOM十、内存管理全景图十一、对照总表十二、本篇小结速查表一、地址空间:物理 vs 虚拟1.1 三种地址地址是什么谁用虚拟地址(VA)进程看到的地址用户态/内核态都用物理地址(PA)真实 RAM 地址内核/DMA 用总线地址DMA 经总线的地址(物理或经 IOMMU)DMA 用1.2 用户态 vs 内核态地址空间64 位 Linux(以 RISC-V rv64 / ARM64 为例,典型 39/48 位 VA):虚拟地址空间(每进程): 高地址 ┌─────────────────────────┐ │ 内核空间(所有进程共享) │ 直接映射区 vmalloc 固定映射 │ (X% 内存) │ ├─────────────────────────┤ ← TASK_SIZE(用户/内核分界) │ 用户栈 ↓ │ │ │ │ mmap 区(文件/匿名映射)│ │ ↑ 堆(brk) │ │ │ │ BSS / Data / Text │ 低地址 └─────────────────────────┘ 0用户态用低地址,内核态用高地址(所有进程共享同一份内核地址空间)TASK_SIZE:用户/内核分界(rv64 约 0x400000000000,即 256TB 用户空间)1.3 对照 FreeRTOS / RISC-VFreeRTOSRISC-V bare-metalLinux地址空间一个共享物理一个物理(或你开 MMU)每进程独立虚拟用户/内核分界无无(全 M)TASK_SIZE内核地址共享共享共享(高地址)用户地址共享—每进程独立(低地址)RISC-V Sv39 知识直接用[[06-内存 PMP-MMU-RVWMO]] 学的 Sv39 三级页表/satp/sfence.vma/A 位 D 位——Linux 内存管理就用这套。每进程一个pgd(页全局目录,satp 指向),切换进程 切 satp flush TLB。你不需要重学页表,只学 Linux 怎么用它组织分配。二、mm_struct:进程地址空间2.1 mm_struct 核心字段structmm_struct{pgd_t*pgd;// 页全局目录(物理页表根,satp 指向)atomic_tmm_users;// 用户数(线程共享)atomic_tmm_count;// 引用计数intmap_count;// VMA 数量unsignedlongstart_code,end_code;// 代码段unsignedlongstart_data,end_data;// 数据段unsignedlongstart_brk,brk;// 堆unsignedlongstart_stack;// 栈unsignedlongmmap_base;// mmap 区基址structvm_area_struct*mmap;// VMA 链表structrb_rootmm_rb;// VMA 红黑树(快速查找)...};每个进程一个mm_struct(线程共享)pgd是页表根(satp 值 pgd物理地址 12 | mode)VMA 链表 红黑树管理地址区间2.2 vm_area_struct(VMA):地址区间structvm_area_struct{unsignedlongvm_start;// 区间起始unsignedlongvm_end;// 区间结束unsignedlongvm_flags;// 属性(VM_READ/WRITE/EXEC/MAYSHARE...)structfile*vm_file;// 关联文件(匿名映射为 NULL)unsignedlongvm_pgoff;// 文件内偏移conststructvm_operations_struct*vm_ops;// 操作(fault/map_pages)structmm_struct*vm_mm;...};VMA 一段连续虚拟地址区间,属性统一一个进程的地址空间 一堆 VMA 拼成mmap 创建 VMA,缺页填充 VMA2.3 看进程地址空间cat/proc/pid/maps# 输出示例:00400000-004b0000 r-xp 00000000 08:01100/bin/ls# 代码006b0000-006c0000 r--p 000a0000 08:01100/bin/ls# 只读数据006c0000-006e0000 rw-p 000b0000 08:01100/bin/ls# 数据7f1234560000-7f1234720000 r-xp... libc.so# 库代码7ffc12340000-7ffc12361000 rw-p...[stack]# 栈每行一个 VMA:起始-结束 属性 偏移 设备 inode 文件。三、Buddy 系统:物理页分配3.1 为什么需要 buddy最底层分配单位是页(page,通常 4KB)要分配连续物理页(2^order 页),用 buddy伙伴系统:管理 2^order 大小的空闲块,合并/分裂3.2 buddy 怎么工作每个 zone(Zone DMA/Normal/HighMem)有free_area[0..10]free_area[order]是 2^order 页的空闲块链表分配 2^order 页:从free_area[order]取;没有则从free_area[order1]分裂成两个 buddy,一个给,一个挂free_area[order]释放:挂回free_area[order],若 buddy 也空闲则合并成 2^(order1),递归分配 2 页(order1): free_area[1] 有 → 直接取 free_area[1] 无,free_area[2] 有 → 从 free_area[2] 取 4 页,分裂成 22 一个 2 页给你,一个 2 页挂 free_area[1]3.3 API// 分配structpage*palloc_pages(GFP_KERNEL,order);// 2^order 页void*addrpage_address(p);// 转虚拟地址// 释放__free_pages(p,order);// 便捷宏unsignedlongaddr__get_free_pages(GFP_KERNEL,order);free_pages(addr,order);// 单页structpage*palloc_page(GFP_KERNEL);__free_page(p);3.4 GFP 标志(Get Free Pages)GFP含义GFP_KERNEL内核正常分配(可睡眠,最常用)GFP_ATOMIC原子分配(不睡眠,中断上下文用)GFP_DMADMA 区(老 ISA 设备,低 16MB)GFP_HIGHUSER用户态(可被换出)GFP_NOWAIT不睡眠不等待中断上下文只能 GFP_ATOMIC你写中断处理(顶半部)要分配,只能GFP_ATOMIC——不能睡眠(中断不能睡)。GFP_KERNEL 会睡眠等内存,中断里用会 panic。这是 FreeRTOS 没有的约束(FreeRTOS 分配不睡)。3.5 对照 FreeRTOS heapFreeRTOS heap_4Linux buddy单位字节页(4KB)算法first fit 合并buddy(2^order)连续性字节连续物理页连续适合小对象大块/页级上面还有无slab(对象)buddy 管页,字节级小对象交给 slab(见 §四)。四、Slab/Slub/Slob:对象分配器4.1 为什么需要 slabbuddy 最小一页(4KB),分配 100 字节也给一页,浪费。slab 在 buddy 之上,把页切成对象大小的槽,对象池:频繁分配/释放同类型对象(task_struct/skb/inode),建专属缓存分配过的不销毁,留池里复用,快对象初始化一次,复用时已初始化4.2 三种实现实现特点用途Slab经典,复杂,管理开销大老 LinuxSlub(默认)简化,性能好,debug 友好现代默认Slob极简,小内存MC 级小系统4.3 API// 创建专属缓存structkmem_cache*my_cachekmem_cache_create(my_obj,sizeof(structmy_obj),0,SLAB_HWCACHE_ALIGN,NULL);// 分配(从池取)structmy_obj*okmem_cache_alloc(my_cache,GFP_KERNEL);// 释放(回池)kmem_cache_free(my_cache,o);// 销毁缓存kmem_cache_destroy(my_cache);4.4 kmalloc:通用 slab内核预建一组通用 slab(16/32/64/…/8192 字节),kmalloc从对应大小取:void*pkmalloc(100,GFP_KERNEL);// 取≥100 的 slab(128 字节)kfree(p);void*pkmalloc(8192,GFP_ATOMIC);// 8KB,中断用kfree(p);// 大对象void*pkmalloc(1024*1024,GFP_KERNEL);// 1MB,可能失败(物理连续要求)kmalloc返回物理连续内存(直接映射区)最大几 MB(物理连续难找大块)适合小到中等、要物理连续的4.5 vmalloc:虚拟连续即可void*pvmalloc(1024*1024*100);// 100MB,虚拟连续,物理可散vfree(p);vmalloc返回虚拟连续,物理可散(靠页表拼)可分配超大(几 GB)但不能直接给 DMA(DMA 要物理连续,除非 sg-list)访问稍慢(要过页表,可能 TLB miss)4.6 kmalloc vs vmalloc 决策维度kmallocvmalloc物理连续✅ 是❌ 否(虚拟连续)最大几 MB几 GB速度快(直接映射)稍慢(页表)DMA✅ 可用❌ 不可直接典型小对象/struct/DMA buffer大数组/大缓冲选 kmalloc 还是 vmalloc要 DMA / 物理地址 / 小对象 →kmalloc要超大缓冲 / 不在乎物理连续 →vmalloc中断里 →kmalloc(..., GFP_ATOMIC)(vmalloc 可睡,不能中断里用)你写驱动分配设备缓冲,优先 kmalloc(物理连续,好 DMA)。五、缺页异常(Demand Paging)⭐5.1 缺页是什么进程访问一个虚拟地址,页表里没有(或权限不对)→page faulttrap → 内核do_page_fault→ 分配物理页、填页表、返回重试。5.2 三种缺页缺页类型原因处理Minor fault页表没有,但数据在内存(page cache)建映射,不读盘Major fault页表没有,数据在磁盘读盘到内存,建映射写时复制 fault(COW)写只读页(共享)复制一份,改映射可写5.3 do_page_fault 流程do_page_fault(regs){addrregs-badaddr;// 出错虚拟地址(RISC-V stval)// 1. 找 VMAvmafind_vma(mm,addr);if(!vma||addrvma-vm_start)gotobad_area;// 没映射 → SIGSEGV// 2. 检查权限(读/写/执行)if(写!(vma-vm_flagsVM_WRITE))gotobad_area;// 3. 分配物理页,填页表handle_mm_fault(vma,addr,flags);// - 匿名页:alloc_page,清零// - 文件页:从 page cache 取,没有读盘// - COW:复制页,改映射return;bad_area:force_sig(SIGSEGV);// 段错误}5.4 对照你 RISC-V trap[[04-trap 机制详解]] trap handler 读 mepc/mcause/mtval。Linux page fault 就是其中一类:RISC-V: mcause13(load page fault)/15(store page fault),stval出错地址Linuxdo_page_fault处理这俩page fault 是 Linux 内存管理的发动机[[01-Linux内核心智模型与设计哲学]] 学的惰性分配(COW/demand paging/mmap)——全靠 page fault 触发。内核:① 给地址承诺(建 VMA,不填页表);② 用户访问 → page fault;③ 内核分配页、填页表、返回。理解 page fault,就理解 Linux 内存管理一半。你 RISC-V trap 知识直接落地。5.5 看缺页# 系统缺页统计cat/proc/vmstat|grep-ifault# pgfault: 总缺页# pgmajfault: major fault(读盘)# 进程缺页ps-opid,min_flt,maj_flt,cmd# min_flt: minor fault# maj_flt: major fault(慢,读盘)六、mmap:内存映射6.1 mmap 是什么mmap把文件或设备映射到进程虚拟地址空间,访问内存 访问文件/设备。void*addrmmap(NULL,length,PROT_READ|PROT_WRITE,MAP_SHARED,fd,offset);// 之后读写 addr 读写 fd 的 offset~offsetlengthmunmap(addr,length);6.2 两种映射映射类型是什么用途文件映射(MAP_SHARED/PRIVATE fd)文件内容映射到内存访问文件不经 read/write匿名映射(MAP_ANONYMOUS)不关联文件,纯内存大块内存分配(malloc 大块用)6.3 MAP_SHARED vs MAP_PRIVATEMAP_SHARED:修改写回文件/共享给其他映射(进程间共享内存)MAP_PRIVATE:修改不写回(COW,私有副本)6.4 设备 mmap(驱动用)设备驱动实现file_operations.mmap,把设备内存(如 GPU 显存、FPGA 寄存器)映射到用户态,用户直接访问:// 驱动 mmap 实现staticintmy_mmap(structfile*f,structvm_area_struct*vma){vma-vm_page_protpgprot_noncached(vma-vm_page_prot);// 不缓存(MMIO)remap_pfn_range(vma,vma-vm_start,dev_mem_pfnPAGE_SHIFT,// 设备内存物理页帧vma-vm_end-vma-vm_start,vma-vm_page_prot);return0;}嵌入式视角:mmap 替代 read/write你裸机访问设备寄存器:*(volatile uint32_t*)0x40000000。Linux 用户态不能直接,但驱动mmap把设备内存映射到用户虚拟地址,用户访问该虚拟地址 访问设备寄存器。这是 Linux 给用户态直接操作硬件的合法通道(11 篇驱动框架详讲)。七、Page Cache(文件缓存)⭐7.1 Page Cache 是什么文件内容在内存的缓存读文件:先查 page cache,命中直接返回;不命中读盘 存 cache写文件( buffered):写 page cache,标记 dirty,稍后 writeback 到盘内存富裕就多缓存,自动加速重复访问7.2 读路径read(fd, buf, n) → VFS sys_read → 文件系统 readpage → 查 page cache 命中 → 拷贝到 buf,返回 不命中 → 读盘到 page cache → 拷贝到 buf7.3 写路径(buffered write)write(fd, buf, n) → VFS sys_write → 写 page cache(标记 dirty) → 返回(不立即写盘) → 内核 writeback 线程定期把 dirty 页写盘7.4 direct IO(绕过 cache)intfdopen(file,O_RDWR|O_DIRECT);// 读写不经 page cache,直接用户 buffer ↔ 盘自管缓存,避免 page cache 开销数据库常用7.5 O_SYNC / fsyncO_SYNC:write 立即写盘才返回(慢,但持久)fsync(fd):强制把该 fd 的 dirty 页写盘7.6 看缓存free-h# total used free shared buff/cache available# Mem: 7.8Gi 2.1Gi 1.0Gi 200Mi 4.7Gi 5.2Gi# buff/cache 就是 page cache( buffer cache)buff/cache:page cache,内存够时尽量缓存available:实际可用(算上可回收的 cache)7.7 回收(reclaim)内存紧张时,内核回收 page cache:干净页(cache 命中过未改):直接丢弃脏页:writeback 到盘再丢弃匿名页:swap 到交换区(若有)Page Cache 是 Linux 又快又省的核心FreeRTOS 无 cache(直接读写)。Linux 用空闲内存缓存文件,重复访问飞快,内存紧张自动回收。这是 Linux 服务器性能的基石。代价:① 突然断电 dirty 页丢(要 fsync);② OOM 时回收慢。你调性能要懂 page cache(16 篇 perf 详讲)。八、DMA 内存映射(驱动必懂)8.1 三种 DMA 映射类型API特点一致性(Coherent)dma_alloc_coherentCPU/DMA 自动一致,无需 flush;常用但贵流式(Streaming)dma_map_single / dma_map_page临时映射,需 sync;性能好散射聚集(SG)dma_map_sg多段物理散页,一次映射8.2 一致性 DMA// 分配 DMA buffer,CPU/DMA 自动一致void*cpu_addr;dma_addr_tdma_handle;cpu_addrdma_alloc_coherent(dev,size,dma_handle,GFP_KERNEL);// cpu_addr: CPU 访问用// dma_handle: 设备 DMA 用(物理/总线地址)// 用完释放dma_free_coherent(dev,size,cpu_addr,dma_handle);自动 cache 同步(硬件或软件),CPU/DMA 看到一致数据适合频繁收发的小 buffer(如设备控制环)8.3 流式 DMA// 已有 buffer,临时映射给 DMAdma_addr_tdma_handledma_map_single(dev,cpu_buf,size,DMA_TO_DEVICE);// 给设备用 dma_handle// 发送前 sync(DMA_TO_DEVICE cache clean,让 DMA 看到新数据)dma_sync_single_for_device(dev,dma_handle,size,DMA_TO_DEVICE);// 设备读完,CPU 要读dma_sync_single_for_cpu(dev,dma_handle,size,DMA_FROM_DEVICE);// (DMA_FROM_DEVICE cache invalidate,让 CPU 看到新数据)dma_unmap_single(dev,dma_handle,size,DMA_FROM_DEVICE);性能好(不长期占一致性域)要手动 sync(对应你 [[06b-内存架构深挖 PMA-ePMP-Cache-CMO-MMU-RVWMO]] 的 clean/invalidate)8.4 对照你 RISC-V cache 一致性你 RISC-V(06b)Linux DMAcache flush(clean)dma_sync_*_for_device(DMA_TO_DEVICE)cache invalidatedma_sync_*_for_cpu(DMA_FROM_DEVICE)coherent interconnectdma_alloc_coherent(硬件一致)non-coherent CMO流式 DMA syncDMA cache 一致性——Linux 用dma_*API 封装。用dma_alloc_coherent省心(自动一致),用流式要记得 sync。九、OOM(Out of Memory)9.1 OOM 何时触发内存严重不足,回收后还不够内核调out_of_memory()→ 选个进程 kill 释放9.2 OOM Killer 选谁算每个进程的 oom_score(占内存多 运行时间短 高分)kill oom_score 最高的(常是占内存多的胖子)可设/proc/pid/oom_score_adj调整(-1000 免杀)9.3 调 OOM# 看内存free-hcat/proc/meminfo# 看 OOM 历史dmesg|grep-ikilled process# OOM killer 杀的# 防某进程被杀echo-1000/proc/pid/oom_score_adj# 容器 OOM(cgroup)cat/sys/fs/cgroup/.../memory.oom_control嵌入式视角:嵌入式 OOM 常致命服务器 OOM 杀个进程,系统还活。嵌入式内存小,一个进程占爆 → OOM 杀关键服务 → 系统崩。你 BSP 要:① 设 cgroup 内存限额防止单进程吃爆;② 关键进程oom_score_adj-1000;③ 监控内存预警。这是嵌入式 Linux 比 服务器更敏感的点。十、内存管理全景图用户态分配: malloc(小) → ptmalloc/jemalloc(用户态堆) → brk/mmap syscall malloc(大) → mmap(匿名) │ ▼ 内核分配: 用户页(匿名/文件) → page cache 匿名页 → buddy 分配页 内核小对象 → kmalloc → slab/slub(对象池) → buddy 内核大块 → vmalloc(虚拟连续) → buddy 散页 内核大页 → hugetlb(2MB/1GB 页) DMA → dma_alloc_coherent / dma_map_single │ ▼ 物理页分配: buddy 系统(2^order 页,zone:DMA/Normal/HighMem) │ ▼ 硬件: 物理内存(DDR/SRAM) MMU(页表) cache十一、对照总表概念FreeRTOSRISC-V bare-metalLinux地址空间一个共享一个物理每进程虚拟页表无satp 可配pgd per-process分配单位字节字节/页页(buddy) 对象(slab)小对象heap_4静态/你写kmalloc→slab大块heap静态数组vmalloc/get_free_pages缺页无你写 handlerdo_page_faultmmap无无文件/匿名映射page cache无无文件缓存DMA 一致性你手动 flush你手动 flushdma_alloc_coherent/syncOOM无(分配失败返回)无OOM killer十二、本篇小结地址空间:每进程独立虚拟,内核空间共享高地址;TASK_SIZE 分界mm_struct管进程地址空间,vm_area_struct(VMA)管区间;/proc/pid/maps看 VMABuddy:页分配(2^order),alloc_pages/__get_free_pages,GFP_KERNEL(可睡)/GFP_ATOMIC(中断)Slab/Slub:对象池,kmem_cache_create/kmalloc;kmalloc 物理连续vmalloc:虚拟连续,物理可散,可超大,不能直接 DMA缺页:page fault 触发 demand paging/COW/mmap,你 RISC-V trap 知识直接用(mcause13/15)mmap:文件/匿名/设备映射,驱动file_operations.mmap把设备内存映射给用户态Page Cache:文件缓存,读命中快/写 dirty 后 writeback,free的 buff/cacheDMA:coherent(自动一致)/streaming(手动 sync),对接你 06b 的 clean/invalidateOOM:内存不足杀高分进程,嵌入式要防关键服务被杀速查表想干啥用什么内核分配小对象kmalloc(size, GFP_KERNEL)中断里分配kmalloc(size, GFP_ATOMIC)大块虚拟连续vmalloc(size)物理连续大块__get_free_pages(GFP_KERNEL, order)专属对象池kmem_cache_create/alloc用户态大块mmap(MAP_ANONYMOUS)文件映射mmap(fd)DMA 自动一致dma_alloc_coherentDMA 流式dma_map_single sync看进程地址空间cat /proc//maps看内存free / cat /proc/meminfo看缺页ps -o min_flt,maj_flt防 OOM 杀echo -1000 /proc//oom_score_adj强制写盘fsync(fd)绕过 cacheopen(O_DIRECT)技术之路漫漫分享是为了更好地交流。如果本文的内容对你有启发希望能得到你的点赞 和收藏 ⭐。如果你在调试过程中遇到了其他问题欢迎在评论区 留言我们一起探讨。也欢迎关注 我一起交流底层开发的那些事儿。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询