从零写OS内核 | USB 协议——从 HID 到高速设备
你有没有想过:插一个鼠标,系统怎么就知道这是个鼠标而不是键盘? 为什么 U 盘插上去就能用,但老式打印机却要装驱动?USB 看似简单,背后的协议栈却深不见底。
本文从 USB 的树形拓扑讲起,拆解传输类型、描述符、xHCI 控制器,并给出可复制的 Linux 调试命令。读完你会发现——USB 不只是”插上去就能用”,它是一套完整的外设通信标准。
一、USB 的物理拓扑:为什么它是树形结构
USB(Universal Serial Bus)设计上是一个树形拓扑,通过 Hub 扩展,每个节点是一个设备或 Hub。
┌─────────────┐
│ USB Host │ ← 只有 1 个 host(PC 端)
│ (控制器) │
└──────┬──────┘
│ roothub
┌──────────────┼──────────────┐
│ │ │
┌────▼───┐ ┌────▼───┐ ┌────▼────┐
│ HubPort│ │HubPort │ │ Hub │
│ 1 │ │ 2 │ │ (总线) │
└────┬───┘ └────┬───┘ └───┬────┘
│ │ │
┌────▼───┐ ┌────▼───┐ ┌────▼────┐
│ 键盘 │ │ 鼠标 │ │ Hub │
│ │ │ │ │(下行×4) │
└────────┘ └────────┘ └───┬────┘
│
┌────▼────┐
│ 摄像头 │
└─────────┘关键约束:
- 每个 USB Host 最多接 127 个设备(包括 Hub)
- Hub 每个端口是一个独立事务(transaction),Hub 本身也算一个设备
- 全速(12Mbps)/高速(480Mbps)/超高速(5Gbps) 是物理层差异,协议层统一
二、四种传输类型:USB 的”路”分几种
USB 为了满足不同场景的需求,定义了四种传输类型——就像公路分快车道、慢车道、应急车道:
┌──────────────┬────────────┬──────────┬────────────┬──────────────────┐
│ 传输类型 │ 方向 │ 实时性 │ 带宽 │ 典型设备 │
├──────────────┼────────────┼──────────┼────────────┼──────────────────┤
│ Control │ 双向 │ 无保证 │ 低(1ms帧) │ 枚举、配置 │
│ Interrupt │ 双向 │ 保证 │ 低 │ 键盘、鼠标 │
│ Bulk │ 单向 │ 无保证 │ 剩余带宽 │ U 盘、打印机 │
│ Isochronous │ 单向 │ 保证 │ 高带宽 │ 摄像头、音频 │
└──────────────┴────────────┴──────────┴────────────┴──────────────────┘为什么键盘用 Interrupt 而不是 Bulk?
键盘要求”按下去立刻响应”,Interrupt 每 10ms 最多 1 次轮询,保证延迟。Bulk 会被队列挤压,不适合实时输入。
三、USB 描述符:设备是怎么自述的
每个 USB 设备在插入时会向 Host 报告自己的”简历”——这就是描述符(Descriptor)。它有四层嵌套:
设备描述符(1 个设备只有 1 个)
└── 配置描述符(1 个设备可以有多个配置,但同时只有 1 个生效)
└── 接口描述符(1 个配置有多个接口,代表功能分组)
└── 端点描述符(每个接口有多个端点,端点 0 用于控制传输)实际例子:插一个 U 盘后发生了什么
# 1. 插入 U 盘,设备插入 Hub 端口
# 2. Hub 检测到设备插入,发中断通知主机
# 3. 主机发 GetDescriptor(DEVICE),读设备描述符
# → 获取:Vendor ID (VID) / Product ID (PID) / 设备类
# 4. 主机发 GetDescriptor(CONFIGURATION),读配置描述符
# → 获取:配置值、接口数、供电方式
# 5. 主机加载对应驱动(如 usb-storage)
# 6. 驱动通过 Bulk 传输读写 U 盘数据用 Linux 查看 U 盘的描述符信息:
$ lsusb
Bus 002 Device 003: ID 0951:1666 Kingston Technology DataTraveler 3.0
$ lsusb -v -d 0951:1666
Bus 002 Device 003: ID 0951:1666 Kingston Technology DataTraveler 3.0
Device Descriptor:
bcdUSB: 0x0300 (USB 3.0)
idVendor: 0x0951 Kingston Technology
idProduct: 0x1666 DataTraveler 3.0
bNumConfigurations: 1
Configuration Descriptor:
bNumInterfaces: 1
bConfigurationValue: 1
MaxPower: 200mA
Interface Descriptor:
bInterfaceClass: 0x08 (Mass Storage)
bInterfaceSubClass: 0x06 (SCSI)
bInterfaceProtocol: 0x50 (BBB)
Endpoint Descriptor:
bEndpointAddress: 0x81 (IN, EP1)
bmAttributes: 0x02 (Bulk)
wMaxPacketSize: 1024四、xHCI 控制器:USB 的”交通调度中心”
USB 控制器芯片负责把 USB 总线和 PCIe 总线桥接起来。目前主流控制器有三种:
| 控制器 | 全称 | 对应 USB 版本 | 特点 |
|---|---|---|---|
| UHCI | Universal Host Controller Interface | USB 1.1 | Intel 创建,硬件简单,软件复杂 |
| OHCI | Open Host Controller Interface | USB 1.1 | 康柏创建,硬件复杂,软件简单 |
| EHCI | Enhanced Host Controller Interface | USB 2.0 | 高速控制器 |
| xHCI | eXtensible Host Controller Interface | USB 3.x/4 | 最新,支持 USB 3.0+,支持虚拟化 |
xHCI 的内部结构:
PCIe 总线
│
▼
┌────────────┐
│ xHCI MMIO │ ← 寄存器映射(BAR 地址)
│ (MMIO) │
└─────┬──────┘
│
▼
┌────────────┐
│ HC (Host │
│ Controller)│
└─────┬──────┘
│ 多个 port
┌───┼────────────────────┐
│ │ │
┌─▼─┐┌─▼─┐ ┌─▼──┐
│Port││Port│ │Port│
│ 1 ││ 2 │ ... │ N │
└────┘└────┘ └────┘
│ │ │
│ │ [设备]
└───┴─── [Hub] ───────┘Linux 查看控制器类型:
$ lspci -nnk | grep -i usb
00:14.0 USB controller [0c03]: Intel Corporation Tiger Lake-H USB 3.2 Gen 2x2 XHCI (20d5)
Subsystem: Intel Corporation Device 9a13
Kernel driver in use: xhci_hcd
# 注意 driver 是 xhci_hcd → 这是 xHCI 控制器
$ ls /sys/bus/pci/drivers/xhci_hcd/
bind Module new_id parameters uevent unbind 0000:00:14.0五、实战:Linux USB 调试命令
# 查看系统所有 USB 设备树
lsusb
lsusb -t # 以树形展示拓扑(包含速度信息)
lsusb -v # 详细描述符
# 查看特定设备
lsusb -d 0951:1666
# 监控 USB 事件(实时插拔)
udevadm monitor --subsystem-match=usb
# 查看设备在内核的模块绑定
ls /sys/bus/usb-serial/drivers/
# 挂载 U 盘后的块设备
lsblk | grep sd
dmesg | grep -i usb | tail -20
# 查看 USB 设备的具体端点信息
cat /sys/bus/usb/devices/*/idVendor强制重新加载 USB 存储驱动:
modprobe usb-storage
# 或查看具体块设备
dmesg | grep "SCSI command"六、wandos 的 USB 支持
wandos 目前没有 USB 协议栈实现。 wandos 的 I/O 模型以文件系统为核心(VFS/ext2),对硬件外设的抽象层尚未建立。
如果你想在 wandos 上支持 USB,需要:
- 在
arch/x86/core/下实现 EHCI/xHCI MMIO 寄存器访问 - 实现 USB Hub 检测和设备枚举
- 为 HID 设备实现输入子系统
这一块是 wandos 的 roadmap 缺口,欢迎 contribution。
公众号 footer
相关阅读:
- 《从零写OS内核 | 总线与地址空间——CPU 是怎么找到每个设备的》(343)
- 《从零写OS内核 | I2C 总线——嵌入式系统的血管》(348)
动手环节:
运行 lsusb -t,观察你电脑上有多少 USB 设备,哪几个是高速(480M)或超高速(5G)?
仓库地址:
github.com/golang12306/os-kernel-from-scratch
对应 Demo:demos/usb/(概念验证代码)
关注公众号「AI不着急」,回复”资料”获取内核学习路线图。
评论