网卡、端口与外设:
它们如何与 CPU、硬件、操作系统对话

从一根网线到 CPU 寄存器,从按下键盘到屏幕显示——本文用图、动画和交互,讲清楚 I/O 设备与计算机核心之间完整的通信原理。

I/O 体系结构 MMIO / PMIO 中断 & DMA 设备驱动 网卡收发包

1全景:一次 I/O 涉及哪些"角色"

任何一次设备通信,本质都是数据在"设备 ↔ 总线 ↔ CPU/内存 ↔ 操作系统"之间的一次旅行。先认识这条链路上的常驻角色,后面每一节都是放大其中某一环。

CPU 运算 + 控制核心 内存控制器 (集成于 CPU) 主存 RAM 数据最终落脚点 PCIe 总线 (高速串行,设备主要通道) 网卡 NIC 外部设备 USB 控制器 键鼠/存储 磁盘/NVMe 块设备 GPU 显示
CPU/控制 网卡 USB 磁盘 GPU

👆 点一下上面任意模块,看它与 CPU/内存之间的通信路径被高亮(其它变暗)。这是理解"它们怎么对话"的几何直觉。

① CPU

总指挥。通过读写寄存器和设备"对话",并通过中断线被动接收设备信号。

② 总线 Bus

所有设备共享的"公路"。PCIe 是今天的高速主干;USB/ SATA 等挂在它下面的"支线"上。

③ 设备控制器 Controller

每个设备自带的小芯片(网卡有网卡芯片、磁盘有磁盘控制器)。它替设备理解总线和寄存器

④ 操作系统 / 驱动

住在 RAM 里的软件层。它封装硬件差异,让上层应用只看到统一的"文件"或"套接字"。

2硬件底座:端口、总线与控制器

设备从不直接怼到 CPU 上。它们通过控制器连到总线,总线再连到 CPU。CPU 只跟"地址 + 数据 + 控制"三条线打交道。

🔢 地址总线

CPU 用来"指名"要访问谁:是内存的某个格子,还是某设备的某个寄存器。位数决定可寻址空间(如 64 位)。

📦 数据总线

真正搬运数据的线。PCIe 用高速差分串行信号(lane),带宽 = lane 数 × 单 lane 速率。

🚦 控制总线

读写方向、时钟、中断请求(IRQ)等"红绿灯"信号。比如 INTR 线是设备喊 CPU 的喇叭。

CPU 三总线汇出 地址总线 Address 数据总线 Data 控制总线 Control / IRQ 设备控制器 寄存器组 + 缓冲 FIFO 物理 设备 IRQ 中断请求(反向:设备→CPU)

关键概念:控制器内部有"寄存器"和"缓冲区"

控制器的寄存器就是 CPU 与设备之间的"信箱"。CPU 往控制寄存器写命令("开始读"),从状态寄存器读结果("忙/就绪"),从数据寄存器搬数据。数据多了则先进控制器的 FIFO/缓冲区,避免 CPU 频繁来取。这就是后面所有机制的物理基础。

💡 为什么需要控制器?网卡的世界是以太网电气信号、是 MAC 帧;USB 的世界是差分串行包。CPU 不可能为每种设备学一种"方言"。控制器把设备专属的模拟/串行/时序信号,翻译成 CPU 能懂的"读写寄存器"。控制器 = 翻译官 + 缓冲池。

3CPU 如何"看见"设备:I/O 编址

CPU 只会做一件事:按地址读/写。设备寄存器也得有个"地址",CPU 才能访问。历史上有两套编址方案。

内存映射 I/O(MMIO)

把设备寄存器映射到一段物理内存地址。CPU 用普通的 MOV 指令读写这些地址,就像访问内存一样。

  • 寄存器地址落在地址空间里,与 RAM 共享
  • 指令无差别:mov rax, [0xFE000000]
  • 现代主流:PCIe 设备、绝大多数外设都用 MMIO
  • 优点:编译器/CPU 能用所有访存优化;缺点:占用地址空间

端口映射 I/O(PMIO / x86)

独立一套I/O 地址空间(64K 端口),用专用指令 IN/OUT 访问,与内存地址互不干扰。

  • 经典 x86 遗留方案,老式并口/串口常用
  • in al, 0x60 读键盘端口;out 0x20, al
  • 优点:地址空间独立、不占内存;缺点:指令专用、数量有限
  • 今天多用于早期启动、老设备,新设备几乎都 MMIO
MMIO:同一地址空间 0x00000000 ──────────── RAM ────────────▶ ↑ 高端地址:网卡/显卡寄存器也在这条线上 PMIO:独立 I/O 空间 内存空间 0x0000 ~ 0xFFFF 独立 I/O 空间 0x00 ~ 0xFFFF(IN/OUT)
; 例子:用 MMIO 读网卡状态寄存器(假设映射到 0xFE000000)
mov rax, [0xFE000000]   ; 像读内存一样读"网卡状态"
test rax, 0x1           ; 第 0 位 = 就绪位?
jnz  ready              ; 就绪则跳转

; 同样的事,若用老式 PMIO 端口 0x300:
in  al, 0x300           ; 专用 IN 指令
test al, 0x1
jnz  ready
✅ 一句话:CPU 不管对面是内存还是网卡,它只认地址。MMIO 让"设备"混进内存地址表里;PMIO 给设备单独开了一本"门牌簿"。

4寄存器与"握手协议"

有了地址,CPU 还不能乱写。设备很慢、CPU 很快,两者靠寄存器做状态握手。这三类寄存器是 I/O 的"语法"。

控制寄存器 CPU 写命令:启动/停止 状态寄存器 设备写标志:忙/就绪/错 数据寄存器 真正的数据进出 ① CPU 写控制 ② 设备更新状态 ③ 就绪后搬数据

👆 点上方三类寄存器之一,下面会解释它在握手里的角色。

点击上图查看 控制 / 状态 / 数据 寄存器各自在"握手"中的职责。

// 典型握手伪代码(CPU 视角,读一个字节)
while (status_reg & BUSY)  ; 轮询:等到设备不忙
control_reg = READ_CMD     ; 写控制:我要读
while (!(status_reg & READY)); 等"数据就绪"
data = data_reg            ; 取走数据
⚠️ 这里暴露了一个核心矛盾:CPU 比设备快几百万倍。如果 CPU 一直"while 等待",叫忙等/轮询,CPU 在空转。于是有了第 5 节的三种传输方式来解决"CPU 怎么不浪费地等"。

5三种数据传输方式(核心原理)

CPU 与设备之间搬数据,有三条路:轮询(PIO)、中断、DMA。它们解决的是同一个问题——"CPU 怎么高效地把数据从设备搬到内存"。下面用对比 + 动画讲清。

CPU 工作 设备工作 等待/空闲 中断信号

① 轮询 PIO

CPU 反复读状态寄存器,设备没就绪就空转。实现最简单,但CPU 被绑死,只适合极慢/极简设备(早期串口)。

② 中断驱动

CPU 先去干别的;设备就绪后发 IRQ 打断 CPU,CPU 再来取数据。解放了 CPU,但每个字节都惊动 CPU,高频数据仍费 CPU

③ DMA

CPU 把"从设备搬 N 字节到内存地址 X"的任务委托给 DMA 控制器。设备自己把数据写进内存,搬完才中断一次。大块数据几乎零 CPU 占用。

✅ 现代网卡 = DMA + 中断 的黄金组合:数据由网卡经 DMA 直接灌进内存环形缓冲区,全部搬完(或攒一批)才触发一次中断,CPU 一次性处理一整批包。既解放 CPU 又高吞吐。

6中断机制:设备如何"喊"CPU

中断是异步 I/O 的灵魂。设备不能打断 CPU 的"思考",但能拉一根 IRQ 线请求注意。CPU 收到后会暂停当前任务、保存现场、跳转处理、恢复现场

CPU 时间线: [ 进程A ] 被打断 [ 恢复进程A ] 设备 拉高 IRQ 线 中断控制器 APIC / 8259 CPU 核心 收到中断 IDT → 中断处理 保存现场 → 执行 ISR → 恢复现场 现场压栈

中断做了什么(微观)

  1. 设备拉 IRQ 线,中断控制器把"向量号"发给 CPU
  2. CPU 执行完当前指令,关中断、保存寄存器到内核栈
  3. 查 IDT(中断描述符表)找到对应 ISR 入口
  4. 执行 ISR(如网卡驱动取包),开中断
  5. iret 恢复现场,回到原进程

上半部 / 下半部

网卡中断不能做太久(否则卡住其它中断)。所以驱动把活分成两半:

  • 上半部(top half):极快,只收包、入队、清中断
  • 下半部(softirq / tasklet / NAPI):稍后慢慢处理协议栈、交给应用
⚠️ 中断风暴:若每个包都触发一次中断,10G 网卡每秒上千万包会把 CPU 淹没。现代方案 NAPI:中断来了先"关中断",改为轮询收一批,收完再开——把"中断 + 轮询"结合起来,取两者之长。

7操作系统与设备驱动:软件这一层

硬件差异巨大,但应用只想"open / read / write"。这层翻译由操作系统 + 设备驱动完成。驱动是内核里"懂某款硬件"的专属代码。

应用程序 系统调用接口 read/write/open/ioctl 内核 I/O 子系统 VFS / 网络设备层 / 块层 设备驱动(懂硬件) 统一抽象 "一切皆文件" 物理设备 屏蔽差异

字符设备 / 块设备 / 网络设备

键盘是字符设备(按字节流);磁盘是块设备(按扇区块);网卡是网络设备(按包,走 socket 而非"文件")。三类有不同抽象接口。

驱动到底干了啥

1) 探测/初始化硬件;2) 把 MMIO 地址映射进内核;3) 注册中断处理;4) 实现 read/write 等回调;5) 用 DMA 描述符让硬件搬数据。

用户态 vs 内核态

驱动跑在内核态(特权级高,能碰硬件)。应用通过系统调用跨界请求,避免任意程序乱改硬件。

为什么"一切皆文件"

Unix 把设备也暴露成 /dev/... 节点。应用用同一套 open/read/write 就能操作串口、磁盘,不必关心硬件细节。

8网卡专题:一个数据包是怎么进来的(动画)

把前面所有原理串起来:下面用分步动画演示接收(RX)一个网络包的完整旅程。点"下一步"或"自动播放"。

发送方向(TX)完全对称:应用 write(socket) → 协议栈封帧 → 驱动写 TX 描述符 → 网卡 DMA 取数据 → 编码成电信号发出 → 发完中断通知 CPU。注意数据同样不经 CPU 中转,全靠 DMA。

9对比总结:一张表看懂全局

环节主角机制解决的问题
设备挂接控制器 + 总线PCIe/USB 等把设备连入系统让五花八门的硬件有统一入口
CPU 寻址MMIO / PMIO寄存器映射到地址,CPU 按地址读写CPU 只认地址,设备"伪装"成内存/端口
速度匹配状态寄存器忙/就绪位握手弥合 CPU 与设备百万倍速度差
搬数据PIO / 中断 / DMA轮询 → 中断通知 → DMA 自搬高效、低 CPU 占用地传输
异步通知IRQ + 中断控制器设备拉线打断 CPU,走 IDT→ISR设备就绪时再叫 CPU,不空等
软件封装OS + 驱动统一抽象(文件/socket)+ 特权隔离屏蔽硬件差异,安全暴露给应用
网卡实战NIC + DMA + NAPIDMA 灌内存环 + 中断/轮询混合百万级 PPS 而 CPU 不被淹没

一句话记 DMA

CPU 当包工头,DMA 是搬运工——交代"从哪搬、搬多少、放哪",工人自己干,干完喊一声。

一句话记中断

门铃。设备在门口按铃(IRQ),CPU 暂停手头事去开门(ISR),处理完继续。

一句话记驱动

翻译官。把内核的"读网卡"翻译成该硬件寄存器的一串特定操作。

10小测验:检验你真的懂了

答完可以回看第 5、6 节对照。核心就三句话:设备靠寄存器与 CPU 对话;DMA 解决"搬数据",中断解决"怎么叫 CPU";驱动把它们藏起来变成文件/套接字。