驱动是操作系统中用于管理特定设备的代码:驱动控制设备硬件,通知硬件执行操作,处理中断,与等待该设备IO的进程进行交互。

当设备需要与操作系统进行交互时,就会产生中断(陷阱的一种),之后内核的陷阱处理代码就会识别中断设备并调用对应的驱动处理程序。在XV6这一步发生在trap.cdevintr中。

大部分设备驱动在两个上下文中执行代码:顶层部分运行在进程的内核线程中,底层部分在中断处理时执行。顶层部分通过系统调用如readwrite来调用,这一部分代码会请求硬件开始一个操作的执行(如请求硬盘读取块);之后就会进入等待状态等待操作的完成。当设备完成操作后,就会触发一个中断,驱动的中断处理程序,即底层部分就会判断完成的操作,唤醒对应的正在等待的进程,之后通知硬件执行下一个操作。

代码:控制台输入

控制台的驱动程序console.c是一个驱动结构的简单抽象。控制台驱动通过UART(Universal asynchronous receiver-transmitter,通用异步收发传输器)串口读取用户输入的字符。驱动程序一次会累积一行的输入,并处理特定的字符如退格和ctrl-u。用户进程通过read系统调用来获取一行输入。

驱动调用的UART硬件是由QEMU模拟的16550芯片,一个16550芯片可以管理一条连接到终端或其他电脑的RS232串行链路。在QEMU中,其连接到键盘和显示器。

UART硬件可以看作一组映射到内存中的控制寄存器,对硬件的控制可以直接通过loadstore特定内存来完成。UART内存映射地址开始于0x10000000UART0(定义于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并行。

驱动中的并行

consolereadconsoleintr中会调用acquire函数。这些调用会申请一个锁,用于在并行访问中保护驱动的数据结构。在这里有三种并行风险:两个不同CPU上的进程同时调用consoleread;当CPU在执行consoleread函数时硬件触发了一个中断;当consoleread在执行时,硬件在其他CPU上触发了一个中断。

在并行中需要关注的另一个点是一个进程可能会等待设备的输入,但是中断信号在另一个进程运行时产生,因此中断处理程序是必须上下文无关的(不允许考虑中断时的进程或代码)。例如中断处理程序不能安全地在当前进程地页表上调用copyout函数。中断处理程序应该仅执行上下文无关的工作(如拷贝输入到缓冲区),之后唤醒顶层部分来处理剩余工作。

定时器中断

XV6通过定时器中断来维护时钟以及进行进程切换;在usertrapkerneltrap中的yield函数会执行进程切换。定时器中断会由RISC-V CPU内部的时钟硬件产生。XV6对此时钟硬件进行编程以定期中断每个CPU。

RISC-V要求定时器中断必须在机器模式下执行,而不是在监管模式下执行。RISC-V的机器模式在无分页环境下执行,并且具有一系列单独的控制寄存器,因此在机器模式下运行普通的 xv6 内核代码是不切实际的。所有XV6的定时器中断处理程序是和陷阱机制完全分开的。

start.c中的代码执行于机器模式中,main函数之前,在timerinit函数中对定时器中断进行了设置:对CLINT硬件编程使其在一定时间后产生一次中断;设置scratch区域(类似于trapframe),帮助定时器中断处理程序保存寄存器和CLINT寄存器的地址。最后函数会设置mtvectimervec函数地址并开启定时器中断。

定时器中断会在任何时候发生,内核在执行关键操作时也无法禁用定时器中断。因此定时器中断处理程序必须保证不会干扰被中断的内核代码执行。处理程序最基本的策略就是产生一个软件中断之后立即返回。产生的软件中断就可以通过通用的陷阱机制进行处理,并且可以进行关闭。软件中断的处理程序在devintr函数中。

机器模式的时钟中断向量为timervec,该函数保存了三个寄存器在start函数准备的scratch区域中,通知CLINT下一个中断的时刻,通过csrw sip, a1a1为2)指令触发一个软件中断,最后恢复寄存器并返回。

真实操作系统

XV6运行设备和时钟中断在内核执行时产生。定时器中断在中断处理程序中强制线程切换,即使是在内核态执行中。这个功能可以使得内核线程公平地获取CPU时间片,尤其是当内核线程耗费大量时间进行计算而不返回用户态。但是,这使得内核代码需要考虑到其可能会被暂停并在一段时间后再另一个CPU上恢复,而这给XV6带来了一定的复杂性。如果设备中断和定时器中断只在用户代码执行时运行触发,内核可以变得更加简单。

在许多操作系统中,驱动程序的代码量远远大于内核本身。要支持所有设备在计算机上运行是十分繁杂的工作:有大量设备需要支持,设备有很多特性,设备间的协议十分复杂并且缺少文档。

UART设备通过读取控制寄存器一次接收一个字节数据,这种模式称为程序I/O(programmed I/O),因为数据移动由软件驱动。这种方式十分简单但是在高速设备上是十分缓慢的。高速设备通常通过DMA方式来进行数据传输。DMA设备硬件可以直接对内存进行读写,现代硬盘和网络设备就是通过这种方式进行的。DMA设备驱动会在内存中准备数据,之后通过一次控制寄存器的写入告诉设备对准备好的数据进行处理。

当设备需要在无法预知但不太频繁的时间上需要进行处理时,中断是有效的。但是中断有很大的CPU开销。因此高速设备会使用一些技巧来减少中断次数。一个技巧就是对一整批的输入或输出请求发起一次中断。另一个是驱动完全禁用中断,转为定时查询设备是否需要处理,这种技术被称为轮询(polling)。如果设备操作执行非常频繁,那么轮询是有意义的,反之如果设备大部分时间都是空闲的,那么轮询会浪费CPU时间。一些驱动会根据设备负载自动切换轮询和中断。

UART驱动先拷贝输入数据到内核缓冲区,之后再拷贝到用户空间。这在低数据传输率的情况下是有效的,但是对于高速设备,两次拷贝会显著地降低性能。一些操作系统可以直接将数据在用户态缓冲区和设备硬件之间移动,通常是通过DMA。

XV6学习(8)中断和设备驱动的更多相关文章

  1. Linux嵌入式学习-烟雾传感器驱动-字符设备驱动-按键驱动

    MQ-2烟雾气敏传感器模块在X210v3开发板上的驱动. 现在需要一个MQ-2烟雾气敏传感器模块的驱动.其检测烟雾超过一定的标准后,会返回一个不同的电平,和按键驱动差不多. 但是在编写驱动的时候,需要 ...

  2. 蜕变成蝶~Linux设备驱动之中断与定时器

    “我叮咛你的 你说 不会遗忘 你告诉我的 我也全部珍藏 对于我们来说 记忆是飘不落的日子 永远不会发黄 相聚的时候 总是很短 期待的时候 总是很长 岁月的溪水边 捡拾起多少闪亮的诗行 如果你要想念我  ...

  3. Hasen的linux设备驱动开发学习之旅--时钟

    /** * Author:hasen * 參考 :<linux设备驱动开发具体解释> * 简单介绍:android小菜鸟的linux * 设备驱动开发学习之旅 * 主题:时钟 * Date ...

  4. 嵌入式Linux驱动学习之路(二十)USB设备驱动

    USB在接入系统的时候,以0的设备ID和主机通信,然后由主机为其分配新的ID. 在主机端,D+和D-都是下拉接地的.而设备端的D-接上拉时,表明此设备为高速设备:12M/s. D+接上拉时则是全速设备 ...

  5. 字符设备驱动之Led驱动学习记录

    一.概述 Linux内核就是由各种驱动组成的,内核源码中大约有85%的各种渠道程序的代码.一般来说,编写Linux设备驱动大致流程如下: 1.查看原理图,数据手册,了解设备的操作方法. 2.在内核中找 ...

  6. linux设备驱动归纳总结(六):3.中断的上半部和下半部——tasklet【转】

    本文转载自:http://blog.chinaunix.net/uid-25014876-id-100005.html linux设备驱动归纳总结(六):3.中断的上半部和下半部——tasklet x ...

  7. linux设备驱动归纳总结(六):2.分享中断号【转】

    本文转载自:http://blog.chinaunix.net/uid-25014876-id-90837.html xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx ...

  8. linux设备驱动归纳总结(六):1.中断的实现【转】

    本文转载自:http://blog.chinaunix.net/uid-25014876-id-90740.html linux设备驱动归纳总结(六):1.中断的实现 xxxxxxxxxxxxxxxx ...

  9. linux字符设备驱动学习笔记(一):简单的字符设备驱动

    最近在鼓捣lnux字符设备驱动,在网上搜集的各种关于linux设备驱动的代码和注释,要么是针对2.4的,要么是错误百出,根本就不能运行成功,真希望大家在发博客的时候能认真核对下代码的正确性,特别是要把 ...

随机推荐

  1. 多维数组遍历.php

    $a=array('fruits'=>array('a'=>'orange','b'=>'grape','c'=>'apple'),     'numbers'=>arr ...

  2. HTTP高级(Cookie,Session ,LocalStorage )

    Cookie 服务器通过 Set-Cookie 头给客户端一串字符串 客户端每次访问相同域名的网页时,必须带上这段字符串 客户端要在一段时间内保存这个Cookie Cookie 默认在用户关闭页面后就 ...

  3. sendfile“零拷贝”和mmap内存映射

    在学习sendfille之前,我们先来了解一下浏览器访问页面时,后台服务器的大致工作流程. 下图是从用户访问某个页面到页面的显示这几秒钟的时间当中,在后台的整个工作过程. 如上图,黑色箭头所示的过程, ...

  4. c通过ctfshow学习php反序列化

    web254 web255 web256 web257 web258 web259 web260 web262 web263 web264 web265 web266 web254 error_rep ...

  5. python实现域名注册查询

    author:摘繁华-蓝白社区 联合出品 域名生成与查询 文件说明: [x] .py源文件 [x] .exe可执行文件 [x] .config.json配置文件 ps: .exe和config.jso ...

  6. Netty源码解析 -- 对象池Recycler实现原理

    由于在Java中创建一个实例的消耗不小,很多框架为了提高性能都使用对象池,Netty也不例外. 本文主要分析Netty对象池Recycler的实现原理. 源码分析基于Netty 4.1.52 缓存对象 ...

  7. 如何将项目推到github上面

    1.先查看是否安装git. 2.如果没有安装git ,下载之后别忘了配置环境变量.(右击此电脑 --属性--高级系统设置--环境变量--系统变量中的path) 3.推代码 查看状态(可查可不查) gi ...

  8. C语言指针-从底层原理到花式技巧,用图文和代码帮你讲解透彻

    这是道哥的第014篇原创 目录 一.前言 二.变量与指针的本质 1. 内存地址 2. 32位与64位系统 3. 变量 4. 指针变量 5. 操作指针变量 5.1 指针变量自身的值 5.2 获取指针变量 ...

  9. Hbase 手动执行MajorCompation

    说明: Major Compaction 的作用: 1.将一个Region下的所有StoreFile合并成一个StoreFile文件 2.对于删除.过期.多余版本的数据进行清除 由于MajorComp ...

  10. 【Azure Developer】解决Azure Key Vault管理Storage的示例代码在中国区Azure遇见的各种认证/授权问题 - C# Example Code

    问题描述 使用Azure密钥保管库(Key Vault)来托管存储账号(Storage Account)密钥的示例中,从Github中下载的示例代码在中国区Azure运行时候会遇见各种认证和授权问题, ...