linux kernel 学习手记3 Interrupt

 

 10  中断处理

大部分设备都规定在设定启用前,不会产生中断。

对于并口:并口标准规定设置 port 2 (0x37a, 0x27a, 或者任何) bit 4 就使能中断报告. short 在模块初始化时进行一个简单的 outb 调用来设置这个位.

一旦中断启用,任何时候在管脚 10 (所谓的 ACK )上的电信号从低变到高, 并口产生一个中断. 最简单的方法来强制接口产生中断( 没有挂一个打印机到端口 )是连接并口连接器的管脚 9 管脚 10.

安装一个中断

中断线是一个宝贵且常常有限的资源, 特别当它们只有 15 或者 16 个时.

使用前需需要申请一个中断线,

下面的函数, 声明在 <linux/interrupt.h>, 实现中断注册接口:

int request_irq(unsigned int irq,

                irqreturn_t (*handler)(int, void *, struct pt_regs *),

                unsigned long flags,

 

                const char *dev_name,

                void *dev_id);

 

void free_irq(unsigned int irq, void *dev_id);

request_irq 返回给请求函数的返回值或者是 0 指示成功, 或者是一个负的错误码, 如同平常. 函数返回 -EBUSY 来指示另一个驱动已经使用请求的中断线是不寻常的. 函数的参数如下:

unsigned int irq

请求的中断号

irqreturn_t (*handler)

安装的处理函数指针. 我们在本章后面讨论给这个函数的参数以及它的返回值.

unsigned long flags

如你会希望的, 一个与中断管理相关的选项的位掩码(后面描述).

const char *dev_name

这个传递给 request_irq 的字串用在 /proc/interrupts 来显示中断的拥有者(下一节看到)

void *dev_id

用作共享中断线的指针. 它是一个独特的标识, 用在当释放中断线时以及可能还被驱动用来指向它自己的私有数据区(来标识哪个设备在中断). 如果中断没有被共享, dev_id 可以设置为 NULL, 但是使用这个项指向设备结构不管如何是个好主意. 我们将在"实现一个处理"一节中看到 dev_id 的一个实际应用.

flags 中可以设置的位如下:

SA_INTERRUPT

当置位了, 这表示一个"快速"中断处理. 快速处理在当前处理器上禁止中断来执行(这个主题在"快速和慢速处理"一节涉及).在现代内核中,慢速中断和快速中断区别已经消失,只余下快速的,执行时,当前处理器上其他所有的中断都被禁止,当然其他cpu上还可以处理,如时钟中断就是用这个,但是一般的中断没必要使用。

SA_SHIRQ

这个位表示中断可以在设备间共享. 共享的概念在"中断共享"一节中略述.

SA_SAMPLE_RANDOM

这个位表示产生的中断能够有贡献给 /dev/random /dev/urandom 使用的加密池.

/proc接口

文件/proc/interrupts是中断报告文件,可以查看中断发生的次数。

Linux内核通常会在第一个CPU上处理中断,以便最大化缓存本地性。

/proc中还有个和中断相关的文件/proc/stat纪录一些系统活动的底层统计信息

intr 5167833 5154006 2 0 2 4907 0 2 68 4 0 4406 9291 50 0 0 

第一个数是所有中断的总数, 而其他每一个代表一个单个 IRQ 线, 从中断 0 开始. 所有的计数跨系统中所有处理器而汇总的. 这个快照显示, 中断号 4 已使用 4907 , 尽管当前没有安装处理. 如果你在测试的驱动请求并释放中断

在每个打开和关闭循环, 你可能发现 /proc/stat /proc/interrupts 更加有用.

2 个文件的另一个不同是, 中断不是体系依赖的(也许, 除了末尾几行), stat ; 字段数依赖内核之下的硬件. 可用的中断数目少到在 SPARC 上的 15 , 多到 IA-64 上的 256, 并且其他几个系统都不同. 有趣的是要注意, 定义在 x86 中的中断数当前是 224, 不是你可能期望的 16; 如同在 include/asm-i386/irq.h 中解释的, 这依赖 Linux 使用体系的限制, 而不是一个特定实现的限制( 例如老式 PC 中断控制器的 16 个中断源).

APIC vs 8259A

X86 计算机的 CPU 为中断只提供了两条外接引脚:NMI INTR。其中 NMI 是不可屏蔽中断,它通常用于电源掉电和物理存储器奇偶校验;INTR是可屏蔽中断,可以通过设置中断屏蔽位来进行中断屏蔽,它主要用于接受外部硬件的中断 信号,这些信号由中断控制器传递给 CPU

常见的中断控制器有两种:

1. 可编程中断控制器8259A

传统的 PICProgrammable Interrupt Controller)是由两片 8259A 风格的外部芯片以级联的方式连接在一起。每个芯片可处理多达 8 个不同的 IRQ。因为从 PIC INT 输出线连接到主 PIC IRQ2 引脚,所以可用 IRQ 线的个数达到 15 个,如图 1 所示。


18259A 级联原理图
8259A 级联原理图

2. 高级可编程中断控制器(APIC

8259A 只适合单 CPU 的情况,为了充分挖掘 SMP 体系结构的并行性,能够把中断传递给系统中的每个 CPU 至关重要。基于此理由,Intel 引入了一种名为 I/O 高级可编程控制器的新组件,来替代老式的 8259A 可编程中断控制器。该组件包含两大组成部分:一是本地 APIC”,主要负责传递中断信号到指定的处理器;举例来说,一台具有三个处理器的机器,则它必须相对的要有三个本地 APIC。另外一个重要的部分是 I/O APIC,主要是收集来自 I/O 装置的 Interrupt 信号且在当那些装置需要中断时发送信号到本地 APIC,系统中最多可拥有 8 I/O APIC

每个本地 APIC 都有 32 位的寄存器,一个内部时钟,一个本地定时设备以及为本地中断保留的两条额外的 IRQ 线 LINT0 LINT1。所有本地 APIC 都连接到 I/O APIC,形成一个多级 APIC 系统,如图 2 所示。


2:多级I/O APIC系统
多级I/O APIC系统

目前大部分单处理器系统都包含一个 I/O APIC 芯片,可以通过以下两种方式来对这种芯片进行配置:

1) 作为一种标准的 8259A 工作方式。本地 APIC 被禁止,外部 I/O APIC 连接到 CPU,两条 LINT0 LINT1 分别连接到 INTR NMI 引脚。

2) 作为一种标准外部 I/O APIC。本地 APIC 被激活,且所有的外部中断都通过 I/O APIC 接收。

辨别一个系统是否正在使用 I/O APIC,可以在命令行输入如下命令:

# cat /proc/interrupts

           CPU0      

  0:      90504    IO-APIC-edge  timer

  1:        131    IO-APIC-edge  i8042

  8:          4    IO-APIC-edge  rtc

  9:          0    IO-APIC-level  acpi

 12:        111    IO-APIC-edge  i8042

 14:       1862    IO-APIC-edge  ide0

 15:         28    IO-APIC-edge  ide1

177:          9    IO-APIC-level  eth0

185:          0    IO-APIC-level  via82cxxx

...        

       

 

如果输出结果中列出了 IO-APIC,说明您的系统正在使用 APIC。如果看到 XT-PIC,意味着您的系统正在使用 8259A 芯片。

中断分类

中断可分为同步(synchronous)中断和异步(asynchronous)中断:

1. 同步中断是当指令执行时由 CPU 控制单元产生,之所以称为同步,是因为只有在一条指令执行完毕后 CPU 才会发出中断,而不是发生在代码指令执行期间,比如系统调用。

2. 异步中断是指由其他硬件设备依照 CPU 时钟信号随机产生,即意味着中断能够在指令之间发生,例如键盘中断。

根据 Intel 官方资料,同步中断称为异常(exception),异步中断被称为中断(interrupt)。

中断可分为可屏蔽中断(Maskable interrupt)和非屏蔽中断(Nomaskable interrupt)。异常可分为故障(fault)、陷阱(trap)、终止(abort)三类。

从广义上讲,中断可分为四类:中断故障陷阱终止。这些类别之间的异同点请参看下表。

1:中断类别及其行为

类别

原因

异步/同步

返回行为

中断

来自I/O设备的信号

异步

总是返回到下一条指令

陷阱

有意的异常

同步

总是返回到下一条指令

故障

潜在可恢复的错误

同步

返回到当前指令

终止

不可恢复的错误

同步

不会返回

X86 体系结构的每个中断都被赋予一个唯一的编号或者向量(8 位无符号整数)。非屏蔽中断和异常向量是固定的,而可屏蔽中断向量可以通过对中断控制器的编程来改变。

自动检测 IRQ

有些设备设计得更高级并且简单地"宣布"它们要使用的中断. 在这个情况下, 驱动获取中断号通过从设备的一个 I/O 端口或者 PCI 配置空间读一个状态字节. 当目标设备是一个有能力告知驱动它要使用哪个中断的设备时, 自动探测中断号只是意味着探测设备, 探测中断没有其他工作要做.

内核提供2个函数,但他们只能在非共享机制中使用,在头文件linux/interrupt.h中声明(该文件也描述了探测机制):

unsigned long probe_irq_on(void);

这个函数返回一个未安排的中断的位掩码. 驱动必须保留返回的位掩码, 并且在后面传递给 probe_irq_off. 在这个调用之后, 驱动应当安排它的设备产生至少一次中断.

int probe_irq_off(unsigned long);

在设备已请求一个中断后, 驱动调用这个函数, 作为参数传递之前由 probe_irq_on 返回的位掩码. probe_irq_off 返回在"probe_on"之后发出的中断号. 如果没有中断发生, 返回 0 (因此, IRQ 0 不能探测, 但是没有用户设备能够在任何支持的体系上使用它). 如果多于一个中断发生( 模糊的探测 ), probe_irq_off 返回一个负值.

程序员应当小心使能设备上的中断, 在调用 probe_irq_on 之后以及在调用 probe_irq_off 后禁止它们. 另外, 你必须记住服务你的设备中挂起的中断, probe_irq_off 之后.

一般的硬件会有几个可能irq值,并口只容许用户选择3 5 7 94个之一,在无法预知可能irq时候需要探测所有的空闲中断号,从0NR_IRQS-1.

x86上中断处理的内幕

这个描述是从 arch/i386/kernel/irq.c, arch/i386/kernel/ apic.c, arch/i386/kernel/entry.S, arch/i386/kernel/i8259.c, include/asm-i386/hw_irq.h 它们出现于 2.6 内核而推知的; 尽管一般的概念保持一致, 硬件细节在其他平台上不同.

中断处理的最低级是在 entry.S, 一个汇编语言文件处理很多机器级别的工作. 通过一点汇编器的技巧和一些宏定义, 一点代码被安排到每个可能的中断. 在每个情况下, 这个代码将中断号压栈并且跳转到一个通用段, 称为 do_IRQ, irq.c 中定义.

实现中断处理例程:

一个处理者不能传递数据到或者从用户空间, 因为它不在进程上下文执行. 处理者也不能做任何可能睡眠的事情, 例如调用 wait_event, 使用除 GFP_ATOMIC 之外任何东西来分配内存, 或者加锁一个旗标. 最后, 处理者不能调用调度.但它可以唤醒在该设备上睡眠的进程。

第一步常常包括清除接口板上的一位; 如果需要进行长时间计算, 最好的方法是使用一个 tasklet 或者 workqueue 来调度计算在一个更安全的时间。

 

处理者的参数和返回值:

    中断号( int irq )作为你可能在你的 log 消息中打印的信息是有用的, 如果有. 第二个参数, void *dev_id, 是一类客户数据; 一个 void* 参数传递给 request_irq, 并且同样的指针接着作为一个参数传回给处理者, 当中断发生时. 你常常传递一个指向你的在 dev_id 中的设备数据结构的指针, 因此一个管理相同设备的几个实例的驱动不需要任何额外的代码, 在中断处理中找出哪个设备要负责当前的中断事件. struct pt_regs *regs, 很少用到. 它持有一个处理器的上下文在进入中断状态前的快照. 寄存器可用来监视和调试; 对于常规地设备驱动任务, 正常地不需要它们.

    中断处理应当返回一个值指示是否真正有一个中断要处理. 如果处理者发现它的设备确实需要注意, 它应当返回 IRQ_HANDLED; 否则返回值应当是 IRQ_NONE. 你也可产生返回值, 使用这个宏:IRQ_RETVAL(handled) handled 是非零, 如果你能够处理中断. 内核用返回值来检测和抑制假中断. 如果你的设备没有给你方法来告知是否它确实中断, 你应当返回 IRQ_HANDLED.
   
   

 

启用和禁止中断

启用关闭一个中断:内核提供了 3 个函数为此目的, 所有都声明在 <asm/irq.h>. 这些函数是内核 API 的一部分, 因此我们描述它们, 但是它们的使用在大部分驱动中不鼓励. 在其他的中, 你不能禁止共享的中断线, 并且, 在现代的系统中, 共享的中断是规范.

void disable_irq(int irq);
void disable_irq_nosync(int irq);
void enable_irq(int irq);

调用任一函数可能更新在可编程控制器(PIC)中的特定 irq 的掩码, 因此禁止或使能跨所有处理器的特定 IRQ. 对这些函数的调用能够嵌套 -- 如果 disable_irq 被连续调用 2 , 需要 2 enable_irq 调用在 IRQ 被真正重新使能前.

启用关闭所有中断:关闭当前处理器中断的2 个函数(定义在 <asm/system.h>):

void local_irq_save(unsigned long flags);
void local_irq_disable(void);
void local_irq_restore(unsigned long flags); 
void local_irq_enable(void);

第一个版本恢复由 local_irq_save 存储于 flags 的状态, local_irq_enable 无条件打开中断.

 

前和后半部

inux (许多其他系统一起)解决这个问题通过将中断处理分为 2 . 所谓的前半部是实际响应中断的函数 -- 你使用 request_irq 注册的那个. 后半部是由前半部调度来延后执行的函数, 在一个更安全的时间. 最大的不同在前半部处理和后半部之间是所有的中断在后半部执行时都是启用的。

tasklet

tasklet 常常是后半部处理的首选机制; 它们非常快, 但是所有的 tasklet 代码必须是原子的. tasklet 的可选项是工作队列, 它可能有一个更高的运行周期但是允许睡眠. tasklet 调度不累积; tasklet 只运行一次,

tasklet 必须使用 DECLARE_TASKLET 宏来声明:

DECLARE_TASKLET(name, function, data);

name 是给 tasklet 的名子, function 是调用来执行 tasklet (它带一个 unsigned long 参数并且返回 void )的函数, 以及 data 是一个 unsigned long 值来传递给 tasklet 函数.

工作队列

在一个特殊工作者进程的上下文中. 因为这个工作队列函数在进程上下文运行, 它在需要时能够睡眠. 但是, 你不能从一个工作队列拷贝数据到用户空间, 除非你使用我们在 15 章演示的高级技术; 工作者进程不存取任何其他进程的地址空间.

static struct work_struct short_wq;
/* this line is in short_init() */
INIT_WORK(&short_wq, (void (*)(void *)) short_do_tasklet, NULL);

 

中断共享

共享中断通过 request_irq 来安装就像不共享的一样, 但是有 2 个不同:

  • SA_SHIRQ 位必须在 flags 参数中指定, 当请求中断时.
  • dev_id 参数必须是独特的. 任何模块地址空间的指针都行, 但是 dev_id 明确地不能设置为 NULL.

内核保持着一个与中断相关联的共享处理者列表, 并且 dev_id 可认为是区别它们的签名.

当内核收到一个中断, 所有的注册的处理者被调用. 一个共享的处理者必须能够在它需要的处理的中断和其他设备产生的中断之间区分.

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值