精华内容
下载资源
问答
  • 第7章中断处理程序
    千次阅读
    2018-09-17 20:40:11

    7.2 中断处理程序

    在响应一个特定中断的时候,内核会执行一个函数,该函数叫做中断处理程序或中断服务例程。产生中断的每个设备都有一个相应的中断处理程序。例如,由一个函数专门处理来自系统时钟的中断,而另外一个函数专门处理由键盘产生的中断。一个设备的中断处理程序是它设备驱动程序的一部分——设备驱动程序是用于对设备进行管理的内核代码。

    在Linux中,中断处理程序是普通的C函数。只不过这些函数必须按照特定的类型声明,以便内核能够以标准的方式传递处理程序的信息,在其他方面,它们与一般的函数没有差别。中断处理程序与其他内核函数的真正区别在于,中断处理程序是被内核调用来响应中断的,而它们运行于称之为中断上下文的特殊上下文中。需要指出的是,中断上下文偶尔也称为原子上下文,该上下文中的执行代码不可阻塞。

    中断可能随时发生,因此中断处理程序随时可能执行。所以必须保证中断处理程序能够快速执行,才能保证尽可能快地恢复中断代码的执行。因此,尽管对硬件而言,操作系统能迅速对其中断进行服务非常重要;当然对系统的其他部分而言,让中断处理程序在尽可能短的时间内完成运行也同样重要。

    最起码,中断处理程序要负责通知硬件设备中断已被接收,但是中断处理程序还要完成大量其他的工具。例如,可以考虑一下网络设备的中断处理程序面临的挑战。该处理程序除了要对硬件应答,还要把来自硬件的网络数据包拷贝到内存,对其进行处理后再交给合适的协议栈或应用程序。

    7.3 上半部与下半部的对比

    又想中断处理程序运行得快,又想中断处理程序完成的工作量多,这两个目的显然有所抵触。鉴于两个目的之间存在此消彼长的矛盾关系,一般把中断处理切为两个部分或两半。中断处理程序是上半部——接收到一个中断,它就立即开始执行,但只做有严格时限的工作,例如对接收的中断进行应答或复位硬件,这些工作都是在所有中断被禁止的情况下完成的。能够被允许稍后完成的工作会推迟到下半部去。在合适的时机,下半部会被开中断执行。Linux提供了实现下半部的各种机制。

    考察一下上半部和下半部分割的例子,以网卡作为实例。当网卡接收来自网络的数据包时,需要通知内核数据包到了。网卡需要立即完成这件事,从而优化网络的吞吐量和传输周期,以避免超时。因此,网卡立即发出中断,有最新数据包了。内核通过执行网卡已注册的中断处理程序来做出应答。

    中断开始执行,通知硬件,拷贝最新的网络数据包到内存,然后读取网卡更多的数据包。这些都是重要、紧迫而又与硬件相关的工作。内核通常需要快速的拷贝网络数据包到系统内存,因为网卡上接收网络数据包的缓存大小固定,而且相比系统内存也要小得多。所以上述拷贝动作一旦被延迟,必然造成缓存溢出——进入的网络包占满了网卡的缓存,后续的入包只能被丢弃。当网络数据包被拷贝到系统内存后,中断的任务算是完成了,这时它将控制权交还给系统被中断前原先运行的程序。处理和操作数据包的其他工作在随后的下半部中进行。

    7.4 注册中断处理程序

    中断处理程序是管理硬件的驱动程序的组成部分。每一设备都有相关的驱动程序,如果设备使用中断,那么相应的驱动程序就注册一个中断处理程序。

    驱动程序可以通过request_irq()函数注册一个中断处理程序,声明在文件linux/interrupt.h中,并且激活给定的中断线,以处理中断:

    int  request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,
                const char *name, void *dev)

    第一个参数irq表示要分配的中断号。对某些设备,如传统PC设备上的系统时钟或键盘,这个值通常是预先确定的。而对于大多数其他设备来说,这个值要么是可以通过探测获取,要么可以通过编程动态确定。

    第二个参数handler是一个指针,指向处理这个中断的实际中断处理程序。只要操作系统一接收到中断,该函数就被调用。

    typedef irqreturn_t (*irq_handler_t)(int, void *);

    注意handler函数的原型,它接受两个参数,并有一个类型为irqreturn_t 的返回值。

    7.4.1 中断处理程序标志

    第三个参数flags可以为0,也可能是下列一个或多个标志的位掩码。其定义在文件linux/interrupt.h。在这些标志中最重要的是:

    IRQF_DISABLED——该标志被设置后,意味着内核在处理中断处理程序本身期间,要禁止所有的其他中断。如果不设置,中断处理程序可以与除本身外的其他任何中断同时运行。多数中断处理程序是不会去设置该位的,因为禁止所有中断是一种野蛮行为。这种用法留给希望快速执行的轻量级中断。这一标志是SA_INTERRUPT标志的当前表现形式,在过去的中断用以区分快速和慢速中断。

    IRQF_TIMER——该标志是特别为系统定时器的中断处理而准备的。

    IRQF_SHARED——此标志标明可以在多个中断处理程序之间共享中断线。在同一个给定线上注册的每个处理程序必须指定这个标志;否则,在每条线上只能有一个处理程序。

    第四个参数name是与中断相关的设备的ASCII文本表示。例如,PC机上键盘中断对应的这个值为“keyboard”。这些名字会被/proc/irq和/proc/interrupts文件使用,以便与用户通信。

    第五个参数dev用于共享中断线。当一个中断处理程序需要释放时,dev将提供唯一的标志信息,以便从共享中断线的诸多中断处理程序中删除指定的那一个。如果没有这个参数,那么内核不可能知道在给定的中断线上到底要删除哪一个处理程序。如果无须共享中断线,那么将该参数赋值为NULL就可以了,但是,如果中断线是被共享的,那么就必须传递唯一的信息。另外,内核每次调用中断处理程序时,都会把这个指针传递给它。实践中会通过它传递驱动程序的设备结构:这个指针是唯一的,而且有可能在中断处理程序内被用到。

    request_irq()成功执行会返回0。如果返回非0值,表示有错误发生,在这种情况下,指定的中断处理程序不会被注册。最常见的错误是-EBUSY,它表示给定的中断线已经在使用。

    注意:request_irq()函数可能会睡眠,因此,不能在中断上下文或其他不允许阻塞的代码中调用该函数。在睡眠不安全的上下文中调用request_irq()函数,是一种常见错误。造成这种错误的部分原因是为什么request_irq()会引起阻塞。在注册的过程中,内核需要在/proc/irq文件中创建一个与中断对应的项。函数proc_mkdir()就是用来创建这个新的procfs项的。proc_mkdir()通过调用函数proc_create()对这个新的procfs项进行设置,而proc_create()会调用函数kmalloc()来请求分配内存。备注:函数kmalloc()是可以睡眠的。

    7.4.2 一个中断例子

    在一个驱动程序中请求一个中断线,并在通过request_irq()注册中断处理程序:

    request_irq():

    if(request_irq(irqn, my_interrupt,  IRQF_SHARED, "my_device", my_dev)){

            printk(KERN_ERR "my_device: cannot register IRQ %d\n", irqn);

            return -EIO;

    }

    在这个例子中,irqn是请求的中断号;my_interrupt是中断处理程序;通过标志设置中断号可以共享;设备命名为“my_device”;最后是传递my_dev变量给dev形参。如果请求失败,那么这段代码将打印出一个错误并返回。如果调用返回0,则说明中断处理程序已经成功注册。此后,处理程序就会在响应该中断时被调用。有一点很重要,初始化硬件和注册中断处理程序的顺序必须正确,以防止中断处理程序在设备初始化完成之前就开始执行。

    7.4.3 释放中断处理程序

    卸载驱动程序时,需要注销相应的中断处理程序,并释放中断号。上述动作需要调用:

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

    如果指定的中断号不是共享的,那么,该函数删除处理程序的同时将禁用这个中断号。如果中断号是共享的,则仅删除dev所对应的处理程序,而这个中断号本身只有在删除了最后一个处理程序时才会被禁用。可以看出为什么唯一的dev如此重要。对于共享的中断号,需要一个唯一的信息来区分其上面的多个处理程序,并让free_irq()仅仅删除指定的处理程序。不管在哪种情况下,如果dev非空,它都必须与需要删除的处理程序相匹配。必须从进程上下文中调用free_irq()。

    中断处理函数的注册和注销。

    static inline int __must_check request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,
                const char *name, void *dev)
              {
                     return request_threaded_irq(irq, handler, NULL, flags, name, dev);
              }
              void free_irq(unsigned int, void *);

    更多相关内容
  • 中断指当出现中断信号时,控制系统暂时停止执行当前的程序,转而执行处理新情况的程序,执行完毕 后,再返回原程序继续执行。
  • 中断和中断处理程序1 中断2 中断处理程序3 注册中断处理程序4 编写中断处理程序共享的中断处理程序中断处理程序实例5 中断上下文6 中断处理机制的实现7 中断控制禁止和激活中断禁止指定中断线中断系统的状态 ...

    Linux内核要对连接到计算机上的所有硬件设备进行管理,要与它们进行通信。但是,处理器的速度跟外围硬件设备的速度往往不是一个数量级的,硬件的响应很慢,内核应该在此期间处理其他事物,等到硬件真正完成了请求的操作之后,再回过头来对它进行处理。中断机制让硬件在需要的时候再向内核内核发出信号,内核来处理硬件的请求。

    1 中断

    中断使得硬件得以与处理器进行通信。硬件设备生成中断的时候并不考虑与处理器的时钟同步,换句话说,中断随时都可以产生,因此,内核随时可能因为新到来的中断而被打断。

    从物理学的角度看,中断是一种电信号,由硬件设备生成,并直接送入中断控制器的输入引脚上,然后再由中断控制器向处理器发送相应的信号。处理器一经检测到此信号,便中断自己的当前工作转而处理中断。此后,处理器会通知操作系统已经产生中断,这样,操作系统就可以对这个中断进行适当的处理了。

    不同的设备对应的中断不同,而每个中断都通过一个唯一的数字标识,这些中断值通常被称为中断请求(IRQ)线。特定的中断总是与特定的设备相关联,并且内核要知道这些消息。

    异常

    异常与中断不同,它在产生时必须考虑与处理器时钟同步。异常也常称为同步中断。在处理器执行到由于编程失误而导致的错误指令的时候,或者是在执行期间出现特殊情况(例如缺页),必须靠内核来处理的时候,处理器就会产生一个异常。

    2 中断处理程序

    在响应一个特定中断的时候,内核会执行一个函数,该函数叫做中断处理程序或中断服务例程(interrupt service routine,ISR)。产生中断的每个设备都有一个相应的中断处理程序。中断处理程序通常不是和特定设备关联,而是和特定中断关联的,也就是说,如果一个设备可以产生多种不同的中断,那么该设备就可以对应多个中断处理程序,相应的,该设备的驱动程序也就需要准备多个这样的函数。

    在Linux中,中断处理程序看起来就是普普通通的C函数,只不过这些函数必须按照特定的类型声明,以便内核能够以标准的方式传递程序的信息。中断处理程序与其他内核函数的真正区别在于:中断处理程序是被内核调用来响应中断的,而它们运行在我们称之为中断上下文的特殊上下文中。

    上半部与下半部的对比

    一般把中断处理切为两个部分,中断处理程序是上半部,接受到一个中断,它就立即开始执行,但只做有严格时限的工作,例如对接受的中断进行应答或复位硬件,这些工作都是在所有中断被禁止的情况下完成的。能够被允许稍后完成的工作会被推迟到下半部去。此后,在合适的时机,下半部就开中断执行,Linux提供了实现下半部的各种机制,下一篇文章会讨论,现在我们要记住上半部是中断处理程序,只做有严格时限的工作。

    3 注册中断处理程序

    中断处理程序是驱动程序的组成部分。每一设备都有相关的驱动程序,如果设备使用中断,那么相应的驱动程序就注册一个中断处理程序。

    驱动程序可以通过下面的函数注册并激活一个中断处理程序:

    /* request_irq分配一个中断线 */
    int request_irq(unsigned int irq,
    				irqreturn_t (*handler)(int,void *,struct pt_regs *),
    				unsigned long irqflags,
    				const char * devname,
    				void *dev_id)
    

    第一个参数irq表示要分配的中断号。对某些设备,如传统PC设备上的系统时钟或键盘,这个值通常是预先定死的。而对于大多数其他设备,这个值要么是可以通过探测获取,要么可以通过编程动态确定。

    第二个参数handle是实际的中断处理程序。只要操作系统一接收到该中断,该函数就被调用。

    第三个参数irqflags可以为0,也可能是下列一个或多个标志的位掩码:

    • SA_INTERRUPT:此标志表明给定的中断处理程序是一个快速中断处理程序。在本地处理器上,快速中断处理程序在禁止所有中断的情况下运行
    • SA_SAMPLE_RANDOM:此标志表明这个设备产生的中断对内核熵池有贡献。内核熵池负责提供从各种随机事件导出的真正的随机数。
    • SA_SHIRQ:此标志表明可以在多个中断处理程序之间共享中断线。

    第四个参数devname是给中断相关的设备起的名字。这些名字会被/proc/irq和/proc/Interrupt文件使用,以便与用户通信。

    第五个参数dev_id主要用于共享中断线。如果无需共享中断线,那么将该参数赋值为NULL就可以了,但是,如果中断线是被共享的,那么必须传递唯一的信息,用来区分用的是共享中断线上的那个中断处理程序。实践中往往会通过它传递驱动程序的设备结构。

    request_irq()函数可能会睡眠,因此,不能在中断上下文或其他不允许阻塞的代码中用该函数,至于request_irq()会睡眠,那是在注册的过程中,内核需要在/proc/irq文件创建一个中断对应的项,会调用kmalloc()来请求分配内存,函数kmalloc()是可以睡眠的。

    在一个驱动程序中请求一个中断线,并在通过request_irq()注册中断处理程序:

    if(request_irq(irqn,my_interrupt,SA_SHIRQ,"my_device",dev))
    {
    	printk(KERN_ERR "my_device:connot register IRQ %d \n",irqn);
    	return -RIO;
    }
    

    irqn是请求的中断线,my_interrupt是中断处理程序,中断线可以共享,设备名为"my_device",而且我们通过dev_id传递dev结构体。

    释放中断处理程序

    卸载驱动程序时,需要注销相应的中断处理程序,并释放中断线。可以调用void free_irq(unsigned int irq,void *dev_id)来释放中断线。
    如果指定的中断线不是共享的,那么,该函数删除中断处理程序的同时将禁用这条中断线。如果中断线是共享的,则仅删除dev_id所对应的处理程序,而这条中断线本身只有在删除了最后一个程序程序时才会被禁用。

    4 编写中断处理程序

    以下是一个典型的中断处理程序声明:

    static irqreturn_t intr_handler(int irq,void *dev_id,struct pt_regs *regs);
    

    它的类型与request_irq()参数中handler所要求的参数类型相匹配。第一个参数irq就是这个中断处理程序要响应的中断的中断线号。第二个参数dev_id是用一个通用指针,它与中断处理程序注册时传递给request_irq()的采纳数dev_id必须一致,可以用来区分共享同一个中断处理程序的多个设备,最后一个参数regs是一个指向结构的指针,该结构包含处理中断之前处理器的寄存器和状态。除了调试的时候,它们很少使用到。

    中断处理程序的返回值是一个特殊类型:irqreturn_t。中断处理程序可能返回两个特殊的值,IRQ_NONE和IRQ_HANDLED。当中断处理程序检测到一个中断,但该中断对应的设备并不是在注册处理函数期间指定的产生源时,返回IRQ_NONE;当中断处理程序被正确调用,且确实是它所对应的设备产生的中断,返回IRQ_HANDLED。

    重入和中断处理程序

    Linux中的中断处理程序是无需重入的。当一个给定的中断处理程序在执行时,相应的中断线在所有处理器上都会被屏蔽掉,以防止在同一中断线上接受到另一个新的中断。

    共享的中断处理程序

    共享中断线的处理程序和非共享中断线的处理程序在注册和运行方式上比较相似,但主要有以下差异:

    • request_irq()的flags参数必须设置SA_SHIRQ标志
    • 对每个注册的中断处理程序来说,dev_id必须是唯一的。指向任意设备结构的指针就可以满足这一要求,通常会选择设备结构,因为它是唯一的,而且中断处理程序可能会用到它。不能共享就传NULL
    • 中断处理程序必须能区分它的设备是否真的产生了中断,

    中断处理程序实例

    让我们考察一个实际的中断处理程序,它来自RTC(real_time clock)驱动程序,可以在drivers/char/rtc.c中找到。RTC驱动程序装载时,rtc_init()函数会被调用,对这个驱动程序进行初始化。它的职责之一就是注册中断处理程序:

    /*
    	 * XXX Interrupt pin #7 in Espresso is shared between RTC and
    	 * PCI Slot 2 INTA# (and some INTx# in Slot 1). SA_INTERRUPT here
    	 * is asking for trouble with add-on boards. Change to SA_SHIRQ.
    	 */
    	if (request_irq(rtc_irq, rtc_interrupt, SA_INTERRUPT, "rtc", (void *)&rtc_port)) {
    		/*
    		 * Standard way for sparc to print irq's is to use
    		 * __irq_itoa(). I think for EBus it's ok to use %d.
    		 */
    		printk(KERN_ERR "rtc: cannot register IRQ %d\n", rtc_irq);
    		return -EIO;
    	}
    

    中断线号由rtc_irq指定,rtc_interrupt是我们的中断处理程序,驱动程序的名称为rtc,因为这个设备不允许共享中断线,且处理程序没有用到什么特殊的值,因此给dev_id的值是NULL。

    5 中断上下文

    当执行一个中断处理程序或下半部时,内核处于中断上下文中。进程上下文是一种内核所处的操作模式,此时内核代表进程执行,在进程上下文中,可以通过current宏关联到当前进程,此外,因为进程是以进程上下文的形式连接到内核的,因此,在进程上下文可以睡眠,也可以调用调度程序。

    中断上下文和进程并没什么瓜葛。因为没有进程的背景,所有中断上下文不可睡眠

    中断处理程序栈的设置是一个配置选项。它们共享所属中断进程的内核栈。内核栈的大小是两页。因为这种设置,中断处理程序共享别人的堆栈,所以它们从栈中获取内容非常节省空间。

    6 中断处理机制的实现

    中断处理在linux中的实现非常依赖体系结构,实现依赖于处理器、所使用的中断控制器的类型、体系结构的设计以及机器本身。
    下图是中断从硬件到内核的步骤。
    在这里插入图片描述
    设备产生中断,通过总线把电信号发送给中断控制器。如果中断线是激活的,那么中断控制器就会把中断发往处理器。在大多数体系结构中,这个工作就是通过电信号给处理器的特定管脚发送一个信号。除非在处理器禁止该中断,否则,处理器会立即停止它正在做的事,关闭中断系统,然后跳到中断处理程序的入口点去运行。

    对于每条中断线,处理器都会跳到对应的唯一的位置。这样,内核就可知道所接收中断的IRQ号了。入口点只是在栈中保存这个号,并存当前寄存器的值;然后,内核调用函数do_IRQ()。

    do_IRQ()的声明如下:

    unsigned int do_IRQ(struct pt_regs regs);
    

    因为C的调用惯例是要把函数参数放在栈的顶部,因此pt_regs结构包含原始寄存器的值,这些值是以前在汇编入口例程中保存在栈的的,中断的值也会得到保存,所以,do_IRQ()可以将它提取出来。

    计算出中断号后,do_IRQ()对所接收的中断进行应答,禁止这条线上的中断传递。do_IRQ()需要确保在这条中断线上有一个有效的处理程序,而且这个程序已经启动但是当前没有执行。如果是这样的话,do_IRQ()就调用handle_IRQ_event()来运行为这条中断线所安装的中断处理程序,在kernel/irq/handle.c文件中

    /*
     * Have got an event to handle:
     */
    fastcall int handle_IRQ_event(unsigned int irq, struct pt_regs *regs,
    				struct irqaction *action)
    {
    	int ret, retval = 0, status = 0;
    
    	if (!(action->flags & SA_INTERRUPT))
    		local_irq_enable();
    
    	do {
    		ret = action->handler(irq, action->dev_id, regs);
    		if (ret == IRQ_HANDLED)
    			status |= action->flags;
    		retval |= ret;
    		action = action->next;
    	} while (action);
    
    	if (status & SA_SAMPLE_RANDOM)
    		add_interrupt_randomness(irq);
    	local_irq_disable();
    
    	return retval;
    }
    

    7 中断控制

    Linux内核提供了一组接口用于操作机器上的中断状态。这些接口为我们提供了禁止当前处理器的中断系统,或屏蔽掉整个机器的一条中断线的能力,这些接口是与体系有关的,可以在<asm/system.h>和<asm/irq.h>中找到。

    禁止和激活中断

    用于禁止当前处理器(仅仅是当前处理器)上的本地中断,随后又激活它们的语句是:

    local_irq_disable();
    local_irq_enable();
    

    这两个函数通常以单个汇编指令来实现(取决于体系结构)。实际上,在x86中,local_irq_disable()仅仅是cli指令,而local_irq_enable()只不过是sti指令。

    local_irq_disable是禁止当前处理器上的所有中断,local_irq_enable是开启当前处理器上的所有中断,即使在禁止前有些中断是关闭的,在调用local_irq_enable()后也会被激活。所以我们需要一种机制把中断恢复到以前的状态而不是简单地禁止或激活。在禁止之前保存当前的中断系统,激活时恢复就行。

    unsigned long flags;
    lcoal_irq_save(flags)
    local_irq_restore(flags);
    

    flags必须是unsigned long类型。local_irq_save () 是保存本地中断传递的当前状态,然后禁止本地中断传递。local_irq_restore () 是恢复本地中断到flags的状态。

    禁止指定中断线

    在某些情况下,只禁止整个系统中一条特定的中断线就够了。为此,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);
    

    disable_irq和disable_irq_nosync禁止中断控制器上指定的中断线,即禁止给定中断向系统中所有处理器的传递。我们来看看disable_irq的具体实现:

    /**
     *	disable_irq - disable an irq and wait for completion
     *	@irq: Interrupt to disable
     *
     *	Disable the selected interrupt line.  Enables and disables
     *	are nested.  This functions waits for any pending IRQ
     *	handlers for this interrupt to complete before returning.
     *	If you use this function while holding a resource the IRQ
     *	handler may need you will deadlock.
     *
     *	This function may be called - with care - from IRQ context.
     */
    void disable_irq(unsigned int irq)
    {
    	struct irqdesc *desc = irq_desc + irq;
    
    	disable_irq_nosync(irq);
    	if (desc->action)
    		synchronize_irq(irq);
    }
    

    此函数在返回之前等待中断irq的任何挂起的 中断处理程序完成。disable_irq会阻塞,所以不能在在中断处理函数中使用,如果在中断处理程序中使用,会导致死锁,电脑会死机。

    disable_irq_nosync不会阻塞,会立即返回,可在中断处理函数中使用。

    enable_irq函数的参数是int型变量,代表操作中断对应的中断号

    函数synchronize_irq等待一个特定的中断处理程序的退出,所以该函数也会阻塞。

    这些函数的调用都可以嵌套,但要记住在一条指定的中断线上,对disable_irq()或disable_irq_nosync()的每次调用,都需要相应地调用一次enable_irq()。只有在对enable_irq()完成最后一次调用后,才真正重新激活了中断线。例如,如果disable_irq()被调用了两次,那么直达第二次调用enable_irq()后,才能真正地激活中断线。

    禁止多个中断处理程序共享的中断线是不合适的。禁止中断线也就禁止了这条线上所有设备的中断传递。因此,用于新设备的驱动程序应该尽量不使用这些接口。

    中断系统的状态

    宏irqs_disable()定义在<asm/system.h>中,如果本地处理器上的中断系统被禁止,则它返回非0,否则返回0.
    在<asm/hardirq.h>中定义了的两个宏提供了一个用来检查内核的当前上下文的接口,它们是:

    in_interrupt();
    in_irq();
    

    in_interrupt用来检测内核是否处于中断上下文中,如果是的话,返回非0。说明此刻内核正在执行中断处理程序,或者正在执行下半段处理程序,如果返回0,此时内核处于进程上下文中。宏in_irq只有在内核确实正在执行中断处理程序时才返回非0.

    8 总结

    中断就是由硬件打断操作系统。内核提供的接口包括注册和注销中断处理程序,禁止中断,屏蔽中断线,以及检查中断系统的状态。
    因为中断打断了其他代码的执行(进程,内核本身,甚至其他中断处理程序),它们必须赶快执行完。为了大量的工作与必须快速执行完之间求得平衡,内核把处理中断的工作分为两部分。中断处理程序,也就是上半部,下半部我们还没有讨论。

    展开全文
  • 标志寄存器入栈,置IF=0,TF=0 (为什么要这一步,后面有解释) // 这一步可以模拟为 pushf //标志寄存器入栈 ;下面的步骤完成置IF和TF pushf pop ax and ax,11111100B push ax popf //置IF=0,TF=0 CS和IP入栈 令IP...

    PART1:前言

    int n指令的格式为:int n,n为中断类型码
    cpu执行int中断,实际上就相当于引发一个n号中断的中断过程,他的大致执行过程如下:

    1. 取中断类型n
    2. 标志寄存器入栈,置IF=0,TF=0 (为什么要这一步,后面有解释)
    // 这一步可以模拟为
    pushf 	//标志寄存器入栈
    ;下面的步骤完成置IF和TF
    pushf
    pop ax
    and ax,11111100B
    push ax
    popf	//置IF=0,TF=0
    
    1. CS和IP入栈
    2. 令IP=(n*4),CS=(n*4+2)
      8086的中断向量表是放在0地址处的,而每一个中断向量由两个字组成,低位字存放IP值,高位字存放CS值,所以上面的赋值就完成了跳转到中断处理程序的功能

    为什么中断时要处理TFIF的值?

    TF位:CPU提供的单步中断支持

    cpu在执行完一次指令后,如果检测到标志寄存器TF=1,则产生单步中断,引发中断过程。单步中断的中断类型码为1,也就是说:cpu在执行完一条指令后,如果检测到TF=1,那么就会转去执行1号中断处理程序
    作用:为单步调试程序提供支持,例如debug的t命令

    IF位:外中断之可屏蔽中断与不可屏蔽中断
    • 可屏蔽中断:cpu可以不响应的外中断,具体响不响应看IF的值
    • 不可屏蔽中断:cpu必须立即响应的外中断,cpu会在当前指令执行完后立即响应该中断,不可屏蔽中断的类型码固定为2

    IF=1:CPU在执行完一条指令后会检测有无外中断产生,并且响应这个外中断,如键盘鼠标等的输入
    IF=0:CPU在执行完一条指令不会响应可屏蔽中断

    相关指令

    sti 设置IF=1
    cli 设置IF=0 (close interupt)

    PART2:编写并安装0号中断处理程序(除法溢出中断程序)

    本来是想写9号中断处理程序的,有一些错误没处理好

    1.安装

    在这里插入图片描述
    安装过程就是,先保存原来的中断处理程序的CS和IP,然后通过movsb指令将我们写的程序传送到0000:0200处,然后更改0号中断向量表的地址指向我们安装的程序地址。在这里插入图片描述
    s段就是我们自己写的中断处理程序,当发生溢出时会在屏幕上打印一个绿色的a,最后三行是通过retf指令调用了原来的0号中断处理程序

    2.执行

    在这里插入图片描述

    展开全文
  • 一种方式是使用轮训(polling)的方式,这种方式周期性地查看所有硬件设备的状态并做相应处理,这会造成很多不必要的系统开销。Linux内核使用中断的方式来管理硬件设备,中断本质上是一种电信号,设备通过和中断控制...

    中断

    为了对计算机的硬件(键盘,硬盘,鼠标,网卡等)进行管理,内核需要和这些硬件通信。一种方式是使用轮询(polling)的方式,这种方式周期性地查看所有硬件设备的状态并做相应处理,这会造成很多不必要的系统开销。Linux内核使用中断的方式来管理硬件设备,中断本质上是一种电信号,设备通过和中断控制器引脚相连的总线发出电信号来发出中断。中断控制器是一种控制芯片,多个设备的中断请求线同时连接到中断控制器上,如果多个设备同时发出中断信号,中断控制器根据优先级选择其中一个发送给处理器处理器,处理器收到中断请求后,就中断当前正在执行的任务,进行中断处理。内核通过中断号(中断号是系统为每个中断请求线interrupt request line分配的编号)来区分不同的设备产生的中断,从而执行对应的中断处理程序。

    人们经常把中断和异常(Exception)放在一起讨论,前者是异步中断,后者是同步中断。同步是指在一个处理器指令执行完毕后产生的(是由处理器产生的),异步指的是在任何时候都有可能发生,和处理器是否完成当前指令无关,当然处理器会将正在执行的指令执行完毕才会去检查是否有中断请求。异常又可以分为处理器检测到的异常(Processor-detected exception)和可编程的异常(programmmed exception),前者又分为fault, trap, abort三种,后者是为了进入内核态(软中断/系统调用)。缺页异常和除以0都属于fault。异常的具体分类在网上看到了很多,各有各的说法,看得我大脑都混乱了,这里的分类参考的是哥伦比亚大学的一份上课PPT的分类法。

    中断处理程序

    当接收到一个中断时,内核会执行中断处理程序(interrupt handler),每个可以产生中断的设备都有一个对应的中断处理程序。中断处理程序是设备驱动的一部分,中断处理程序的函数声明必须遵照规定的格式,中断处理程序本质上是一个函数,和内核其他函数的区别在于中断处理程序是由内核响应中断时调用的,它运行在一个被称为中断上下文的特殊上下文中。中断上下文中不能被阻塞,所以有时候也会被称为原子上下文。

    中断处理程序必须快速地完成执行,这样才能快速地对中断做出响应的同时确保被中断抢占的代码可以尽快地恢复执行。但是中断处理程序往往有大量工作要做,比如网卡的中断处理程序就需要将网络中的数据包从硬件上复制到内存中,处理数据包,最后将数据包交给合适的协议栈或者应用程序。

    上半部和下半部

    中断程序有两个目标:快速完成执行和处理大量工作。很显然,这两个目标是冲突的。为了能够同时达到这两个目标,对中断的响应被分成了两部分:上半部和下半部。上半部是中断处理程序,上半部在接受到中断请求后立刻执行,但是只处理对时延敏感的任务,如对中断进行应答(通过给中断线置低电平告诉设备处理器已经收到中断了)或者复位硬件。对时延不敏感的任务都被放在了下半部,延后执行(下一篇博文将会介绍下半部)。

    还是以网卡为例,网卡收到数据包,发出中断请求,告诉内核收到了数据包。因为网卡上接受数据包的缓冲区大小固定,并且比系统内存小得多,为了避免网卡缓冲区溢出丢包,内核需要快速的完成以下工作:通知网卡中断已经响应(通常是给网卡的寄存器复位)并将数据包拷贝到系统内存,到此中断处理程序(上半部)完成,然后将处理器的使用权交还给内核其他部分(调度程序这个时候就要负责选择下一个执行的进程了,详见Linux内核学习笔记(六)进程调度

    中断处理程序的注册、注销和实现

    注册

    每个设备都有一个对应的设备驱动,如果该设备使用中断(大部分都会使用),那么设备驱动必须要注册一个中断处理程序。注册中断处理程序的函数是 request_irq(),声明在<linux/interrupt.h>中,声明如下:

    /* 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)

    参数irq是对应的终端号。系统时钟和键盘这样的传统PC设备的中断号通常是固定的,对于大部分设备,这个值是动态确定的(通过探测或者编程方式)。
    参数handler是一个函数指针,指向要注册的中断处理程序。

    参数flags是作为bitmask使用,规定了中断和中断处理程序的一些属性,比较重要的有:

    • IRQF_DISABLED 内核在执行该中断处理程序时会禁止所有中断。如果没有这个,内核只会禁止当前的中断处理程序的中断请求线,其他中断请求线仍然可以发出中断,这意味着当前中断处理程序可能会被其他优先级更高的中断请求线的处理程序抢占,这就也叫做中断嵌套。
    • IRQF_SAMPLE_RANDOM 该设备产生的中断会为内核熵池(entropy pool)作出贡献,内核熵池负责从各种随机事件(比如中断)中衍生出真正的随机数。
    • IRQF_SHARED 这个标志表示该中断线可以被多个中断处理程序共享(即多个设备共用同一个中断线,个人感觉一个设备注册多个共享中断线的中断处理程序也是有可能的,但是估计不会有人这样做)

    参数name用于传入设备的名字。

    参数dev用于区分共享同一个中断线的多个中断处理程序(注销共享中断线的其中一个中断处理程序时要用到)以及给中断处理程序传递有价值的数据(实践中通常会传递设备驱动用来表示设备的device结构)。

    如果irq对应的中断线当前处于禁用状态(说明没有中断处理程序注册在该中断线上),request_irq()还会激活该中断线。因为request_irq()可能阻塞,所以不能在中断上下文(interrupt context,稍后会详细介绍)中调用。并且无论IRQF_DISABLED是否被设置,内核都会禁用当前中断处理程序的中断线,所以共享同一个中断线的中断处理程序不会发生嵌套或者重入(后面有详细讨论)。

    注销

    当卸载一个设备的时候,需要注销该设备的中断处理程序。注销函数是

    void free_irq(unsigned int irq, void *dev)

    如果中断号irq对应的中断线不是共享的,free_irq()会将irq的中断处理程序移除,并禁用irq对应的中断线。如果中断号irq是共享的,free_irq()会将dev对应的中断处理程序移除,如果移除的中断处理程序是irq的最后一个中断处理程序,那么free_irq()会同时禁用irq对应的中断线。因为free_irq()可能被阻塞,所以同样不能在中断上下文中调用。

    实现

    中断处理程序的声明规范如下:

    static irqreturn_t intr_handler(int irq, void *dev)

    irq是该中断处理程序要处理的中断请求线的中断号。dev和传递给request_irq()的dev是一样的,用于区分共享中断线的中断处理程序。dev可以指向中断处理程序使用的数据结构,通常驱动中用于表示设备的device结构,这样既能保证唯一性,也可以给中断处理程序提供有价值的数据。中断处理程序的返回值irqreturen_t实质上就是int,但是只有两种返回值:IRQ_NONE和IRQ_HANDLED。中断处理程序被调用时如果发现不是自己的设备发出的中断,就会返回IRQ_NONE。如果判断是自己的设备发出的中断时,就会在处理后返回IRQ_HANDLED。

    中断处理程序的工作内容完全取决于设备和产生中断的原因,但是一个最基本的任务是告诉设备中断已经被响应(中断应答)。复杂的设备通常需要发送、接受数据以及执行额外的工作。驱动程序的编写者需要决定哪些任务必须放在上半部,哪些任务可以放在下半部,能放在下半部的任务要尽可能地放在下半部。

    中断上下文

    当执行一个中断处理程序时,内核处于中断上下文(interrupt context)中。与中断上下文相对的是进程上下文,进程上下文是内核所处的工作模式(注意只有在内核态才可能处于进程上下文),在进程上下文中,内核代表进程执行——比如执行系统调用和运行内核线程,此时可以通过current宏访问当前进程。在进程上下文中可以睡眠。(我个人的理解是中断处理不是一个进程,也没有关联的进程,只有关联的中断。而系统调用或者内核线程执行时有关联的进程,所以是在进程上下文中。此外《Linux Kernel Development 3rd》的作者Robert Love在Quora上亲自回答了进程上下文的问题,说得很详细,可以点击去看一下。)

    中断上下文不和一个进程关联(虽然current宏指向被中断的进程),因为没有一个后备的关联进程,因为无法被再次调度,所以中断上下文不能睡眠(我个人对此的理解是因为没有关联的进程,所以没有可用于切换时保存上下文数据的对象。那为什么不让中断处理程序关联一个进程呢,因为这样的话,虽然可以切换出去了,但是在中断处理程序(或者说上半部)中的任务是时延敏感的,必须立刻执行完毕,所以不能切换出去,那么也就没必要关联一个进程了)。所以不能在中断上下文中调用可能睡眠的函数,这限制了中断处理程序的能力(可以做的事情)。

    中断处理程序使用自己的栈——中断栈(interrupt stack)。中断栈每个处理器一个,栈大小为一个页大小(在32bit机器中通常为4KB)。早起内核版本中,中断程序共用被中断的进程的内核栈。

    一个中断处理的详细过程

    中断处理程序的实现是和体系结构、处理器、中断控制器类型、机器本身密切相关的,下图展示了从硬件发出中断到中断处理返回的完整流程图

    中断处理流程图

    硬件设备通过和中断控制器连接的总线发出电信号来向中断控制器发出中断,如果中断请求线是激活的(中断线是可以被屏蔽的),中断控制器将中断发送给处理器,除非处理器禁用中断了(这也同样是可能的,稍后会说),否则,处理器会在执行完当前指令后,禁用处理器中断,然后跳转到预先设置好的内存位置,并执行那里的代码。这个位置是内核设置的处理中断请求的程序的入口点,入口点是中断在内核中旅行的起点。

    入口点会将中断号和被中断进程的寄存器值保存到中断栈中,然后调用do_IRQ(),do_IRQ()从中断栈中提取中断号,然后给该中断线发送一个中断应答信号(其实就是让CPU回送一个低电平的电信号),并禁用该中断线,然后调用handle_IRQ_event()。

    handle_IRQ_event()先检查IRQF_DISABLE是否被设置,如果没有设置,就重新打开处理器中断(处理器收到中断后把处理器中断禁用了),需要注意的是当前的中断线仍然是禁用的。然后handle_IRQ_event()会按序调用注册在该中断线上的所有中断处理程序,每个中断处理程序都要先检查自己的设备是否发出了中断,如果没有立刻返回,返回值为IRQ_NONE,如果有就进行中断处理,最后返回IRQ_HANDLED。通常这种判断是通过查看设备上的状态寄存器来判断的。(补充说明一点:多个设备共享一个中断线时,如果该中断线产生了中断,那么该中断线上可能只有一个设备发出了中断,也有可能有多个设备同时发出了中断(其实就是中断线被置了高电平),所以该中断线上的每个中断处理程序都会被调用一次)

    查看中断系统的状态 /proc/interrupts

    查看/proc/interrupts可以获得当前系统中注册的中断以及他们的统计信息,包括被注册过的中断号、每个CPU收到的该中断号的次数以及该中断号的中断处理程序的名字、设备的名字。

    参考资料

    《Linux Kernel Development 3rd Edition》
    《Understanding The Linux Kernel 3rd Edition》

    展开全文
  • ARM编程特别是系统初始化代码的编写中...当异常中断处理程序执行完成后,程序返回到发生中断的指令的下一条指令处执行。在进入异常中断处理程序时,要保存被中断的程序的执行现场。从异常中断处理程序退出时,要恢复被
  • 一,中断的定义 二 ,中断处理的过程 三,8086/8088CPU可以处理256种不同类型的终端 四,中断服务程序的设计方法 五中断向量表的建立
  • ARM编程特别是系统初始化代码的编写中通常...当异常中断处理程序执行完成后,程序返回到发生中断的指令的下一条指令处执行。在进入异常中断处理程序时,要保存被中断的程序的执行现场。从异常中断处理程序退出时,要恢
  • 中断处理程序及设备驱动程序 中断是指CPU在执行一个程序时,对系统中发生的某个事件做出的一个反应,它在操作系统中有着重要的有着重要的地位,时多道程序得以实现的基础。 引入缓冲区的原因: 外部中断:简称...
  • 中断中断返回指令

    2021-06-07 20:06:00
    INT n软件中断指令2. INTO 溢出中断指令3. IRET 中断返回指令 1. INT n软件中断指令 产生类型号n的中断。(0~255) 2. INTO 溢出中断指令 当OF = 1时,执行INTO可产生类型4的中断。 当OF=0时,执行INTO不产生...
  • 汇编设置中断处理程序并用调用

    千次阅读 2019-05-18 13:52:53
    源码: data segment ends stack segment ends code segment start : CLI ;设置中断向量 MOV AX,SEG INTROUT9 MOV DS, AX MOV DX, OFFSET INTROUT9 MOV AL, 9 ;将中断处理程序INTRO...
  • 处理器控制指令中断指令 1. 标志位操作指令 汇编格式 操作 作用 CLC CF = 0 清零 进位标志位 STC CF = 1 置1 进位标志位 CMC CF = !CF 取反 进位标志位 CLD DF = 0 清零 方向标志位 或 串操作从底...
  • 中断和中断处理流程

    千次阅读 2020-12-20 04:41:20
    1. 中断概念中断是指由于...硬件中断导致处理器通过一个上下文切换(context switch)来保存执行状态(以程序计数器和程序状态字等寄存器信息主);软件中断则通常作为CPU指令集中的一个指令,以可编程的方式直接指...
  • 当一个协处理器硬件不能执行属于它的协处理器指令时,将产生未定义指令异常中断,在该异常中断处理程序时,可以通过软件模拟该硬件操作。比如,如果系统中不包含向量浮点运算器,则可以选择浮点运算软件模拟包来支持...
  • 满意答案0519gao2013.10.06采纳率:46%等级:12已帮助:11303人子程序...子程序用于为程序分段和分块,使其成为较小的、更易管理的块。在程序中调试和维护时,您可以利用这项优势。通过使用较小的程序块,对这些区...
  • 2.中断处理

    2020-08-09 22:00:40
    其实这里所说的中断指的是外中断,内中断统称异常 内外中断的区别: 内中断中断与CPU正在执行的指令有关 外中断:与CPU执行的指令无关,中断来自外部硬件 #mermaid-svg-OTPdzad02sGJCM8t .label { font-...
  • 所有指令命令,方便编程的时候查询,适用松下的PLC ,包含时序控制基本指令,基本功能指令,控制指令,步进程序指令,子程序指令中断指令,特殊设置指令,数据比较指令,数据传输指令,BIN算术运算指令,BCD算术...
  • 汇编语言编写中断处理程序

    千次阅读 2021-02-12 20:41:22
    CPU都具有执行完当前正在执行的指令之后,检测到从CPU外部(外中断)或内部(内...首先,CPU接收到中断类型码(一个字节型数据),然后通过中断向量表找到对应的中断处理程序的入口地址。中断向量表是入口地址的列表
  • 中断及中断处理过程

    万次阅读 多人点赞 2019-07-24 22:00:23
    Intel的官方文档里将中断和异常理解两种中断当前程序执行的不同机制。这是中断和异常的共同点。 不同点在于: 中断(interrupt)是异步的事件,典型的比如由I/O设备触发;异常(exception)是同步的事件,典型的...
  • 8086中断处理程序分析

    千次阅读 2017-09-02 14:50:41
    中断处理程序的入口地址,即中断向量必须存储在对应的中断向量表表项中,通俗的讲就是中断处理程序的地址放在一块内存中,这块存储中断处理程序的入口的地址空间叫中断向量表,而实际地址就叫中断向量
  • 完整的中断处理过程分为 1)中断响应的事前准备: 系统要想能够应对各种不同的中断信号,总的来看就是需要知道每种信号应该由哪个中断服务程序负责以及这些中断服务程序具体是如何工作的。系统只有事前对这两件事都...
  • 汇编之控制转移指令之子程序、中断指令和系统功能调用前言子程序CALL调用指令RET返回指令中断指令和系统功能调用中断与中断源中断种类中断向量软中断指令中断响应过程中断嵌套中断处理程序设计设置中断向量DOS系统...
  • 一、指令流水 ...通常分为4个级别:作业级或程序级、任务级或进程级、指令之间级和指令内部级。前两级粗粒度,又称为过程级;后两级细粒度,又称为指令级。 粗粒度并行性一般用算法实现,细粒度并行性一
  • 模拟中断事件的处理

    2018-03-08 11:19:27
    模拟时钟中断的产生及设计一个对时钟中断事件进行处理的模拟程序。本实习中,用从键盘读入信息来模拟中断寄存器的作用,用计数器加 1 来模拟处理器执行了一条指令。每模拟一条指令执行后,从键盘读入...中断处理程序
  • 中断指令INT 理解

    2021-05-06 21:33:30
    前言 我们在学习操作系统时,在引导部分代码中,会...INT n就好像是调用子程序,只不过其调用的是中断处理程序。 格式 : int n 功能:转到相应的中断处理程序执行。 举例 mov ah, 1 ; 设置入参 int 21h ; 调用2
  • 中断处理程序

    千次阅读 2019-01-30 18:02:52
    引例 除法错误中断处理 assume ds:a ...我们执行完div bh,我们应该执行076a:0007的mov ax,0001,但是因为这个除法溢出,所以获取调用0号中断处理程序,即0000:0 ~0000:3 四个字节的内容,就是这个中断...
  • 第八章子程序中断调用8.3 子程序与主程序间的参数传递 用高级语言实现的程序,其主程序与子程序之间的参数传递方法是由编译系统来规定的。 一般是采用堆栈或者参数区(地址表)来进行参数的传递。 * 子程序总结: 1....
  • 当这些情况发生时,在ARM系统里,由异常和中断处理程序做出相应的处理,当处理完成后,要返回到被中止的指令,使被中止的指令能够继续正常执行下去。因此,确定异常和中断处理程序的返回地址是一个非常重要的问题。
  • 五、中断处理程序实例 一、中断: 定义:中断就是使CPU暂时挂起当前正在进行的工作并转向某紧急事件的服务与处理程序(该服务与处理程序称为中断服务程序),在执行完中断服务程序后再返回到被中止

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 159,852
精华内容 63,940
关键字:

中断处理程序指令为什么指令