物理内存与虚拟内存:深入解析MMU工作原理

说实话,很多做MTK驱动的兄弟,一开始都被内存这块搞晕过。我当年刚接触MTK平台时,也踩过不少坑。今天咱们就好好聊聊物理内存和虚拟内存,把MMU这个"黑盒子"彻底拆开看看。

为什么需要虚拟内存?

你想想看,如果所有程序都直接操作物理内存,会是什么场景?

  • 程序A用了地址0x1000,程序B也得避开
  • 一个程序崩溃,整个系统跟着挂
  • 内存碎片化严重,大块连续内存根本申请不到

虚拟内存就是来解决这些问题的。它给每个进程一个"假地址",让进程觉得自己独占整个内存空间。实际上,MMU(内存管理单元)在背后偷偷做地址转换。

核心概念:虚拟地址是进程看到的地址,物理地址是硬件实际访问的地址。MMU就是这两者之间的"翻译官"。

MMU工作原理速览

MMU的工作流程,说白了就三步:

  1. CPU发出虚拟地址
  2. MMU查页表,找到对应的物理地址
  3. 如果页表里没有,触发缺页异常

我在项目中遇到过一个问题:某个外设驱动死活访问不了寄存器。查了两天,最后发现是MMU没给那段物理地址建立映射。嗯,这种低级错误,犯过一次就再也不会忘了。

MTK平台中的地址映射

MTK平台的内存布局,我习惯把它分成三块来看:

区域 物理地址范围 用途
系统内存 0x4000_0000 ~ 0x9FFF_FFFF 主内存,跑Linux内核和用户程序
外设寄存器 0x1000_0000 ~ 0x1FFF_FFFF GPIO、UART、I2C等外设控制寄存器
多媒体内存 0xB000_0000 ~ 0xBFFF_FFFF GPU、ISP、VPU专用内存

注意,这只是我手头一个项目的例子。不同MTK芯片的地址分配会有差异,具体要看datasheet。

通过/proc/iomem查看内存布局

这是我最常用的调试手段。在MTK板子上跑一下:

# cat /proc/iomem
40000000-4fffffff : System RAM
  40008000-40ffffff : Kernel code
  41000000-411fffff : Kernel data
10000000-10000fff : mtk_uart
10001000-10001fff : mtk_gpio
10002000-10002fff : mtk_i2c
b0000000-bfffffff : Multimedia memory

每一行都告诉你:哪段物理地址被谁占用了。我曾经靠这个定位过一个内存冲突问题——两个驱动模块不小心映射到了同一段物理地址,结果数据互相覆盖,系统随机崩溃。

我的小技巧:调试时先看/proc/iomem,确认你的目标物理地址没有被其他模块占用。这能省下不少排查时间。

MTK平台中的映射方式

在MTK Linux内核里,地址映射主要有两种方式:

  • 静态映射:在设备树或板级文件中提前定义好。适合固定外设,比如UART、GPIO。
  • 动态映射:驱动运行时通过ioremap()申请。适合不确定地址的场景,比如DMA缓冲区。

我建议能静态映射的就别动态映射。为什么?静态映射在系统启动时就建立好了,运行效率高,而且不容易出错。动态映射虽然灵活,但多了ioremap/unmap的开销,搞不好还会内存泄漏。

避坑指南:MMU相关的常见问题

我曾经踩过的坑

  • DMA缓冲区没做cache一致性处理,数据死活不对。后来加了dma_alloc_coherent才搞定。
  • ioremap时忘了检查返回值,结果访问了空指针,内核直接panic。
  • 多个驱动共享同一段物理内存,但没有同步机制,数据竞争导致随机崩溃。

嗯,这些坑说起来都是泪。但反过来想,踩过一次就记住了,以后写驱动时自然会多留个心眼。

实战:查看并理解MTK板子的内存映射

我建议你拿到一块MTK板子后,先做这几步:

  1. 开机后执行cat /proc/iomem,看看整体布局
  2. 找到你关心的外设地址段,比如UART在0x10000000
  3. 写一个简单的驱动,用ioremap映射这段地址
  4. 用readl/writel读写寄存器,验证映射是否成功

这个过程走一遍,你对MMU和地址映射的理解就会深很多。纸上得来终觉浅,绝知此事要躬行嘛。

好了,关于物理内存和虚拟内存,今天就聊到这儿。下一节咱们会深入DMA传输,看看数据是怎么在内存和外设之间高效流动的。到时候我会分享一些我在MTK平台上做DMA优化的实战经验,敬请期待。