xv6 第5章 中断与设备驱动程序

第5章 中断与设备驱动程序

驱动程序是操作系统中管理特定设备的代码:它配置设备硬件,告诉设备执行操作,处理由此产生的中断,并与可能正在等待来自设备的I/O的进程交互。驱动程序代码可能很棘手,因为驱动程序与它所管理的设备同时执行。此外,驱动程序必须了解设备的硬件接口,这可能是复杂的和缺乏文档的。

 

需要操作系统注意的设备通常可以配置为生成中断,中断是陷入的一种类型。内核陷入处理代码识别设备何时引发中断并调用驱动程序的中断处理程序;在xv6中,这种调度发生在devintr(kernel/trap.c:177)中。

 

许多设备驱动程序在两种上下文中执行代码:上半部分在进程的内核线程中运行,下半部分在中断时执行。上半部分通过需要设备执行I/O的读写等系统调用调用。此代码可能要求硬件启动一个操作(例如,要求磁盘读取一个块);然后代码等待操作完成。最后,设备完成操作并引发中断。驱动程序的中断处理程序作为下半部分,计算出已完成的操作,在适当的情况下唤醒等待进程,并通知硬件开始处理任何等待的下一个操作。

 

5.1 代码:控制台输入

控制台驱动程序(console.c)是驱动程序结构的简单说明。控制台驱动程序通过连接到RISC-V的UART串行端口硬件接受人类输入的字符。控制台驱动程序一次累积一行输入,处理退格符和control-u等特殊输入字符。用户进程(如shell)使用read系统调用从控制台获取输入行。当您在QEMU中输入到xv6时,您的击键将通过QEMU的模拟UART硬件传递到xv6。

 

驱动程序与之对话的UART硬件是QEMU模拟的16550芯片[11]。在一台真正的计算机上,16550将管理一个连接到终端或其他计算机的RS232串行链路。运行QEMU时,它连接到键盘和显示器。

 

UART硬件在软件看来是一组内存映射控制寄存器。也就是说,RISC-V硬件连接到UART设备的一些物理地址,以便加载和存储与设备硬件而不是RAM交互。UART的内存映射地址从0x10000000或UART0开始(kernel/memlayout.h:21)。有几个UART控制寄存器,每个都有一个字节的宽度。它们与UART0的偏移在(kernel/uart.c:22)中定义。例如,LSR寄存器包含指示输入字符是否等待软件读取的位。这些字符(如有)可从RHR寄存器读取。每次读取一个时,UART硬件从等待字符的内部FIFO中删除它,并在FIFO为空时清除LSR中的“ready”位。UART发送硬件在很大程度上独立于接收硬件;如果软件向THR写入一个字节,UART发送该字节。

 

Xv6的main调用consoleinit(kernel/console.c:184)来初始化UART硬件。此代码将UART配置为在UART接收到每个输入字节时生成一个接收中断,并在UART每次完成发送一个输出字节时生成一个发送完成中断(kernel/UART.c:53)。

 

xv6 shell通过init.c打开的文件描述符(user/init.c:19)从控制台读取数据。对read系统调用的调用通过内核传递到consoleread(kernel/console.c:82)。consoleread等待输入到达(通过中断)并被缓存在cons.buf中,将输入复制到用户空间,然后(在整个行到达之后)返回到用户进程。如果用户还没有输入完整的行,任何读取进程都将在sleep调用(kernel/console.c:98)中等待。(第7章解释了睡眠的细节)。

 

当用户键入一个字符时,UART硬件要求向RISC-V发送一个中断请求,这将激活xv6的陷入处理程序。陷入处理程序调用devintr(kernel/trap.c:177),它查看RISC-V scause寄存器,以发现中断来自外部设备。然后它请求一个叫做PLIC[1]的硬件单元来告诉它哪个设备中断了(kernel/trap.c:186)。如果是UART,devintr会调用uartintr。

 

uartintr(kernel/uart.c:180)从uart硬件读取任何等待的输入字符并将它们交给consoleintr(kernel/console.c:138);它不等待字符,因为将来的输入将引发一个新的中断。consoleintr的工作是累计cons.buf中的输入字符直到整行到达。consoleintr专门处理退格和其他一些特殊字符。当一个新行到达时,consoleintr唤醒一个等待的consoleread(如果有)。

 

一旦被唤醒,consoleread将在cons.buf中发现一整行字符,将其复制到用户空间,然后(通过系统调用机制)返回到用户空间。

 

5.2 代码:控制台输出

对连接到控制台的文件描述符的write系统调用最终到达uartputc(kernel/uart.c:87)。设备驱动程序维护一个输出缓冲区(uart_tx_buf),以便写入过程不必等待uart完成发送;相反,uartputc将每个字符附加到缓冲区,调用uartstart启动设备传输(如果尚未发送),然后返回。uartputc等待的唯一情况是缓冲区已满。

 

每当UART发送完一个字节,它就会生成一个中断。uarttinr调用uartstart,后者检查设备是否真正完成了发送,并将下一个缓冲输出字符交给设备。因此,如果进程向控制台写入多个字节,通常第一个字节将由uartputc调用uartstart发送,其余的缓冲字节将在传输完成中断到达时将由uartintr调用uartstart发送。

 

需要注意的一般模式是通过缓冲和中断将设备活动与流程活动分离。控制台驱动程序可以处理输入,即使没有进程在等待读取它;随后的读取将看到输入。类似地,进程可以发送输出而不必等待设备。这种解耦可以通过允许进程与设备I/O并发执行来提高性能,并且在设备速度慢(如UART)或需要立即关注(如回声类型字符)时尤为重要。这种想法有时被称为I/O并发。

 

5.3 驱动程序中的并发

您可能已经注意到在consoleread和consoleintr中调用acquire。这些调用获取一个锁,以保护控制台驱动程序的数据结构不被并发访问。这里有三种并发危险:不同CPU上的两个进程可能同时调用consoleread;当CPU已经在consoleread中执行时,硬件可能会要求CPU传递控制台(实际上是UART)中断;当consoleread执行时,硬件可能会在另一个CPU上传递控制台中断。第6章探讨了锁在这些场景中的帮助。

 

驱动程序中并发性需要小心的另一种方式是,一个进程可能正在等待来自设备的输入,但是输入的中断信号到达可能在另一个进程(或根本没有进程)运行时到达。因此,不允许中断处理程序考虑它们中断的进程或代码。例如,中断处理程序无法安全地使用当前进程的页表调用copyout。中断处理程序通常只做相对较少的工作(例如,只需将输入数据复制到缓冲区),然后唤醒上半部分代码来完成其余的工作。

 

5.4 定时器中断

Xv6使用定时器中断来维护其时钟,并使其能够在计算密集型进程之间切换;usertrap和kerneltrap中的yield调用导致了这种切换。定时器中断来自连接到每个RISC-V CPU的时钟硬件。Xv6对这个时钟硬件进行编程,以定期中断每个CPU。

 

RISC-V要求定时器中断在机器模式,而非监督模式下获取。RISC-V机器模式在没有分页的情况下执行,并且使用一组单独的控制寄存器,因此在机器模式下运行普通的xv6内核代码是不实际的。因此,xv6处理计时器中断完全独立于上面列出的陷入机制。

 

在main之前的start.c中以机器模式执行的代码设置为接收计时器中断(kernel/start.c:57)。工作的一部分是对CLINT硬件(核心本地中断器)进行编程,以在一定延迟后生成中断。另一部分是建立一个暂存区,类似于trapframe,以帮助计时器中断处理程序保存寄存器和CLINT寄存器的地址。最后,start将mtvec设置为timervec并启用计时器中断。

 

当用户或内核代码执行时,定时器中断可以在任何时候发生;内核无法在关键操作期间禁用计时器中断。因此,定时器中断处理程序必须以一种保证不会干扰中断的内核代码的方式来完成它的工作。基本策略是让处理程序请求RISC-V发出“软件中断”并立即返回。RISC-V通过普通的陷入机制将软件中断传递到内核,并允许内核禁用它们。处理定时器中断产生的软件中断的代码可以在devintr(kernel/trap.c:204)中找到。

 

机器模式计时器中断向量是timervec(kernel/kernelvec.S:93)。它在start准备的暂存区保存一些寄存器,告诉CLINT何时生成下一个定时器中断,要求RISC-V触发软件中断,恢复寄存器,然后返回。定时器中断处理程序中没有C代码。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

lhw---9999

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值