第6章 中断和中断处理程序
6.1 中断
1)物理学角度,中断是一种电信号,由硬件生产,并直接送入中断控制器的输入引脚上,然后再由中断控制器向处理器发送相应的信号。处理器检测到此信号,便中断当前工作转而处理中断。此后,处理器通知操作系统已经产生中断,操作系统对这个中断进行适当的处理。
2)中断使得硬件得以与处理器通信
3)不同设备对应的中断不同,每个中断都有一个唯一的数字标识
4)异常:它在产生时必须考虑与处理器时钟同步,称为同步中断
6.2 中断处理程序
1)响应中断时,内核会执行一个函数,该函数叫做中断处理函数
2)它是驱动程序的一部分
3)和其他内核函数的区别:中断处理程序是被内核调用来响应中断的,运行与中断上下文。
4)上半部和下半部:中断处理程序就是上半部,做有严格实现的工作
6.3 注册中断处理程序
1)int
request_irq(unsigned int irq, //要分配的中断号
irqreturn_t (*handler)(int, void *, struct
pt_regs *), //指向处理这个中断的实际中断处理程序
unsigned long irflags, //SA_INTERRUPT,SA_SAMPLE_RANDOM,SA_SHIRQ
const char *devname, //与中断相关的设备的ASCII文本表示法
void *dev_id);//主要用于共享中断线
2)释放中断处理程序
Void free_irq(unsigned int irq, void *dev_id);
该函数注销相应中断处理程序,并释放中短线。如果指定中断线不是共享的,该函数删除处理程序的同时将禁止这条中断线。如果是共享的,则仅删除dev_id所对应的处理程序,而这条中断线本身只有在删除最后一个处理程序时才会被解禁。该函数必须从进程上下文中调用
6.4 编写中断处理程序
Static irqreturn_t intr_handler(int irq, void *dev_id,
struct pt_regs *regs);
Irq:处理程序要响应的中断的中断号,只在打印日志信息时会用
Dev_id:通用指针,它与在中断处理程序注册时传递给request_irq()的参数dev_id必须一致。如果该函数有唯一确定性,那它相当于一个cookie,可以用来区分共享同一个中断处理程序的多个设备。另外dev_id也可能指向中断处理程序使用的一个数据结构。对每一个设备,设备结构都是唯一的,而且可能在中断处理程序也用得到。因此通常会把设备结构传递给dev_id。
Regs:一个指向结构体的指针,该结构包含处理中断之前处理器的寄存器和状态。除了调试,很少使用
返回值:irqreturn_t,可能返回两个特殊的值IRQ_NONE和IRQ_HANDLED.当中断处理程序检查到一个中断,但该中断对应的设备并不是在处理函数期间指定的产生源时,返回IRQ_NONE;当中断处理程序被正确调用,且确实是它所对应的设备产生的中断时,返回IRQ_HANDLED。
中断处理程序扮演的角色取决于产生中断的设备和该设备为什么发生中断
3)重入和中断处理函数
中断程序正在执行时,相应的中断线在所有处理器上都会被屏蔽,以防止在同一个中断线上接收另一个新的中断。
通常情况下,所有其他的中断都是打开的,所以这些不同中断线上的其他中断都能被处理,但当前中断总是被禁止。
同一个中断处理程序绝对不会被同时调用以处理嵌套的中断。
6.4.1 共享的中断处理程序
共享与非共享
相同点:注册和运行方式比较相似
区别点:
A) request_irq()的参数flags必须设置SA_SHIRQ
B) 对每个注册的中断处理程序来说,dev_id参数必须是唯一的。指向任一设备结构的指针就可以满足这一要求;通常会选择设备结构,因为它是唯一的,而且中断处理程序可能会用到它。不能给共享的处理程序传递NULL
C) 中断处理程序必须能够区分它的设备释放真的产生了中断。这既需要硬件的支持,也需要处理程序中有相关的处理逻辑。如果硬件不支持这一功能,那么中断处理程序肯定就束手无策,它根本没法知道到底是与它所对应的设备发出了这个中断,还是共享这条中断的其他设备发送了这个中断
所有共享中断线的驱动程序必须满是以上要求。只要任何一个设备没有按照规则进行共享,那么中断线就无法共享。指定SA_SHIRQ标志以调用request_irq()时,只要以下两种情况才可能成功:中断线当前未被注册,或在该线上的所有已注册处理程序都指定了SA_SHIRQ。
内核接收到一个中断后,它将依次调用在该中断线上注册的每一个处理程序。因此一个处理程序必须知道它是否应该为这个中断负责。如果与它相关的设备并没有产生中断,那么处理程序应该立即退出。这需要硬件设备提供状态寄存器,以便中断处理程序进行检查。
6.4.2 中断处理程序实例
6.5 中断上下文
1)进程上下文:一种内核所处的操作模式,此时内核代表进程执行,可以通过current宏关联当前进程。进程上下文可以睡眠,也可以调用调度程序
2)中断上下文:不可以睡眠,否则又怎能再对它重新调度,如果一个函数睡眠,就不能在中断处理程序中使用它,其具有严格的时间限制
3)中断处理程序栈的设置是一个配置选项。曾经,中断程序共享中断进程的内核栈。
4)内核栈的大小是两页。
5)2.6早期的内核中,增加了一个选项,把栈的大小从两页减到一页。
6)系统中每个进程原先都需要两页不可换出的内核内存,为了应对栈 减少,中断处理程序拥有了自己的栈,每个处理器一个,大小一页。这个就是中断栈。
7)中断栈的大小是原先共享栈的一般,但平均可用栈空间大得多,因为中断处理程序拥有这一整夜
6.6 中断处理机制的实现
1)其依赖体系结构(处理器、所使用的中断控制器的类型、体系结构的设计及机器本身)
2)设备产生中断,通过总线把电信号发送给中断控制器。如果中断线是激活的(允许被屏蔽),那么中断控制器就会把中断发往处理器。
3)中断控制器通过电信号给处理器的特定管脚发送一个信号,如果处理没有禁止该中断,则处理器会立即停止它正在做的事,关闭中断系统,然后跳到内存中预定义的位置开始执行那里的代码。这个预定义的位置是由内核设置的,是中断处理程序的入口处。
4)在内核中,中断开始于预定义入口点,这类似于系统调用通过预定义的异常句柄进入内核。
5)每条中断线,处理器都会跳到对应的一个唯一的位置,这样内核就可以知道所接收中断的IRQ号了。
6)初始入口点只是在栈中保存这个号,并存放当前寄存器的值
7)然后,内核调用函数do_IRQ()。从这里开始,大多数中断处理代码是用c写的,它们依然与体系结构youguan
8)unsigned int do_IRQ(struct pt_regs regs)
9)中断从硬件到内核的路由
硬件------产生一个中断—》中断控制器—》处理器—》处理器中断内核—》do_IRQ()—》该线上是否有中断处理程序----》
A)是—》handle_IRQ_even()—》在该线上允许所有中断处理程序 –》ret_from_intr()—》返回内核运行中断的代码
B)否—》ret_from_intr()—》返回内核运行中断的代码
。。。。。。
10)/proc/interrupts
1)procfs是一个虚拟文件系统,只能存放在内核内存,一般安装在/procMULXIA .
- 调用内核函数读写procfs中的文件,这些函数模拟从真实文件中读或写
3)中断线,接收中断数目的计数器,中断控制器,中断相关的设备名字
6.7 中断控制
1)Linux内核提供一组接口用于操作机器上的中断状态,这些接口提供了能够禁止当前处理器的中断系统,或屏蔽整个机器的一条中断线的能力。
2)<asm/system.h>和<asm/irq.h>
3)控制中断系统的原因是需要提供同步
4)禁止中断可以确保某个中断处理程序不会抢占当前的代码,还可以禁止内核抢占
5)禁止中断和禁止内核抢占都没有提供任何保护机制来防止来自其他处理器的并发访问
6)锁能提供保护机制,防止来自其他处理器的并发访问,而禁止中断提供的保护机制,则是防止来自其他中断处理程序的并发访问。
7)理解内核中断的控制接口
6.7.1禁止和激活中断
1)禁止当前处理器上的本地中断
local_irq_disable();==》cli指令
local_irq_disable();==》sti指令
存在问题:在调用loca_irq_disable()之前已经禁止中断和调用loca_irq_enable()之前已经开启,将存在潜在问题。loca_irq_enable()将无条件地激活中断,尽管这些中断可能在开始时就已经关闭。
解决方法:需要一种机制把中断恢复到以前的状态,而不是简单地禁止或激活。内核关心这点,因为内核中一个给定的代码路径既可以在中断激活的情况下达到,也可以在中断禁止的情况下达到,这取决于具体的调用链。总之,在禁止中断之前保存中断系统的状态会更加安全一下,相反,在准备激活中断时,只需要把中断恢复到它们原来的状态。
Unsigned long flags;
Local_irq_save(flags);
Loca_irq_restore(flags);
注:必须在同一个函数中被调用,这些函数既可以中断中调用,也可以在进程上下文调用
2)不要使用全局的cli()
A)以前的内核提供了一种“禁止系统中所有处理器上中断的方法”—cli(),相应的激活中断函数为sti(),2.5版本后被取消。
B)所有的中断同步现在必须结合使用本地中断控制和自旋锁,也就是说,为了确保对共享数据的互斥访问,以前的代码仅仅需要通过全局禁止中断达到互斥,而现在则需要多做些工作
3)取消全局cli的优点:
A)强制驱动程序编写者实现真正的加锁,实现细粒度锁
B)代码更具有流线型,避免代码的成簇布局
6.7.2 禁止指定中断线
1)禁止整个处理器上所有中断的函数
2)禁止整个系统中一条特定的中断线,即屏蔽掉(masking out)一条中断线
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);
A) 前两个函数禁止中断控制器上指定的中断线,即禁止给定中断向系统中所有处理器的传递。函数只有在当前执行的所有处理程序完成后,disable_irq()才能返回,调用者不仅要确保不在指定线上传递新的中断,同时还要确保所有已经开始执行的处理程序全部退出。
B) 函数disabled_irq_nosync()不会等待当前处理程序执行完毕。
C) 函数synchronize_irq()等待一个特定的中断处理程序的退出。如果该处理程序正在执行,那么该函数必须退出后才能返回。
D)对这些函数的调用可以嵌套,一条指定的中断线上,对disable_irq()或disable_irq_nosync()的每次调用,都需要相应地调用一次enable_irq()。只有在对enable_irq()完成最后一次调用后,才真正重新激活了中断线。
E) 其中三个函数可以从中断或进程上下文中调用,而且不会睡眠。但如果从中断上下文中调用,要特别小心。
F) 禁止多个中断处理程序共享的中断线是不合适的。禁止中断线也就禁止了这条线上所有设备的中断传递。因此用于新设备的驱动程序应该倾向于不使用这些接口。
G)根据规范,PCI设备必须支持中断线共享,因此,它们根本不需要使用这些接口。Disable_irq()及相关函数在老式设备的驱动程序中更容易被找到
6.7.3 中断系统的状态
1)Irqs_disable() 定义在<asm/system.h>.
如果本地处理器上的中断系统被禁止,则返回非0,否则,返回0
2)宏in_interrupt()和in_irq()
<asm/hardirq.h>
A)第一个宏最有用:如果内核处于中断上下文中,它返回非0.说明内核此刻正在执行中断处理程序,或者正在执行下半部处理程序。
B)宏in_irq()只有在内核确实正在执行中断处理程序时才返回非0.
C)通常情况下,要检查自己是否处于进程上下文。也就是说,确保自己不在中断上下文。因为代码要做一些像睡眠这样只能从中断上下文中做的事。如果in_interrupt()返回0,则此刻内核处于进程上下文
3)中断控制方法
1.local_irq_disable() 禁止本地中断传递
2.local_irq_enable() 激活本地中断传递
3.local_irq_save() 保存本地中断传递的当前状态,然后禁止本地中断处理
4.local_irq_restore() 恢复本地中断传递到给定的状态
5.disable_irq() 禁止给定中断线,并确保该函数返回之前在该中断线上没有处理程序在运行
6.disable_irq_nosync() 禁止给定中断线
7.enable_irq() 激活给定中断线
8.irqs_disable() 如果本地中断传递被禁止,则返回非0,否则返回0
9.in_interrupt() 如果在中断上下文,则返回非0,如果在中断上下文中,则返回0
10.in_irq() 如果正在执行中断处理程序,则返回非0,否则,返回0
6.8 总结
1)中断是一种由设备使用的硬件资源异步地向处理器发信号
2)中断就是由硬件打算操作系统
3)大多数现代硬件都通过中断与操作系统通信
4)对给定硬件进行管理的驱动程序注册中断处理程序,是为了响应并处理来自硬件相关的中断
5)中断过程所做的 工作包括应答并重新设置硬件、从设备拷贝数据到内存以及反之,处理硬件请求,并发送新的硬件请求
6)内核提供的接口包括注册和注销中断处理程序,禁止中断,屏蔽中断线,以及检测中断系统的状态
7)中断打断了其他代码的执行(进程,内核本身,其他中断处理程序),它们必须尽快执行完。
8)为了在大量工作与必须快速执行之间求得一种平衡,内核把处理中断的工作分成两半。中断处理程序就是上半部。