# 9.4 在XV6中设置中断

当XV6启动时，Shell会输出提示符“$ ”，如果我们在键盘上输入ls，最终可以看到“$ ls”。我们接下来通过研究Console是如何显示出“$ ls”，来看一下设备中断是如何工作的。

实际上“$ ”和“ls”还不太一样，“$ ”是Shell程序的输出，而“ls”是用户通过键盘输入之后再显示出来的。

对于“$ ”来说，实际上就是设备会将字符传输给UART的寄存器，UART之后会在发送完字符之后产生一个中断。在QEMU中，模拟的线路的另一端会有另一个UART芯片（模拟的），这个UART芯片连接到了虚拟的Console，它会进一步将“$ ”显示在console上。

![](https://1977542228-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MHZoT2b_bcLghjAOPsJ%2F-MNYn8xQ_nM7UyU1sa-g%2F-MN_yeWDGnO43Lo6y4tH%2Fimage.png?alt=media\&token=cb62632c-e0f1-4b23-b5fd-7177aa0a6ef1)

另一方面，对于“ls”，这是用户输入的字符。键盘连接到了UART的输入线路，当你在键盘上按下一个按键，UART芯片会将按键字符通过串口线发送到另一端的UART芯片。另一端的UART芯片先将数据bit合并成一个Byte，之后再产生一个中断，并告诉处理器说这里有一个来自于键盘的字符。之后Interrupt handler会处理来自于UART的字符。我们接下来会深入通过这两部分来弄清楚这里是如何工作的。

![](https://1977542228-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MHZoT2b_bcLghjAOPsJ%2F-MNYn8xQ_nM7UyU1sa-g%2F-MN_zlccGc5Fd8klMxRQ%2Fimage.png?alt=media\&token=5665e882-bc5a-470a-a37b-1ef6b2616ee7)

RISC-V有许多与中断相关的寄存器：

* SIE（Supervisor Interrupt Enable）寄存器。这个寄存器中有一个bit（E）专门针对例如UART的外部设备的中断；有一个bit（S）专门针对软件中断，软件中断可能由一个CPU核触发给另一个CPU核；还有一个bit（T）专门针对定时器中断。我们这节课只关注外部设备的中断。
* SSTATUS（Supervisor Status）寄存器。这个寄存器中有一个bit来打开或者关闭中断。每一个CPU核都有独立的SIE和SSTATUS寄存器，除了通过SIE寄存器来单独控制特定的中断，还可以通过SSTATUS寄存器中的一个bit来控制所有的中断。
* SIP（Supervisor Interrupt Pending）寄存器。当发生中断时，处理器可以通过查看这个寄存器知道当前是什么类型的中断。
* SCAUSE寄存器，这个寄存器我们之前看过很多次。它会表明当前状态的原因是中断。
* STVEC寄存器，它会保存当trap，page fault或者中断发生时，CPU运行的用户程序的程序计数器，这样才能在稍后恢复程序的运行。

我们今天不会讨论SCAUSE和STVEC寄存器，因为在中断处理流程中，它们基本上与之前（注，lec06）的工作方式是一样的。接下来我们看看XV6是如何对其他寄存器进行编程，使得CPU处于一个能接受中断的状态。

接下来看看代码，首先是位于start.c的start函数。

![](https://1977542228-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MHZoT2b_bcLghjAOPsJ%2F-MNYn8xQ_nM7UyU1sa-g%2F-MNa5Rv4ANj0GOmpMXf9%2Fimage.png?alt=media\&token=99fa1a9b-b983-46ec-9c0f-616220592cd9)

这里将所有的中断都设置在Supervisor mode，然后设置SIE寄存器来接收External，软件和定时器中断，之后初始化定时器。

接下来我们看一下main函数中是如何处理External中断。

![](https://1977542228-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MHZoT2b_bcLghjAOPsJ%2F-MNYn8xQ_nM7UyU1sa-g%2F-MNa6N7vHde52fObSxUz%2Fimage.png?alt=media\&token=65580d62-73c5-46eb-8767-e2fde2daac36)

我们第一个外设是console，这是我们print的输出位置。查看位于console.c的consoleinit函数。

![](https://1977542228-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MHZoT2b_bcLghjAOPsJ%2F-MNYn8xQ_nM7UyU1sa-g%2F-MNa6mHXaOc6Dtv5U6bj%2Fimage.png?alt=media\&token=80ea954c-2230-4eba-adcf-1a8e386bdb4a)

这里首先初始化了锁，我们现在还不关心这个锁。然后调用了uartinit，uartinit函数位于uart.c文件。这个函数实际上就是配置好UART芯片使其可以被使用。

![](https://1977542228-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MHZoT2b_bcLghjAOPsJ%2F-MNYn8xQ_nM7UyU1sa-g%2F-MNa7OdMH3taGmHfE7Wj%2Fimage.png?alt=media\&token=0538d371-3758-431d-98e6-907f5f5a6ab9)

这里的流程是先关闭中断，之后设置波特率，设置字符长度为8bit，重置FIFO，最后再重新打开中断。

> 学生提问：什么是波特率？
>
> Frans教授：这是串口线的传输速率。

以上就是uartinit函数，运行完这个函数之后，原则上UART就可以生成中断了。但是因为我们还没有对PLIC编程，所以中断不能被CPU感知。最终，在main函数中，需要调用plicinit函数。下图是plicinit函数。

![](https://1977542228-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MHZoT2b_bcLghjAOPsJ%2F-MNa7TJFrEQk2gYzkwCG%2F-MNcl8NhzO719lb6xtPl%2Fimage.png?alt=media\&token=ceb45ee2-8509-48fb-9166-7d6bc9930fef)

PLIC与外设一样，也占用了一个I/O地址（0xC000\_0000）。代码的第一行使能了UART的中断，这里实际上就是设置PLIC会接收哪些中断，进而将中断路由到CPU。类似的，代码的第二行设置PLIC接收来自IO磁盘的中断，我们这节课不会介绍这部分内容。

main函数中，plicinit之后就是plicinithart函数。plicinit是由0号CPU运行，之后，每个CPU的核都需要调用plicinithart函数表明对于哪些外设中断感兴趣。

![](https://1977542228-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MHZoT2b_bcLghjAOPsJ%2F-MNa7TJFrEQk2gYzkwCG%2F-MNcmoGEKLSU8ifrGFC6%2Fimage.png?alt=media\&token=4bbb1a15-4f10-427c-961e-51b801adf8ef)

所以在plicinithart函数中，每个CPU的核都表明自己对来自于UART和VIRTIO的中断感兴趣。因为我们忽略中断的优先级，所以我们将优先级设置为0。

到目前为止，我们有了生成中断的外部设备，我们有了PLIC可以传递中断到单个的CPU。但是CPU自己还没有设置好接收中断，因为我们还没有设置好SSTATUS寄存器。在main函数的最后，程序调用了scheduler函数，

![](https://1977542228-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MHZoT2b_bcLghjAOPsJ%2F-MNa7TJFrEQk2gYzkwCG%2F-MNcnlTuggw_Il7m9iIW%2Fimage.png?alt=media\&token=ac9df287-e059-4438-957e-548f1b22e030)

scheduler函数主要是运行进程。但是在实际运行进程之前，会执行intr\_on函数来使得CPU能接收中断。

![](https://1977542228-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MHZoT2b_bcLghjAOPsJ%2F-MNa7TJFrEQk2gYzkwCG%2F-MNcoC3QhbWZHXRz2BEt%2Fimage.png?alt=media\&token=eca193c8-ff3d-4d96-b837-5cfbbc2b2ecc)

intr\_on函数只完成一件事情，就是设置SSTATUS寄存器，打开中断标志位。

在这个时间点，中断被完全打开了。如果PLIC正好有pending的中断，那么这个CPU核会收到中断。

以上就是中断的基本设置。

> 学生提问：哪些核在intr\_on之后打开了中断？
>
> Frans教授：任何一个调用了intr\_on的CPU核，都会接收中断。实际上所有的CPU核都会运行intr\_on函数。
