← 返回首页
学习思考

Interrupt

思考一下,假设你正在用金山文档或者google docs编辑文字,当你敲下一个字符,屏幕上就会显示一个字符。操作系统做了哪些工作来支持这一过程?为了实现过程,在开机时操作系统又需要做什么工作来准备即将到来的中断?

Basic concepts

driver → the code in an operating system that manages a particular device

Device drivers execute code in two contexts:

Console input

console driver 是一种driver,负责与uart硬件相沟通

UART与输入输出设备(如键盘屏幕)相连,也就是console driver是从UART中读写相关的数据,console直接交互的对象是UART,UART中的各种寄存器的状态也是由console来管理,包括读写‘初始化等等

Code

Console output

Code

PLIC

Device Driver

bottom部分:主要是Interrupt handler。当一个进程送到了CPU,并且CPU设置接收中断,那么CPU会调用Interrupt handler处理中断

top部分:主要是用户进程,或者是内核其他调用的接口。对UART来说,这里有read/wirte接口,可以被更高层代码调用

top:

read → fileread →consoleread → buffer空,就阻塞,否则读取数据

bottom:

kerneltrap/usertrap → deintr → uartintr → 循环uartgetc和consoleintr,满了之后,唤醒consoleread,如果空就调用uartstart

top:

printf → for循环putc每次都是一个字符 → uartintr → write → sys_write → filewrite → consolewrite → for循环uartputc → uartputc将字符输出到output buffer,满了就阻塞,否则 → uartstart → while循环写入THR寄存器,发现空了就返回

bottom:

kerneltrap/usertrap → devintr → uartintr → uartstart write到寄存器中

xv6的中断初始化过程

xv6中shell程序输出$的top部分

  1. init.c:main():该函数由main.c:userinit()创建,也就是xv6的第一个用户进程,该函数首先通过mknod()创建了一个代表console的设备,type为FD_DEVICE,major为CONSOLE,然后通过open函数打开该文件,这是第一个打开的文件,文件描述符为0。接着通过dup系统调用创建stdout和stderr,分别为1和2,所以文件描述符0,1,2都代表console,之后通过fork+exec创建子进程,即执行sh.c:main()
  2. sh.c:main():该函数主要通过getcmd()函数来读取输入,而getcmd首先会调用fprintf()来向console写入一个字符:fprintf()putc()write()syswrite()filewrite()
  3. file.c:consolewrite():该函数会首先判断文件的类型,f→type == FD_DEVICE,紧接着向对应的设备调用write函数,即consolewirite()
  4. console.c:consolewrite():这里先通过either_copyin()将字符串拷入,之后调用uartputc()uartputc()将字符写入给UART设备。uartputc()会实际的打印字符
  5. uart.c:uartputc():在该函数中,有一个buffer用来发送数据,和其读写指针共同构成一个循环队列。队列满时会让进程sleep
  6. uart.c:uartstart()uartstart()通知设备执行操作。如果buffer没有问题,就将字符写入THR,一旦数据送到设备,系统调用就会返回,shell就会继续执行。从内核返回机制与trap一样
  7. UART会将数据输出到屏幕,并再产生一个中断,接下来:

xv6中shell程序输出的bottom部分

  1. trap.c:usertrap():进入devintr(),首先判断是否是一个supervisor external interrupt,是的话就调用plic_claim()获取中断号,对UART来说,中断号是10,进而进入uartintr()
  2. uart.c:uartintr():该函数通过uartgetc()读取寄存器中的字符,这时没有字符,返回-1,直接进入uartstart()
  3. uart.c:uartstart():该函数将shell存储在buffer中的任意字符送出,实际上是在还有一个空格要输出。write系统调用可以在UART发送提示符的同时并发地将空格写入buffer,所以在UART的中断触发时,可以发现在buffer中还有一个空格字符,之后会将这个字符送出

中断相关内存布局

  1. UART0: 这个宏定义了一个 UART (Universal Asynchronous Receiver-Transmitter) 外设的物理地址。UART 是用于串行通信的硬件设备,通常用于与外部设备(如终端或串行通信设备)进行通信。
  2. UART0_IRQ: 定义了 UART0 外设的中断号。
  3. VIRTIO0: 定义了 virtio 驱动的物理地址,virtio 是一种用于虚拟化环境的设备驱动标准。
  4. VIRTIO0_IRQ: 定义了 virtio 驱动的中断号。
  5. CLINT: 定义了 CLINT(Core Local Interruptor)的物理地址,CLINT 包含了计时器等信息。
  6. CLINT_MTIMECMP(hartid): 定义了 CLINT 中特定 Hart(硬件线程)的 MTIMECMP 寄存器的地址偏移量,用于设置时钟中断触发时间。
  7. CLINT_MTIME: 定义了 CLINT 中的 MTIME 寄存器的地址偏移量,表示自系统启动以来的时钟周期数。
  8. PLIC: 定义了 PLIC(Platform-Level Interrupt Controller)的物理地址,PLIC 用于管理外部中断。
  9. PLIC_PRIORITY, PLIC_PENDING: 定义了 PLIC 中优先级和中断挂起寄存器的地址偏移量。
  10. PLIC_MENABLE(hart), PLIC_SENABLE(hart): 定义了使能特定 Hart 的中断的寄存器地址偏移量。
  11. PLIC_MPRIORITY(hart), PLIC_SPRIORITY(hart): 定义了特定 Hart 的中断优先级寄存器的地址偏移量。
  12. PLIC_MCLAIM(hart), PLIC_SCLAIM(hart): 定义了特定 Hart 的中断声明寄存器的地址偏移量。

参考

本文由 GJJ 创作,内容来源于 Notion 数据库,随时可在 Notion 中编辑更新。 本站由 DeepSeek-v4-flash 辅助构建,项目参考 NotionNext

← 返回首页
61
文章
6
标签
3
分类
962
运行天数