6. IO架构

将计算机的任务进行一个粗略的分类, 其实只有两种: CPU运算I/O操作.

1 x86的I/O架构

I/O是CPU访问外部设备的方法.

设备通常通过寄存器设备RAM将自身的功能展现给CPU, CPU读写这些寄存器和RAM(!!!)即可完成对设备的访问和操作. 通过访问方式的不同, 可以将x86架构的I/O分为如下两类.

Port I/O(端口I/O): 即通过I/O端口访问设备寄存器.

x8665536个8位的I/O端口(!!!), 编号为0x0000 ~ 0xFFFF. 如果将端口号看作访问设备端口的地址, 那么这65536个端口就构成了64KB的地址空间, 称为I/O端口地址空间. 与前面说的线性地址空间和物理地址空间不同, I/O端口地址空间是独立的, 也就说它并不是线性地址空间或物理地址空间的一部分(!!!). 使用IN/OUT指令访问端口时, CPU通过一个特殊的管脚标识这是一次I/O端口访问, 于是芯片组知道地址线上的地址是I/O端口号并进行相应操作. 此外, 2个4个连续的8位I/O端口, 可以组成16位32位的I/O端口.

⓶ MMIO(Memory Map I/O, 内存映射I/O): 即通过内存访问的形式访问设备寄存器或设备RAM.

x86架构下, MMIOPort I/O最大不同在于MMIO要占用CPU的物理地址空间. 它把设备的寄存器或设备RAM映射到物理地址空间某段地址, 使用MOV这样的访存指令访问此段地址即可访问到映射的设备. 很多CPU架构都没有Port I/O, 采用统一的MMIO方式. 由此可见, MMIO是一种更先进的I/O访问方式.

对于Port I/O, 由于编译器不能产生IN/OUT指令(!!!), 操作系统通常会把汇编指令封装成类似inb()、outb()这样的函数.

对于MMIO. 由于整个物理地址空间都会被映射到线性地址空间, 程序访问I/O资源时也要做线性地址到物理地址的转换(!!!). 与普通物理地址到线性地址的映射不同, MMIO地址通常是不可缓存的(un-cacheable!!!).

2 DMA

DMA(直接内存访问)是将CPU从I/O操作中解放出来(!!!)的一种技术. 如果设备向内存复制数据经过CPU, 则会消耗大量的CPU时间(大量中断负载), 不利于系统性能. 中断过程中, CPU对其他任务来讲无法使用, 不利于系统性能的提高.

通过DMA,CPU只负责初始化这个传输动作,而传输动作本身DMA 控制器(后文简称为DMAC)来实行和完成。在实现 DMA 传输时,由 DMAC 直接控制总线,在DMA 传输前CPU 要把总线控制权交给 DMAC,结束 DMA 传输后,DMAC立即把总线控制权交回给CPU

一个完整的DMA传输过程的基本流程如下。

1)DMA请求CPUDMAC进行初始化,并向I/O端口发出操作命令I/O端口提出DMA请求

2)DMA响应DMAC对DMA请求进行优先级判别屏蔽判别,然后向总线裁决逻辑提出总线请求CPU执行完当前总线周期释放总线控制权。此时,总线裁决逻辑发出总线应答,表示DMA已被响应,并通过DMAC通知I/O端口开始DMA传输

3)DMA传输:DMAC获得总线控制权后,CPU即可挂起只执行内部操作,由DMAC发出读/写命令,直接控制RAM与I/O端口进行DMA传输

4)DMA结束:当完成规定的成批数据传送后,DMAC释放总线控制权,并向 I/O 端口发出结束信号。当 I/O 端口接收到结束信号后,停止 I/O 设备的工作并向 CPU提出中断请求,使 CPU 执行一段检查本次 DMA传输操作正确性判断的代码,并从不介入的状态退出。

由此可见,DMA无须CPU直接控制传输,也没有中断处理方式那样保留现场恢复现场的过程,通过硬件(DMAC)为 RAMI/O 设备开辟了一条直接传送数据的通路,极大地提高了CPU效率。需要注意的是,DMA操作访问的必须是连续的物理内存。DMA传输的过程如图1-15所示。  图1-15 DMA传输示意图:

6. IO架构

通过DMA, 驱动程序(!!!)可以事先(或在需要的时候!!!)设定一个内存地址!!!, 设备就可以绕开CPU直接向内存中复制(或读取)数据(!!!).

根据发起者不同, DMA可以被分为两种.

⓵ 同步DMA: 是指DMA操作由软件发起. 一般流程是设备驱动设定好需要被DMA访问的内存地址后, 写某个寄存器通知设备发起DMA. 此时, 设备直接从该内存地址读取内容并操作. 典型例子就是声卡, 当播放一段音频时, 驱动将该音频存放的地址通知声卡, 设备从内存直接读取数据并播放, 完成后以一个中断通知驱动操作完成.

⓶ 异步DMA: 是指DMA操作由设备发起. 一般流程是设备数据直接复制到一个事先设定好的内存地址, 再通过一个中断通知驱动程序. 典型例子是网卡收包, 当网卡接收到数据包后, 会直接复制到驱动程序设定的内存地址去, 并以中断形式通知网络包的到来.

设备的DMA操作都是使用物理地址访问内存!!!, 不经过线性地址到物理地址的转换!!!. 但IOMMU出现后, 这个情况就被改变了, 后面说明.

驱动的角度看, 它要提供一片内存区域供设备访问, DMA要求这段内存区域在 物理上是连续!!! 的.

现代设备支持一种称为"分散 - 聚合(Scatter - gather)"DMA的机制, 允许驱动向设备提供不连续的物理内存. 实际上, 驱动是将一组(!!!)以"起始地址 - 长度"为属性的内存描述符提供给设备, 每个描述符描述了一块连续的物理内存, 但连续两个描述符描述的内存不需要连续的. 从宏观上看, 通过这组内存描述符可以向设备提供一片不连续的内存区域; 但从微观角度看, DMA操作访问的仍然是连续的物理内存.

3 PCI设备

在PCI总线之前, 各种平台都拥有自己特定的总线, 例如x86的ISA总线、Power PC的VME总线。PCI出现后, 由于速度快, 具有动态配置功能和独立于CPU架构等特点, 迅速被接收, 成为一种通用的总线架构.

3.1 PCI总线架构

PCI总线是一种典型的树结构. 把北桥中HOST-PCI桥看作根, 总线中其它PCI-PCI桥、PCI-ISA桥(ISA总线转PCI总线桥)等桥设备和直接接PCI总线的设备看作节点, 整个PCI架构可概括成图2-14所示.

6. IO架构

通过桥, PCI总线可以很容易被扩展, 并且与其它总线互相挂接, 构成整个系统的总线网络. 与HOST-PCI相连的总线被称为总线0!!!, 其它层次总线的编号!!!, 是在**BIOS(或操作系统)枚举设备时确定!!!**的.

3.2 设备标识符

设备标识符可以看作是设备在PCI总线上的地址, 格式如图2-15.

6. IO架构

其中,

  • 8位的Bus字段代表设备所在的总线号, 故系统中最多有256条总线.
  • Device字段表示设备号, 代表在Bus所表示总线上的某个设备.
  • Function字段表示功能号, 标识具体设备上的某个功能单元. "功能单元"可以理解为逻辑设备.

举一个简单的例子, 一块PCI卡, 它上面有两个独立的设备, 这两个设备共享了一些电子线路, 那么这两个设备就是这块PCI卡两个功能单元. 但从软件角度看, 它们和两个独立接入PCI总线的设备无异. 如同Function字段长度描述的, 一个独立的PCI设备上最多能有8个功能单元. DeviceFunction两个字段一般合起来使用, 表示一条总线上最多有256个设备. 通常, 用设备标识符三个字段的缩写BDF来代表它.

程序通过BDF访问某个设备, 先通过Bus字段选定特定的总线, 再根据Device字段选定特定的设备, 最后通过Function字段就可以选定特定的功能单元(逻辑设备)了.

3.3 PCI配置空间

对于程序员来说, 不需要了解PCI设备电路实现细节, 只需要了解操作它的接口. PCI配置空间就是这样一个接口, 其结构如图.

6. IO架构

PCI设备规范规定, 设备的配置空间最多256个字节, 其中前64个字节的格式和用途是统一的, 如图. 各字段的具体含义参见"PCI Local Bus Specification Revision 3.0"的第6章, 这里只关心对程序员最重要的Base Address Registers和Interrupt Pin、Interrupt Line。

(1) Base Address Registers: 基地址寄存器, 也就是常说的PCI Bar!!!.

它报告设备寄存器!!!设备RAM!!!I/O端口地址空间!!!(或物理地址空间!!!中)的地址. 地址是由软件(BIOS或操作系统)动态配置!!!的, 这就一改ISA设备通过跳线进行配置!!! 的不灵活的特点.

通常枚举PCI设备的软件(BIOS或操作系统!!!)会在获得平台所有PCI设备后, 根据设备数量, 依照固定的算法!!!每个设备的PCI Bar分配I/O端口(或物理地址).

设备的电子线路(非软件!!!)负责将这些端口(或地址)映射到自身的寄存器(设备RAM)上, 这样, CPU就可以通过端口号(Port I/O方式)、物理地址(MMIO)方式访问到设备了.

使用哪种方式访问, 由PCI Bar最后一位表示.

  • 当该位为1时, 表示是I/O端口;
  • 该位为0时, 表示是MMIO端口.

某些架构没有Port I/O, 只有MMIO.

根据访问目标性质不同, PCI Bar又可以划分为如下两种类型.

可预取(Prefetchable)类型: 主要是设备RAM. 由于RAM具有在每次读操作后内容不自动改变的性质, 所以可以使用预读机制. 例如, 程序在读第N个字节的内容时, 总线可能已经读出了第N+1个字节的内容. 当预读出的内容不需要时, 只要简单的抛弃就行, 不会有什么影响.

⓶ 不可预取类型(Un-Prefetchable): 主要指设备寄存器. 寄存器和RAM有不同的性质, 有些寄存器本身就是设备的FIFO队列的接口. 很可能当一次读操作完成后, 寄存器的值就改变了. 如果使用预读机制, 例如程序本身只读了寄存器的第一个字节, 而总线却连续读了4个字节, 那么后面3个字节的内容可能就会改变, 下次程序真正访问时得到的就是错误的值. 对于PCI Bar是否为可预取类型, 可以根据该PCI Bar的第3个位判断, 1为可预取, 否则不可预取.

(2) Interrupt Pin: 中断针脚.

PCI中断线的标准设计是4条: INTA、INTB、INTC和INTD, 分别对应值0~3. 该寄存器的表示设备连接的是哪个中断针脚.

(3) Interrupt Line: 设备的中断线.

该寄存器只起一个保存作用, BIOS操作系统可以自由使用它. BIOS通常用它来保存设备所连PIC/IOAPIC管脚号.

3.3.1 PCI配置空间的访问

x86架构把I/O端口地址空间中的0xCF8~0xCFF段预留给了PCI总线, 用于访问设备的配置空间!!!. 其中,

  • 前32位的寄存器为"地址寄存器!!!",
  • 后32位为"值寄存器!!!".

软件!!! 通过把设备的BDF!!!要访问的配置空间的字节偏移!!! 写入"地址寄存器!!!"中, 就可以通过"值寄存器!!!"读写该配置空间了.

3.4 PCI设备枚举过程

PCI设备的枚举和资源分配(即配置PCI配置空间)通常是由BIOS!!! 完成的, 并提供特殊的PCI设备枚举接口!!!保护模式下的操作系统使用, 这些接口称为PCIBIOS. 由于某些平台, 例如嵌入式, 是没有BIOS的, 并且操作系统厂商对BIOS的可靠性也不信任, 某些操作系统!!!也实现了自己的PCI设备枚举接口. 无论是BIOS, 还是操作系统, 其枚举设备过程遵循一般规律.

从前面的PCI总线概要图, PCI设备总线一起构成了树结构, 其中PCI-PCI桥(或PCI-ISA等其它桥, 这里只关心PCI-PCI桥)是子树的根节点, 设备枚举过程就是要在内存中建立一棵和实际总线情况相符合的设备树. 枚举过程中最关键的步骤!!!就是发现PCI-PCI桥!!!, 这个可以通过PCI配置空间Header Type字段判断, 该字段为1表示为桥设备.

PCI-PCI桥!!! 主要有三个属性.

  • Primary Bus: 表示该桥所属的根总线.
  • Secondary Bus: 表示以该桥为根节点的子总线.
  • Subordinate Bus: 表示该桥为根的子树中, 最大的总线号.

利用图2-17说明三者关系

6. IO架构

如图, 对于"PCI-PCI桥1", 其Primary Bus是总线0, Secondary Bus是总线1, 而以它为根的总线中最大的总线号是2, 所以其Subordinate Bus为总线2.

设备枚举从根节点HOST-PCI桥开始, 首先探测总线0上的各个设备. 当探测到第一个桥设备(!!!)时, 为其分配Primary Bus号和Secondary Bus号, 其中Secondary Bus号为1(即当前系统中最大总线号加1), Subordinate Bus暂定为和Secondary Bus相同, 当在子树中发现新总线后会动态调整该值. 接着以该桥为根节点, 继续探测其下属总线, 其过程和前面相同, 发现第一个桥设备后则以其为根继续往下探测.

当PCI-PCI桥收到写入0xCF8中的BDF后, 会将Bus字段与自身的Secondary Bus相比, 相符则在下属总线上搜寻设备; 如不相符, 但Bus值落在Subordinate Bus范围内, 则把该地址传递给下属总线中各桥.

通过这种方式, BIOS或操作系统能枚举出总线上所有设备并为之分配资源, 一旦PCI配置空间设定好, 软件就能直接通过PCI Bar访问设备了.

4 PCI Express

PCI Express设计目标是代替PCI、PCI-X和AGP等总线标准.

4.1 PCI Express架构

PCIe抛弃了PCI采用的多个设备共享的并行的总线结构, 转而使用了与网络协议类似的点对点的串行通信机制.

多个PCIe设备(Endpoint)通过交换器(Switch)互相连接. 与PCI总线中的桥设备类似, 通过Switch(交换器), 可以搭建一个树形的PCIe的拓扑结构.

标准PCIe拓扑如图2-18. 树的根节点Root Complex, 用来连接处理器内存系统IO系统, 作用类似于PCI总线树中的Host-PCI桥.

6. IO架构

4.2 PCIe的优点

PCIe比PCI总线拥有更高的带宽. 串行通信机制可以让物理链路工作在很高的频率. 点对点的通信方式使得链路两端的设备!!! 可以独占通信带宽, 并且多个链路可以并发传输数据.

PCIe在与PCI总线两个硬件基础完全不同, 构建了与PCI总线完全兼容的软件接口. PCIe定义了基于数据包分层通信协议, 包括物理层(Physical Layer)、数据链路层(Data Link Layer)和事物层(Transaction Layer). 在事物层协议中, PCIe定义了内存读写IO读写配置空间读写消息事务. 通过这些事务的定义, PCIe可实现所有PCI总线事务.

PCIePCI总线配置空间!!!256字节扩展到了4KB字节, 解决了PCI配置空间过小的问题, 可容纳更多的设备功能配置. PCIe还增加了一种新的MMIO方法访问扩充过的配置空间. 为保持兼容性, 4KB字节配置空间前256字节仍然可以使用原来的方式访问.

PCIe除了保留了PCI总线的优点外, 还增加了诸如QoS服务(服务质量, Quality of Service)、高级错误报告机制(AER)等新特性. 软件可以通过PCIe提供的软件接口来配置和使用这些新的功能.

此外, PCIe标准具有良好的扩充性. PCI-SIG!!!SRIO-V标准!!!PCIe基础上做了扩展, 支持该标准的设备可以动态地生成新的逻辑设备!!!. DMA重映射可以利用PCI Express内存读写事务数据包中所包含的设备标识符地址信息, 为每个逻辑设备提供独立的地址转换.