Skip to content

Latest commit

 

History

History
295 lines (180 loc) · 8.13 KB

14-设备.md

File metadata and controls

295 lines (180 loc) · 8.13 KB

设备

1. 设备抽象

Linux系统三种设备抽象

  1. 字符设备:设备上的信息抽象为连续的字节流,顺序读写,字节粒度

    • 例:LED、键盘、串口、打印机

    • 访问模式: 顺序访问,每次读取一个字节;调用驱动程序和设备直接交互

    • 通常使用文件抽象:open(), read(), write(), close()

  2. 块设备:随机读写,块粒度

    • 例:磁盘、U盘、闪存等(以存储设备为主)

    • 访问模式:

      1. 随机访问,以块粒度进行读写
      2. 在驱动程序之上增加一层缓冲,避免和慢设备频繁交互
    • 通常使用内存抽象:

      • 内存映射文件(Memory-Mapped File):直接访问数据
      • 同样可以使用文件抽象,但内存抽象更受欢迎(灵活性更好)
  3. 网络设备

    • 例:以太网、WiFi、蓝牙等(以通信设备为主)

    • 访问模式:

      • 面向格式化报文的收发
      • 在驱动层之上维护多种协议,支持不同策略
    • 通常使用套接字抽象:socket(), send(), recv(), close()

Linux设备驱动的主要抽象是那些?请列举sysfs文件系统中子项,并且指出他们之间的关系

主要的抽象:Class,Bus,Device

Sysfs下的子项:/sys/class, /sys/bus, /sys/devices

2. CPU与外设的数据交互

2.1 可编程 I/O(Programmable I/O)

PIO (Port IO)

  • IO设备具有独立的地址空间
  • 使用特殊的指令(如x86中的in/out指令)

MMIO (Memory-mapped IO)

  • 将设备映射到连续物理内存中
  • 使用内存访问指令
  • 行为与内存不完全一样,读写会有副作用

2.2 DMA

DMA

3. 中断与中断管理

CPU中断处理流程

CPU中断处理流程

AArch64中断分类

AArch64中断分类

3.1 GIC

3.1.1 Distributor

  • 中断分发器:
    • 将当前最高优先级中断转发给对应CPU Interface
  • 寄存器:GICD

3.1.2 CPU Interface

  • CPU接口:
    • 将GICD发送的中断信息,通过IRQ、FIQ管脚,发送给连接到interface的core
  • 寄存器:GICC

3.2 ARM中断的生命周期

  1. Generate:外设发起一个中断
  2. Distribute:Distributor对收到的中断源进行仲裁,然后发送 给对应的CPU Interface
  3. Deliver:CPU Interface将中断传给core
  4. Activate:core读 GICC_IAR 寄存器,对中断进行确认
  5. Priority drop: core写 GICC_EOIR 寄存器,实现优先级重置
  6. Deactivate:core写 GICC_DIR 寄存器,来无效该中断

中断确认

  • CPU开始响应中断:
    • IRQ状态:pendingàactive
  • 寄存器:GICC_IAR,记录当前等待处理的中断号
  • 通过访问GICC_IAR寄存器,来对中断进行确认

中断完成

▲ 只有中断完成后,对应的中断才能重新被响应

🔺 为了提高中断响应的实时性,中断完成分两步

  • CPU处理完中断:
    • 设置IRQ状态:active -> inactive
  • 优先级重置(priority drop):
    • 将当前中断屏蔽的最高优先级进行重置,以便能够响应低优先级中断
    • 寄存器:GICC_EOIR
  • 中断无效(interrupt deactivation):
    • 将中断的状态置为inactive状态
    • 寄存器: GICC_DIR

3.3 如何设计中断处理函数

  • 中断应该尽快响应 – 提高系统对外部的实时响应能力
  • 尽量短 – Linux上半部:马上处理
  • 可重入 – 应允许在中断过程的任意时刻被抢占

3.4 中断嵌套

  • 中断也能被“中断”!
  • 在处理当前中断(ISR)时:
    • 更高优先级的中断产生;或者
    • 相同优先级的中断产生
  • 那么该如何响应?
    • 允许高优先级抢占
    • 同级中断无法抢占
  • ARM的FIQ能抢占任意IRQ,FIQ不可抢占

🔺 中断上下文不能睡眠!!!

  • 考虑如下场景:
    1. Process 1进入内核态
    2. Process 1获得 Lock A
    3. 中断发生
    4. ISR 试图拿锁 Lock A
    5. ISR 调用sleep,等待Lock A被释放
  • 死锁: – Process 1必须等待ISR返回,但ISR在等待Process 1释放锁LockA
  • 在中断上下文中睡眠,内核将被挂起

4. 管理设备

4.1 Linux设备驱动抽象

  • Device(设备):用于抽象系统中所有的硬件
    • 包括CPU和内存
  • Bus(总线):CPU连接Device的通道
    • 所有的Device都通过bus相连
  • Class(分类):具有相似功能或属性的设备集合
    • 类似面向对象程序设计中的Class
    • 抽象出一套可以在多个设备之间共享的数据结构和接口
    • 从属于相同Class的设备驱动程序,直接继承

5. 设备树

见PPT

6. LINUX的上下半部

6.1 上半部

▲ 执行上半部期间关闭中断

▲ 硬中断处理函数实质是上半部

  • 最小化公共例程:
    • 保存寄存器、屏蔽中断
    • 恢复寄存器,返回现场
  • 最重要:调用合适的由硬件驱动提供的中断处理handler
  • 因为中断被屏蔽,所以不要做太多事情(时间、空间)

6.2 下半部

▲ 延迟完成,执行时间由系统调度决定,下半部属于具有较高优先级的内核任务

  • 提供可以推迟完成任务的机制
    • softirqs
    • tasklets (建立在softirqs之上)
    • 工作队列
    • 内核线程

6.2.1 软中断 (softirqs)

  • 静态分配:在内核编译时期确定,数量有限
  • 执行时间点:
    • 中断之后(上半部之后)
    • 系统调用或是异常发生之后
    • 调度器显式执行ksoftirqd
  • 并发:
    • 可以在多核上同时执行
    • 必须是可重入的
    • 或根据需要加锁
  • 可中断:Softirq运行时可再被中断抢占

要求:软中断要求能被重调度(在处理软中断A时,能切换至软中断B(挂起A唤醒B))

  • 问题:在处理软中断A时,软中断产生了B,怎么办?
    • 不处理àB响应被延迟
    • 总是处理à如果软中断很长 -> 用户程序被饿死? <活锁>
  • 方案:配额(quota)+ ksoftirqd
    • Softirq调度器每次只运行有限数量的请求
    • 剩余请求有内核线程ksoftirqd代为执行,和用户进程抢CPU
    • ksoftirqd和用户进程都被调度器调度

6.2.2 Tasklet

优势

  • 可动态分配,数量不限
  • 直接运行在调度它的CPU上(缓存亲和性)
    • 避免一个tasklet实例被多个CPU接管的情况
  • 同一时间只允许有一个相同类型的tasklet实例存在
  • 执行期间不能被其它下半部抢占
    • 不存在重入的问题
    • 无需加锁
  • 编程友好性

问题

  • 难以正确实现
    • 要防止休眠代码
  • 任务不可抢占性(仍可被中断)
    • 比其他任务的优先级都高,影响任务实时性
    • 导致不可控的延迟
  • Linux社区一直在讨论是否要移除Tasklet

6.2.3 工作队列(Work Queues)

🔺 Softirq和Tasklet使用中断上下文,工作队列使用进程上下文,可以睡眠!!!!

  • 方式
    • 在内核空间维护FIFO队列, workqueue内核进程不断轮询队列
    • 中断负责enqueue(fn, args), workqueue负责dequeue并执行fn(args)
  • 特点
    • 只在内核空间,不和任何用户进程关联,没有跨模式切换和数据拷贝

6.2.4 内核线程(Kernel Threads)

  • 始终运行在内核态

下半部

为什么ARM中断完成确认分为两步走?

Priority dropping:允许低优先级的中断被触发Interrupt

Deactivation:使制定的IRQ处于未活跃的状态,该中断可以被再次触发

Linux上半部:使用了priority dropping(GICC_EOIR)防止低优先级中断阻塞

Linux下半部:使用interrupt deactivation(GICC_DIR)完成该IRQ,并且通知GIC接受后续的IRQs

为什么要共享中断

  • IRQ是有限资源
    • 可以通过多个设备共享同一中断号来解决需求
  • Linux将同一中断的ISR组成链表
    • IRQ到来后,内核对每个中断处理程序都要执行
    • 所有该中断的“订阅者”都会查询自己的设备寄存器,以确定当前中 断是不是自己的设备发出的
  • 对于慢速设备,就会造成很大的开销