xv6 lec9 Interrupts
记录问题 https://mit-public-courses-cn-translatio.gitbook.io/mit6-s081/lec09-interrupts/9.1-memory-in-real-os
9.1 真实操作系统内存使用情况
- os(mit的Athena计算机)把大量内存都用在buff/cache了
- 由于
demand paging
等技术,大部分进程的实际使用内存都很少RES
,即使虚拟内存VIRT
很大
9.2 Interrupt硬件部分
- 中断就是硬件想要得到操作系统的关注
- 中断处理过程与系统调用的处理类似,os先保存当前工作,处理中断,完成之后恢复之前的工作
- 系统调用与中断的区别
- 通过PLIC管理来自外部的中断,PLIC是一个中断路由:
9.3 设备驱动概述
- 管理设备的代码称为设备驱动
- 驱动代码分为top/bottom两个部分
- bottom一般是Interrupt handler,处理中断
- top用来作为用户进程,或者是内核其他部分的接口
- 驱动代码存在着一些队列(buffer),top部分可以从队列中读数据,bottom可以从队列中写数据,这种buffer可以使得设备与CPU解耦并行工作。
- os通过load/sotre指令对设备的控制寄存器进行读写,来控制设备,通过memory mapped I/O完成对于控制寄存器的编程。
- UART(Universal Asynchronous Receiver/Transmitter)设备有多个控制寄存器,其中
000
号寄存器是读写寄存器,比如键盘输入的一个字节可以放到该寄存器上,那么驱动代码就可以读这个数据
9.4 在XV6中设置中断
- RISC-V中与中断相关的寄存器:
- SIE(Supervisor Interrupt Enable)寄存器, 其中一个bit(E)用于针对UART这种外部设备的中断,还有一个bit(S)用于针对软件中断,还有个bit(T)针对于定时器中断。
- SSTATUS(Supervisor Status)寄存器。有一个bit用来控制所有中断。
- SIP(Supervisor Interrupt Pending)寄存器。当发生中断时,处理器可以通过查看这个寄存器知道当前是什么类型的中断。
- SCAUSE寄存器,它会表明当前状态的原因是中断。
- STVEC寄存器,它会保存当trap,page fault或者中断发生时,CPU运行的用户程序的程序计数器,这样才能在稍后恢复程序的运行。
- start()函数把所有中断设置在Supervisor mode,然后设置SIE寄存器来接收External,软件和定时器中断,之后初始化定时器
- main()函数处理external 中断,先是consoleinit(),其中调用了uartinit函数,这个函数配置号UART芯片,使其可以被使用
- 还需要初始化plic,其中表示plic使能了UART中断与IO磁盘中断
- 还需要对于每个CPU使能相关中断,表示每个CPU对哪些中断感兴趣,并设置优先级,这里忽略优先级的设置
- main函数最后调用scheduler函数来运行进程,scheduler函数中
调用intr_on使能中断
intr_on就只是设置SSTATUS寄存器,打开中断标志位
9.5 UART驱动的top部分
- init.c的main函数是系统启动后的第一个进程,其中文件描述符被绑定到了console,stdout,stderr
- 可以将consolewrite当作UART驱动的top部分
对于uartputc函数存在着一个可以读写的大小为32个字符的buffer,同时还存在为consumer提供的读指针与为producer提供的写指针,如果buffer满了,当前进程就sleep,之后调用uartstart函数去通知设备执行操作,也就是当前主板上的uart
- uartstart函数一开始去检查buffer是否不为空,以及设备是否空闲,
之后从buffer中读取数据,并放到THR中,这里相当于告诉设备,我这里有一个字节需要你来发送。一旦数据送到了设备,系统调用会返回,用户应用程序Shell就可以继续执行
9.6 UART驱动的bottom部分
- 在SSTATUS寄存器中打开了中断,所以处理器会被中断。假设键盘生成了一个中断并且发向了PLIC,PLIC会将中断路由给一个特定的CPU核,并且如果这个CPU核设置了SIE寄存器的E bit(注,针对外部中断的bit位):
- 首先,清除SIE寄存器想响应bit,处理完中断之后再去恢复响应bit
- 将当前程序计数器存在SEPC寄存器中。
- 保存当前mode
- 设置当前mode为Supervisor mode。
- 最后将程序计数器的值设置成STVEC的值。在XV6中,STVEC保存的要么是uservec或者kernelvec函数的地址,具体取决于发生中断时程序运行是在用户空间还是内核空间。
- 在usertrap中处理中断
通过SCAUSE寄存器判断当前中断是否来自外设中断,如果使得话,调用plic_claim获取中断
- 之后调用
uartintr
,这里的中断假设的是当THR的数据发送完之后的中断,那么在uartintr
中就可以调用uartstart
继续填满THR
9.7 Interrupt相关的并发
- 硬件中断中的并发:
- 设备与CPU并行。UART向Console发送字符,CPU可以在这个时候继续执行Shell,而Shell可能会再执行一次系统调用,向buffer中写入另一个字符,这些都是在并行的执行。这里的并行称为producer-consumer并行。
- 中断会停止当前运行的程序。
- 驱动的top与bottom是并行运行的,也就是说一个CPU可以去调用write系统调用去写buffer,而同时另一个CPU核收到UART的中断,去执行bottom,也就是读buffer,所以需要通过??保证buffer被并行访问。
9.8 UART读取键盘输入
- console也有一个buffer,之前Shell打印完
$
之后便睡眠了,当用户在键盘上输入譬如l
,这会导致l
被发送到主板上的UART芯片上,产生中断,PLIC路由到某个CPU核,触发devintr,获取到字符,传递给consoleintr函数通过
consputc
回显给用户,并存储在cons.buf
中当遇到换行符,唤醒Shell程序,Shell从console的buffer中读取数据
9.9 Interrupt的演进
- 对于快设备,CPU采取polling的方式去处理接收数据
- 对于慢设备,CPU采取中断的方式去处理接收设备
问题
- 有两个UART芯片吗?,设备上有一个,SiFive主板上有一个?
确实是两个
- 软中断相关
- 对于在THR寄存器写数据,
- plic_claim是在干嘛?
- 对于buffer中的互斥,采用互斥锁,睡眠在
uart_tx_r
,也就是需要等待的变量,而wake_up
函数也是传入uart_tx_r