2. BAR地址空间类型:Memory、IO、64-bit

说白了,BAR(Bar Address Register)就是设备向系统申报的“地址户口本”。

系统在枚举PCIe总线时,会读取每个BAR的配置信息,然后给设备分配一段连续的物理地址空间。分配完以后,CPU就能通过内存访问指令直接读写设备寄存器了。

我自己刚入行那会儿,总觉得BAR配置随便写写就行。后来吃过一次亏才发现——BAR类型选错,系统可能根本不认你的设备。


2.1 Memory BAR:最常用的类型

现在99%的PCIe设备都用Memory BAR。为什么?因为CPU访问内存空间最快、延迟最低。

Memory BAR的几个关键属性:

  • 可预取(Prefetchable):允许CPU提前读取数据,适合存放只读数据结构
  • 不可预取(Non-Prefetchable):读取有副作用,比如读一次状态寄存器会清除中断标志
  • 宽度:32-bit或64-bit,32-bit最多寻址4GB,64-bit可以寻址整个系统内存

我做过一个项目,把一块网卡的接收描述符环放在不可预取区,结果性能惨不忍睹。后来改成可预取区,吞吐量直接翻了一倍。

实战建议:

  • 设备控制寄存器 → 不可预取
  • 数据缓冲区、FIFO → 可预取
  • 我个人习惯:所有Memory BAR都配上64-bit地址,除非芯片面积实在受限

2.2 IO BAR:快被淘汰的老前辈

IO空间是个老东西,从x86时代就有了。它的地址范围只有64KB(0x0000 ~ 0xFFFF)。

你会问:谁还在用IO BAR?嗯,主要是老旧设备或者兼容性需求。

对比项 Memory BAR IO BAR
地址空间范围 取决于位数,最大2^64 固定64KB
访问方式 普通load/store指令 专用IN/OUT指令
可预取 可以设置 始终不可预取
排序保证 有弱排序/强排序之分 严格排序
当下推荐 强烈推荐 不推荐新设计使用

避坑指南:

我曾经在一个嵌入式项目里选了IO BAR,结果发现ARM架构根本不支持IO空间!那套代码在x86上跑得好好的,一交叉编译到ARM就报错。从那以后,除非客户明确要求x86兼容,否则我都只用Memory BAR。

2.3 64-bit Memory BAR:大内存时代的标配

现在的系统内存都是GB起步,32-bit的4GB上限根本不够用。所以64-bit BAR成了主流。

配置64-bit BAR要注意一个细节:它实际占用两个连续的BAR寄存器。BAR0配低32位,BAR1就自动变成高32位。

// 伪代码:配置一个64-bit非预取Memory BAR
// 假设设备内部寄存器占用4KB地址空间
pci_write_config_dword(dev, PCI_BASE_ADDRESS_0, 
    PCI_BASE_ADDRESS_MEM_TYPE_64 |
    PCI_BASE_ADDRESS_SPACE_MEMORY |
    0x00001000);  // 低32位地址+请求大小

// BAR1自动变为高32位映射
pci_write_config_dword(dev, PCI_BASE_ADDRESS_1, 
    0x00000000);  // 高32位,这里演示设为0

我遇到过一个坑:某款SoC的PCIe控制器在64-bit BAR模式下,地址对齐要求是1MB而不是通常的4KB。文档里用一行小字写了,我根本没看到。结果板子一上电,系统响应BAR请求时总是报错。找了两天才定位到是alignment mismatch。

64-bit BAR的硬性规则:

  • 两个BAR必须连续使用,不能中间有其他BAR
  • 地址必须对齐到请求大小的自然边界
  • 系统可能会因为地址空间碎片而分配失败——我一般采用分配两次失败就降级到32-bit的兜底策略

2.4 三种类型速查表

类型 BAR寄存器占用 最大空间 常见场景 我个人的选择原则
Memory 32-bit 1个 4GB 小规模控制寄存器 几乎不用了
Memory 64-bit 2个(连续) 2^64 大缓冲区、高性能设备 默认首选
IO 1个 64KB x86老旧兼容设备 仅在x86且有明确兼容要求时才用

一点经验:

我设计多BAR设备时,习惯把最重要的、访问频率最高的寄存器组放在BAR0,并且配成64-bit非预取Memory。

这样做有两个好处:一是系统分配地址时优先给BAR0分配连续大空间;二是调试时用BAR0做快速验证,其他BAR出了问题也不会堵死核心功能。


BAR地址空间类型对比关系图 Memory BAR 主流,最快访问 IO BAR 老旧,64KB限制 64-bit BAR 大地址,占2个BAR Memory BAR 属性 • 可预取/不可预取 • 32位或64位 • 默认推荐类型 • load/store指令访问 IO BAR 属性 • 仅64KB空间 • 严格排序 • 仅x86原生支持 • IN/OUT指令 64-bit BAR 属性 • 占用2个连续BAR • 地址对齐严格 • 大地址空间支持 • 高性能场景首选 设计决策建议 新设计 → 首选64-bit Memory BAR 仅x86老兼容才用IO 系统分配失败时:考虑降级到32-bit或调整地址对齐