LINUX 操作系统 第6章 中断和中断处理程序.ppt
第第6章章 中断和中断处理程序中断和中断处理程序6.1 中断、陷阱和异常中断、陷阱和异常l中断中断(interrupt):设备发出的中断,此类中断的):设备发出的中断,此类中断的发生是发生是无法预测无法预测的,它是的,它是被动被动的。用户空间和系的。用户空间和系统空间都可能发生中断。统空间都可能发生中断。l陷阱陷阱(trap):由软件产生的中断,它是由专设):由软件产生的中断,它是由专设的指令,如的指令,如X86的的“int n”在程序中在程序中有意设置有意设置的,的,他是他是主动主动的。陷阱只发生在用户空间。的。陷阱只发生在用户空间。l异常异常(exception):由于违规操作产生的,如除):由于违规操作产生的,如除法操作法操作DIV的除数为的除数为0。多半是不小心,。多半是不小心,而而不是不是故意故意的,也是的,也是被动被动的。的。l系统调用,通过系统调用,通过int 0 x80实现,同实现,同“陷阱陷阱”。2中断、陷阱和异常中断、陷阱和异常l共同点共同点:CPU对它们的响应过程基本一致。即:对它们的响应过程基本一致。即:在执行完当前指令后,或者在执行当前指令的中在执行完当前指令后,或者在执行当前指令的中途,就途,就根据根据中断源所提供的中断源所提供的“中断向量中断向量”,在内,在内存中存中找到相应的服务程序的入口地址找到相应的服务程序的入口地址,并,并调用该调用该服务程序服务程序。l“中断中断”的向量是由软件或硬件设置好了的;陷的向量是由软件或硬件设置好了的;陷阱的向量是在阱的向量是在“自陷自陷”指令中发出的指令中发出的(int n中的中的n);而各种异常的向量则是而各种异常的向量则是CPU的硬件结构中预先规的硬件结构中预先规定好的。定好的。l因此,在实践中常常将它们作为统一的模式加以因此,在实践中常常将它们作为统一的模式加以考虑和实现,而且常常统称为考虑和实现,而且常常统称为“中断中断”。3中断中断l中断使硬件得以与处理器进行通信。中断使硬件得以与处理器进行通信。l中断随时可能发生,因此内核随时可能因中断随时可能发生,因此内核随时可能因为新到来的中断而被打断。为新到来的中断而被打断。l不同的设备对应的中断不同,不同的设备对应的中断不同,每个中断都每个中断都通过一个唯一的数字标识通过一个唯一的数字标识,从而使得操作,从而使得操作系统能够对中断进行区分。系统能够对中断进行区分。l这些中断值通常被称为中断请求这些中断值通常被称为中断请求(IRQ)线,线,通常通常IRQ都是一些数值量。都是一些数值量。46.2 中断处理程序中断处理程序l在响应一个特定中断的时候,内核会执行在响应一个特定中断的时候,内核会执行一个函数一个函数中断处理程序中断处理程序(或叫(或叫中断服中断服务例程务例程)。)。l一个设备的中断处理程序是它的设备驱动一个设备的中断处理程序是它的设备驱动程序的一部分。程序的一部分。l中断处理程序与其他内核函数的真正区别:中断处理程序与其他内核函数的真正区别:中断处理程序是被内核调用来响应中断的,中断处理程序是被内核调用来响应中断的,它们运行于中断上下文中它们运行于中断上下文中。5l中断随时可能发生,因此中断处理程序也中断随时可能发生,因此中断处理程序也就随时可能执行。所以就随时可能执行。所以必须保证中断处理必须保证中断处理程序能够快速执行程序能够快速执行,这样才能保证尽可能,这样才能保证尽可能快地恢复中断代码的执行。快地恢复中断代码的执行。l因此,尽管对硬件而言,迅速对其中断进因此,尽管对硬件而言,迅速对其中断进行服务非常重要;但对系统的其它部分而行服务非常重要;但对系统的其它部分而言,让中断处理程序在尽可能短的时间内言,让中断处理程序在尽可能短的时间内完成运行也同样重要。完成运行也同样重要。6上半部与下半部上半部与下半部l对中断处理程序而言,既想它运行得快,对中断处理程序而言,既想它运行得快,又想它完成的工作量多,鉴于这两个目的又想它完成的工作量多,鉴于这两个目的之间的矛盾,一般把中断处理分为两个部之间的矛盾,一般把中断处理分为两个部分:分:n上半部上半部:中断处理程序,接收到一个中断,它:中断处理程序,接收到一个中断,它就立即开始执行,但只做有严格时限的工作。就立即开始执行,但只做有严格时限的工作。这些工作都是在所有中断被禁止的情况下完成这些工作都是在所有中断被禁止的情况下完成的。的。n下半部下半部:能够被允许稍后执行的工作推迟到下:能够被允许稍后执行的工作推迟到下半部执行。下半部在开中断的情况下执行。半部执行。下半部在开中断的情况下执行。7上半部和下半部分开的例子上半部和下半部分开的例子l例:网卡接收数据包例:网卡接收数据包l应在上半部完成的工作:应在上半部完成的工作:应答硬件,拷贝应答硬件,拷贝最新的网络数据包到内存,读取网卡上更最新的网络数据包到内存,读取网卡上更多的数据包。多的数据包。l可以在下半部完成的工作:可以在下半部完成的工作:处理和操作数处理和操作数据包。据包。86.3 注册中断处理程序注册中断处理程序l中断处理程序是驱动程序的组成部分。中断处理程序是驱动程序的组成部分。l驱动程序通过以下函数注册并激活一个中驱动程序通过以下函数注册并激活一个中断处理程序。断处理程序。int request_irq(unsigned int irq,irqreturn_t(*handler)(int,void*,struct pt_regs*),unsigned long irqflags,const char*devname,void*dev_id)9request_irq()的参数的参数l第第1个参数个参数irq:要分配的中断号。:要分配的中断号。l第第2个参数个参数handler:一个指针,指向处理这个中:一个指针,指向处理这个中断的实际中断处理程序。断的实际中断处理程序。l第第3个参数个参数irqflags:可以为:可以为0,也可以是,也可以是SA_INTERRUPT,SA_SAMPLE_ RANDOM,SA_SHIRQ其中的一个或多个标志的位掩码。其中的一个或多个标志的位掩码。l第第4个参数个参数devname:与中断相关的设备的:与中断相关的设备的ASCII文本表示法。文本表示法。l第第5个参数个参数dev_id:主要用于共享中断线。:主要用于共享中断线。10request_irq()的返回值的返回值l若成功执行,返回若成功执行,返回0;如果返回非如果返回非0值,表示有错误发生,在这值,表示有错误发生,在这种情况下,指定的中断处理程序不会被注种情况下,指定的中断处理程序不会被注册。册。l注意:注意:request_irq()函数可能会睡眠,因函数可能会睡眠,因此,不能在中断上下文或其它不允许阻塞此,不能在中断上下文或其它不允许阻塞的代码中调用该函数。的代码中调用该函数。11使用使用request_irq()函数函数if(request_irq(irqn,my_interrupt,SA_SHIRQ,“my_device”,dev)printk(KERN_ERR“my_device:cannot register IRQ%d,n”,irqn);return EIO;12释放中断处理程序释放中断处理程序l卸载驱动程序时,需要注销相应的中断处卸载驱动程序时,需要注销相应的中断处理程序,并释放中断线。理程序,并释放中断线。l可以调用可以调用void free_irq(unsigned int irq,void*dev_id)来释放中断线。来释放中断线。l如果指定的中断线不是共享的,那么,该函数删如果指定的中断线不是共享的,那么,该函数删除处理程序的同时将禁用这条中断线。除处理程序的同时将禁用这条中断线。如果中断线是共享的,则仅删除如果中断线是共享的,则仅删除dev_id所对应的所对应的处理程序,而这条中断线本身只有在删除了最后处理程序,而这条中断线本身只有在删除了最后一个处理程序时才会被禁用。一个处理程序时才会被禁用。136.4 编写中断处理程序编写中断处理程序l典型的中断处理程序声明:典型的中断处理程序声明:static irqreturn_t intr_handler(int irq,void*dev_id,struct pt_regs*regs)l其返回值为其返回值为irqreturn_t型,实际上是型,实际上是int型。型。l中断处理程序可能返回两个特殊的值:中断处理程序可能返回两个特殊的值:nIRQ_NONE:当中断处理程序检测到一个中断,但该:当中断处理程序检测到一个中断,但该中断对应的设备并不是在注册处理函数期间指定的产中断对应的设备并不是在注册处理函数期间指定的产生源时,返回该值;生源时,返回该值;nIRQ_HANDLED:当中断处理程序被正确调用,且确:当中断处理程序被正确调用,且确实是它所对应的设备产生了中断时,返回该值。实是它所对应的设备产生了中断时,返回该值。14l在在include/linux/interrupt.h中有如下几行,中有如下几行,定义了中断处理程序的返回值:定义了中断处理程序的返回值:typedef int irqreturn_t;#define IRQ_NONE(0)#define IRQ_HANDLED(1)#define IRQ_RETVAL(x)(x)!=0)15典型中断处理程序的参数说明典型中断处理程序的参数说明lirq:是这个处理程序要响应的中断的中断线号。:是这个处理程序要响应的中断的中断线号。现在,该参数已没有太大用处。现在,该参数已没有太大用处。ldev_id:一个通用指针,它与传递给:一个通用指针,它与传递给request_irq()的参数的参数dev_id必须一致。如果该值有唯一确定必须一致。如果该值有唯一确定性,那么它就相当于一个性,那么它就相当于一个cookie,可以用来区分,可以用来区分共享同一中断处理程序的多个设备。共享同一中断处理程序的多个设备。lregs:一个指向结构的指针,该结构包含处理中:一个指向结构的指针,该结构包含处理中断之前处理器的寄存器和状态。除了调试,很少断之前处理器的寄存器和状态。除了调试,很少用到它。用到它。16中断处理程序和重入中断处理程序和重入l当一个给定的中断处理程序正在执行时,当一个给定的中断处理程序正在执行时,相应的中断线在所有处理器上都会被屏蔽相应的中断线在所有处理器上都会被屏蔽掉,以防止在同一中断线上接收到另一个掉,以防止在同一中断线上接收到另一个新的中断。因此,新的中断。因此,同一个中断处理程序绝同一个中断处理程序绝对不会被同时调用以处理嵌套的中断对不会被同时调用以处理嵌套的中断。l所以,所以,Linux的中断处理程序是的中断处理程序是无需重入无需重入的。的。这极大地简化了中断处理程序的编写。这极大地简化了中断处理程序的编写。176.4.1 共享的中断处理程序共享的中断处理程序l共享的处理程序与非共享的处理程序在注共享的处理程序与非共享的处理程序在注册和运行方式上比较相似,但差异主要有册和运行方式上比较相似,但差异主要有三点:三点:nrequest_irq()的参数的参数irqflags必须设置必须设置SA_SHIRQ标志标志。n对每个注册的中断处理程序来说,对每个注册的中断处理程序来说,dev_id参数参数必须唯一必须唯一。指向任一设备结构的指针就可以满。指向任一设备结构的指针就可以满足这一要求。足这一要求。n中断处理程序中断处理程序必须能够区分它的设备是否真的必须能够区分它的设备是否真的产生了中断产生了中断。186.4.2 中断处理程序实例中断处理程序实例l例:来自例:来自RTC(real_time clock)驱动程序的驱动程序的一个实际的中断处理程序,它在一个实际的中断处理程序,它在drivers/char/rtc.c中定义。中定义。l在在RTC驱动程序装载时,会调用驱动程序装载时,会调用rtc_init()函数,进行初始化。函数,进行初始化。l在在rtc_init()函数中,将注册中断处理程序,函数中,将注册中断处理程序,代码见下页:代码见下页:19l其中的第其中的第2个参数是中断处理程序个参数是中断处理程序rtc_interrupt,其代码见下页。,其代码见下页。if(request_irq(rtc_irq,rtc_interrupt,SA_INTERRUPT,rtc,(void*)&rtc_port)printk(KERN_ERR rtc:cannot register IRQ%dn,rtc_irq);return-EIO;20中断处理程序中断处理程序rtc_interrupt()static unsigned long rtc_irq_data=0;irqreturn_t rtc_interrupt(int irq,void*dev_id,struct pt_regs*regs)spin_lock(&rtc_lock);rtc_irq_data+=0 x100;rtc_irq_data&=0 xff;rtc_irq_data|=(CMOS_READ(RTC_INTR_FLAGS)&0 xF0);if(rtc_status&RTC_TIMER_ON)mod_timer(&rtc_irq_timer,jiffies+HZ/rtc_freq+2*HZ/100);21rtc_interrupt()(续续)spin_unlock(&rtc_lock);/*Now do the rest of the actions*/spin_lock(&rtc_task_lock);if(rtc_callback)rtc_callback-func(rtc_callback-private_data);spin_unlock(&rtc_task_lock);wake_up_interruptible(&rtc_wait);kill_fasync(&rtc_async_queue,SIGIO,POLL_IN);return IRQ_HANDLED;226.5 中断上下文中断上下文l当执行一个中断处理程序或下半部时,内当执行一个中断处理程序或下半部时,内核处于中断上下文中。核处于中断上下文中。l中断上下文不能睡眠,因此不能从中断上中断上下文不能睡眠,因此不能从中断上下文中调用某些会睡眠的函数。下文中调用某些会睡眠的函数。236.6 中断处理机制的实现中断处理机制的实现l中断处理系统的实现依赖于体系结构。依中断处理系统的实现依赖于体系结构。依赖于处理器、所使用的中断控制器的类型、赖于处理器、所使用的中断控制器的类型、体系结构的设计及机器本身。体系结构的设计及机器本身。l中断从硬件到内核的路由见下页图。中断从硬件到内核的路由见下页图。24中断从硬件到内核的路由中断从硬件到内核的路由中断控制器中断控制器处理器处理器处理器处理器中断内核中断内核do_IRQ()该线上是否有该线上是否有中断处理程序中断处理程序handle_IRQ_event()在该线上运行所在该线上运行所有中断处理程序有中断处理程序返回内核运行返回内核运行中断的代码中断的代码ret_from_intr()是是否否硬件硬件产生一产生一个中断个中断25l在内核中,中断的旅程开始于预定义入口在内核中,中断的旅程开始于预定义入口点,对于每条中断线,处理器都会跳到对点,对于每条中断线,处理器都会跳到对应的一个唯一位置。这样,内核就可以知应的一个唯一位置。这样,内核就可以知道所接收中断的道所接收中断的IRQ号了。号了。l然后,内核调用然后,内核调用do_IRQ()函数。函数。26do_IRQ()函数函数l通过通过orig_eax读出堆栈中的值,并将高位屏读出堆栈中的值,并将高位屏蔽掉,得到蔽掉,得到IRQ号。号。unsigned int do_IRQ(struct pt_regs*regs)int irq=regs-orig_eax&0 xff;return 1;27补充:补充:pt_regs结构结构struct pt_regs long ebx;long ecx;long edx;long esi;long edi;long ebp;long eax;int xds;int xes;long orig_eax;long eip;int xcs;long eflags;long esp;int xss;28do_IRQ()handle_IRQ_event()int handle_IRQ_event(unsigned int irq,struct pt_regs*regs,struct irqaction*action)int status=1;int retval=0;if(!(action-flags&SA_INTERRUPT)local_irq_enable();do status|=action-flags;retval|=action-handler(irq,action-dev_id,regs);action=action-next;while(action);if(status&SA_SAMPLE_RANDOM)add_interrupt_randomness(irq);local_irq_disable();return status;29补充:补充:irqaction数据结构数据结构struct irqaction irqreturn_t(*handler)(int,void*,struct pt_regs*);unsigned long flags;cpumask_t mask;const char*name;void*dev_id;struct irqaction*next;int irq;struct proc_dir_entry*dir;30/proc/interruptslprocfs是一个虚拟文件系统,它只存在于内是一个虚拟文件系统,它只存在于内核内存,一般安装于核内存,一般安装于/proc目录下。目录下。l/proc/interrupts文件:存放系统中与中文件:存放系统中与中断相关的统计信息。见下页。断相关的统计信息。见下页。31/proc/interrupts文件的内容文件的内容326.7 中断控制中断控制lLinux内核提供了一组接口用于操作机器上内核提供了一组接口用于操作机器上的中断状态。这些接口为我们提供了能够的中断状态。这些接口为我们提供了能够禁止当前处理器的中断系统、或屏蔽掉整禁止当前处理器的中断系统、或屏蔽掉整个机器的一条中断线的能力。个机器的一条中断线的能力。336.7.1 禁止和激活中断禁止和激活中断l用于禁止当前处理器上的本地中断,随后用于禁止当前处理器上的本地中断,随后又激活它们的语句为:又激活它们的语句为:local_irq_disable();/*禁止中断禁止中断*/local_irq_enable();l这两个函数通常以单个汇编指令实现,其这两个函数通常以单个汇编指令实现,其代码在代码在include/asm-i386/system.h中,见下中,见下页:页:34l如果在调用如果在调用local_irq_disable()之前已经禁之前已经禁止了中断,那么该例程往往会带来潜在的止了中断,那么该例程往往会带来潜在的危险。同样危险。同样local_irq_enable()也存在潜在也存在潜在危险。危险。l因此因此需要一种机制把中断恢复到以前的状需要一种机制把中断恢复到以前的状态而不是简单地禁止或激活态而不是简单地禁止或激活。#define local_irq_disable()_asm_ _volatile_(cli:memory)#define local_irq_enable()_asm_ _volatile_(sti:memory)35l在禁止中断之前应该保存中断系统的状态;在禁止中断之前应该保存中断系统的状态;而在准备激活中断时,只需要把中断恢复而在准备激活中断时,只需要把中断恢复到它们原来的状态。如下所示:到它们原来的状态。如下所示:unsigned long flags;local_irq_save(flags);/*/local_irq_restore(flags);l对对local_irq_save()的调用和对的调用和对local_irq_restore()的调用必须在同一个函数中进行。的调用必须在同一个函数中进行。366.7.2 禁止指定中断线禁止指定中断线l在某些情况下,只禁止整个系统中一条特在某些情况下,只禁止整个系统中一条特定的中断线就够了。这就是所谓的定的中断线就够了。这就是所谓的屏蔽掉屏蔽掉一条中断线。一条中断线。lLinux提供了四个接口:提供了四个接口:nvoid disable_irq(unsigned int irq);nvoid disable_irq_nosync(unsigned int irq);nvoid enable_irq(unsigned int irq);nvoid synchronize_irq(unsigned int irq);376.7.3 中断系统的状态中断系统的状态l宏宏irqs_disabled():如果本地处理器上的:如果本地处理器上的中断系统被禁止,则它返回非中断系统被禁止,则它返回非0,否则返回,否则返回0。l宏宏in_interrupt():如果内核处于中断上下:如果内核处于中断上下文中,返回非文中,返回非0。说明内核此时正在执行中。说明内核此时正在执行中断处理程序,或者正在执行下半部处理程断处理程序,或者正在执行下半部处理程序。序。l宏宏in_irq():只有在内核确实正在执行中断:只有在内核确实正在执行中断处理程序时才返回非处理程序时才返回非0。386.8 下半部简介下半部简介l中断处理程序是内核中必不可少的,但本身存在中断处理程序是内核中必不可少的,但本身存在一些局限:一些局限:n它以异步方式执行并且有可能会打断其他重要代码的它以异步方式执行并且有可能会打断其他重要代码的执行,因此,应执行得越快越好。执行,因此,应执行得越快越好。n它在执行时,通常会禁止中断,因此,应执行得越快它在执行时,通常会禁止中断,因此,应执行得越快越好。越好。n它往往需要对硬件进行操作,所以通常有很高的时限它往往需要对硬件进行操作,所以通常有很高的时限要求。要求。n它不在进程上下文中运行,所以不能阻塞。它不在进程上下文中运行,所以不能阻塞。l所以,中断处理程序只能完成整个中断处理流程所以,中断处理程序只能完成整个中断处理流程的上半部分。的上半部分。39下半部下半部l下半部的任务就是执行与中断处理密切相下半部的任务就是执行与中断处理密切相关但中断处理程序本身不执行的工作。关但中断处理程序本身不执行的工作。l下半部并不需要指明一个确切时间,只要下半部并不需要指明一个确切时间,只要把这些任务推迟一点,让它们在系统不太把这些任务推迟一点,让它们在系统不太繁忙并且中断恢复后执行就可以了。通常,繁忙并且中断恢复后执行就可以了。通常,下半部在中断处理程序一返回就会马上运下半部在中断处理程序一返回就会马上运行。行。40下半部的实现机制下半部的实现机制l在在2.6版中,内核提供了三种不同形式的下版中,内核提供了三种不同形式的下半部实现机制:半部实现机制:n软中断软中断ntaskletn工作队列工作队列41