精华内容
下载资源
问答
  • 10.5 外部中断处理过程

    千次阅读 2019-09-20 21:03:07
    10.5 外部中断处理过程 我们之前已经学习了内部中断的处理方法,那么外部中断和内部中断总体上是很类似的。只是在一些具体的处理细节上和硬件的连接方式上有所不同。那我们现在就来看一看外部中断是如何处理的。 ...

    计算机组成

    10 输入输出设备

    10.5 外部中断的处理过程

    Screen Shot 2018-09-30 at 10.49.24 am

    我们之前已经学习了内部中断的处理方法,那么外部中断和内部中断总体上是很类似的。只是在一些具体的处理细节上和硬件的连接方式上有所不同。那我们现在就来看一看外部中断是如何处理的。

    Screen Shot 2018-09-30 at 10.49.33 am

    外部中断,也叫做硬件中断。这是由CPU外部的中断请求信号启动的中断。以x86 CPU为例,连到外部的中断请求信号一共有两个。一个信号叫做NMI,这就是非屏蔽中断的缩写。另一个信号叫INTR,这就是中断这个词的缩写,相对于非屏蔽中断,我们一般也称它为可屏蔽中断。不光是x86,其他很多别的体系结构的CPU也往往提供这两种类型的中断引脚。

    Screen Shot 2018-09-30 at 10.49.42 am

    这两个信号,是对应了CPU上真实存在的两个管脚。来自外设的中断请求信号就可以通过主板上的连线,连到这两个管脚上。这个NMI,也就是非屏蔽中断,一般会连接一个在这个系统当中非常重要,不希望被屏蔽的中断请求信号。那至于什么是非常重要,就取决于系统设计者的观点。例如在某些计算机当中,会将表示电池即将没电的这个信号连接到NMI上。电池马上就要没电了,确实是一个非常紧急的情况。因为非屏蔽中断是不受中断允许标志的影响,即使CPU现在将IF标志位设为0,关闭对外部中断的响应,CPU仍然会响应这个NMI的中断请求。这样就可以调用中断服务程序,在断电之前把一些重要的信息保存到硬盘上去。当然不同的系统可能会连接不同的中断请求信号到NMI上,但都会是一些非常重大,不处理就会导致严重错误的事件。

    一般的外设,它的中断请求都会连接到可屏蔽中断上,但是CPU的可屏蔽中断信号的请求输入只有一根,那就需要通过一些转换电路。现在计算机当中比较常见的是使用中断控制器这个芯片。中断控制器会将外设输出的中断请求信号作为它的输入连接进来,然后输出一根信号连接到CPU的可屏蔽中断请求信号上。这个中断控制器也可以看作是一个I/O接口,它内部也有一些被称为I/O端口的寄存器,CPU可以访问这些端口,对中断控制器进行配置。例如可以配置这些外设的中断请求,哪个优先级高,哪个优先级低,或者可以在这些中断请求当中,屏蔽其中的一部分。这些都是中断控制器的基本功能。

    Screen Shot 2018-09-30 at 10.49.50 am

    因为这样的中断控制器是可以由编写程序进行配置的,所以又称为可编程中断控制器,简称PIC。有一个广泛使用的可编程中断控制器,就是英特尔的8259。我们可以看到8259上,从IR0一直到IR7,一共有8个中断请求的输入可以用来连接外设的中断请求信号。然后它还有一些地址和数据信号,用以连接到系统总线上。CPU对I/O端口的访问,就通过这些信号线来传递。INT信号则是由中断控制器发出的中断请求信号,连接到CPU的可屏蔽中断请求信号上。实际上CPU在收到中断请求后,还会发出一个中断响应信号,这个信号会被连接到中断控制器的INTA引脚上。

    后来在8259的基础上,又有了一些升级和功能的扩充,这就是后来的高级可编程中断控制器,简称为APIC。

    那么再来看一看中断控制器在系统当中的连接情况。

    Screen Shot 2018-09-30 at 10.49.58 am

    IBM PC/XT当中,就使用了一片8259。8259和CPU之间,有中断请求信号和中断响应信号。而另一端,8259则连接了来自各个外设的中断请求信号。比如有来自定时器,有键盘,有串行接口,有硬盘,有软盘,还有打印机。那2号中断请求信号是被保留的,并没有连接外设,等一会儿我们还会提到,这根保留的中断请求信号用来做什么。这是三十多年前最早的个人计算机。在现在的个人计算机当中,有些设备都已经没有了,比如软盘,但是现代的计算机系统仍然要遵守最早的个人计算机当中确定的一些规则。那么就以键盘所对应的这个中断为例,来看一看现在最新的个人计算机当中的情况。

    Screen Shot 2018-09-30 at 10.50.15 am

    如果我们在一台装了Windows操作系统的个人计算机上,调出设备管理器,然后在设备管理器中找到键盘,用右键看这个键盘的属性。我们就会发现,它的中断请求信号仍然是连接到中断控制器的1号接口,仍然遵守三十多年前IBM PC所制定的规范。

    Screen Shot 2018-09-30 at 10.50.28 am

    那在刚才的那个例子中我们可以看到,一个中断控制器最多可以连接七个外设。如果有更多的外设应该怎么办呢?我们可以在系统中再增加一个中断控制器,这个中断控制器的请求信号不是连接到CPU的,而是连接到原有的中断控制器的2号中断请求信号上。那在这个中断控制器上,又可以再连接更多的外设。这样两级中断控制器的结构,在早期的个人计算机当中使用了很长时间。

    Screen Shot 2018-09-30 at 10.50.37 am

    不过现在情况有一点不太一样,因为很多I/O接口都集成到了南桥当中。所以,这些I/O接口的中断请求信号实际上都是在南桥内部了。因此,在南桥内一般也会实现一个中断控制器,当然现在是APIC这样的中断控制器了。这个中断控制器负责接收所有I/O接口的中断请求信号,包括南桥内部集成的,和在外部独立的I/O接口。它会将中断请求信号再送到CPU中去。而现在的计算机当中,往往有多个CPU,其实每个CPU当中,都还会带一个中断控制器。因为现在的CPU不但要接受中断请求信号,它也会发出中断,现在CPU发出的中断,是用来跟别的CPU进行交互的。比如两个CPU要进行一些协同的工作,其中一个CPU在处理完了内存当中的一部分数据,它就可以通过发出中断请求来通知另一个CPU进行后续的工作。因此,在现代的个人计算机当中,可能已经找不到一个独立的中断控制器的芯片了,但其实中断控制器的功能已经变得更为丰富,数量也变得更多了。

    Screen Shot 2018-09-30 at 10.50.51 am

    我们还是回到简单的情况,来看看这些来自外设的,可屏蔽中断的处理过程。当外设有中断的需求,它就会通过中断控制器向CPU发出中断请求信号,而CPU则会中断当前正在执行的程序,向中断控制器发出中断响应信号,然后中断控制器再会通过其他的信号线,将对应外设的中断类型码发给CPU。而这个类型码,其实也是在系统初始化时,通过写入中断控制器的I/O端口而设置的。CPU在得到了中断类型码之后,后续的处理过程就和内部中断是一样的了。我们快速地浏览一遍。

    CPU将相关的寄存器压栈,然后清除IF和TF标志位,再取得对应的中断向量。然后程序就会跳转到中断服务程序,在中断服务程序当中,可以在适当的时机通过设置IF标志位,开放中断。一旦开放了中断,就意味着在执行这个中断服务程序的过程中,CPU还可能会响应其他外设发来的中断。那在中断服务程序执行完之后,就会执行中断返回指令,将返回地址等信息从堆栈中弹出,然后就可以回到刚才被中断的位置继续执行了。如果在这个中断服务程序当中,开放中断后,外设又发来中断,那其实CPU是会中断这个中断服务程序的执行,转而去响应这个新发生的中断。这种情况就被称为中断嵌套

    Screen Shot 2018-09-30 at 10.50.59 am

    当然,要想发生中断嵌套的情况,必须要有比当前正在处理的中断优先级更高的中断请求,那这时CPU就会去响应这个优先级更高的中断请求,在执行完这个新的中断服务程序之后,再返回到刚才的中断服务程序当中继续执行。

    我们再根据图示来看一看这个过程。假设在一段主程序当中,开始是关闭了中断响应的。然后在这里打开了中断响应(STI),之后就发生了中断,于是CPU就会转向中断服务程序(第一层)。那么在这里CPU的硬件已经自动设置了IF标志位为0,屏蔽了外部的中断请求,如果在这个中断服务程序的某个地方(指第一层中的STI处)又打开了中断响应,而且在这个过程中又有外设发起了更高优先级的中断请求,那CPU又会去处理这个新的中断(第二层)。我们要注意,对于CPU来说,现在在执行的这个中断服务程序也就像是一个普通的程序,所以在这个过程中如果发生了中断请求,它依然会进行同样的那些处理步骤,比如压栈、关中断、保存现场等等,然后根据取回的中断向量进入到第二层的中断服务程序。那如果在这个中断服务程序当中没有开中断,或者开了中断但是没有更高优先级的外设发起中断请求,那一直执行到中断返回指令(IRET),CPU就会回到刚才发生中断的地方继续执行,也就是第一层的中断服务程序。如果之后没有再遇到中断请求,就会一直执行到第一层中断服务程序的中断返回指令(IRET),再返回到刚才主程序中断的地方。这就是一个简单的两层中断嵌套的过程。

    Screen Shot 2018-09-30 at 10.51.08 am

    现在我们已经知道了内部中断和外部中断是如何协同工作的,而在现代的计算机系统当中大量地使用了中断来控制输入输出设备。至于如何进行中断优先级的调配,什么时候能屏蔽中断,什么时候不能屏蔽中断,这些问题都是值得深入研究的。

    转载于:https://www.cnblogs.com/houhaibushihai/p/9745217.html

    展开全文
  • ARM Linux外部中断处理过程

    千次阅读 2008-08-04 10:39:00
    ARM Linux外部中断处理过程http://www.linuxforum.net/forum/showflat.php?Cat=&Board=linuxK&Number=652682&page=12&view=collapsed&sb=7&o=all&fpart=1&vc=1作者:muxiaowei 整理:Nathan.Yu 最近在学习arm linux...

    ARM Linux外部中断处理过程

    http://www.linuxforum.net/forum/showflat.php?Cat=&Board=linuxK&Number=652682&page=12&view=collapsed&sb=7&o=all&fpart=1&vc=1

    作者:muxiaowei 整理:Nathan.Yu

     

    最近在学习arm linux的整套外部中断的处理过程,在网上汇总了一些资料,整个过程差不多都了解到了。如果没有这些资料我真是没信心从汇编开始读代码,感谢 奔腾年代的jimmy.lee linux论坛的bx_bird

    在下面的的注释中有一些我读代码时遇到的问题,要是大家知道是怎么回事,希望多多回复。

     

    =============================================

    一.ARM linux的中断向量表初始化分析

    ARM linux内核启动时,通过start_kernel()->trap_init()的调用关系,初始化内核的中断异常向量表.

     

    * arch/arm/kernel/traps.c */

    void __init trap_init(void)

    {

    extern void __trap_init(unsigned long);

    unsigned long base = vectors_base();

    __trap_init(base);

    if (base != 0)

    oopsprintk(KERN_DEBUG "Relocating machine vectors to 0x%08lx/n", base);

    #ifdef CONFIG_CPU_32

    modify_domain(DOMAIN_USER, DOMAIN_CLIENT);

    #endif

    }

    vectors_base是一个宏,它的作用是获取ARM异常向量的地址,该宏在include/arch/asm-arm/proc-armv/system.h中定义:

     

    extern unsigned long cr_no_alignment; /* defined in entry-armv.S */

    extern unsigned long cr_alignment; /* defined in entry-armv.S */

    #if __LINUX_ARM_ARCH__ >= 4

    #define vectors_base() ((cr_alignment & CR_V) ? 0xffff0000 : 0)

    #else

    #define vectors_base() (0)

    #endif

      对于ARMv4以下的版本,这个地址固定为0ARMv4及其以上的版本,ARM异常向量表的地址受协处理器CP15c1寄存器(control register)V(bit[13])的控制,如果V=1,则异常向量表的地址为0x00000000~0x0000001C;如果V=0,则为:0xffff0000~0xffff001C。(详情请参考ARM Architecture Reference Manual)

      下面分析一下cr_alginment的值是在哪确定的,我们在arch/arm/kernel/entry-armv.S找到cr_alignment的定义:

     

    .globl SYMBOL_NAME(cr_alignment)

    .globl SYMBOL_NAME(cr_no_alignment)

    SYMBOL_NAME(cr_alignment):

    .space 4

    SYMBOL_NAME(cr_no_alignment):

    .space 4

     

      分析过head-armv.S文件的朋友都会知道,head-armv.S是非压缩内核的入口:

     

    .section ".text.init",#alloc,#execinstr

    .type stext, #function

    ENTRY(stext)

    mov r12, r0

    mov r0, #F_BIT | I_BIT | MODE_SVC @ make sure svc mode

    msr cpsr_c, r0 @ and all irqs disabled

    bl __lookup_processor_type

    teq r10, #0 @ invalid processor?

    10 moveq r0, #'p' @ yes, error 'p'

    11 beq __error

    12 bl __lookup_architecture_type

    13 teq r7, #0 @ invalid architecture?

    14 moveq r0, #'a' @ yes, error 'a'

    15 beq __error

    16 bl __create_page_tables

    17 adr lr, __ret @ return address

    18 add pc, r10, #12 @ initialise processor

    19 @ (return control reg)

    20

    21 .type __switch_data, %object

    22__switch_data: .long __mmap_switched

    23 .long SYMBOL_NAME(__bss_start)

    24 .long SYMBOL_NAME(_end)

    25 .long SYMBOL_NAME(processor_id)

    26 .long SYMBOL_NAME(__machine_arch_type)

    27 .long SYMBOL_NAME(cr_alignment)

    28 .long SYMBOL_NAME(init_task_union)+8192

    29

    30 .type __ret, %function

    31__ret: ldr lr, __switch_data

    32 mcr p15, 0, r0, c1, c0

    33 mrc p15, 0, r0, c1, c0, 0 @ read it back.

    34 mov r0, r0

    35 mov r0, r0

    36 mov pc, lr

    这里我们关心的是从17行开始,17code处将lr放置为__ret标号处的相对地址,以便将来某处返回时跳转到31行继续运行18,对于我所分析的pxa270平台,它将是跳转到arch/arm/mm/proc-xscale.S中执行__xscale_setup函数,(在s3c2410平台中,它跳转到arch/arm/mm/proc-arm920.S,在

    type __arm920_proc_info,#object

    __arm920_proc_info:

    .long 0x41009200

    .long 0xff00fff0

    .long 0x00000c1e @ mmuflags

    b __arm920_setup

    .long cpu_arch_name

    .long cpu_elf_name

    .long HWCAP_SWP | HWCAP_HALF | HWCAP_THUMB

    .long cpu_arm920_info

    .long arm920_processor_functions

    可以知道add pc, r10, #12 的#12意思是跳过3个指令,执行b _arm920_setup

    arm920_setup设置完协处理器和返回寄存器r0之后,跳回到__ret:(31)

    __xscale_setup中会读取CP15control register(c1)的值到r1寄存器,并在r1寄存器中设置相应的标志位(其中包括设置V位=1),但在__xscale_setup中,r1寄存器并不立即写回到Cp15control register中,而是在返回后的某个地方,接下来会慢慢分析到。__xscale_setup调用move pc, lr指令返回跳转到31行。

      31行,在lr寄存器中放置__switch_data中的数据__mmap_switched,在36行程序会跳转到__mmap_switched处。

      3233行,把r0寄存器中的值写回到cp15control register(c1),再读出来放在r0中。

      

      接下来再来看一下跳转到__mmap_switched处的代码:

    40 _mmap_switched:

    41 adr r3, __switch_data + 4

    42 ldmia r3, {r4, r5, r6, r7, r8, sp}@ r2 = compat

    43 @ sp = stack pointer

    44

    45 mov fp, #0 @ Clear BSS (and zero fp)

    46 1: cmp r4, r5

    47 strcc fp, [r4],#4

    48 bcc 1b

    49

    50 str r9, [r6] @ Save processor ID

    51 str r1, [r7] @ Save machine type

    52 bic r2, r0, #2 @ Clear 'A' bit

    53 stmia r8, {r0, r2} @ Save control register values

    54 b SYMBOL_NAME(start_kernel)

    41~42行的结果是:r4=__bss_startr5=__end,...,r8=cr_alignment,..,这里r8保存的是cr_alignment变量的地址.

      到了53行,由于之前r0保存的是cp15control register(c1)的值,这里把r0的值写入r8指向的地址,即cr_alignment=r0.到此为止,我们就看清楚了cr_alignment的赋值过程。

      

      让我们回到trap_init()函数,经过上面的分析,我们知道vectors_base返回0xffff0000。函数__trap_init由汇编代码编写,在arch/arm/kernel/entry-arm.S

        .align 5

    __stubs_start:

    vector_IRQ:

         ...

    vector_data:

        ....

    vector_prefetch:

         ...

    vector_undefinstr:

         ...

    vector_FIQ: disable_fiq

         subs pc, lr, #4

    vector_addrexcptn:

         b vector_addrexcptn

        ...

    __stubs_end:

         .equ __real_stubs_start, .LCvectors + 0x200

    .LCvectors: swi SYS_ERROR0

         b __real_stubs_start + (vector_undefinstr - __stubs_start)

         ldr pc, __real_stubs_start + (.LCvswi - __stubs_start)

         b __real_stubs_start + (vector_prefetch - __stubs_start)

         b __real_stubs_start + (vector_data - __stubs_start)

         b __real_stubs_start + (vector_addrexcptn - __stubs_start)

         b __real_stubs_start + (vector_IRQ - __stubs_start)

         b __real_stubs_start + (vector_FIQ - __stubs_start)

    ENTRY(__trap_init)

        stmfd sp!, {r4 - r6, lr} /* 压栈,保存数据*/

        /* 复制异常向量表(.LCvectors起始的8个地址)到r0指向的地址(异常向量地址),r0就是__trap_init(base)函数调用时传递的参数,不明白的请参考ATPCS*/(传递参数顺次利用r0r1r2r3

        adr r1, .LCvectors @ set up the vectors

        ldmia r1, {r1, r2, r3, r4, r5, r6, ip, lr}

         stmia r0, {r1, r2, r3, r4, r5, r6, ip, lr}

     

    /* 在异常向量地址后的0x200偏移处,放置散转代码,即__stubs_start~__stubs_end之间的各个异常处理代码*/

         add r2, r0, #0x200

         adr r0, __stubs_start @ copy stubs to 0x200

         adr r1, __stubs_end

    1: ldr r3, [r0], #4

         str r3, [r2], #4

         cmp r0, r1

    blt 1b

    LOADREGS(fd, sp!, {r4 - r6, pc}) /*出栈,恢复数据,函数__trap_init返回*/

    __trap_init函数填充后的向量表如下:

    虚拟地址 异常 处理代码

    0xffff0000 reset swi SYS_ERROR0

    0xffff0004 undefined b __real_stubs_start + (vector_undefinstr - __stubs_start)

    0xffff0008 软件中断 ldr pc, __real_stubs_start + (.LCvswi - __stubs_start)

    0xffff000c 取指令异常 b __real_stubs_start + (vector_prefetch - __stubs_start)

    0xffff0010 数据异常 b __real_stubs_start + (vector_data - __stubs_start)

    0xffff0014 reserved b __real_stubs_start + (vector_addrexcptn - __stubs_start)

    0xffff0018 irq b __real_stubs_start + (vector_IRQ - __stubs_start)

    0xffff001c fiq b __real_stubs_start + (vector_FIQ - __stubs_start)

     

       当有异常发生时,处理器会跳转到对应的0xffff0000起始的向量处取指令,然后,通过b指令散转到异常处理代码.因为ARMb指令是相对跳转,而且只有+/-32MB的寻址范围,所以把__stubs_start~__stubs_end之间的异常处理代码复制到了0xffff0200起始处.这里可直接用b指令跳转过去,这样比使用绝对跳转(ldr)效率高。

    二.ARM Linux中断处理过程分析(1)

    在我的上一篇文章(ARM linux的中断向量表初始化分析)中已经分析了ARM Linux中断向量表是如何建立的,在这篇文章中,我将分析一下Linux内核的ARM体系下,中断处理是如何响应的一个过程。

    ARM体系架构下,定义了7种异常,每一种异常都有自己的入口地址,即异常向量表,当异常发生时,处理器会自动跳转到相应的入口处执行。对于ARMv4及其以上的版本,异常向量表的起始位置由协处理器15cp15)的控制寄存器(c1)里的V(bit13)有关,当V=0时,异常向量表的起始位置在0x00000000,而当V=1时,异常向量表就起始于0xffff0000位置。在上一篇文章中,我们已经分析知道异常向量表放置于0xffff0000起始位置,而IRQ中断处理入口地址为:0xffff0018,所以当发生一IRQ中断异常时,处理器会自动跳转到0xffff0018这个虚拟地址上。

    0xffff0018这个虚拟地址上是一条跳转指令:

    b __real_stubs_start + (vector_IRQ - __stubs_start)

     

    所以对于IRQ的处理就是从vector_IRQ标号处开始的。在linux2.4.19内核中相应代码如下:

    __stubs_start:

    /*

    * Interrupt dispatcher

    * Enter in IRQ mode, spsr = SVC/USR CPSR, lr = SVC/USR PC

    */说明其实linux只用到了armsvcusr模式,其他的几个模式都没怎么用。

    1 vector_IRQ: @

    2 @ save mode specific registers

    3 @

    4 ldr r13, .LCsirq

    5 sub lr, lr, #4

    6 str lr, [r13] @ save lr_IRQ

    7 mrs lr, spsr

    8 str lr, [r13, #4] @ save spsr_IRQ

    9 @

    10 @ now branch to the relevent MODE handling routine

    11 @

    12 mrs r13, cpsr

    13 bic r13, r13, #MODE_MASK

    14 orr r13, r13, #I_BIT | MODE_SVC

    15 msr spsr_c, r13 @ switch to SVC_32 mode

    16

    17 and lr, lr, #15

    18 ldr lr, [pc, lr, lsl #2]

    19 movs pc, lr @ Changes mode and branches

    20

    21.LCtab_irq: .word __irq_usr @ 0 (USR_26 / USR_32)

    22 .word __irq_invalid @ 1 (FIQ_26 / FIQ_32)

    23 .word __irq_invalid @ 2 (IRQ_26 / IRQ_32)

    24 .word __irq_svc @ 3 (SVC_26 / SVC_32)

    25 .word __irq_invalid @ 4

    26 .word __irq_invalid @ 5

    27 .word __irq_invalid @ 6

    28 .word __irq_invalid @ 7

    29 .word __irq_invalid @ 8

    30 .word __irq_invalid @ 9

    31 .word __irq_invalid @ a

    32 .word __irq_invalid @ b

    33 .word __irq_invalid @ c

    34 .word __irq_invalid @ d

    35 .word __irq_invalid @ e

    36 .word __irq_invalid @ f

    首先,行4~8是保存进入IRQ模式之前的pc指针(在lr_IRQ)和CPSR(在SPSR_IRQ)到.LCsirq所指向的地址中。.LCsirq相关代码也是位于entry-armv.S中:

    .LCsirq: .word __temp_irq

    __temp_irq: .word 0 @ saved lr_irq

    .word 0 @ saved spsr_irq

    .word -1 @ old_r0

    在这里补充一下ARM对于异常的处理过程,可以用下面的一段伪码来表示:

    r14_<异常模式> = return link

    SPSR_<异常模式> = CPSR

    CPSR[4:0] = 异常模式编码

    CPSR[5] = 0 ;运行于ARM状态

    If<异常模式> == Reset or FIQ then{

    ;当复位或响应FIQ异常时,禁止新的fiqirq异常

    CPSR[6] = 1;

    CPSR[7] = 1;

    }else if<异常模式> == IRQ then{

    ;当响应IRQ异常时,禁止新的IRQ异常

    CPSR[7] = 1;

    }

    PC = 异常向量地址

     

    所以在运行到行4~8之前时,lr为进入IRQ之前的pc指针,spsr为进入IRQ之前的cpsr指针。

    接着,行12~15更新spsr寄存器为SVR模式,并关闭IRQ,为从IRQ模式切换到SVR模式做准备。

    17,根据进入IRQ模式之前的psr(因为在行7lr已经被置以spsr_irq),获取之前的处理器模式(psr &0b1111)。

    18,根据获取的进入IRQ之前的处理器模式,查找相应的跳转入口(__irq_usr 对应于之前是USR模式,__irq_svc对于之前是SVC模式,对于其它模式均跳转到__irq_invalid,在linux系统中处理器进入IRQ之前只有usrsvc两种模式,其它模式均不允许开启IRQ)。此行实际上是:lr = pc+lr<<2pc指向当前指令地址值加8个字节的地址,即pc指向当前指令的下两条指令的地址,所以pc在此时指向的是.LCtab_irq地址。

    (这里有点疑惑要进入__irq_usr,则18lr应该为pc+4那么向回推算第7行的mrs lr, spsrspsr[3:0]应该为0b0001;如果要进入__irq_svc,则18lr应该为pc16,那么spsr[3:0]应该为0b0100

    cprs[4:0]=

    10000 User 模式

    10011 SVC 模式

    请达人指点迷津。。。。)

    19,跳转到相应入口,并且ARM寄存器r13r14则切换到了SVC模式下的寄存器

    三.ARM Linux中断处理过程分析(2)

    续前文,让我们先分析进入IRQ之前的处理器模式为SVC时的情况,程序会跳转到__irq_svc继续运行,其相应代码如下:

    20__irq_svc: sub sp, sp, #S_FRAME_SIZE

    21 stmia sp, {r0 - r12} @ save r0 - r12

    22 ldr r7, .LCirq

    23 add r5, sp, #S_FRAME_SIZE

    24 ldmia r7, {r7 - r9}

    25 add r4, sp, #S_SP

    26 mov r6, lr

    27 stmia r4, {r5, r6, r7, r8, r9} @ save sp_SVC, lr_SVC, pc, cpsr, old_ro

    28 1: get_irqnr_and_base r0, r6, r5, lr

    29 movne r1, sp

    30 @

    31 @ routine called with r0 = irq number, r1 = struct pt_regs *

    32 @

    33 adrsvc ne, lr, 1b

    34 bne asm_do_IRQ

    35 ldr r0, [sp, #S_PSR] @ irqs are already disabled

    36 msr spsr, r0

    37 ldmia sp, {r0 - pc}^ @ load r0 - pc, cpsr

     

    20~27:保存进入中断之前的寄存器,把它们放在堆栈中。其中#S_FRAME_SIZE#S_SP的定义在arch/arm/kernel/entry-header.S中:

    #ifdef CONFIG_CPU_32

    #define S_FRAME_SIZE 72

    #define S_OLD_R0 68

    #define S_PSR 64

    #else

    #define S_FRAME_SIZE 68

    #define S_OLD_R0 64

    #define S_PSR 60

    #endif

     

    #define S_PC 60

    #define S_LR 56

    #define S_SP 52

    #define S_IP 48

    #define S_FP 44

    #define S_R10 40

    #define S_R9 36

    #define S_R8 32

    #define S_R7 28

    #define S_R6 24

    #define S_R5 20

    #define S_R4 16

    #define S_R3 12

    #define S_R2 8

    #define S_R1 4

    #define S_R0 0

    #define S_OFF 8

     

    .LCirqentry-armv.S中是这样定义的:

    .LCirq: .word __temp_irq

    这与行4处的.LCsirq定义是一样的,可见整个过程利用__temp_irq作为中转,把进入中断之前的CPSRPC(中断处理结束后要返回的地址)放入堆栈,以便中断返回时直接恢复。

    20~27执行的结果是:

    r5-> old_r0

    cpsr

    pc

    lr_svc

    r4-> sp_svc

    r12

    r11

    r1

    sp-> r0

    28get_irqnr_and_base,它是一个宏定义,作用是获取中断号(irq number),它将被保存在r0中。另外,get_irqnr_and_base还会改变cpsr寄存器中的Z位,如果确实找到了发生的中断号,则Z位被清除,否则Z位被置位。get_irqnr_and_base这个宏定义的实现是依赖具体的硬件的,对于pxa270 cpu,其实现如下:

    .macro get_irqnr_and_base, irqnr, irqstat, base, tmp

    mov /base, #io_p2v(0x40000000) @ IIR Ctl = 0x40d00000

    add /base, /base, #0x00d00000

    ldr /irqstat, [/base, #0] @ ICIP

    ldr /irqnr, [/base, #4] @ ICMR

    ands /irqstat, /irqstat, /irqnr

    beq 1001f /* 没找到中断,跳转*/

    rsb /irqnr, /irqstat, #0

    and /irqstat, /irqstat, /irqnr

    clz /irqnr, /irqstat

    rsb /irqnr, /irqnr, #(31 - PXA_IRQ_SKIP)

    #ifdef CONFIG_CPU_BULVERDE

    b 1002f

    #endif

    1001:

    1002:

    .endm

     

    .macro irq_prio_table

    .endm

    bics /irqstat, /irqstat, /irqnr 对照intmskintpnd中禁止的中断清0。因为intpnd在某一时刻只可以有一位为1,所以有一位被bics0了,就会影响标志位从而beq跳转,return r00;从1001:开始所作的事情是循环查intpnd哪一位置为了1。有点疑惑的是tst 指令:

    tst 类似于 CMP,不产生放置到目的寄存器中的结果。而是在给出的两个操作数上进行操作并把结果反映到状态标志上。使用 tst 来检查是否设置了特定的位。操作数 1 是要测试的数据字而操作数 2 是一个位掩码。经过测试后,如果匹配则设置 Zero 标志,否则清除它。

    那么这里的tst /irqstat, #1,当zero1了表示有中断位,为什么下面是bne 1002f而不是beq?请教请教。。。。。。。)

    asm_do_IRQ是用C语言编码的函数,它在arch/arm/kernel/irq.c中被定义,其原型为:

    asmlinkage void asm_do_IRQ(int irq, struct pt_regs *regs);

    这里牵扯到一个问题就是,在汇编中如何调用C语言的函数,参数是如何传递的?为了让ARM的汇编代码可与C代码一起连接,在编写ARM汇编时,应遵循一套标准,这就是ATPCS(The ARM-Thumb Procedure Call Standard)ATPCS定义{r0~r3}为参数传递和结果返回寄存器;若参数超过4个字型(32bit),则使用堆栈进行传递;头4个参数依次存于r0...r3,大于4个的后续字型参数通过栈传送。关于栈的使用,是使用满递减的堆栈标准,也就是栈是从高地址向低地址方向增长的(递减堆栈),栈指针寄存器指向的数据是最后压入堆栈内的有效数据(满堆栈)。

    所以在跳转到asm_do_IRQ函数之前,r0就必须设置为中断号(行28get_irqnr_and_base把中断号放置于r0),r1就必须是指向pt_regs这样结构(定义于include/asm-arm/proc-armv/ptrace.h)的指针,而行29sp指针赋予r1,就完成了这样的一个调用准备。

    35~37:恢复寄存器,返回到发生中断之前的代码中继续执行。

    这就是整个ARM linux中断处理的过程。以后有时间,再继续展开asm_do_IRQ继续分析。对于进入中断前处理器模式是USR的中断处理过程(__irq_usr),这里就不再做分析,这与__irq_svc基本相同

    asmlinkage void do_IRQ(int irq, struct pt_regs * regs)

    {

    struct irqdesc * desc;

    struct irqaction * action;

    int cpu;

     

    irq = fixup_irq(irq);// 查找子中断号,如无子中断return irq

    /*

    * Some hardware gives randomly wrong interrupts. Rather

    * than crashing, do something sensible.

    */

    if (irq >= NR_IRQS)

    goto bad_irq;

     

    desc = irq_desc + irq;

     

    spin_lock(&irq_controller_lock);

    desc->mask_ack(irq);

    /*----------------------------------

    void __init init_IRQ(void)

    {

    extern void init_dma(void);

    int irq;

     

    for (irq = 0; irq < NR_IRQS; irq++) {

    irq_desc[irq].probe_ok = 0;

    irq_desc[irq].valid = 0;

    irq_desc[irq].noautoenable = 0;

    irq_desc[irq].mask_ack = dummy_mask_unmask_irq;

    irq_desc[irq].mask = dummy_mask_unmask_irq;

    irq_desc[irq].unmask = dummy_mask_unmask_irq;

    }

    init_arch_irq();

    init_dma();

    }

    init_arch_irq(); init_dma();最后被指向/mach-s3c2410中的s3c2410_init_irq(void)s3c2410_init_dma(void), desc->mask_ack(irq);将在那里被填充。

     

    --------------------------------*/

     

    spin_unlock(&irq_controller_lock);

     

    cpu = smp_processor_id(); //#define smp_processor_id() 0

    irq_enter(cpu, irq);

    kstat.irqs[cpu][irq]++;

    desc->triggered = 1;

     

    /* Return with this interrupt masked if no action */

    action = desc->action;

    /* 这个结构由driver通过request_irq()挂入,包括了具体的中断处理程序入口和flags.一个中断的irq_desc下面可能会挂几个action(一个action队列)来实现中断的复用。也就是说几个driver可以公用一个中断号。*/

     

    if (action) {

    int status = 0;

     

    if (desc->nomask) {

    spin_lock(&irq_controller_lock);

    desc->unmask(irq);

    spin_unlock(&irq_controller_lock);

    }

     

    if (!(action->flags & SA_INTERRUPT))

    /* SA_INTERRUPT Disable local interrupts while processing

    SA_SHIRQ is shared

    这个flag可以一直追到request irqaction->flags = irq_flags(传递参数);

    */

    __sti();//清除cpsrI_bit,开中断。

    /*如果在上面的nomask处判断后,没有执行unmask动作,那么这里的__sti只是允许不同中断通道(即icip上不同的位)上的嵌套*/

    do {

    status |= action->flags;

    action->handler(irq, action->dev_id, regs);

    action = action->next;

    } while (action);

    /*值得注意的是:整个action队列都会被调用,所以在driver里要判定是否是属于自己的中断*/

    if (status & SA_SAMPLE_RANDOM)

    add_interrupt_randomness(irq);

    __cli();

     

    if (!desc->nomask && desc->enabled) {

    spin_lock(&irq_controller_lock);

    desc->unmask(irq);

    spin_unlock(&irq_controller_lock);

    }

    }

     

    unsigned int fixup_irq(int irq) {

    unsigned int ret;

    unsigned long sub_mask, ext_mask;

     

    if (irq == OS_TIMER)

    return irq;

     

    switch (irq) {

    case IRQ_UART0:

    sub_mask = SUBSRCPND & ~INTSUBMSK;

    ret = get_subIRQ(sub_mask, 0, 2, irq);

    break;

    case IRQ_UART1:

    sub_mask = SUBSRCPND & ~INTSUBMSK;

    ret = get_subIRQ(sub_mask, 3, 5, irq);

    break;

    case IRQ_UART2:

    sub_mask = SUBSRCPND & ~INTSUBMSK;

    ret = get_subIRQ(sub_mask, 6, 8, irq);

    break;

    case IRQ_ADCTC:

    sub_mask = SUBSRCPND & ~INTSUBMSK;

    ret = get_subIRQ(sub_mask, 9, 10, irq);

    break;

    case IRQ_EINT4_7:

    ext_mask = EINTPEND & ~EINTMASK;

    ret = get_extIRQ(ext_mask, 4, 7, irq);

    break;

    case IRQ_EINT8_23:

    ext_mask = EINTPEND & ~EINTMASK;

    ret = get_extIRQ(ext_mask, 8, 23, irq);

    break;

    default:

    ret = irq;

    }

    这个函数一看就知道是找子中断号的,

    inline unsigned int get_subIRQ(int irq, int begin, int end, int fail_irq) {

    int i;

     

    for(i=begin; i <= end; i++) {

    if (irq & (1 << i))

    return (EXT_IRQ_OFFSET + i);

    }

    return fail_irq;

    }

     

    inline unsigned int get_extIRQ(int irq, int begin, int end, int fail_irq) {

    int i;

     

    for(i=begin; i <= end; i++) {

    if (irq & (1 << i))

    return (NORMAL_IRQ_OFFSET - 4 + i);

    }

    return fail_irq;

    }

    #define NORMAL_IRQ_OFFSET 32

    #define EXT_IRQ_OFFSET (20 +NORMAL_IRQ_OFFSET)

     

    =========================================

    申请中断:

    int request_irq(unsigned int irq, void (*handler)(int, void *, struct pt_regs *),

    unsigned long irq_flags, const char * devname, void *dev_id)

    {

    unsigned long retval;

    struct irqaction *action;

     

    if (irq >= NR_IRQS || !irq_desc[irq].valid || !handler ||

    (irq_flags & SA_SHIRQ && !dev_id))

    return -EINVAL;

     

    action = (struct irqaction *)kmalloc(sizeof(struct irqaction), GFP_KERNEL);

    if (!action)

    return -ENOMEM;

     

    action->handler = handler;

    action->flags = irq_flags;

    action->mask = 0;

    action->name = devname;

    action->next = NULL;

    action->dev_id = dev_id;

     

    retval = setup_arm_irq(irq, action); /* 把这个action挂到对应irqaction链表中*/

     

    if (retval)

    kfree(action);

    return retval;

    }

     

    int setup_arm_irq(int irq, struct irqaction * new)

    {

    int shared = 0;

    struct irqaction *old, **p; /*这里的**p 用的太妙了*/

    unsigned long flags;

    struct irqdesc *desc;

     

    /*

    * Some drivers like serial.c use request_irq() heavily,

    * so we have to be careful not to interfere with a

    * running system.

    */

    if (new->flags & SA_SAMPLE_RANDOM) {

    /*

    * This function might sleep, we want to call it first,

    * outside of the atomic block.

    * Yes, this might clear the entropy pool if the wrong

    * driver is attempted to be loaded, without actually

    * installing a new handler, but is this really a problem,

    * only the sysadmin is able to do this.

    */

    rand_initialize_irq(irq); /*这个函数的作用是利用中断的随机性来产生随机数列*/

    }

     

    /*

    * The following block of code has to be executed atomically

    */

    desc = irq_desc + irq;

    spin_lock_irqsave(&irq_controller_lock, flags);

    p = &desc->action;

    if ((old = *p) != NULL) {

    注意/* Can't share interrupts unless both agree to */

    if (!(old->flags & new->flags & SA_SHIRQ)) {

    spin_unlock_irqrestore(&irq_controller_lock, flags);

    return -EBUSY;

    }

     

    /* add new interrupt at end of irq queue */

    do {

    p = &old->next;

    old = *p;

    } while (old);/*当没有下一个irqaction链表元素时,next就位null*/

    shared = 1;

    }

     

    *p = new;

     

    if (!shared) {

    desc->nomask = (new->flags & SA_IRQNOMASK) ? 1 : 0;

    desc->probing = 0;

    if (!desc->noautoenable) {

    desc->enabled = 1;

    desc->unmask(irq);

    }

    }

     

    spin_unlock_irqrestore(&irq_controller_lock, flags);

    return 0;

    }

    四.ARM Linux中断处理过程分析(3)

     

    在之前的文章中,我分析了进入IRQ之前处理器模式为SVC的情况,在本篇文章中,将要讨论的是进入IRQ之前处理器模式为USR的情形。

    843 __irq_usr: sub sp, sp, #S_FRAME_SIZE

    844 stmia sp, {r0 - r12} @ save r0 - r12

    845 ldr r4, .LCirq

    846 add r8, sp, #S_PC

    847 ldmia r4, {r5 - r7} @ get saved PC, SPSR

    848 stmia r8, {r5 - r7} @ save pc, psr, old_r0

    849 stmdb r8, {sp, lr}^

    850 alignment_trap r4, r7, __temp_irq

    851 zero_fp

    852 1: get_irqnr_and_base r0, r6, r5, lr

    853 movne r1, sp

    854 adrsvc ne, lr, 1b

    855 @

    856 @ routine called with r0 = irq number, r1 = struct pt_regs *

    857 @

    858 bne asm_do_IRQ

    859 mov why, #0

    860 get_current_task tsk

    861 b ret_to_user

    __irq_usr关于中断处理的过程大体与__irq_svc是一样的,这里我们重点要分析中断处理返回时的不同。

    研读过linux内核进程调度的朋友都知道,进程的调度可以自愿的方式随时进行(内核里:scheduleschedule_timeout;用户空间:pausenanosleep),还可以非自愿的发生,即强制地发生在每次系统调用返回的前夕,以及每次从中断或异常处理返回到用户空间的前夕(只有在用户空间发生的中断或异常才会引起调度)。可参阅毛德操的《Linux内核源代码情景分析》上册的第4章关于进程调度的相关地方。

    那我们就来看一下,__irq_usr在返回到usr模式(用户空间)前夕是如何强制进行进程调度的。

    Line860,这是中断处理返回后,获取当前进程的task_struct指针,get_current_task是一个宏,它定义于arch/arm/kernel/entry-header.S中:

    .macro get_current_task, rd

    mov /rd, sp, lsr #13

    mov /rd, /rd, lsl #13

    .endm

    该宏是先将sp的值右移13位,再左移13位,把结果返回给参数,其实也就是只保留sp值的高19位,这代表着把堆栈指针的地址round8K地址边界上,这样它认为就得到了当前进程的task_struct数据结构了。它是因为内核在为每个进程分配一个task_struct结构时,实际上是分配两个连续的物理页面的(共8K),这两个页面的底部是用作进程的task_struct结构,而在结构的上面就用作进程的系统空间堆栈;数据结构task_struct的大小约为1K,进程系统空间堆栈大小就约为7K。当进程在系统空间运行时,常常需要访问当前进程自身的task_struct数据结构,为此内核中定义了一个宏操作current,提供指向当前进程task_struct结构的指针,它的实现实际上也与这里的get_current_task宏是差不多的。

    /* include/asm-arm/current.h */

    static inline struct task_struct *get_current(void)

    {

    register unsigned long sp asm ("sp");

    return (struct task_struct *)(sp & ~0x1fff);

    }

     

    #define current (get_current())

     

    再回到lin860get_current_task的参数是tsk,它实际上是r9寄存器,它也是定义于arch/arm/kernel/entry-header.S中的:

    tsk .req r9 @ current task

    这样r9寄存器就保存了当前进程的task_struct结构的指针了。

    Line861,程序跳转到ret_to_user,以完成从中断处理到返回用户空间的过程,前面提到的进程重新调度将在那里得以体现。ret_to_user定义于arch/arm/entry-common.S中:

    55 reschedule:

    56 bl SYMBOL_NAME(schedule)

    57 ret_disable_irq:

    58 disable_irq r1 @ ensure IRQs are disabled

    59 ENTRY(ret_to_user)

    60 ret_slow_syscall:

    61 ldr r1, [tsk, #TSK_NEED_RESCHED]

    62 ldr r2, [tsk, #TSK_SIGPENDING]

    63 teq r1, #0 @ need_resched => schedule()

    64 bne reschedule

    65 1: teq r2, #0 @ sigpending => do_signal()

    66 bne __do_signal

    67 restore:

    68 restore_user_regs

    69

    70 __do_signal:

    71 enable_irq r1

    72 mov r0, #0 @ NULL 'oldset'

    73 mov r1, sp @ 'regs'

    74 mov r2, why @ 'syscall'

    75 bl SYMBOL_NAME(do_signal) @ note the bl above sets lr

    76 disable_irq r1 @ ensure IRQs are disabled

    77 b restore

    Line61TSK_NEED_RESCHED值为20,它是task_struct结构中其成员变量need_resched相对于结构首地址的偏移量,所以此时r1的值就是当前进程task_struct结构里need_resched变量的值。同理在line62r2存储就是task_struct->sigpenging的值。

    line63~64可见,只有在当前进程的task_struct结构中的need_resched字段为非0时才会转到reschedule处去调用schedule,那么,谁来设置这个字段呢?当然是内核,从用户空间是访问不到进程的task_struct结构的,那么,内核又是在什么情况下设置这个字段的呢?除当前进程通过系统调用自愿让出运行以及在系统调用中因某种原因受阻以外,主要就是当因某种原因唤醒一个进程的时候,以及在时钟中断服务程序发现当前进程已经连续运行太久的时候。(此段摘抄于Linux内核源代码情景分析》)

    Line65~66,如果当前进程的task_struct结构中的sigpedding字段为非0时才会转到__do_signal处去调用do_signal处理信号。

    Line68 restore_user_regs,它是一个宏定义于arch/arm/kernel/head-header.S中:

    102 /*

    103 * Must be called with IRQs already disabled.

    104 */

    105 .macro restore_user_regs

    106 ldr r1, [sp, #S_PSR] @ Get calling cpsr

    107 ldr lr, [sp, #S_PC]! @ Get PC

    108 msr spsr, r1 @ save in spsr_svc

    109 ldmdb sp, {r0 - lr}^ @ Get calling r0 - lr

    110 mov r0, r0

    111 add sp, sp, #S_FRAME_SIZE - S_PC

    112 movs pc, lr @ return & move spsr_svc into cpsr

    113 .endm

     

    展开全文
  • 外部中断

    2020-11-20 12:41:42
    文章目录外部中断硬件控制外部中断矢量外部中断控制寄存器外部中断控制位软件控制外部中断0初始化方法外部中断1初始化方法外部中断程序框架示例P3.2口脉冲输入计数 硬件控制 外部中断矢量 中断源名称 中断源地址...

    外部中断

    硬件控制

    外部中断矢量

    中断源名称 中断源地址RAM 中断矢量ROM
    INT0 P3.2 (0B2H) 0003H
    INT1 P3.3 (0B3H) 0013H

    外部中断控制寄存器

    地址 A8H AFH AAH A8H
    名称 IE EA EX1 EX0
    地址 B8H BAH B8H
    名称 IP PX1 PX0
    地址 88H 8BH 8AH 89H 88H
    名称 TCON IE1 IT1 IE0 IT0

    外部中断控制位

    位地址 名称 作用
    AFH EA 总中断允许位。EA=1,开中总中断。即允许cpu中断。
    88H IT0 外部中断0触发方式选择位。IT0=1,电平触发,IT0=0,下降沿触发
    8AH IT1 外部中断1触发方式选择位。IT1=1,电平触发,IT1=0,下降沿触发
    89H IE0 外部中断0中断请求标志位。由中断源置1,在cpu处理中断后自动清零。
    8BH IE1 外部中断1中断请求标志位。由中断源置1,在cpu处理中断后自动清零。
    A8H EX0 外部中断0允许位。EX0=1,开中断,即允许cpu处理外部中断0
    AAH EX1 外部中断1允许位。EX1=1,开中断,即允许cpu处理外部中断1
    B8H PX0 外部中断0优先级选择位,PX0=1,高优先级。
    BAH PX1 外部中断1优先级选择位,PX1=1,高优先级。

    软件控制

    外部中断0初始化方法

    ; 外部中断0初始化程序
    ;谨慎使用低电平触发方式,cpu清零IE失效,IE只由中断源电平决定。
    ; 低优先级电平触发方式
    INT0initb:	;操作控制位
    SETB EA	
    SETB EX0
    RET
    
    INT0init:	;操作字节
    ORL IE, #81H
    RET
    
    ; 低优先级电平触发方式单开
    INT0inits:	;single open 单开
    MOV IE, #81H
    RET
    
    ; 高优先级电平触发方式
    INT0initp:	;p 高优先级
    ;SETB EA
    ;SETB EX0
    ;SETB PX0
    ORL IE, #81H
    ORL IP, #01H
    RET
    
    ; 高优先级电平触发方式单开
    INT0initps:	;single open 单开
    MOV IE, #81H
    MOV IP, #01H
    RET
    
    ; 低优先级下降沿触发方式
    INT0initf:	   ; PLF 下降沿 failling edge
    ;SETB EA
    ;SETB EX0
    ;SETB IT0
    ORL IE, #81H
    ORL TCON, #01H
    RET
    
    ; 低优先级下降沿触发方式单开
    INT0initfs:	;single open 单开
    MOV IE, #81H
    MOV TCON, #01H
    RET
    
    ; 高优先级下降沿触发方式
    INT0initpf:	;p 高优先级
    ;SETB EA
    ;SETB EX0
    ;SETB PX0
    ;SETB IT0
    ORL IE, #81H
    ORL IP, #01H
    ORL TCON, #01H
    RET
    
    ; 高优先级下降沿触发方式单开
    INT0initpfs:	;single open 单开
    MOV IE, #81H
    MOV IP, #01H
    MOV TCON, #01H
    RET
    

    外部中断1初始化方法

    ; 外部中断1初始化程序
    ; 低优先级电平触发方式
    INT1init:
    ;SETB EA
    ;SETB EX1
    ORL IE, #84H
    RET
    
    ; 低优先级电平触发方式单开
    INT1inits:	;single open 单开
    MOV IE, #84H
    RET
    
    ; 高优先级电平触发方式
    INT1initp:	;p 高优先级
    ;SETB EA
    ;SETB EX1
    ;SETB PX1
    ORL IE, #84H
    ORL IP, #04H
    RET
    
    ; 高优先级电平触发方式单开
    INT1initps:	;single open 单开
    MOV IE, #84H
    MOV IP, #04H
    RET
    
    ; 低优先级下降沿触发方式
    INT0initf:	   ; PLF 下降沿 failling edge
    ;SETB EA
    ;SETB EX1
    ;SETB IT1
    ORL IE, #84H
    ORL TCON, #04H
    RET
    
    ; 低优先级下降沿触发方式单开
    INT0initfs:	;single open 单开
    MOV IE, #84H
    MOV TCON, #04H
    RET
    
    ; 高优先级下降沿触发方式
    INT0initpf:	;p 高优先级
    ;SETB EA
    ;SETB EX1
    ;SETB PX1
    ;SETB IT1
    ORL IE, #84H
    ORL IP, #04H
    ORL TCON, #04H
    RET
    
    ; 高优先级下降沿触发方式单开
    INT0initpfs:	;single open 单开
    MOV IE, #84H
    MOV IP, #04H
    MOV TCON, #04H
    RET
    

    外部中断程序框架

    ;外部中断0入口地址
    ORG 0003H
    LJMP INT0PRO	;中断跳转使用LJMP
    
    ;外部中断1入口地址
    ORG 0013H
    LJMP INT1PRO	;中断跳转使用LJMP
    
    
    ;外部中断0程序
    INT0PRO:
    CLR EA	   ;保护现场
    PUSH ACC
    PUSH 02H  ; 保护R2
    PUSH PSW
    ;...
    SETB EA
    
    ;...		;中断程序
    
    CLR EA		;恢复现场
    ;...
    POP PSW
    POP 02H
    POP ACC
    SETB EA
    RETI			;中断返回使用RETI
    
    ;外部中断0调用子程序
    INT0PROs:		; subroutine 子程序
    CLR EA	   ;保护现场
    PUSH ACC
    PUSH 02H  ; 保护R2
    PUSH PSW
    ;...
    SETB EA
    
    LCALL SRT0		;调用子程序
    
    CLR EA		;恢复现场
    ;...
    POP PSW
    POP 02H
    POP ACC
    SETB EA
    RETI			;中断返回使用RETI
    
    SRT0:
    
    
    ;外部中断1程序
    INT1PRO:
    CLR EA	   ;保护现场
    PUSH ACC
    PUSH 02H  ; 保护R2
    PUSH PSW
    ;...
    SETB EA
    
    ;...		;中断程序
    
    CLR EA		;恢复现场
    ;...
    POP PSW
    POP 02H
    POP ACC
    SETB EA
    RETI			;中断返回使用RETI
    
    ;外部中断1调用子程序
    INT1PROs:		; subroutine 子程序
    CLR EA	   ;保护现场
    PUSH ACC
    PUSH 02H  ; 保护R2
    PUSH PSW
    ;...
    SETB EA
    
    LCALL SRT1		;调用子程序
    
    CLR EA		;恢复现场
    ;...
    POP PSW
    POP 02H
    POP ACC
    SETB EA
    RETI			;中断返回使用RETI
    
    SRT1:
    

    示例

    P3.2口脉冲输入计数

    ORG 0000H
    LCALL MAIN
    
    ;外部中断0入口地址
    ORG 0003H
    LJMP INT0PROs	;中断跳转使用LJMP
    
    MAIN:
    MOV R2, #0H		   ;R2 计数
    LCALL INT0initf
    MOV A, #14H		   ;用A和C演示保护、恢复现场
    SETB C			 
    SJMP $
    
    ; 初始化:低优先级下降沿触发方式
    INT0initf:	   ; PLF 下降沿 failling edge
    ;SETB EA
    ;SETB EX0
    ;SETB IT0
    ORL IE, #81H
    ORL TCON, #01H
    RET
    
    ;外部中断0
    INT0PROs:		; subroutine 子程序
    CLR EA	   ;保护现场
    PUSH ACC
    PUSH PSW
    SETB EA
    
    LCALL SRT0		;调用中断子程序
    
    CLR EA		;恢复现场
    POP PSW
    POP ACC
    SETB EA
    RETI
    
    ;中断程序
    SRT0:
    INC R2			;R2计算脉冲数目
    CLR C			;中断程序中修改A、C
    MOV A, #0H
    RET
    
    END
    
    展开全文
  • 外部中断抗干扰处理

    2020-08-18 13:23:40
    很多时候检测外部中断的时候,发现有误触发的情况。 1.通过软件方法 过滤。 2.加小电容.

    很多时候检测外部中断的时候,发现有误触发的情况。

    1.通过软件方法 过滤。

    2.加小电容.

    展开全文
  • 按键处理外部中断

    2017-10-28 10:38:49
    按键处理外部中断http://blog.csdn.net/qq_32220231/article/details/52273855 按键和中断部分  以按键触发中断为例,谈谈S5PV210的中断体系  要使用中断,首先要做好两个部分的工作:CPU中断的...
  • STM32外部中断处理流程及注意事项

    千次阅读 2012-10-09 11:35:04
    STM32的外部中断是以组为单位,例如PA0、PB0、PC0、PD0、PE0、PF0、PG0共用外部中断0,我们再使用时从中选择一个座位外部中断0即可,其他中断类推。 外部中断包括EXTI0、EXTI1、EXTI2、EXTI3、EXTI4、EXTI9_5、EXTI...
  • 中断程序中断处理程序 中断是指CPU接受到I/O设备发送的中断信号的一种响应。CPU会暂停正在执行的程序,保留CPU环境后自动转去执行该I/O设备的中断处理程序。执行完毕后回到断点。继续执行原来的程序。中断是由外部...
  • STM32外部中断

    2021-05-22 21:58:11
    中断处理完毕后.又返回被中断的程序处,继续执行下去 STM32的每个IO口都可以作为外部中断输入 STM32的中断控制器支持19个外部中断/事件请求: 线0~15:对应外部IO口的输入中断 线16:连接到PVD输出 线17:连接到...
  • CPU响应外部中断过程   step1:PIC一直在监视其IRQ引脚,检测是否有来自外部设备的中断信号。 step2:外部设备,如键盘,鼠标的某根信号线连接到IRQ0到IRQ n的一个引脚上。当有外部中断产生时,逻辑电路...
  • 从v都是vs的
  • 对于c51外部中断程序调用的理解 四川师范大学工学院2018级 徐浩宇 首先介绍一下C51外部中断的基本概念 中断的优点: 1、实时处理。需要CPU立即做出相应或处理。 2、异常处理。运行过程中,出现断电、程序出错...
  • Arduino 定时器中断 外部中断

    万次阅读 2019-04-09 17:29:31
    但如果此时发生了某一件事件B请求CPU迅速去处理(中断发生),CPU暂时中断当前的工作,转去处理事件B(中断响应和中断服务),待CPU将事件B处理完毕后,再回到原来被中断的地方继续执行程序(中断返回),这一过程成...
  • 一.轮询方式处理按键 ...中断方式处理按键 2017/11/21 23:30 参考:http://blog.csdn.net/u010479322/article/details/51447181 http://blog.51cto.com/9291927/1787523 1.异常向量表分析:  (1)、复位
  • 外部中断实验

    2019-04-12 17:23:33
    中断处理完毕后.又返回被中断的程序处,继续执行下去。 比如你正在家里看电视,而燃气炉上正在烧的热水已经沸腾,你听到了热水沸腾的声音,那么此时此刻你就要暂停你正在看电视的状态,转身去关闭煤气灶。也就是说...
  • 中断机构和中断处理程序

    千次阅读 2018-12-29 18:06:32
    什么是中断? 中断实际上是对信号做出的一种反应, 即CPU对I/O设备发来的中断信号的一种反应。是由外部设备引起的。俗称外中断。 在此插一嘴什么是陷入?...中断处理程序位于I/O系统的底层,直接与硬件进行交...
  • 要想正确地执行2440的外部中断,一般需要完成两个部分内容:中断初始化和中断处理函数。 在具体执行中断之前,要初始化好要用的中断。2440的外部中断引脚EINT与通用IO引脚F和G复用,要想使用中断功能,就要把相应...
  • IA64 Linux 外部中断处理机制(转)

    千次阅读 2008-09-20 14:49:00
    IA64 Linux 外部中断处理机制文档选项<!--document.write(打印本页);//-->打印本页<!--document.write(将此页作为电子邮件发送);//--> 将此页作为
  • 外部中断事件

    2018-11-12 23:13:23
    外部中断事件 众所周知,CPU在计算机系统中,除了能够执行指令,进行运算外,还应该有输入输出功能。比如说,我现在在打字输入‘a’,CPU他会处理这个按键,并在你的屏幕上显示‘a’。我们想想,它能显示,是不是...
  • 8051外部中断1解析

    千次阅读 2019-07-23 10:41:21
    程序实现的功能: 在实验板上左下方有一个黑色的独立按键(K4), 当我们按下它时, D1(LED灯)的状态就会...中断处理程序标号: 外部中断0 -----------> 0 定时器0中断 -----------> 1 外部中断1 -----------&...
  • 各种Arduino外部中断程序

    万次阅读 2017-02-12 14:57:08
    一、中断(Interrupt)的基本概念 ... 但如果此时发生了某一事件B请求CPU迅速去处理中断发生),CPU暂时中断当前的工作,转去处理事件B(中断响应和中断服务). 待CPU将事件B处理完毕后, 再回到原来被中断
  • 外部中断和内部中断

    千次阅读 2014-12-18 15:07:50
    举一个例子,外部中断:你在吃饭,这时候电话响了,你暂时放下餐具去接听电话在这里吃饭是你目前正在执行的程序,电话响了,是一个中断源,他是随机的,不定时发生,接完电话(处理完中断事件)你回来继续吃,(继续...
  • STM 32 外部中断

    2021-04-18 19:36:24
    中断处理完毕后.又返回被中断的程序处,继续执行下去 中断的功能 : (1)实时处理功能:在实时控制中,现场的各种参数、信息均随时问和现场而变化。这些外界变量可根据要求随时向CPU发出中断申请.请求CPU及时处珲...
  • 重要声明: 以下代码有粘贴 截取他人劳动成果的成分 如有...它能使CPU在运行过程中对外部事件发出的中断请求及时地进行处理处理完成后又立即返回断点,继续进行CPU原来的工作。引起中断的原因或者说发出中断请求的

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 124,366
精华内容 49,746
关键字:

外部中断处理过程是如何的