物理内存与虚拟内存:深入解析MMU工作原理
说实话,很多做MTK驱动的兄弟,一开始都被内存这块搞晕过。我当年刚接触MTK平台时,也踩过不少坑。今天咱们就好好聊聊物理内存和虚拟内存,把MMU这个"黑盒子"彻底拆开看看。
为什么需要虚拟内存?
你想想看,如果所有程序都直接操作物理内存,会是什么场景?
- 程序A用了地址0x1000,程序B也得避开
- 一个程序崩溃,整个系统跟着挂
- 内存碎片化严重,大块连续内存根本申请不到
虚拟内存就是来解决这些问题的。它给每个进程一个"假地址",让进程觉得自己独占整个内存空间。实际上,MMU(内存管理单元)在背后偷偷做地址转换。
核心概念:虚拟地址是进程看到的地址,物理地址是硬件实际访问的地址。MMU就是这两者之间的"翻译官"。
MMU工作原理速览
MMU的工作流程,说白了就三步:
- CPU发出虚拟地址
- MMU查页表,找到对应的物理地址
- 如果页表里没有,触发缺页异常
我在项目中遇到过一个问题:某个外设驱动死活访问不了寄存器。查了两天,最后发现是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板子后,先做这几步:
- 开机后执行
cat /proc/iomem,看看整体布局 - 找到你关心的外设地址段,比如UART在0x10000000
- 写一个简单的驱动,用ioremap映射这段地址
- 用readl/writel读写寄存器,验证映射是否成功
这个过程走一遍,你对MMU和地址映射的理解就会深很多。纸上得来终觉浅,绝知此事要躬行嘛。
好了,关于物理内存和虚拟内存,今天就聊到这儿。下一节咱们会深入DMA传输,看看数据是怎么在内存和外设之间高效流动的。到时候我会分享一些我在MTK平台上做DMA优化的实战经验,敬请期待。