精华内容
下载资源
问答
  • 中断向量表

    千次阅读 2021-01-21 13:34:06
    中断向量表(interrupt vector table,IVT)是中断源的识别标志,可用来形成相应的中断服务程序的入口地址或存放中断服务程序的首地址称为中断向量。在Pc/AT中由硬件产生的中断标识码被称为中断类型号(当然,中断类...

    中断向量表(interrupt vector table,IVT)是中断源的识别标志,可用来形成相应的中断服务程序的入口地址或存放中断服务程序的首地址称为中断向量。在Pc/AT中由硬件产生的中断标识码被称为中断类型号(当然,中断类型号还有其他的产生方法,如指令中直接给出、CPU自动形成等),即在中断响应期间8259A产生的是当前请求中断的最高优先级的中断源的中断类型号。中断类型号和中断向量之间有下面的关系: 中断类型号×4=存放中断向量的首地址 有了存放中断向量的首地址,从该地址开始的4个存储单元中取出的就是中断服务程序的入口。

    介绍

    是指中断服务程序入口地址的偏移量与段基值一个中断向量占据4字节空间。中断向量表是8086系统内存最低端1K字节空间,它的作用就是按照中断类型号从小到大的顺序存储对应的中断向量,总共存储256个中断向量。在中断响应过程中,CPU通过从接口电路获取的中断类型号(中断向量号)计算对应中断向量在表中的位置,并从中断向量表中获取中断向量,将程序流程转向中断服务程序的入口地址。

    列表

    80x86系统是把所有的中断向量集中起来,按中断类型号从小到大的顺序存放到存储器的某一区域内,这个存放中断向量的存储区叫做中断向量表,即中断服务程序入口地址表。

    由于中断向量表可以在操作系统层面灵活修改,因此,不同的系统的中断向量表可能是不同的。此外,intel在CPU的保护模式下,占用了0x00 ~ 0x1F共32个中断号,在Linux下,是从0x20开始用于系统自身的中断的,包括8259芯片的中断重置。

    BIOS(实模式)的中断向量表如下:

    80x86

    I/O ADDR

    INT TYPE(16进制)

    FUNCTION

    00 ~ 03

    0

    除法溢出中断

    04 ~ 07

    1

    单步(用于DEBUG)

    08 ~ 0B

    2

    非屏蔽中断(NMI)

    0C ~ 0F

    3

    断点中断(用于DEBUG)

    10 ~ 13

    4

    溢出中断

    14 ~ 17

    5

    打印屏幕

    18 ~ 1F

    6/7

    保留

    8259主片

    I/O ADDR

    INT TYPE(16进制)

    FUNCTION

    20 ~ 23

    8

    定时器(IRQ0)

    24 ~ 27

    9

    键盘(IRQ1)

    28 ~ 2B

    A

    彩色/图形(IRQ2)

    2C ~ 2F

    B

    串行通信COM2(IRQ3)

    30 ~ 33

    C

    串行通信COM1(IRQ4)

    34 ~ 37

    D

    LPT2控制器中断(IRQ5)

    38 ~ 3B

    E

    磁盘控制器中断(IRQ6)

    3C ~ 3F

    F

    LPT1控制器中断(IRQ7)

    BIOS

    I/O ADDR

    INT TYPE(16进制)

    FUNCTION

    40 ~43

    10

    视频显示 I/O

    44 ~ 47

    11

    设备检验

    48 ~ 4B

    12

    测定存储器容量

    4C ~ 4F

    13

    磁盘 I/O

    50 ~ 53

    14

    RS-232 串行口 I/O

    54 ~ 57

    15

    系统描述表指针

    58 ~ 5B

    16

    键盘 I/O

    5C ~ 5F

    17

    打印机 I/O

    60 ~ 63

    18

    ROM BASIC 入口代码

    64 ~ 67

    19

    引导装入程序

    68 ~ 6B

    1A

    日时钟

    提供用户中断

    I/O ADDR

    INT TYPE(16进制)

    FUNCTION

    6C ~ 6F

    1B

    Ctrl - Break 控制的软中断

    70 ~ 73

    1C

    定时器控制的软中断

    74 ~ 77

    1D

    视频参数块

    78 ~ 7B

    1E

    软盘参数块

    7C ~ 7F

    1F

    图形字符扩展码

    在DOS系统(实模式)下,从0x20开始,用于操作系统本身。

    DOS

    I/O ADDR

    INT TYPE(16进制)

    FUNCTION

    80 ~ 83

    20

    DOS 中断返回

    84 ~ 87

    21

    DOS 系统功能调用

    88 ~ 8B

    22

    程序中止时 DOS 返回地址(用户不能直接调用)

    8C ~ 8F

    23

    Ctrl - Break 处理地址(用户不能直接调用)

    90 ~ 93

    24

    严重错误处理(用户不能直接调用)

    94 ~ 97

    25

    绝对磁盘读功能

    98 ~ 9B

    26

    绝对磁盘写功能

    9C ~ 9F

    27

    终止并驻留程序

    A0 ~ A3

    28

    DOS安全使用

    A4 ~ A7

    29

    快速写字符

    A8 ~ AB

    2A

    Microsoft 网络接口

    B8 ~ BB

    2E

    基本 SHELL 程序装入

    BC ~ BF

    2F

    多路服务中断

    CC ~ CF

    33

    鼠标中断

    104 ~ 107

    41

    硬盘参数块

    118 ~ 11B

    46

    第二硬盘参数块

    11C ~ 3FF

    47 ~ FF

    BASIC 中断

    保护模式

    在Linux下(保护模式),没有使用BIOS设置的中断向量表,0x00 ~ 0x1F是CPU保护模式下的默认中断向量,而0x20开始,都是被Linux系统重新设置的。

    X86占用的中断向量表如下:

    向量号

    助记符

    说明

    类型

    错误号

    产生源

    0

    #DE

    除出错

    故障

    DIV或IDIV指令。

    1

    #DB

    调试

    故障/陷阱

    任何代码或数据引用,或是INT 1指令。

    2

    --

    NMI中断

    中断

    非屏蔽外部中断。

    3

    #BP

    断点

    陷阱

    INT 3指令。

    4

    #OF

    溢出

    陷阱

    INTO指令。

    5

    #BR

    边界范围超出

    故障

    BOUND指令。

    6

    #UD

    无效操作码

    故障

    UD2指令或保留的操作码。

    7

    #NM

    设备不存在

    故障

    浮点或WAIT/FWAIT指令。

    8

    #DF

    双重错误

    异常终止

    有(0)

    任何可产生异常、NMI或INTR的指令。

    9

    --

    协处理器段超越(保留)

    故障

    浮点指令

    10

    #TS

    无效的任务状态段TSS

    故障

    任务交换或访问TSS

    11

    #NP

    段不存在

    故障

    加载段寄存器或访问系统段

    12

    #SS

    堆栈段错误

    故障

    堆栈操作或SS寄存器加载

    13

    #GP

    一般保护错误

    故障

    任何内存引用和其他保护检查

    14

    #PF

    页面错误

    故障

    任何内存引用

    15

    --

    (intel保留)

     

     

    16

    #MF

    x87 FPU浮点错误

    故障

     

    17

    #AC

    对齐检查

    故障

    有(0)

    对内存中任何数据的引用。

    18

    #MC

    机器检查

    异常终止

    错误码(若有)和产生源与CPU类型有关。

    19

    #XF

    SIMD浮点异常

    故障

     

    Linux自己设置的中断以后再添加。

    展开全文
  • 单片机中断向量表

    2012-04-12 23:05:57
    单片机中断向量表
  • ARM Cortex-M系列之中断向量表

    千次阅读 2020-04-22 14:13:48
    ARM Cortex-M架构的芯片的中断向...ARM Cortex-M手册规定在片上闪存起始地址处需要有一个有效的中断向量表。芯片上电或复位后首先从中向量表中读出入口函数地址和栈指针。将入口函数地址和栈指针装载入寻址寄存器...

    ARM Cortex-M架构的芯片的中断向量表(Interrupt Vector Table)前16位的中断由ARM核设定。16位以后的中断为芯片厂商自行定义。ARM Cortex-M架构芯片一般带有片上闪存(flash)。ARM Cortex-M手册规定在片上闪存起始地址处需要有一个有效的中断向量表。芯片上电或复位后首先从中向量表中读出入口函数地址和栈指针。将入口函数地址和栈指针装载入寻址寄存器(PC)和栈指针(SP)寄存器后,开始执行程序。

    中断向量表格式

    中断向量表每一位为一个32bit的地址。每一个地址对应一个中断函数的地址(第一位除外)。除了第一位以外,所有地址的目标都为寻址寄存器(PC)。当相应中断触发时,ARM Cortex-M硬件会自动把中断向量表中相应的中断函数地址装载入寻址寄存器(PC)然后开始执行中断函数。如上所述,前16位为ARM保留的系统中断,建议读者熟记。之后的中断为芯片自定义的外部中断,可以在使用时查询手册或者厂商提供的驱动程序。

    Exception NumberIRQ Number中断注释
    NANASP初始栈指针
    1NAReset复位函数地址
    2-14NMI不可屏蔽中断
    3-13Hard fault
    4-12Memory fault (Reserved in CM0/0+)内存管理错误中断
    5-11Bus fault (Reserved in CM0/0+)总线错误中断
    6-10Usage fault (Reserved in CM0/0+)使用错误中断
    7-9Reserved保留位(未使用)
    8-8Reserved保留位(未使用)
    9-7Reserved保留位(未使用)
    10-6Reserved保留位(未使用)
    11-5SVC通常用于请求privileged模式,或者在OS中用于请求系统资源
    12-4Reserved保留位(调试用)
    13-3Reserved保留位(未使用)
    14-2PendSV通常用于在OS中切换任务
    15-1SysTick系统节拍时钟中断

    中断向量表定义

    中断向量表可以通过汇编语言定义也可以通过C语言定义。以下列出两种方式的示例程序。

    C

    这里我们定义了一个数组,数组的每一项对应相应的中断函数。如上所述,数组的第一项为初始栈指针,第二项为入口函数地址。余下的所有中断都指向了一个通用中断函数。开发者可以根据需求替代相应的中断函数。
    上文还提到中断向量表需要放置于闪存起始地址处。这里__attribute__((section(".vectors")))为gcc的特定语法(如果开发者使用IAR或者其他编译器,语法有所不同),目的是告诉编译器在链接所有对象文件(objects)时把_vector[]数组放在链接脚本(linker script)中的.vectors段落(section)。在链接脚本中,.vector段落被定义于闪存起始处。

    #ifndef ARMV7M_PERIPHERAL_INTERRUPTS
    #  error ARMV7M_PERIPHERAL_INTERRUPTS must be defined to the number of I/O interrupts to be supported
    #endif
    
    extern void exception_common(void);
    unsigned _vectors[] __attribute__((section(".vectors"))) =
    {
      /* Initial stack */
      IDLE_STACK,
      /* Reset exception handler */
      (unsigned)&__start,
      /* Vectors 2 - n point directly at the generic handler */
      [2 ... (15 + ARMV7M_PERIPHERAL_INTERRUPTS)] = (unsigned)&exception_common
    };
    

    Assembly

    这里以STM32Cube生成的startup_stm32f401retx.S为示例。同样的,汇编程序中也定义了默认中断函数。所有中断也都指向了默认的中断函数Default_Handler(默认为无限循环)。

       .section  .text.Default_Handler,"ax",%progbits
    Default_Handler:
    Infinite_Loop:
      b  Infinite_Loop
      .size  Default_Handler, .-Default_Handler
    

    之后是中断向量表。这里为了缩减示例代码长度,略去了中间的中断函数定义。注意在初始处 .section .isr_vector,"a",%progbits语句指定了g_pfnVectors在链接是需要被放置在isr_vector段落,也就是闪存起始地址处。

    /******************************************************************************
    *
    * The minimal vector table for a Cortex M3. Note that the proper constructs
    * must be placed on this to ensure that it ends up at physical address
    * 0x0000.0000.
    * 
    *******************************************************************************/
      .section  .isr_vector,"a",%progbits
      .type  g_pfnVectors, %object
      .size  g_pfnVectors, .-g_pfnVectors
        
    g_pfnVectors:
      .word  _estack
      .word  Reset_Handler
      .word  NMI_Handler
      .word  HardFault_Handler
      .word  MemManage_Handler
      .word  BusFault_Handler
      .word  UsageFault_Handler
      .word  0
      .word  0
      .word  0
      .word  0
      .word  SVC_Handler
      .word  DebugMon_Handler
      .word  0
      .word  PendSV_Handler
      .word  SysTick_Handler
      
      /* External Interrupts */
      .word     WWDG_IRQHandler                   /* Window WatchDog              */                                        
      .word     PVD_IRQHandler                    /* PVD through EXTI Line detection */                        
      .word     TAMP_STAMP_IRQHandler             /* Tamper and TimeStamps through the EXTI line */            
      .word     RTC_WKUP_IRQHandler               /* RTC Wakeup through the EXTI line */                      
      .word     FLASH_IRQHandler                  /* FLASH                        */                                          
      .word     RCC_IRQHandler                    /* RCC                          */                                            
      .word     EXTI0_IRQHandler                  /* EXTI Line0                   */                                                            
      ... /* 省略 */
      .word     FPU_IRQHandler                    /* FPU                          */
      .word     0                                 /* Reserved                     */                   
      .word     0                                 /* Reserved                     */
      .word     SPI4_IRQHandler                   /* SPI4                         */   
    

    在中断向量表的定义之后,程序还将所有函数定义为.weak。也就是说如果开发者在其他地方重新定义了同样名称的中断函数,那么默认的中断函数实现会被自动覆盖。weak是GNU GCC编译器定义的关键词,如果采用其他编译器会有对应的关键词。

    /*******************************************************************************
    *
    * Provide weak aliases for each Exception handler to the Default_Handler. 
    * As they are weak aliases, any function with the same name will override 
    * this definition.
    * 
    *******************************************************************************/
       .weak      NMI_Handler
       .thumb_set NMI_Handler,Default_Handler
      
       .weak      HardFault_Handler
       .thumb_set HardFault_Handler,Default_Handler
      
       .weak      MemManage_Handler
       .thumb_set MemManage_Handler,Default_Handler
      
       .weak      BusFault_Handler
       .thumb_set BusFault_Handler,Default_Handler
    
       .weak      UsageFault_Handler
       .thumb_set UsageFault_Handler,Default_Handler
    
       .weak      SVC_Handler
       .thumb_set SVC_Handler,Default_Handler
    
       .weak      DebugMon_Handler
       .thumb_set DebugMon_Handler,Default_Handler
    ... /* 省略 */
       .weak      FPU_IRQHandler                  
       .thumb_set FPU_IRQHandler,Default_Handler  
    
       .weak      SPI4_IRQHandler                  
       .thumb_set SPI4_IRQHandler,Default_Handler 
    

    中断向量表偏移寄存器 (Vector Table Offset Register)

    ARM Cortex-M默认的中断向量表地址位于闪存起始地址处。但是ARM Cortex-M3/4系列提供了一个中断向量表偏移寄存器(Vector Table Offset Reigster)。系统中中断向量表的位置是0x00000000加上偏移寄存器的值。上电复位后这个寄存器值为0,所以中断向量表默认位于0x00000000闪存起始处。这个寄存器的目的是为了让开发者可以重新设置中断向量表的位置。

    • 中断向量表偏移寄存器第29位(bit 29)定义了中断向量表的位置。0表示位于闪存程序(code)中,1表示位于内存中(SRAM)。
    • 中断向量表寄存器低7位(bit 6~0)为系统预留位。
    • 偏移地址寄存器值有对齐要求。这个要求和系统中断数量或者说中断向量表长度相关。偏移地址寄存器值至少是128 (32words = 128bytes)的整数倍,这也意味着中断偏移寄存器中地址的低7位始终会是0。如果系统中断数量大于16个,则总中断数为ARM预留的16个中断加上n个系统中断。如果(n+2)不为2的指数,则向上找到最近的2的指数m。每个地址为4bytes所以对齐要求为m*4。例如系统有21个中断,加上ARM预留的16个中断位,则中断向量表有效长度为37words。最近的2的指数值为64words = 256bytes。所以偏移寄存器的值必须为256的整数倍。

    中断向量表偏移寄存器应用

    中断向量表偏移寄存器最普遍的用法是在系统上电复位后将中断向量表指向内存RAM地址空间。如上所述,系统上电复位后默认中断向量表位于闪存起始地址处。闪存的缺点是在系统运行时不能方便的修改任意字节。大部风芯片闪存采取的是NOR Flash。修改特定字节需要先将整个Bank的数据复制入内存RAM,擦除Bank中所有数据后,将RAM中特定字节更新后将整个Bank重新写入闪存。一般NOR Flash Bank的大小为512bytes到2Kbytes。这里就会存在很大的数据丢失或者损坏的风险。所以如果开发者希望在系统运行中更新闪存中的中断向量表中中断函数地址会很不方便而且风险很大。这时可以在系统复位后将闪存中的中断向量表复制进内存地址空间。然后利用中断向量表偏移寄存器将中断向量表指向位于内存中的中断向量表。这以后开发者就可以在程序运行过程中随时更新中断向量表中中断函数地址。

    在移植实时操作系统RTOS时,RTOS常常会在系统初始化时将中断向量表指向内存,然后将除ARM预留系统中断外,所有中断函数都指向统一的操作系统中断函数。中断触发后先调用操作系统中断函数,然后再调用开发者定义的中断函数。

    /****************************************************************************
     * Name: up_ramvec_initialize
     *
     * Description:
     *   Copy vectors to RAM an configure the NVIC to use the RAM vectors.
     *
     ****************************************************************************/
    
    void up_ramvec_initialize(void)
    {
      const up_vector_t *src;
      up_vector_t *dest;
      int i;
    
      /* The vector table must be aligned */
    
      DEBUGASSERT(((uint32_t)g_ram_vectors & ~NVIC_VECTAB_TBLOFF_MASK) == 0);
    
      /* Copy the ROM vector table at address zero to RAM vector table.
       *
       * This must be done BEFORE the MPU is enable if the MPU is being used to
       * protect against NULL pointer references.
       */
    
      src  = (const CODE up_vector_t *)getreg32(NVIC_VECTAB);
      dest = g_ram_vectors;
    
      irqinfo("src=%p dest=%p\n", src, dest);
    
      for (i = 0; i < ARMV7M_VECTAB_SIZE; i++)
        {
          *dest++ = *src++;
        }
    
      /* Now configure the NVIC to use the new vector table. */
    
      putreg32((uint32_t)g_ram_vectors, NVIC_VECTAB);
    
      /* The number bits required to align the RAM vector table seem to vary
       * from part-to-part.  The following assertion will catch the case where
       * the table alignment is insufficient.
       */
    
      irqinfo("NVIC_VECTAB=%08x\n", getreg32(NVIC_VECTAB));
      DEBUGASSERT(getreg32(NVIC_VECTAB) == (uint32_t)g_ram_vectors);
    }
    

    这样的优势是操作系统可以在调用用户中断函数前后进行个性化操作。比如中断函数本身并没有参数,如果操作系统调用用户中断函数,其就可以传递用户参数。以下为从开源RTOS Nuttx中摘录并简化的一个操作系统中断函数。中断触发后,系统用中断号搜索由操作系统创建的中断函数列表。如果用户在列表中注册了自定义的中断函数并且设置的中断函数参数,则系统会调用中断函数并传递用户预设的参数。如果列表中对应中断函数为空,系统会执行默认中断函数。

    void irq_dispatch(int irq, FAR void *context)
    {
      xcpt_t vector = irq_unexpected_isr;
      void *arg = NULL;
      unsigned int ndx = irq;
    
    #if NR_IRQS > 0
      if ((unsigned)irq < NR_IRQS)
      {
          if (g_irqvector[ndx].handler)
          {
              vector = g_irqvector[ndx].handler;
              arg    = g_irqvector[ndx].arg;
          }
          INCR_COUNT(ndx);
        }
    #endif
    
      /* Then dispatch to the interrupt handler */
      CALL_VECTOR(ndx, vector, irq, context, arg);
      UNUSED(ndx);
    
      /* Record the new "running" task.  g_running_tasks[] is only used by
       * assertion logic for reporting crashes.
       */
    
      g_running_tasks[this_cpu()] = this_task();
    }
    
    展开全文
  • ARM中断向量表的简单分析

    千次阅读 2018-01-10 20:38:43
    一般编写arm的裸机程序的时候,创建中断向量表就把它放在0x00000000~0x0000001c中,一般都放在这个位置上。但是中断向量表也可以放在0xffff0000~0xffff001c中,知道这是怎么设置的么?开始看到的时候真的有点奇怪,...

    一般编写arm的裸机程序的时候,创建中断向量表就把它放在0x00000000~0x0000001c中,一般都放在这个位置上。但是中断向量表也可以放在0xffff0000~0xffff001c中,知道这是怎么设置的么?开始看到的时候真的有点奇怪,因为在学习arm的时候,根本没去看arm中的协处理器CP15中的c1控制寄存器中的v位来控制,我们一般都使用默认的值0,则必须将中断向量表放在0x00000000~0x0000001c中。
    在看Linux内核对arm中的中断的初始化的时候,就一直对0xffff0000的地址有点怀疑,果然在网上发现这个地址不是随便写的,当我看到arm的协处理器进行控制,中断向量表的地址的时候,真的是哭笑不得啊!!
    有人肯定会问?v位是什么时候设置的呢?其实仔细的朋友就知道在head.S中,在创建完页表的时候,如add pc,r10,#PROCINFO_INITFUNC
    别急,r10保存在前面设置的procinfo的地址,但是很多人就觉得PROCINFO_INITFUNC的宏定义就不知道在哪找了,在include/asm/asm-offset.h中有定义。
    这些搞懂了,首先必须将中断向量表拷贝到0xffff0000的地址上去,把中断处理函数也拷贝到0xffff0200的地址上去,那么在中断向量表进行跳转的时候,如b vector_irq+stubs_offset,但是stubs_offset的偏移怎么设置呢?如果用b vector_irq的话,它就会跳转到原先的中断处理函数中去,因为它也拷贝到了0xffff0200的地址上去,所以将__vector_start-_stubs_start+0x200的话就转移到拷贝后的地址上去执行了。
    很多人应该会有点疑问吧,vector_irq好像找不到,别急,细心点,就在宏定义.macro vector_stubs,name,mode,correction中对各种处理函数有定义,所以很快就将中断向量表创建好了。

    Linux Version : 2.6.29
    1. start_kernel-->setup_arch-->early_trap_init
       1:  
        memcpy((void
     *)vectors, __vectors_start, __vectors_end - __vectors_start);
       2:  
        memcpy((void
     *)vectors + 0x200, __stubs_start, __stubs_end - __stubs_start);
       3:  
        memcpy((void
     *)vectors + 0x1000 - kuser_sz, __kuser_helper_start, kuser_sz);

    对于第一行:
    __vectors_start 和 __vectors_end 定义在 arch/arm/kernel/entry-armv.S , 它们之间保存了中断向量表。

       1:  
        .globl    __vectors_start
       2:  
    __vectors_start:
       3:  
        swi    SYS_ERROR0   
       4:  
        b    vector_und + stubs_offset
       5:  
        ldr    pc, .LCvswi + stubs_offset
       6:  
        b    vector_pabt + stubs_offset
       7:  
        b    vector_dabt + stubs_offset
       8:  
        b    vector_addrexcptn + stubs_offset
       9:  
        b    vector_irq + stubs_offset
      10:  
        b    vector_fiq + stubs_offset
      11:  
    
      12:  
        .globl    __vectors_end
      13:  
    __vectors_end:

    vectors 的地址为CONFIG_VECTORS_BASE , 在.config中定义为0xffff0000
    所以 第1行就是把中断向量表拷贝到0xffff0000
    对于第二行:
    vector_stub是一个带参数的宏,第一个是name,第二个是arm excepiton mode,第三个是为了得到返回地址,lr需要减去的偏移

      1:  
        .macro    vector_stub, name, mode, correction=0
       2:  
        .align    5
       3:  
    
       4:  
    vector_/name:
       5:  
        .if
     /correction
       6:  
        sub    lr, lr, #/correction          @得到正确的返回地址
       7:  
        .endif
       8:  
    
       9:  
        @
      10:  
        @ Save r0, lr_<exception> (parent PC) and spsr_<exception>
      11:  
        @ (parent CPSR)
      12:  
        @
      13:  
        stmia    sp, {r0, lr}        @ save r0, lr
      14:  
        mrs    lr, spsr
      15:  
        str    lr, [sp, #8]        @ save spsr
      16:  
    
      17:  
        @
      18:  
        @ Prepare for
     SVC32 mode.  IRQs remain disabled.
      19:  
        @ 
      20:  
        mrs    r0, cpsr
      21:  
        eor    r0, r0, #(/mode ^ SVC_MODE) @把cpsr内容与(mode^SVC_mode)异或,即r0里为SVC_MODE      
      22:  
        msr    spsr_cxsf, r0  @把r0的值写入整个spsr寄存器(cxsf表示要往哪个字节写入)
      23:  
    
      24:  
        @
      25:  
        @ the branch table must immediately follow this
     code
      26:  
        @
      27:  
        and    lr, lr, #0x0f  @lr为spsr_<exception>的值,此语句取到进入异常前的mode
      28:  
        mov    r0, sp         @ 
      29:  
        ldr    lr, [pc, lr, lsl #2] @lr=pc+mode*4,其中pc为紧接着30的指令,即vector_stub后的第一条指令
      30:  
        movs    pc, lr            @ movs会把spsr的值赋给cpsr,所以branch to handler in
     SVC mode
      31:  
    ENDPROC(vector_/name)
      32:  
        .endm

    再来看下vector 跳转表

       1:  
        .long
        __irq_usr            @  0  (USR_26 / USR_32)
       2:  
        .long
        __irq_invalid            @  1  (FIQ_26 / FIQ_32)
       3:  
        .long
        __irq_invalid            @  2  (IRQ_26 / IRQ_32)
       4:  
        .long
        __irq_svc            @  3  (SVC_26 / SVC_32)
       5:  
        .long
        __irq_invalid            @  4
       6:  
        .long
        __irq_invalid            @  5
       7:  
        .long
        __irq_invalid            @  6
       8:  
        .long
        __irq_invalid            @  7
       9:  
        .long
        __irq_invalid            @  8
      10:  
        .long
        __irq_invalid            @  9
      11:  
        .long
        __irq_invalid            @  a
      12:  
        .long
        __irq_invalid            @  b
      13:  
        .long
        __irq_invalid            @  c
      14:  
        .long
        __irq_invalid            @  d
      15:  
        .long
        __irq_invalid            @  e
      16:  
        .long
        __irq_invalid            @  f

    这里只有usr 和svc 有入口,而其他都是invalid ,是因为linux只会从usr(application) 和svc(kernel)两种mode跳转到exception来
    __stubs_start 和 __stubs_end 之间的代码简化后为:

       1:  
    __stubs_start:
       2:  
       vector_irq:    @vector_stub    irq, IRQ_MODE, 4
       3:  
       vector_dabt:   @vector_stub    dabt, ABT_MODE, 8
       4:  
       vector_pabt:   @vector_stub    pabt, ABT_MODE, 4
       5:  
       vector_und:    @vector_stub    und, UND_MODE
       6:  
       vector_fiq:
       7:  
       vector_addrexcptn:
       8:  
       .LCvswi:
       9:  
    __stubs_end:

    由此可以知道 __stubs_start 和 __stubs_end 之间定义了各种异常的入口
    我们再来看为什么异常入口是“b vector_und + stubs_offset”, 同时为什么stubs_offset 的定义如下
    .equ stubs_offset, __vectors_start + 0x200 - __stubs_start
    arm 的跳转指令b 是跳转到相对于PC的一个偏移地址( offset ),汇编器在编译时会对label 减去PC 得到offset,同时vector 拷贝后是如下排列的

    __vectors_start
    
    
    
    B vector_<exception>
    
    
    
    __vectors_end
    
    
    +0x200
    __stubs_start
    
    
    
    vector_<exception>
    
    
    
    __stubs_end

    因此,”b vector_” 的label –PC = offset, 而offset 为 b 指令与vector的offset,即

                             vector_<exception>-__stubs_start + ( 0x200 – ( PC_old – __vectors_start ) )
                           = vector_<exception> + __vectors_start + 0x200 – __stubs_start – PC_old

    所以异常入口为“b vector_und + stubs_offset”, 同时stubs_offset= __vectors_start + 0x200 – __stubs_start
    我们可以通过objdump反汇编来验证:

    00000060 <vector_irq>: 
        .globl    __stubs_start 
    __stubs_start: 
    /* 
    * Interrupt dispatcher 
    */ 
        vector_stub    irq, IRQ_MODE, 4 
      60 :    e24ee004     sub    lr, lr, #4    ; 0x4 
      64:    e88d4001     stm    sp, {r0, lr}
    …
    
    1d4:    e1a00000     .word    0xe1a00000 
    1d8:    e1a00000     .word    0xe1a00000 
    1dc:    e1a00000     .word    0xe1a00000
    000001e0 <vector_und>:
    /* 
    * Undef instr entry dispatcher 
    * Enter in UND mode, spsr = SVC/USR CPSR, lr = SVC/USR PC 
    */__vectors_start: 
        swi    SYS_ERROR0 
    284:    ef9f0000     svc    0x009f0000 
        b    vector_und + stubs_offset 
     288:    ea0000dd     b    604 <vector_swi+0x604> 
        ldr    pc, .LCvswi + stubs_offset 
    28c:    e59ff410     ldr    pc, [pc, #1040]    ; 6a4 <vector_swi+0x6a4> 
        b    vector_pabt + stubs_offset 
    290:    ea0000bb     b    584 <vector_swi+0x584> 
        b    vector_dabt + stubs_offset 
    294:    ea00009a     b    504 <vector_swi+0x504> 
        b    vector_addrexcptn + stubs_offset 
    298:    ea0000fa     b    688 <vector_swi+0x688> 
        b    vector_irq + stubs_offset 
    29c:    ea000078     b    484 <vector_swi+0x484> 
        b    vector_fiq + stubs_offset 
    2a0:    ea0000f7     b    684 <vector_swi+0x684>
                                  0x1e00x60 + 0x200 – ( 0x288 + 8 ) – 0x284 = 0xdd*4

    ARM Linux外部中断处理过程

    最近在学习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以下的版本,这个地址固定为0;ARMv4及其以上的版本,ARM异常向量表的地址受协处理器CP15的c1寄存器(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 
    3ENTRY(stext) 
    4 mov r12, r0 
    5 
    6 mov r0, #F_BIT | I_BIT | MODE_SVC @ make sure svc mode 
    7 msr cpsr_c, r0 @ and all irqs disabled 
    8 bl __lookup_processor_type 
    9 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行开始,17行code处将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中会读取CP15的control register(c1)的值到r1寄存器,并在r1寄存器中设置相应的标志位(其中包括设置V位=1),但在__xscale_setup中,r1寄存器并不立即写回到Cp15的control register中,而是在返回后的某个地方,接下来会慢慢分析到。__xscale_setup调用move pc, lr指令返回跳转到31行。
      31行,在lr寄存器中放置__switch_data中的数据__mmap_switched,在36行程序会跳转到__mmap_switched处。
      32,33行,把r0寄存器中的值写回到cp15的control 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_start,r5=__end,…,r8=cr_alignment,..,这里r8保存的是cr_alignment变量的地址.
      到了53行,由于之前r0保存的是cp15的control 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*/(传递参数顺次利用r0,r1,r2,r3) 
        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指令散转到异常处理代码.因为ARM中b指令是相对跳转,而且只有+/-32MB的寻址范围,所以把__stubs_start~__stubs_end之间的异常处理代码复制到了0xffff0200起始处.这里可直接用b指令跳转过去,这样比使用绝对跳转(ldr)效率高。

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

    在我的上一篇文章(ARM linux的中断向量表初始化分析)中已经分析了ARM Linux中断向量表是如何建立的,在这篇文章中,我将分析一下Linux内核的ARM体系下,中断处理是如何响应的一个过程。
    在ARM体系架构下,定义了7种异常,每一种异常都有自己的入口地址,即异常向量表,当异常发生时,处理器会自动跳转到相应的入口处执行。对于ARMv4及其以上的版本,异常向量表的起始位置由协处理器15(cp15)的控制寄存器(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只用到了arm的svc和usr模式,其他的几个模式都没怎么用。 
    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异常时,禁止新的fiq和irq异常 
    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(因为在行7,lr已经被置以spsr_irq),获取之前的处理器模式(psr &0b1111)。
    行18,根据获取的进入IRQ之前的处理器模式,查找相应的跳转入口(__irq_usr 对应于之前是USR模式,__irq_svc对于之前是SVC模式,对于其它模式均跳转到__irq_invalid,在linux系统中处理器进入IRQ之前只有usr和svc两种模式,其它模式均不允许开启IRQ)。此行实际上是:lr = pc+lr<<2,pc指向当前指令地址值加8个字节的地址,即pc指向当前指令的下两条指令的地址,所以pc在此时指向的是.LCtab_irq地址。
    (这里有点疑惑要进入__irq_usr,则18行lr应该为pc+4那么向回推算第7行的mrs lr, spsr中spsr[3:0]应该为0b0001;如果要进入__irq_svc,则18行lr应该为pc+16,那么spsr[3:0]应该为0b0100;
    而cprs[4:0]=
    10000 User 模式
    10011 SVC 模式
    请达人指点迷津。。。。)
    行19,跳转到相应入口,并且ARM寄存器r13和r14则切换到了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 

    .LCirq在entry-armv.S中是这样定义的:

    .LCirq: .word __temp_irq 

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

    r5-> old_r0 
    cpsr 
    pc 
    lr_svc 
    r4-> sp_svc 
    r12 
    r11r1 
    sp-> r0 

    行28的get_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 对照intmsk将intpnd中禁止的中断清0。因为intpnd在某一时刻只可以有一位为1,所以有一位被bics清0了,就会影响标志位从而beq跳转,return r0=0;从1001:开始所作的事情是循环查intpnd哪一位置为了1。有点疑惑的是tst 指令:
    tst 类似于 CMP,不产生放置到目的寄存器中的结果。而是在给出的两个操作数上进行操作并把结果反映到状态标志上。使用 tst 来检查是否设置了特定的位。操作数 1 是要测试的数据字而操作数 2 是一个位掩码。经过测试后,如果匹配则设置 Zero 标志,否则清除它。
    那么这里的tst \irqstat, #1,当zero置1了表示有中断位,为什么下面是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)的指针,而行29把sp指针赋予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 irq的action->flags = irq_flags(传递参数); 
    */ 
    __sti();//清除cpsr的I_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挂到对应irq的action链表中*/ 
    
    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内核进程调度的朋友都知道,进程的调度可以自愿的方式随时进行(内核里:schedule、schedule_timeout;用户空间:pause、nanosleep),还可以非自愿的发生,即强制地发生在每次系统调用返回的前夕,以及每次从中断或异常处理返回到用户空间的前夕(只有在用户空间发生的中断或异常才会引起调度)。可参阅毛德操的《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位,这代表着把堆栈指针的地址round到8K地址边界上,这样它认为就得到了当前进程的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()) 

    再回到lin860,get_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 

    Line61,TSK_NEED_RESCHED值为20,它是task_struct结构中其成员变量need_resched相对于结构首地址的偏移量,所以此时r1的值就是当前进程task_struct结构里need_resched变量的值。同理在line62,r2存储就是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 
    
    
    
    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) 

    这里有点疑惑要进入__irq_usr,则18行lr应该为pc+4那么向回推算第7行的mrs lr, spsr中spsr[3:0]应该为0b0001;如果要进入__irq_svc,则18行lr应该为pc+16,那么spsr[3:0]应该为0b0100;
    而cprs[4:0]=
    10000 User 模式
    10011 SVC 模式
    请达人指点迷津。。。。)
    行19,跳转到相应入口,并且ARM寄存器r13和r14则切换到了SVC模式下的寄存器

    这里第18行中的pc值正好是21行的.LCtab_irq,如果是在用户空间,User模式10000,逻辑左移两位为0x0=0b0000,即pc+0x0,恰好到了.word __irq_usr ,如果是在内核空间,svc模式10011,移位后为0xc=0b1100,及pc+0xc,正好到了.word __irq_svc,一点都没错(当然不可能错,系统不是跑得好好的吗)
    注意,pc值是当前指令地址+8
    关于get_irqnr_and_base宏中:

    bics \irqstat, \irqstat, \irqnr 对照intmsk将intpnd中禁止的中断清0。因为intpnd在某一时刻只可以有一位为1,所以有一位被bics清0了,就会影响标志位从而beq跳转,return r0=0;从1001:开始所作的事情是循环查intpnd哪一位置为了1。有点疑惑的是tst 指令:
    tst 类似于 CMP,不产生放置到目的寄存器中的结果。而是在给出的两个操作数上进行操作并把结果反映到状态标志上。使用 tst 来检查是否设置了特定的位。操作数 1 是要测试的数据字而操作数 2 是一个位掩码。经过测试后,如果匹配则设置 Zero 标志,否则清除它。
    那么这里的tst \irqstat, #1,当zero置1了表示有中断位,为什么下面是bne 1002f而不是beq?请教请教。。。。。。。)

    没找到你看的内核版本中该宏的详细定义,我在我的2.6.12中pxa体系中的此宏中没找到tst指令,但想你的问题估计还是对tst的误解
    pc值是当前指令地址+8

    是因为armv5是三级流水线么?

    pxa的宏里面好像是没用tst,这里我引申到s3c2410的宏里面。
    tst的定义我翻的是网上搜的arm指令集,里面是这么说的:
    TST : 测试位
    (Test bits)

    TST{条件}{P} ,

    Status = op_1 AND op_2

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

    我觉得在这里是有点转不过弯来了,,,


    ARM linux的中断向量表初始化分析
    Author: jimmy.li
    Time: 2007-06-09
      本文分析基于linux2.4.19 source,pxa 270 cpu.
      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以下的版本,这个地址固定为0;ARMv4及其以上的版本,ARM异常向量表的地址受协处理器CP15的c1寄存器(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
    3ENTRY(stext)    
    4               mov     r12, r0
    5                
    6               mov     r0, #F_BIT | I_BIT | MODE_SVC   @ make sure svc mode
    7               msr     cpsr_c, r0                      @ and all irqs disabled
    8               bl      __lookup_processor_type         
    9               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行开始,17行code处将lr放置为__ret标号处的相对地址,以便将来某处返回时跳转到31行继续运行;
      18行,对于我所分析的pxa270平台,它将是跳转到arch/arm/mm/proc-xscale.S中执行__xscale_setup函数,在__xscale_setup中会读取CP15的control register(c1)的值到r1寄存器,并在r1寄存器中设置相应的标志位(其中包括设置V位=1),但在__xscale_setup中,r1寄存器并不立即写回到Cp15的control register中,而是在返回后的某个地方,接下来会慢慢分析到。__xscale_setup调用move pc, lr指令返回跳转到31行。
      31行,在lr寄存器中放置__switch_data中的数据__mmap_switched,在36行程序会跳转到__mmap_switched处。
      32,33行,把r0寄存器中的值写回到cp15的control 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_start,r5=__end,…,r8=cr_alignment,..,这里r8保存的是cr_alignment变量的地址.
      到了53行,由于之前r0保存的是cp15的control 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*/
          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指令散转到异常处理代码.因为ARM中b指令是相对跳转,而且只有+/-32MB的寻址范围,所以把__stubs_start~__stubs_end之间的异常处理代码复制到了0xffff0200起始处.这里可直接用b指令跳转过去,这样比使用绝对跳转(ldr)效率高。

    展开全文
  • 所谓中断是指CPU在正常执行程序的过程中,由于内部/外部事件的触发或由程序的预先安排,引起CPU暂时中断当前正在运行的程序,而...中断向量表:中断类型号与相应中断源的中断处理程序入口地址之间的连接表; 中断服...

    所谓中断是指CPU在正常执行程序的过程中,由于内部/外部事件的触发或由程序的预先安排,引起CPU暂时中断当前正在运行的程序,而转去执行为内部/外部事件或程序预先安排的事件的服务子程序,待中断服务子程序执行完毕后,CPU再返回到被暂时中断的程序处(断点)继续执行原来的程序,这一过程成为中断。

    中断向量:中断的地址的变量;

    中断向量表:中断类型号与相应中断源的中断处理程序入口地址之间的连接表;

    中断服务程序:发送中断时所执行的中断代码

    中断机制
    中断机制是现代计算机系统中的基本机制之一,它在系统中起着通信网络的作用,以协调系统对各种外部事件的响应和处理,中断是实现多道程序设计的必要条件,中断是CPU 对系统发生的某个事件作出的一种反应。引起中断的事件称为中断源。中断源向CPU 提出处理的请求称为中断请求。发生中断时被打断程序的暂停点称为断点。CPU暂停现行程序而转为响应中断请求的过程称为中断响应。处理中断源的程序称为中断处理程序。CPU执行有关的中断处理程序称为中断处理。而返回断点的过程称为中断返回。中断的实现由软件和硬件综合完成,硬件部分叫做硬件装置,软件部分称为软件处理程序。

    众所周知,处理器的速度跟外围硬件设备的速度往往不在一个数量级上,因此,如果内核采取让处理器向硬件发出一个请求,然后专门等待回应的办法,显然降低内核效率。

    既然硬件的响应这么慢,那么内核就应该在此期间处理其他事务,等到硬件真正完成了请求的操作之后,再回过头来对它进行处理。想要实现这种功能,轮询(polling)可能会是一种解决办法。可以让内核定期对设备的状态进行查询,然后做出相应的处理。不过这种方法很可能会让那个内核做不少无用功,因为无论硬件设备是正在忙碌着完成任务还是已经大功告成,轮询总会周期性的重复执行。更好的办法是由我们来提供一种机制,让硬件在需要的时候再向内核发出信号(变内核主动为硬件主动),这就是中断机制。

    中断使得硬件得以与处理器进行通信。举个例子,在你敲打键盘的时候,键盘控制器(控制键盘的硬件设备)会发送一个中断,通知操作系统有键按下。中断本质是一种特殊的电信号,由硬件设备发向处理器。处理器接受到中断后,会马上向操作系统反映此信号的到来,然后就由os负责处理这些新到来的数据。硬件设备生成中断的时候并不考虑与处理器的时钟同步——换句话说就是中断随时可以产生。因此,内核随时可能因为新到来的中断而被打断。

    不同的设备对应的中断不同,而每个中断都通过一个惟一的数字标识。因此,来自键盘的中断就有别于来自硬盘的中断,从而使得操作系统能够对中断进行区分,并知道哪个硬件设备产生了哪个中断。这样,操作系统才能给不同的中断提供不同的中断处理程序。

    在它执行程序的时候,如果有另外的事件发生(比如用户又打开了一个程序)那么这时候就需要由计算机系统的中断机制来处理了。

    中断机制包括硬件的中断装置和操作系统的中断处理服务程序。

    让硬件在需要的时候再向内核发出信号。

    早期的微机系统中将由硬件产生的中断标识码(中断源的识别标志,可用来形成相应的中断服务程序的入口地址或存放中断服务程序的首地址)称为中断向量

    原文链接:https://blog.csdn.net/qq_33726988/article/details/97629164

    1、现实中比喻
    在这里插入图片描述
    2、人和硬件对比
    在这里插入图片描述
    3、中断的处理过程
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    4、异常向量表
    在这里插入图片描述
    转载自:https://blog.csdn.net/qq_18077275/article/details/89304215

    展开全文
  • 9.2 中断向量表的结构

    千次阅读 2019-09-20 21:02:56
    9.2 中断向量表的结构 我现在已经知道了,在运算的时候 一旦遇到了异常情况,就翻到第一页的第一行开始写的这些操作的指示,开始往下执行。开始往下执行,这就能解决问题了。但是问题在于这段操作,解决的是我那个...
  • 嵌入式杂谈之中断向量表

    千次阅读 多人点赞 2020-04-05 10:00:32
    至于为什么会提到中断向量表,主要是因为我自己在学习嵌入式Linux开发的过程中,好像学到的所有开始阶段都是要通过汇编完成的,好像没有汇编程序,整套系统就无法运行,那作为一套完整的系统,究竟从上电开始程序...
  • 问题背景 项目使用STM32F030,需要通过IAP进行固件升级,在FLASH里面要烧录两份代码:一个Boot loader,一个用户应用...但在STM32F0xx系列以Cortex-M0为内核的单片机中却怎么也找不到这个设置中断向量表的寄存器,用
  • 在MCU/ARM/DSP中,都有1个启动过程,这里主要设计复位...复位及中断的入口具有多个间隔固定的程序空间,它们可以是单独的跳转地址及函数指针,如ARM的中断向量表。也可是一块小的固定程序空间,如51的n*8+3和DSP5402的4字。
  • 中断向量表小计

    2012-03-10 12:50:41
    1. 系统引导时,中断向量表放在内存何处?  系统刚引导时,内存0x00000到0x0003FF共1KB的空间用于存放中断向量表。每个中断向量占用4个字节,共可存储256个中断向量。 2. 系统引导时,处在实模式下,只可寻址1MB...
  • F28335的中断源可分为片内外设中断源,如PWM、CAP、QEP、定时器等、片外中断源,外部中断输入引脚XINT1,XINT2引人的外部中断源等。 1、dsp28335三级中断机制 由于外设中断源有58个,而中断线只有12根,这就需要F...
  • linux内核在初始化阶段完成了对页式虚存管理的初始化以后,便调用trap_init和init_IRQ两个函数进行中断机制的初始化。其中trap_init主要是对一些系统保留的中断向量的初始化,而init_IRQ则主要用于外设的中断。 ...
  • 在 main 函数执行过程中,**如果 CPU 得到一个中断请求, PC 指针仍强制跳转到地址0X08000004 中断向量表处,而不是新程序的中断向量表,如图标号④所示;程序再根据我们设置的中断向量表偏移量,跳转到对应中断源新...
  • Linux内核ARM架构异常中断向量表

    千次阅读 2020-07-18 13:21:34
    Linux内核ARM架构异常中断向量表说明ARM中异常中断的种类ARM异常中断向量表内核异常向量表异常向量表跳转vector_srub宏内核启动建立异常向量表   当异常中断发生时,系统执行完当前指令后,将跳转到相应的异常中断...
  • 中断有两种,一种是由CPU外部产生的,对于执行中的软件来说,这种中断的发生完全是“异步”的,根本无法预料此类中断会在什么时候发生,一般由其他硬件设备产生(例如键盘中断);另一种是由CPU本身在执行程序的过程...
  • 一、2812的中断向量表 首先2812的中断向量表映射的地址是由4个标志位决定的:VMAP,M0M1MAP,MP/...而系统复位时VMAP=1,ENPIE=0,根绝中断向量表映射配置表可知,此时中断向量表映射在BootROM区域,地址:0x3FFFC0~0x3
  • stm32f0cortex m0 做bootloader中断向量表重映射 环境 MDK5 STM32F042K6Tx Cortex-M0 问题 使用了一款Cortex-M0内核的芯片STM32F042K6Tx,发现它中断向量表的重映射方法与STM32F10x不同 STM32F10x...
  • BIOS 中断向量表

    2019-04-04 22:02:10
    在bochs虚拟器调试中输入info ivt可以查看BIOS中断向量表 具体说明如下: 中断 描述 INT 00h CPU:除零错,或商不合法时触发 INT 01h CPU:单步陷阱,TF标记为打开状态时,每条指令执行后...
  • STM32中断向量表的偏移量设置方法

    万次阅读 2020-09-28 09:35:13
    STM32中断向量偏移 总结一下在IAP升级中APP程序的中断向量表的偏移 讲解中断偏移之前先看一下程序的启动流程 STM32F4 的内部闪存...序完成启动,而这张“中断向量表”的起始地址是 0x08000004,当中断来临,ST
  • 最近在解决一个问题,看到一篇代码,将中断向量表定位到RAM中,代码所在的文章在这里: ... 大家都知道,MCU的中断向量表通常是在flash的0x00000000地址,这一点从你编译...
  • Cortex-M0【中断向量表】【中断控制和系统控制】【硬件错误后仿真器的作用】   Cortex-M0中断向量表: __Vectors DCD __initial_sp ; Top of Stack  DCD Reset_Handler ; Reset Handler  ...
  • TC3XX 多核ECU的中断向量表解疑

    千次阅读 2020-11-26 05:20:58
    关于多核ECU的中断向量表,热心网友问了一个很好的问题 今天研究了一下,先说结论: 每个core都有自己单独的memory去存储自己的中断向量表,而且每个core的中断优先级是相互独立的。也就是说,每个core都可以有...
  • 80x86 中断向量表

    千次阅读 2017-06-07 12:23:29
    1. 系统引导时,中断向量表放在内存何处?  系统刚引导时,内存0x00000到0x0003FF共1KB的空间用于存放中断向量表。每个中断向量占用4个字节,共可存储256个中断向量。 2. 系统引导时,处在实模式下,只可寻址1MB...
  • ARM中断向量表与响应流程

    万次阅读 2014-10-18 18:24:20
    一首先中断向量表定义在哪里?如何加载?  二  中断向量表与中断服务程序 三处理流程 //////////////////////////////////////////////////////////////////////////////////////////////////// 一首先中断向...
  • 【STM32】NVIC中断优先级管理(中断向量表

    万次阅读 多人点赞 2018-04-08 19:55:22
    Cortex-M3内核支持256个中断,其中包含了16个内核中断(异常)和240个外部中断,并且具有256级的可编程中断设置。但是,STM32并没有使用CM3内核的全部东西,而是只用了它的一部分。STM32有84个中断,包括16个内核...
  • STM32 中断向量表的位置 、重定向

    千次阅读 2014-02-20 17:01:56
     这篇文章已经说了STM32的启动过程: ... ...我们也知道怎么跳到main函数了,那么,...从stm32f10x.s可以看到,已经定义好了一大堆的中断响应函数,这就是中断向量表,标号__Vectors,表示中断向量表入口地址,例
  • 最近又接手另外一个项目,用的是STM32。听说这份代码是大神写的。我粗略看了一下,云里雾里,好多好多的神技...咱们就从一个中断服务函数开始,顺藤摸瓜来看看代码里怎么安排中断向量的。 ISR(USART2_IRQn) { ui...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 10,535
精华内容 4,214
关键字:

中断向量表的地址指针

友情链接: sanjiiao.rar