2. 内存管理基础:物理内存与虚拟内存、MMU工作原理、页表与页框

好,咱们开始聊内存管理。说实话,做Camera驱动这么多年,我踩过最多的坑,就是内存相关的。DMA传着传着数据就飞了,或者buffer地址不对导致图像花屏,十有八九都是内存管理没搞透。这一节,咱们把地基打牢。

2.1 物理内存 vs 虚拟内存

先问个问题:你写代码的时候,malloc出来的地址,真的是物理地址吗?

不是。那是虚拟地址。

物理内存就是板上焊的那几颗DDR颗粒,4GB就是4GB,8GB就是8GB。但每个进程都觉得自己独占4GB空间——这就是虚拟内存的功劳。

我当年刚做驱动时,有个血泪教训。写了个测试程序,直接往物理地址0x10000000写数据,结果系统立马挂掉。为什么?因为那个地址被内核占用了,用户态根本不能碰。后来我才明白,用户态程序看到的地址,全是虚拟的,得通过MMU翻译才能找到真正的物理位置。

核心区别一句话:

  • 物理内存:硬件真实存在的RAM,地址从0开始到最大值
  • 虚拟内存:每个进程独立的地址空间,由MMU+页表映射到物理内存

在Camera驱动里,这个区别特别重要。比如ISP(图像信号处理器)要直接访问内存,它只能认物理地址。你给用户态app传个虚拟地址过去,ISP直接懵圈。所以驱动里经常要做virt_to_phys()或者dma_map_single()这类转换。

2.2 MMU工作原理

MMU,全称Memory Management Unit,内存管理单元。它就是个硬件翻译官。

CPU发出一个虚拟地址,比如0x7f123456,MMU拿到后,去查页表,找到对应的物理地址,比如0x3a004000,然后才真正去内存里读写数据。

这个过程,我习惯叫它「地址翻译流水线」。大致分三步:

  1. 拆分地址:虚拟地址被拆成页目录索引、页表索引、页内偏移
  2. 查页表:逐级查找,找到对应的页表项(PTE)
  3. 组合物理地址:PTE里存着物理页框号,加上偏移量,得到最终物理地址

你想想看,每次内存访问都要走这么一圈,是不是很慢?所以硬件搞了个TLB(Translation Lookaside Buffer),也就是页表缓存。最近用过的地址映射直接存TLB里,下次查就不用走慢吞吞的页表了。

避坑指南:

我曾经在调试一个Camera预览卡顿问题时,发现每次ISP取图都要几十微秒。查来查去,原来是TLB miss率太高。因为ISP访问的buffer是DMA分配的,物理地址连续但虚拟地址不连续,导致TLB频繁失效。后来改成使用dma_alloc_coherent分配一致性内存,问题解决。

2.3 页表与页框

页表,说白了就是一张映射表。虚拟地址的每一页,对应物理内存的一个页框。

页框(Page Frame)是物理内存的最小管理单位,通常是4KB。虚拟内存也按4KB分页,叫页面(Page)。

页表里每个条目(PTE)大概长这样:

| 物理页框号 (20位) | 标志位 (12位) |
|-------------------|---------------|
| 0x3A004           | 0x0C7         |

标志位里包含:

  • Present位:页面是否在物理内存中
  • RW位:可读可写还是只读
  • User位:用户态能否访问
  • Dirty位:页面是否被写过
  • Access位:页面是否被访问过

嗯,这里要注意:在Camera驱动里,我们经常要操作页表。比如给用户态映射一块物理连续的内存,就得用remap_pfn_range()。这个函数会在用户进程的页表里,手动插入一条映射。

警告:

千万不要在中断上下文里操作页表!页表操作可能触发缺页异常,而中断上下文是不能睡眠的。我见过一个同事,在中断里调了get_user_pages(),结果系统直接panic。血的教训。

2.4 多级页表

32位系统用两级页表,64位系统用四级甚至五级页表。为什么搞这么复杂?

你想想看,如果只用一级页表,4GB虚拟空间需要4GB/4KB = 1M个页表项。每个PTE占4字节,那就是4MB。每个进程都要4MB页表,100个进程就是400MB,太浪费了。

多级页表的好处是:按需分配。进程只用了少量内存,那页表就只建几项,不用一次性全建好。

以ARM64的四级页表为例:

虚拟地址: [PGD索引] [PUD索引] [PMD索引] [PTE索引] [页内偏移]
             9位       9位       9位       9位       12位

每一级查表,找到下一级页表的基地址。最后一级PTE里,才存真正的物理页框号。

我在做高通平台Camera驱动时,遇到过一个问题:分配大块连续内存时,kmalloc总是失败。后来发现,kmalloc最大只能分配4MB,而且要求物理连续。对于Camera需要的8MB、16MB buffer,得用alloc_pages或者dma_alloc_coherent。这些函数会通过页表映射,把不连续的物理页框,映射成连续的虚拟地址。

2.5 总结

这一节内容不少,但都是基本功。我建议你记住三点:

  1. 虚拟地址是给CPU用的,物理地址是给硬件用的。驱动里要时刻分清自己在操作哪种地址
  2. MMU是硬件翻译官,TLB是它的缓存。性能调优时别忘了看TLB miss率
  3. 页表是映射规则,多级页表节省内存但增加查表开销

下一节,咱们会深入DMA缓冲区分配,到时候这些基础概念全都会用上。准备好了吗?