外设建模(edu)
在计算机系统中,CPU 与外设的交互本质是 地址访问 和 中断通知:
-
地址访问:CPU 通过 MMIO(内存映射 I/O)或 PIO(端口 I/O)读写设备寄存器
-
中断通知:外设通过中断控制器向 CPU 发送事件信号
下面给出一个伪代码:
// 伪代码:CPU 与设备交互流程
while (1) {
if (中断触发) {
读取设备状态寄存器;
处理数据;
}
写入控制寄存器启动操作;
}
edu 设备介绍
edu 是 QEMU 官方提供的教学用虚拟设备,其功能设计精简却完整:
-
基础功能:实现阶乘计算(fact 寄存器)
-
高级功能:DMA 传输、中断触发
-
典型应用:演示 PCI 设备全生命周期管理
定义设备属性
关键点:通过继承 PCIDevice 自动获得 PCI 配置空间管理能力。
typedef struct EduState {
PCIDevice pdev; // 继承 PCI 设备基类
MemoryRegion mmio; // MMIO 内存区域
uint32_t fact;
uint32_t status; // 状态寄存器 (bit0=计算中,bit7=中断使能)
QemuThread thread; // 后台计算线程
QemuMutex thr_mutex; // 线程锁
bool stopping; // 线程停止标志
} EduState;
初始化 PCI 配置空间
在 pci_edu_realize() 中设置设备标识
pci_config_set_vendor_id(pci_conf, 0x1234); // 厂商 ID
pci_config_set_device_id(pci_conf, 0x11e8); // 设备 ID
pci_config_set_class(pci_conf, PCI_CLASS_OTHERS); // 设备类
此步骤让操作系统能识别设备为 "Edu Device"
映射 MMIO 区域
memory_region_init_io(&edu->mmio, OBJECT(edu), &edu_mmio_ops, edu,
"edu-mmio", 0x1000); // 4KB 地址空间
pci_register_bar(pdev, 0, PCI_BASE_ADDRESS_SPACE_MEM, &edu->mmio);
地址布局:
0x00 : 阶乘输入寄存器 (可写)
0x04 : 阶乘结果寄存器 (只读)
0x80 : DMA 源地址寄存器
0x88 : DMA 目标地址寄存器
实现 MMIO 回调函数
以阶乘计算为例:
static uint64_t edu_mmio_read(void *opaque, hwaddr addr, unsigned size)
{
EduState *edu = opaque;
if (addr == 0x04) { // 读取结果寄存器
return edu->fact;
}
...
}
static void edu_mmio_write(void *opaque, hwaddr addr, uint64_t val, unsigned size)
{
EduState *edu = opaque;
if (addr == 0x00) { // 写入输入值
qemu_mutex_lock(&edu->thr_mutex);
edu->status |= EDU_STATUS_COMPUTING; // 标记计算中
qemu_cond_signal(&edu->thr_cond); // 唤醒计算线程
qemu_mutex_unlock(&edu->thr_mutex);
}
}
实现后台计算线程
关键设计:避免阻塞主线程,通过条件变量实现异步计算。
static void *edu_fact_thread(void *opaque)
{
EduState *edu = opaque;
while (!edu->stopping) {
qemu_mutex_lock(&edu->thr_mutex);
while (!(edu->status & EDU_STATUS_COMPUTING)) {
qemu_cond_wait(&edu->thr_cond, &edu->thr_mutex);
}
uint32_t input = ...; // 从寄存器读取输入值
uint32_t result = compute_factorial(input); // 计算阶乘
edu->fact = result; // 写入结果寄存器
edu->status &= ~EDU_STATUS_COMPUTING; // 清除计算标志
if (edu->status & EDU_STATUS_IRQFACT) {
edu_raise_irq(edu, FACT_IRQ); // 触发中断
}
qemu_mutex_unlock(&edu->thr_mutex);
}
return NULL;
}
中断触发机制
void edu_raise_irq(EduState *edu, uint32_t val)
{
if (msi_enabled(&edu->pdev)) { // 使用 MSI 中断
msi_notify(&edu->pdev, val);
} else { // 使用传统 INTx 中断
pci_irq_assert(&edu->pdev);
}
}
DMA 传输实现
static void edu_dma_timer(EduState *edu)
{
dma_addr_t src = edu->dma.src;
dma_addr_t dst = edu->dma.dst;
uint32_t cnt = edu->dma.cnt;
// 执行 DMA 拷贝
pci_dma_read(&edu->pdev, src, edu->dma_buf, cnt);
pci_dma_write(&edu->pdev, dst, edu->dma_buf, cnt);
// 触发中断
if (edu->dma.cmd & EDU_DMA_IRQ) {
edu_raise_irq(edu, DMA_IRQ);
}
}
客户机视角的设备交互
当客户机程序的驱动访问 edu 设备时:
-
探测设备:通过 PCI ID 0x1234:11e8 识别
-
映射 MMIO:ioremap() 获取寄存器虚拟地址
-
计算阶乘
-
处理中断:在中断服务例程中清除状态标志
要点总结
edu 浓缩了外设建模的 5 大核心要素:
寄存器操作 → 中断通知 → DMA 传输 → 多线程协同 → PCI 规范
主要涉及:
-
状态机模型:通过寄存器位表示设备状态(如计算中/完成)
-
异步处理:耗时操作移交后台线程,避免阻塞 VCPU
-
地址隔离:每个设备拥有独立 MMIO 空间
-
中断抽象:支持传统 INTx 和现代 MSI 两种模式
-
DMA 安全:地址校验防止虚拟机逃逸