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
函数。中断处理程序应该仅执行上下文无关的工作(如拷贝输入到缓冲区),之后唤醒顶层部分来处理剩余工作。
定时器中断
XV6通过定时器中断来维护时钟以及进行进程切换;在usertrap
和kerneltrap
中的yield
函数会执行进程切换。定时器中断会由RISC-V CPU内部的时钟硬件产生。XV6对此时钟硬件进行编程以定期中断每个CPU。
RISC-V要求定时器中断必须在机器模式下执行,而不是在监管模式下执行。RISC-V的机器模式在无分页环境下执行,并且具有一系列单独的控制寄存器,因此在机器模式下运行普通的 xv6 内核代码是不切实际的。所有XV6的定时器中断处理程序是和陷阱机制完全分开的。
start.c
中的代码执行于机器模式中,main
函数之前,在timerinit
函数中对定时器中断进行了设置:对CLINT硬件编程使其在一定时间后产生一次中断;设置scratch区域(类似于trapframe),帮助定时器中断处理程序保存寄存器和CLINT寄存器的地址。最后函数会设置mtvec
为timervec
函数地址并开启定时器中断。
定时器中断会在任何时候发生,内核在执行关键操作时也无法禁用定时器中断。因此定时器中断处理程序必须保证不会干扰被中断的内核代码执行。处理程序最基本的策略就是产生一个软件中断之后立即返回。产生的软件中断就可以通过通用的陷阱机制进行处理,并且可以进行关闭。软件中断的处理程序在devintr
函数中。
机器模式的时钟中断向量为timervec
,该函数保存了三个寄存器在start
函数准备的scratch
区域中,通知CLINT下一个中断的时刻,通过csrw sip, a1
(a1
为2)指令触发一个软件中断,最后恢复寄存器并返回。
真实操作系统
XV6运行设备和时钟中断在内核执行时产生。定时器中断在中断处理程序中强制线程切换,即使是在内核态执行中。这个功能可以使得内核线程公平地获取CPU时间片,尤其是当内核线程耗费大量时间进行计算而不返回用户态。但是,这使得内核代码需要考虑到其可能会被暂停并在一段时间后再另一个CPU上恢复,而这给XV6带来了一定的复杂性。如果设备中断和定时器中断只在用户代码执行时运行触发,内核可以变得更加简单。
在许多操作系统中,驱动程序的代码量远远大于内核本身。要支持所有设备在计算机上运行是十分繁杂的工作:有大量设备需要支持,设备有很多特性,设备间的协议十分复杂并且缺少文档。
UART设备通过读取控制寄存器一次接收一个字节数据,这种模式称为程序I/O(programmed I/O),因为数据移动由软件驱动。这种方式十分简单但是在高速设备上是十分缓慢的。高速设备通常通过DMA方式来进行数据传输。DMA设备硬件可以直接对内存进行读写,现代硬盘和网络设备就是通过这种方式进行的。DMA设备驱动会在内存中准备数据,之后通过一次控制寄存器的写入告诉设备对准备好的数据进行处理。
当设备需要在无法预知但不太频繁的时间上需要进行处理时,中断是有效的。但是中断有很大的CPU开销。因此高速设备会使用一些技巧来减少中断次数。一个技巧就是对一整批的输入或输出请求发起一次中断。另一个是驱动完全禁用中断,转为定时查询设备是否需要处理,这种技术被称为轮询(polling)。如果设备操作执行非常频繁,那么轮询是有意义的,反之如果设备大部分时间都是空闲的,那么轮询会浪费CPU时间。一些驱动会根据设备负载自动切换轮询和中断。
UART驱动先拷贝输入数据到内核缓冲区,之后再拷贝到用户空间。这在低数据传输率的情况下是有效的,但是对于高速设备,两次拷贝会显著地降低性能。一些操作系统可以直接将数据在用户态缓冲区和设备硬件之间移动,通常是通过DMA。
XV6学习(8)中断和设备驱动的更多相关文章
- Linux嵌入式学习-烟雾传感器驱动-字符设备驱动-按键驱动
MQ-2烟雾气敏传感器模块在X210v3开发板上的驱动. 现在需要一个MQ-2烟雾气敏传感器模块的驱动.其检测烟雾超过一定的标准后,会返回一个不同的电平,和按键驱动差不多. 但是在编写驱动的时候,需要 ...
- 蜕变成蝶~Linux设备驱动之中断与定时器
“我叮咛你的 你说 不会遗忘 你告诉我的 我也全部珍藏 对于我们来说 记忆是飘不落的日子 永远不会发黄 相聚的时候 总是很短 期待的时候 总是很长 岁月的溪水边 捡拾起多少闪亮的诗行 如果你要想念我 ...
- Hasen的linux设备驱动开发学习之旅--时钟
/** * Author:hasen * 參考 :<linux设备驱动开发具体解释> * 简单介绍:android小菜鸟的linux * 设备驱动开发学习之旅 * 主题:时钟 * Date ...
- 嵌入式Linux驱动学习之路(二十)USB设备驱动
USB在接入系统的时候,以0的设备ID和主机通信,然后由主机为其分配新的ID. 在主机端,D+和D-都是下拉接地的.而设备端的D-接上拉时,表明此设备为高速设备:12M/s. D+接上拉时则是全速设备 ...
- 字符设备驱动之Led驱动学习记录
一.概述 Linux内核就是由各种驱动组成的,内核源码中大约有85%的各种渠道程序的代码.一般来说,编写Linux设备驱动大致流程如下: 1.查看原理图,数据手册,了解设备的操作方法. 2.在内核中找 ...
- linux设备驱动归纳总结(六):3.中断的上半部和下半部——tasklet【转】
本文转载自:http://blog.chinaunix.net/uid-25014876-id-100005.html linux设备驱动归纳总结(六):3.中断的上半部和下半部——tasklet x ...
- linux设备驱动归纳总结(六):2.分享中断号【转】
本文转载自:http://blog.chinaunix.net/uid-25014876-id-90837.html xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx ...
- linux设备驱动归纳总结(六):1.中断的实现【转】
本文转载自:http://blog.chinaunix.net/uid-25014876-id-90740.html linux设备驱动归纳总结(六):1.中断的实现 xxxxxxxxxxxxxxxx ...
- linux字符设备驱动学习笔记(一):简单的字符设备驱动
最近在鼓捣lnux字符设备驱动,在网上搜集的各种关于linux设备驱动的代码和注释,要么是针对2.4的,要么是错误百出,根本就不能运行成功,真希望大家在发博客的时候能认真核对下代码的正确性,特别是要把 ...
随机推荐
- Error running 'DemoApplication': No jdk for module 'demo' 没有jdk
方案1----- 按理说jdk都是在File->Project Structure里面设置就可以了,而且现在检查了也是没有问题 后来几经折腾,通过直接搜索Settings里面的jdk,发现还有这 ...
- 循序渐进VUE+Element 前端应用开发(30)--- ABP后端和Vue+Element前端结合的分页排序处理
在很多列表展示数据的场合中,大多数都会需要一个排序的处理,以方便快速查找排序所需的数据,本篇随笔介绍如何结合ABP后端和Vue+Element前端结合的分页排序处理过程. 1.Vue+Element前 ...
- 每日一个linux命令4
mkdir命令 linux mkdir 命令用来创建指定的名称的目录,要求创建目录的用户在当前目录中具有写权限,并且指定的目录名不能是当前目录中已有的目录. mkdir test 创建一个空目录 ...
- kubectl常用命令(个人记录)
一.获取pod信息 1.获取当前集群运行的所有的pods的信息 kubectl get pod 2.获取当前集群运行的所有的pod运行在哪个节点 kubectl get pods -owide ...
- Java中常用修饰符浅谈
一.public.protected.default和private修饰符的作用域 public:在java程序中,如果将属性和方法定义为 public 类型,那么此属性和方法所在的类和及其子类,同一 ...
- Xamarin.Form 5.0: 新功能和控件以及调试改进
上周在.NET Conf 2020,Scott Hunter(.NET),Maddy Leger(微软移动开发工具-Xamarin项目经理)和David Ortinau(首席项目经理,移动开发人员工具 ...
- 【SpringMVC】SpringMVC 入门
SpringMVC 入门 文章源码 SpringMVC 基本概念 在 JavaEE 开发中,几乎全都是基于 B/S 架构的开发.在 B/S 架构中,系统标准的三层架构包括:表现层.业务层.持久层. 表 ...
- Docker学习笔记之搭建Docker私有仓库
Docker仓库服务器名为Docker注册(registry)服务器.可以使用docker push命令将镜像上传到注册服务器,也可以使用docker pull命令下载服务器的镜像. Docker注册 ...
- wpf 通过为DataGrid所绑定的数据源类型的属性设置Attribute改变DataGrid自动生成列的顺序
环境Win10 VS2019 .Net Framework4.8 在wpf中,如果为一个DataGrid绑定到一个数据源,默认情况下DataGrid会为数据源类型的每个属性生成一个列(Column)对 ...
- MySQL select join on 连表查询和自连接查询
连表查询 JOIN ON 操作 描述 inner join 只返回匹配的值 right join 会从右表中返回所有的值, 即使左表中没有匹配 left join 会从左表中返回所有的值, 即使右表中 ...