5.0: Interrupts and device drivers
硬件设备通常有一组寄存器,用于控制设备的行为或传递数据。这些寄存器的功能通常包括:
**输入寄存器:**设备将外部数据(如传感器数据、网络包等)写入到寄存器,处理器通过读取这些寄存器来获取输入数据。 **输出寄存器:**处理器向寄存器写入数据,这些数据会被设备读取并用于控制设备或输出到外部。 **状态寄存器:**用于描述设备的当前状态(如是否准备好接收数据、中断标志等)。 这些寄存器在设备设计中被赋予特定的功能,并映射到设备内部的硬件逻辑。
设备驱动程序是操作系统中的一段代码,用于管理特定的设备:它配置设备硬件,指示设备执行操作,处理由设备引发的中断,并与可能在等待设备I/O的进程进行交互。驱动程序代码可能很棘手,因为驱动程序与其管理的设备并发执行。此外,驱动程序还必须理解设备的硬件接口,这些接口通常非常复杂且文档不充分。
需要操作系统关注的设备通常可以配置为生成中断,这是一种陷阱。内核的陷阱处理代码能够识别设备是否发出了中断,并调用驱动程序的中断处理程序;在 xv6 中,这个调度过程发生在 devintr 函数中(位置:kernel/trap.c:185)。
// check if it's an external interrupt or software interrupt,
// and handle it.
// returns 2 if timer interrupt,
// 1 if other device,
// 0 if not recognized.
int
devintr()
{
uint64 scause = r_scause();
if(scause == 0x8000000000000009L){
// this is a supervisor external interrupt, via PLIC.
// irq indicates which device interrupted.
// platform-level interrupt controller (PLIC)
int irq = plic_claim();
if(irq == UART0_IRQ){
uartintr();
} else if(irq == VIRTIO0_IRQ){
virtio_disk_intr();
} else if(irq){
printf("unexpected interrupt irq=%d\n", irq);
}
// the PLIC allows each device to raise at most one
// interrupt at a time; tell the PLIC the device is
// now allowed to interrupt again.
if(irq)
plic_complete(irq);
return 1;
} else if(scause == 0x8000000000000005L){
// timer interrupt.
clockintr();
return 2;
} else {
return 0;
}
}
让我们仔细阅读这段代码
uint64 scause = r_scause();
if(scause == 0x8000000000000009L){
// this is a supervisor external interrupt, via PLIC.
scause寄存器用来获取为什么中断的原因. 如果scause 的值是否等于 0x8000000000000009L,这是一个特定的值,用于标识是否为外部中断(来自外部设备),并且是由 PLIC(平台中断控制器)生成的。
// irq indicates which device interrupted.
int irq = plic_claim();
if(irq == UART0_IRQ){
uartintr();
} else if(irq == VIRTIO0_IRQ){
virtio_disk_intr();
} else if(irq){
printf("unexpected interrupt irq=%d\n", irq);
}
通过调用 plic_claim() 获取触发中断的设备的 IRQ(中断请求)编号 然后通过这个条件块处理不同设备的中断: 如果是 UART0_IRQ,则调用 uartintr() 函数处理 UART 设备的中断。 如果是 VIRTIO0_IRQ,则调用 virtio_disk_intr() 函数处理 VirtIO 磁盘的中断。 如果是其他未知的中断(即 irq 的值非零),则表示发生了一个无法识别的中断
// the PLIC allows each device to raise at most one
// interrupt at a time; tell the PLIC the device is
// now allowed to interrupt again.
if(irq)
plic_complete(irq);
return 1;
} else if(scause == 0x8000000000000005L){
// timer interrupt.
clockintr();
return 2;
} else {
return 0;
}
一旦中断处理完成,通过调用 plic_complete() 通知 PLIC 该设备的中断已经处理完毕。这样,PLIC 允许该设备在以后再次触发中断
如果是外部设备的中断处理完毕,返回 1,表示这是一个外部中断
如果 scause 的值是 0x8000000000000005L,则表示发生了 定时器中断,调用 clockintr() 函数处理定时器相关的逻辑,返回2
其余情况返回0,无法识别
驱动程序的执行上下文
许多设备驱动程序在两种上下文中执行代码:上半部分和下半部分。
上半部分:通过系统调用(如 read 和 write)来触发,要求设备执行I/O操作。上半部分的代码可能会请求硬件启动某个操作(例如要求磁盘读取一个块);然后,代码会等待操作完成。
下半部分:当设备完成操作并发出中断时,由驱动程序的中断处理程序执行,它被称为下半部分。下半部分的代码会确定哪个操作已完成,必要时唤醒一个等待的进程,并指示硬件启动下一个待处理的操作。