Linux系统三种设备抽象:
-
字符设备:设备上的信息抽象为连续的字节流,顺序读写,字节粒度
-
例:LED、键盘、串口、打印机
-
访问模式: 顺序访问,每次读取一个字节;调用驱动程序和设备直接交互
-
通常使用文件抽象:open(), read(), write(), close()
-
-
块设备:随机读写,块粒度
-
例:磁盘、U盘、闪存等(以存储设备为主)
-
访问模式:
- 随机访问,以块粒度进行读写
- 在驱动程序之上增加一层缓冲,避免和慢设备频繁交互
-
通常使用内存抽象:
- 内存映射文件(Memory-Mapped File):直接访问数据
- 同样可以使用文件抽象,但内存抽象更受欢迎(灵活性更好)
-
-
网络设备:
-
例:以太网、WiFi、蓝牙等(以通信设备为主)
-
访问模式:
- 面向格式化报文的收发
- 在驱动层之上维护多种协议,支持不同策略
-
通常使用套接字抽象:socket(), send(), recv(), close()
-
Linux设备驱动的主要抽象是那些?请列举sysfs文件系统中子项,并且指出他们之间的关系
主要的抽象:Class,Bus,Device
Sysfs下的子项:/sys/class, /sys/bus, /sys/devices
PIO (Port IO)
- IO设备具有独立的地址空间
- 使用特殊的指令(如x86中的in/out指令)
MMIO (Memory-mapped IO)
- 将设备映射到连续物理内存中
- 使用内存访问指令
- 行为与内存不完全一样,读写会有副作用
CPU中断处理流程:
AArch64中断分类:
- 中断分发器:
- 将当前最高优先级中断转发给对应CPU Interface
- 寄存器:GICD
- CPU接口:
- 将GICD发送的中断信息,通过IRQ、FIQ管脚,发送给连接到interface的core
- 寄存器:GICC
- Generate:外设发起一个中断
- Distribute:Distributor对收到的中断源进行仲裁,然后发送 给对应的CPU Interface
- Deliver:CPU Interface将中断传给core
- Activate:core读 GICC_IAR 寄存器,对中断进行确认
- Priority drop: core写 GICC_EOIR 寄存器,实现优先级重置
- 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
- 中断应该尽快响应 – 提高系统对外部的实时响应能力
- 尽量短 – Linux上半部:马上处理
- 可重入 – 应允许在中断过程的任意时刻被抢占
- 中断也能被“中断”!
- 在处理当前中断(ISR)时:
- 更高优先级的中断产生;或者
- 相同优先级的中断产生
- 那么该如何响应?
- 允许高优先级抢占
- 同级中断无法抢占
- ARM的FIQ能抢占任意IRQ,FIQ不可抢占
🔺 中断上下文不能睡眠!!!
- 考虑如下场景:
- Process 1进入内核态
- Process 1获得 Lock A
- 中断发生
- ISR 试图拿锁 Lock A
- ISR 调用sleep,等待Lock A被释放
- 死锁: – Process 1必须等待ISR返回,但ISR在等待Process 1释放锁LockA
- 在中断上下文中睡眠,内核将被挂起
- Device(设备):用于抽象系统中所有的硬件
- 包括CPU和内存
- Bus(总线):CPU连接Device的通道
- 所有的Device都通过bus相连
- Class(分类):具有相似功能或属性的设备集合
- 类似面向对象程序设计中的Class
- 抽象出一套可以在多个设备之间共享的数据结构和接口
- 从属于相同Class的设备驱动程序,直接继承
见PPT
▲ 执行上半部期间关闭中断
▲ 硬中断处理函数实质是上半部
- 最小化公共例程:
- 保存寄存器、屏蔽中断
- 恢复寄存器,返回现场
- 最重要:调用合适的由硬件驱动提供的中断处理handler
- 因为中断被屏蔽,所以不要做太多事情(时间、空间)
▲ 延迟完成,执行时间由系统调度决定,下半部属于具有较高优先级的内核任务
- 提供可以推迟完成任务的机制
- softirqs
- tasklets (建立在softirqs之上)
- 工作队列
- 内核线程
- 静态分配:在内核编译时期确定,数量有限
- 执行时间点:
- 中断之后(上半部之后)
- 系统调用或是异常发生之后
- 调度器显式执行ksoftirqd
- 并发:
- 可以在多核上同时执行
- 必须是可重入的
- 或根据需要加锁
- 可中断:Softirq运行时可再被中断抢占
要求:软中断要求能被重调度(在处理软中断A时,能切换至软中断B(挂起A唤醒B))
- 问题:在处理软中断A时,软中断产生了B,怎么办?
- 不处理àB响应被延迟
- 总是处理à如果软中断很长 -> 用户程序被饿死? <活锁>
- 方案:配额(quota)+ ksoftirqd
- Softirq调度器每次只运行有限数量的请求
- 剩余请求有内核线程ksoftirqd代为执行,和用户进程抢CPU
- ksoftirqd和用户进程都被调度器调度
优势:
- 可动态分配,数量不限
- 直接运行在调度它的CPU上(缓存亲和性)
- 避免一个tasklet实例被多个CPU接管的情况
- 同一时间只允许有一个相同类型的tasklet实例存在
- 执行期间不能被其它下半部抢占
- 不存在重入的问题
- 无需加锁
- 编程友好性
问题
- 难以正确实现
- 要防止休眠代码
- 任务不可抢占性(仍可被中断)
- 比其他任务的优先级都高,影响任务实时性
- 导致不可控的延迟
- Linux社区一直在讨论是否要移除Tasklet
🔺 Softirq和Tasklet使用中断上下文,工作队列使用进程上下文,可以睡眠!!!!
- 方式:
- 在内核空间维护FIFO队列, workqueue内核进程不断轮询队列
- 中断负责enqueue(fn, args), workqueue负责dequeue并执行fn(args)
- 特点:
- 只在内核空间,不和任何用户进程关联,没有跨模式切换和数据拷贝
- 始终运行在内核态
为什么ARM中断完成确认分为两步走?
Priority dropping:允许低优先级的中断被触发Interrupt
Deactivation:使制定的IRQ处于未活跃的状态,该中断可以被再次触发
Linux上半部:使用了priority dropping(GICC_EOIR)防止低优先级中断阻塞
Linux下半部:使用interrupt deactivation(GICC_DIR)完成该IRQ,并且通知GIC接受后续的IRQs
为什么要共享中断
- IRQ是有限资源
- 可以通过多个设备共享同一中断号来解决需求
- Linux将同一中断的ISR组成链表
- IRQ到来后,内核对每个中断处理程序都要执行
- 所有该中断的“订阅者”都会查询自己的设备寄存器,以确定当前中 断是不是自己的设备发出的
- 对于慢速设备,就会造成很大的开销