文章目录
中断和异常
中断和异常的产生原因不同,但是内核处理的方式类似,所以容易混淆。
中断:异步中断,由硬件发出通知给处理器
异步中断是由cpu的外设产生的电信号引起的中断,其发生的时间点不可预期。
举例: 如鼠标、键盘、硬盘、时钟中断等等。
异常:异常也称为同步中断,异常是由软件产生
产生异常时必须考虑与处理器时钟同步。编程失误导致的错误指令 或 程序执行期间出现特殊情况必须靠内核处理的时候。
同步中断是由cpu内部的电信号产生的中断,其特点为当前执行的指令结束后才转而产生中断,由于有cpu主动产生,其执行点必然是可控的。
同步中断是指当指令执行时由CPU控制单元产生的中断,之所以称为“同步中断”:是因为只有在一条指令执行后CPU才会发出这类中断信号。
举例: 除0异常、缺页异常、系统调用处理异常等等。
中断处理程序(interrupt handler or interrupt service routine (ISR))
内核响应特定中断后,内核会执行一个函数,该函数就是中断处理程序。
一个设备的中断处理程序是它设备驱动程序的一部分,设备驱动程序是用于对设备进行管理的内核代码。
中断处理程序与其他内核函数的区别在于,中断处理程序是被内核调用来响应中断的,运行于中断上下文中。中断上下文也被称为原子上下文,该上下文中执行的代码不可阻塞。
上半部和下半部的对比
中断随时发生,中断处理程序也就可能随时执行。所以必须保证中断处理程序能够快速执行。
但是中断程序很多时候需要完成大量的任务,如网络设备对应的中断,除了应答硬件,还要处理大量数据,拷贝到内存,处理数据包等。
因此可以把具有严格实现的工作交给上半部(top half) 执行,如应答和复位硬件,这些工作都是在所有中断被禁止的时候完成;把能够允许稍后完成的工作推迟到 下半部(bottom half) 完成。
网卡为例:
网卡接受到数据包,需要通知内核,内核作出应答。
内核收到中断后,开始执行中断处理程序,通知硬件,拷贝最新的数据包到内存,然后读取更多的数据包,这些都是重要、紧迫、与硬件相关的工作。
网络数据包被拷贝到内存后,中断任务算完成,上半部结束,将控制权交还给系统被中断前运行的程序。
处理和操作数据包在下半部中进行。
注册中断处理程序
中断程序是硬件驱动程序的组成部分,如果设备使用中断那么相应的驱动程序就注册一个中断处理函数。
驱动程序通过request_irq()
函数注册一个中断处理程序,并且激活给定的中断线。
irq
:表示要分配的中断号,少数预先设定(时钟和键盘),多数动态确定。
handler
:实际中断处理程序
/* request_irq: allocate a given interrupt line */
int request_irq(unsigned int irq,
irq_handler_t handler,
unsigned long flags,
const char *name,
void *dev)
中断处理程序标志
flags
:可以为0,也可为对应标志的位掩码:
IRQF_DISABLED 禁止其他所有中断,不设置则本中断允许与其他中断同时运行,多数中断不设置。
IRQF_SAMPLE_RANDOM 为内核熵池(entropy pool)做贡献。将中断间隔时间作为随机数据来源。所以像时钟这样的有规律的中断不需要设置,还有可能受外部攻击影响的设备也不行,如联网设备。
IRQF_TIMER 特别为定时器中断准备的。
IRQF_SHARED 共享中断线,不设置的话一条中断线只能有一个处理程序。
name
:中断名称,方便与用户通信。
dev
:用于共享中断线,当内核需要释放中断处理程序时,dev将提供唯一信息cookie,在给定中断线上删除哪个处理程序。
request_irq()
成功返回0,否则返回错误码,如最常见-EBUSY,表示给定中断线忙(或者没有指定IRQF_SHARED)。
request_irq()
函数可能会睡眠,不允许 在中断上下文或者其他不允许阻塞的代码中调用。 因为在注册过程中内核需要在/proc/irq文件中创建一个与中断对应的项,需要用到proc_mkdir() --> proc_create() --> kmalloc()
,而kmalloc()
可以睡眠,哈哈。
注意:初始化硬件设备 和 注册中断处理程序 的顺序不能变,防止中断处理程序在设备初始化完成前就被执行。
释放中断处理程序
卸载驱动程序时需要注销对应的中断处理程序,并释放中断线,
void free_irq(unsigned int irq, void *dev);
free_irq()
该函数删除中断处理函数,如果中断线不共享则也禁用这条中断线;如果是共享的则只删除处理程序。但都必须在进程上下文调用free_irq()
。
编写中断处理程序
irq为这个处理程序要响应的中断对应的中断号,dev与request_irq()
传入的参数要一致。
static irqreturn_t intr_handler(int irq, void *dev)
返回irqreturn_t 类型就是int类型,返回值为IRQ_NONE 或者 IRQ_HANDLED。
当中断处理函数检测到中断,但该中断对应的设备不是在注册处理函数时指定的产生源,则返回IRQ_NONE。
当中断处理程序被正确调用 并 确实是对应设备产生的中断则返回IRQ_HANDLED。
重入问题
Linux的中断处理程序无需重入。
同一中断处理程序绝不会被同时调用以处理嵌套中断。
当一个给定处理程序正在执行时,相应的中断线在所有处理器上都会被屏蔽,防止同一中断线上接收新的中断。所有其他中断线上的中断都能被处理,但当前中断线总是被禁止。
共享中断与非共享中断处理程序
内核接受一个中断后,它将依次调用在该中断线上注册的每一个处理程序,如果不是该函数对应的设备产生的中断那将立即退出。
在注册和运行上相似。主要有三个差异:
request_irq()
的参数flags必须设置为IRQF_SHARED ;- 对于每个注册的中断处理程序来说,dev必须唯一,不能给共享的处理程序传递NULL值;
- 中断处理程序必须能区分 它的设备 发出了这个中断,否则共享中断处理程序根本不知道是本中断线的哪个设备发出的中断。
实时时钟中断处理程序
实时时钟RTC驱动程序的一部分。RTC是从系统定时器独立出来的设备。用于设置系统时钟,提供报警器或者周期性的定时器。
设置系统时钟只需要往某个寄存器或IO地址写入值就行。
而报警器或者周期性的定时器 需要中断来实现。
中断上下文(Interrupt Context)
当处理一个中断处理程序时,内核处于中断上下文。
进程上下文是一种内核所处的操作模式,此时内核代表进程执行,例如执行系统调用、执行内核线程,在进程上下文中,通过current宏关了到当前进程。进程上下文可以睡眠、也可以调用调度程序。
因为没有后备进程,所以中断上下文不可睡眠。
中断栈: 中断处理程序拥有自己的栈,每个处理器一个,大小为一页。 中断处理程序栈的设置是一个配置选项。
中断处理机制的实现
中断处理系统在Linux中非常依赖体系结构。
硬件产生中断—中断控制器—处理器—处理器中断内核—do_IRQ()—handle_IRQ_event()—运行中断处理程序—返回内核
中断从硬件到内核的路由:
设备产生中断,
|
通过总线信号被发送到中断控制器,
|
如果中断线是激活的,中断控制器将信号发往处理器,
|
处理器停止当前工作,关闭中断系统,跳转到内存预定义位置开始执行代码,这里是中断程序的入口点,
|
跳转到对应中断线上的唯一位置,在栈中保存IRQ号,并存放当前任务的寄存器值,
|
内核调用do_IRQ(),
|
do_IRQ()提取传入的中断的值,应答所有接收的中断,禁止本中断线,
|
do_IRQ()调用handle_IRQ_event()执行中断处理程序
/proc/interrupts
procfs是一个虚拟文件系统,只存在于内核内存,在procfs中读写文件都要调用内核函数。
提供/proc/interrupts
的函数是与体系结构相关的,叫做show_interrupts()
。
中断控制
linux内核提供一组接口,用于操作机器上的中断状态。禁止当前处理器的中断系统,屏蔽掉整机的一条中断线等。
控制中断系统的原因归根结底是需要提供同步。所有的中断同步现在必须结合使用本地中断控制和自旋锁。 以前用cli()和sti()去激活和关闭就可以了。
通过禁止中断,可以确保某个中断处理程序不会抢占当前代码,还可以禁止内核抢占。
某种锁可以防止其他处理器对共享数据的并发访问。获取锁的同时也伴随着禁止本地中断。
锁提供保护机制,防止其他处理器并发访问。禁止中断提供保护机制,防止其他中断处理程序并发访问。
禁止和激活中断
禁止当前处理器的本地中断,激活中断,
local_irq_disable();
local_irq_enable();
不是简单的激活和禁止,关闭后再关闭,激活后又激活,存在潜在危险。所以提供把中断恢复到以前的状态而不是简单的激活和禁止。
注意:local_irq_save
和 local_irq_restore
调用必须在同一个函数中进行。因为flag
包含了体系结构信息,不能简单的传递。
unsigned long flags;
local_irq_save(flags); /* interrupts are now disabled */
local_irq_restore(flags); /* interrupts are restored to their previous state */
前面的函数既可以在中断中调用,也可以在进程上下文中调用。
禁止指定中断线
只禁止整个系统中的一条特定的中断线,Linux提供四个接口:
void disable_irq(unsigned int irq);
void disable_irq_nosync(unsigned int irq);
void enable_irq(unsigned int irq);
void synchronize_irq(unsigned int irq);
中断系统的状态
获取中断系统的状态,如处于激活还是关闭、是否处于中断上下文等。
宏irqs_disabled()
,如果本地处理器的中断系统被禁止返回非0,否则返回0;
两个宏in_interrupt()
和in_irq()
用于检查内核当前上下文接口,
in_interrupt()
最有用,内核处于任何类型中断程序返回非0,如中断处理程序、下半部程序;
in_irq()
内核确实在执行中断处理程序时才返回非0。
irqs_disabled();
in_interrupt()
in_irq()