XV6学习(8)中断和设备驱动

驱动是操作系统中用于管理特定设备的代码:驱动控制设备硬件,通知硬件执行操作,处理中断,与等待该设备IO的进程进行交互。

当设备需要与操作系统进行交互时,就会产生中断(陷阱的一种),之后内核的陷阱处理代码就会识别中断设备并调用对应的驱动处理程序。在XV6这一步发生在trap.c的devintr中。

大部分设备驱动在两个上下文中执行代码:顶层部分运行在进程的内核线程中,底层部分在中断处理时执行。顶层部分通过系统调用如read和write来调用,这一部分代码会请求硬件开始一个操作的执行(如请求硬盘读取块);之后就会进入等待状态等待操作的完成。当设备完成操作后,就会触发一个中断,驱动的中断处理程序,即底层部分就会判断完成的操作,唤醒对应的正在等待的进程,之后通知硬件执行下一个操作。

代码:控制台输入

控制台的驱动程序console.c是一个驱动结构的简单抽象。控制台驱动通过UART(Universal asynchronous receiver-transmitter,通用异步收发传输器)串口读取用户输入的字符。驱动程序一次会累积一行的输入,并处理特定的字符如退格和ctrl-u。用户进程通过read系统调用来获取一行输入。

驱动调用的UART硬件是由QEMU模拟的16550芯片,一个16550芯片可以管理一条连接到终端或其他电脑的RS232串行链路。在QEMU中,其连接到键盘和显示器。

UART硬件可以看作一组映射到内存中的控制寄存器,对硬件的控制可以直接通过load和store特定内存来完成。UART内存映射地址开始于0x10000000或UART0(定义于memlayout.h)。每个控制寄存器的大小为1byte,偏移量定义于uart.c。

XV6main函数中的consoleinit对UART设备进行初始化,设置UART设备每接收一个字节的输入就产生一个接收中断,每当完成一个字节输出的发送时就产生一个传输完成中断。

XV6 shell通过init.c中打开的文件描述符对控制台进行读取。read系统调用将会调用consoleread函数,该函数等待输入的到达(通过中断)并保持在cons.buf中,拷贝其到用户空间,当一整行接收完成后返回到用户进程中。如果没有一整行输入到达,read进程就会在sleep调用中等待。

当用户输入一个字符,UART设备就会产生一个中断,激活XV6的陷阱处理程序。陷阱处理程序将会调用devintr,读取scause判断是否为外部设备产生的中断。之后通过PLIC(平台级中断控制器)判断中断设备,如果是UART设备,就会调用uartintr函数。

uartinit从UART设备中读取所有输入字符,并将其交给consoleintr处理,此函数不会等待字符的输入,因为未来的输入会产生新的中断。consoleintr将输入保持在buffer中直到一整行到达,同时对一些特殊符号进行处理。当一整行到达后,就会唤醒一个正在等待的consoleread。

当consoleread被唤醒时,buffer中就保存了完整的一行输入,此时就可以将其拷贝到用户空间并返回。

代码:控制台输出

write系统调用对控制台的写入最终会调用uartputc函数,设备会维护输出缓冲uart_tx_buf,因此写进程不需要等待UART完成发送。uartputc将字符加入缓冲区后,调用uartstart函数开始传输之后返回,该函数唯一的等待情况是缓冲区已满。

每当UART发送一个字节后,就会产生一次中断。uartintr函数会调用uartstart函数判断传输是否完成,未完成就开始传输下一个缓冲的字符。因此,当进程写入多个字符时,第一个字节会通过uartputc调用uartstart进行传输,之后的字节将会被uartintr调用的uartstart进行传输。

对于设备活动和进程活动,常用的解耦方式是通过缓冲和中断。控制台驱动可以处理输入即使没有进程在等待读取,一个随后到来的read会读取到输入。类似的,进程可以进行输出而不需要等待设备响应。解耦可以允许进程并行执行设备IO从而提高性能,尤其是当设备速度很慢或需要立即进行响应(如输入一个字符)。这种思想也被称作I/O并行。

驱动中的并行

在consoleread和consoleintr中会调用acquire函数。这些调用会申请一个锁,用于在并行访问中保护驱动的数据结构。在这里有三种并行风险:两个不同CPU上的进程同时调用consoleread;当CPU在执行consoleread函数时硬件触发了一个中断;当consoleread在执行时,硬件在其他CPU上触发了一个中断。

在并行中需要关注的另一个点是一个进程可能会等待设备的输入,但是中断信号在另一个进程运行时产生,因此中断处理程序是必须上下文无关的(不允许考虑中断时的进程或代码)。例如中断处理程序不能安全地在当前进程地页表上调用copyout函数。中断处理程序应该仅执行上下文无关的工作(如拷贝输入到缓冲区),之后唤醒顶层部分来处理剩余工作。

定时器中断

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wpdxxd.html