精华内容
下载资源
问答
  • 开发人员必须要清楚的明白每一种遍历方式的特点、适用场合、以及在不同底层实现上的表现。下面就详细分析一下这一块内容。         数据元素是怎样在内存中存放的?  数据元素在内存中,...

    转载请注明出处:http://blog.csdn.net/hjf_huangjinfu/article/details/51220253


    概述

            Java语言中,提供了一套数据集合框架,其中定义了一些诸如List、Set等抽象数据类型,每个抽象数据类型的各个具体实现,底层又采用了不同的实现方式,比如ArrayList和LinkedList。

            除此之外,Java对于数据集合的遍历,也提供了几种不同的方式。开发人员必须要清楚的明白每一种遍历方式的特点、适用场合、以及在不同底层实现上的表现。下面就详细分析一下这一块内容。

     

     

     

     

    数据元素是怎样在内存中存放的?


            数据元素在内存中,主要有2种存储方式:

    1、顺序存储,Random Access(Direct Access):

            这种方式,相邻的数据元素存放于相邻的内存地址中,整块内存地址是连续的。可以根据元素的位置直接计算出内存地址,直接进行读取。读取一个特定位置元素的平均时间复杂度为O(1)。正常来说,只有基于数组实现的集合,才有这种特性。Java中以ArrayList为代表。

    2、链式存储,Sequential Access:

            这种方式,每一个数据元素,在内存中都不要求处于相邻的位置,每个数据元素包含它下一个元素的内存地址。不可以根据元素的位置直接计算出内存地址,只能按顺序读取元素。读取一个特定位置元素的平均时间复杂度为O(n)。主要以链表为代表。Java中以LinkedList为代表。

     

     

     

     

    Java中提供的遍历方式有哪些?

     

    1、传统的for循环遍历,基于计数器的:

            遍历者自己在集合外部维护一个计数器,然后依次读取每一个位置的元素,当读取到最后一个元素后,停止。主要就是需要按元素的位置来读取元素。这也是最原始的集合遍历方法。

    写法为:

    for (int i = 0; i < list.size(); i++) {
        list.get(i);
    }
    


    2、迭代器遍历,Iterator:

            Iterator本来是OO的一个设计模式,主要目的就是屏蔽不同数据集合的特点,统一遍历集合的接口。Java作为一个OO语言,自然也在Collections中支持了Iterator模式。

    写法为:

    Iterator iterator = list.iterator();
    while (iterator.hasNext()) {
        iterator.next();
    }


    3、foreach循环遍历:

            屏蔽了显式声明的Iterator和计数器。

            优点:代码简洁,不易出错。

            缺点:只能做简单的遍历,不能在遍历过程中操作(删除、替换)数据集合。

    写法为:

    for (ElementType element : list) {
    }

     

     

     


    每个遍历方法的实现原理是什么?

     

    1、传统的for循环遍历,基于计数器的:

            遍历者自己在集合外部维护一个计数器,然后依次读取每一个位置的元素,当读取到最后一个元素后,停止。主要就是需要按元素的位置来读取元素。

     

    2、迭代器遍历,Iterator:

            每一个具体实现的数据集合,一般都需要提供相应的Iterator。相比于传统for循环,Iterator取缔了显式的遍历计数器。所以基于顺序存储集合的Iterator可以直接按位置访问数据。而基于链式存储集合的Iterator,正常的实现,都是需要保存当前遍历的位置。然后根据当前位置来向前或者向后移动指针。

     

    3、foreach循环遍历:

            根据反编译的字节码可以发现,foreach内部也是采用了Iterator的方式实现,只不过Java编译器帮我们生成了这些代码。

     

     

     

     

    各遍历方式对于不同的存储方式,性能如何?

     

    1、传统的for循环遍历,基于计数器的:

            因为是基于元素的位置,按位置读取。所以我们可以知道,对于顺序存储,因为读取特定位置元素的平均时间复杂度是O(1),所以遍历整个集合的平均时间复杂度为O(n)。而对于链式存储,因为读取特定位置元素的平均时间复杂度是O(n),所以遍历整个集合的平均时间复杂度为O(n2)(n的平方)。

    ArrayList按位置读取的代码:直接按元素位置读取。

    transient Object[] elementData;
    
    public E get(int index) {
        rangeCheck(index);
        return elementData(index);
    }
    
    E elementData(int index) {
        return (E) elementData[index];
    }

    LinkedList按位置读取的代码:每次都需要从第0个元素开始向后读取。其实它内部也做了小小的优化。

    transient int size = 0;
    transient Node<E> first;
    transient Node<E> last;
    
    public E get(int index) {
        checkElementIndex(index);
        return node(index).item;
    }
    
    Node<E> node(int index) {
        if (index < (size >> 1)) {   //查询位置在链表前半部分,从链表头开始查找
            Node<E> x = first;
            for (int i = 0; i < index; i++)
                x = x.next;
            return x;
        } else {                     //查询位置在链表后半部分,从链表尾开始查找
            Node<E> x = last;
            for (int i = size - 1; i > index; i--)
                x = x.prev;
            return x;
        }
    }



     

    2、迭代器遍历,Iterator:

            那么对于RandomAccess类型的集合来说,没有太多意义,反而因为一些额外的操作,还会增加额外的运行时间。但是对于Sequential Access的集合来说,就有很重大的意义了,因为Iterator内部维护了当前遍历的位置,所以每次遍历,读取下一个位置并不需要从集合的第一个元素开始查找,只要把指针向后移一位就行了,这样一来,遍历整个集合的时间复杂度就降低为O(n);

            (这里只用LinkedList做例子)LinkedList的迭代器,内部实现,就是维护当前遍历的位置,然后操作指针移动就可以了:

    代码:

    public E next() {
        checkForComodification();
        if (!hasNext())
            throw new NoSuchElementException();
    
        lastReturned = next;
        next = next.next;
        nextIndex++;
        return lastReturned.item;
    }
    
    public E previous() {
        checkForComodification();
        if (!hasPrevious())
            throw new NoSuchElementException();
    
        lastReturned = next = (next == null) ? last : next.prev;
        nextIndex--;
        return lastReturned.item;
    }


     

    3、foreach循环遍历:

            分析Java字节码可知,foreach内部实现原理,也是通过Iterator实现的,只不过这个Iterator是Java编译器帮我们生成的,所以我们不需要再手动去编写。但是因为每次都要做类型转换检查,所以花费的时间比Iterator略长。时间复杂度和Iterator一样。

    使用Iterator的字节码:

        Code:
           0: new           #16                 // class java/util/ArrayList
           3: dup
           4: invokespecial #18                 // Method java/util/ArrayList."<init>":()V
           7: astore_1
           8: aload_1
           9: invokeinterface #19,  1           // InterfaceMethod java/util/List.iterator:()Ljava/util/Iterator;
          14: astore_2
          15: goto          25
          18: aload_2
          19: invokeinterface #25,  1           // InterfaceMethod java/util/Iterator.next:()Ljava/lang/Object;
          24: pop
          25: aload_2
          26: invokeinterface #31,  1           // InterfaceMethod java/util/Iterator.hasNext:()Z
          31: ifne          18
          34: return

     

    使用foreach的字节码:

        Code:
           0: new           #16                 // class java/util/ArrayList
           3: dup
           4: invokespecial #18                 // Method java/util/ArrayList."<init>":()V
           7: astore_1
           8: aload_1
           9: invokeinterface #19,  1           // InterfaceMethod java/util/List.iterator:()Ljava/util/Iterator;
          14: astore_3
          15: goto          28
          18: aload_3
          19: invokeinterface #25,  1           // InterfaceMethod java/util/Iterator.next:()Ljava/lang/Object;
          24: checkcast     #31                 // class loop/Model
          27: astore_2
          28: aload_3
          29: invokeinterface #33,  1           // InterfaceMethod java/util/Iterator.hasNext:()Z
          34: ifne          18
          37: return


     


     

     

     

    各遍历方式的适用于什么场合?

     

    1、传统的for循环遍历,基于计数器的:

            顺序存储:读取性能比较高。适用于遍历顺序存储集合。

            链式存储:时间复杂度太大,不适用于遍历链式存储的集合。

    2、迭代器遍历,Iterator:

            顺序存储:如果不是太在意时间,推荐选择此方式,毕竟代码更加简洁,也防止了Off-By-One的问题。

            链式存储:意义就重大了,平均时间复杂度降为O(n),还是挺诱人的,所以推荐此种遍历方式。

    3、foreach循环遍历:

            foreach只是让代码更加简洁了,但是他有一些缺点,就是遍历过程中不能操作数据集合(删除等),所以有些场合不使用。而且它本身就是基于Iterator实现的,但是由于类型转换的问题,所以会比直接使用Iterator慢一点,但是还好,时间复杂度都是一样的。所以怎么选择,参考上面两种方式,做一个折中的选择。

     

     

     

     

    Java的最佳实践是什么?

            Java数据集合框架中,提供了一个RandomAccess接口,该接口没有方法,只是一个标记。通常被List接口的实现使用,用来标记该List的实现是否支持Random Access。

            一个数据集合实现了该接口,就意味着它支持Random Access,按位置读取元素的平均时间复杂度为O(1)。比如ArrayList。

            而没有实现该接口的,就表示不支持Random Access。比如LinkedList。

            所以看来JDK开发者也是注意到这个问题的,那么推荐的做法就是,如果想要遍历一个List,那么先判断是否支持Random Access,也就是 list instanceof RandomAccess。

    比如:

    if (list instanceof RandomAccess) {
        //使用传统的for循环遍历。
    } else {
        //使用Iterator或者foreach。
    }
    


     

    展开全文
  • STM32的中断和事件

    千次阅读 2017-07-12 18:53:36
    1. 中断和异常的区别1.1 中断是指系统停止当前正在运行的程序转而其他服务,可能是程序接收了比自身高优先级的请求,或者是人为设置中断中断是属于正常现象。 1.2 异常是指由于cpu本身故障、程序故障或者请求服务...

    学习单片机一贯的套路,搞完时钟和GPIO就要折腾中断了。

    1. 中断和异常的区别

    1.1 中断是指系统停止当前正在运行的程序转而其他服务,可能是程序接收了比自身高优先级的请求,或者是人为设置中断,中断是属于正常现象。
    1.2 异常是指由于cpu本身故障、程序故障或者请求服务等引起的错误,异常属于不正常现象。

    Cortex-M3内核总共支持256个中断,其中包含16个内核异常和240个外部中断,但是各个芯片产商在设计芯片的时候会对CM3内核的芯片进行精简设计,如STM32F103系列,所搭载的异常响应系统,包含10个系统异常和60个外部中断,用一张表将它们管理起来,编号0~15位系统异常,16以上称为外部中断。

    系统异常清单:
    这里写图片描述
    外部中断清单:
    这里写图片描述

    这里写图片描述

    外部中断信号从核外发出,信号最终要传递到NVIC(嵌套向量中断控制器)。NVIC跟内核紧密耦合,它控制着整个芯片中断的相关功能。

    2. 中断优先级

    STM32支持两种优先级:抢占优先级和子优先级。所有优先级可编程的中断源都需要指定这两种优先级。
    抢占优先级决定是否可以产生中断嵌套,子优先级决定中断响应顺序,若两种优先级一样则看中断源在中断向量表中的偏移量,偏移量小的先响应。
    对这句话的解释为:

    (1)抢占优先级高的中断源可以中断抢占优先级低的中断处理函数,进而执行高优先级的中断处理函数,执行完毕后再继续执行被中断的低优先级的处理函数。
    (2)当两个中断源的抢占优先级相同时,即这两个中断将没有嵌套关系,当一个中断到来后,若此时cpu正在处理另一个中断,则这个后到来的中
    断就要等到前一个中断处理函数处理完毕后才能被处理,当这两个中断同时到达,则中断控制器会根据它们的子优先级决定先处理哪个。
    (3)如果它们的抢占优先级和子优先级都相等,则根据它们在中断表中的排位顺序决定先处理哪一个。

    每个中断源都需要被指定抢占优先级和子优先级,自然需要相应的寄存器来记录。在NVIC中有一个专门处理中断优先级的寄存器NICV_IPRx,用于配置中断源的优先级。IPR的宽度为8Bit,对于CM3内核来说,因为它支持的中断源为256个,那么原则上每个外部中断源可配置的优先级位0~255,数值越小优先级越高。但是因为绝大多数CM3芯片都会精简设计,所以不会使用到全部位,在STM32F103中只使用4Bit。
    这里写图片描述
    注意,个别系统系统的优先级是固定的,所以它们的中断优先级是不可编程的。

    2.1 CM3核的优先级分组方式
    CM3中定义了8个Bit用于设置中断源的优先级,这8个Bit可以分配为:

    (1)8bit用于响应优先级
    (2)最高1位用于指定抢占优先级,最低7位用于执行子优先级
    (3)最高2位用于指定抢占优先级,最低6位用于执行子优先级
    (4)最高3位用于指定抢占优先级,最低5位用于执行子优先级
    (5)最高4位用于指定抢占优先级,最低4位用于执行子优先级
    (6)最高5位用于指定抢占优先级,最低3位用于执行子优先级
    (7)最高6位用于指定抢占优先级,最低2位用于执行子优先级
    (8)最高7位用于指定抢占优先级,最低1位用于执行子优先级

    CM3核的优先级分组方式,使用的设置函数

    NVIC_SetPriorityGrouping()

    在Libraries\CMSIS\CM3\CoreSupport\core_cm3.h文件中实现:

    static __INLINE void NVIC_SetPriorityGrouping(uint32_t PriorityGroup)
    {
      uint32_t reg_value;
      uint32_t PriorityGroupTmp = (PriorityGroup & 0x07);                         /* only values 0..7 are used          */
    
      reg_value  =  SCB->AIRCR;                                                   /* read old register configuration    */
      reg_value &= ~(SCB_AIRCR_VECTKEY_Msk | SCB_AIRCR_PRIGROUP_Msk);             /* clear bits to change               */
      reg_value  =  (reg_value                       |
                    (0x5FA << SCB_AIRCR_VECTKEY_Pos) | 
                    (PriorityGroupTmp << 8));                                     /* Insert write key and priorty group */
      SCB->AIRCR =  reg_value;
    }

    该函数写在.h文件中,且声明为内联函数(__INLINE),内联函数跟宏替换差不多,可以避免函数调用的压栈出栈等开销。PriorityGroup的取值为0~7。

    2.2 STM32的优先级分组方式
    CM3核的优先级分组方式是针对256个中断全部用上的场合,但是Cortex-M3也允许在具有较少中断源时用较少的寄存器位指定中断源的优先级。STM32并没有使用Cortex-M3内核嵌套向量中断全套东西,而是使用了它的一部分:

    (1)STM32F103系列有16个内核异常和60个外部中断
    (2)STM32F107系列有16个内核异常和68个外部中断

    STM32的优先级分组使用标准库函数

    NVIC_PriorityGroupConfig()

    该函数在Libraries\STM32F10x_StdPeriph_Driver\src\misc.c中实现:

    void NVIC_PriorityGroupConfig(uint32_t NVIC_PriorityGroup)
    {
      /* Check the parameters */
      assert_param(IS_NVIC_PRIORITY_GROUP(NVIC_PriorityGroup));
    
      /* Set the PRIGROUP[10:8] bits according to NVIC_PriorityGroup value */
      SCB->AIRCR = AIRCR_VECTKEY_MASK | NVIC_PriorityGroup;
    }

    可见,这个函数也是在设置SCB->AIRCR寄存器,只是这里的取值为:

    (1)NVIC_PriorityGroup_0:0bit for 抢占优先级,4bit for 子优先级,即有24次方个子优先级
    (2)NVIC_PriorityGroup_1:1bit for 抢占优先级,3bit for 子优先级,即有23次方个子优先级
    (3)NVIC_PriorityGroup_2:2bit for 抢占优先级,2bit for 子优先级,即有22次方个子优先级
    (4)NVIC_PriorityGroup_3:3bit for 抢占优先级,1bit for 子优先级,即有21次方个子优先级
    (5)NVIC_PriorityGroup_4:4bit for 抢占优先级,0bit for 子优先级,即有20次方个子优先级

    3. NVIC操作相关函数

    NVIC的描述结构体在core_cm3.h中:

    typedef struct
    {
      __IO uint32_t ISER[8];        /* 中断使能寄存器(Interrupt Set Enable Register),Offset: 0x000 */
           uint32_t RESERVED0[24];                                   
      __IO uint32_t ICER[8];        /* 中断清除寄存器(Interrupt Clear Enable Register),Offset: 0x080 */
           uint32_t RSERVED1[24];                                    
      __IO uint32_t ISPR[8];        /* 中断使能挂起寄存器(Interrupt Set Pending Register),Offset: 0x100 */
           uint32_t RESERVED2[24];                                   
      __IO uint32_t ICPR[8];        /* 中断清除挂起寄存器(Interrupt Clear Pending Register),Offset: 0x180 */
           uint32_t RESERVED3[24];                                   
      __IO uint32_t IABR[8];        /* 中断有效位寄存器(Interrupt Active bit Register ),Offset: 0x200 */
           uint32_t RESERVED4[56];                                   
      __IO uint8_t  IP[240];        /* 中断优先级寄存器(Interrupt Priority Register),Offset: 0x300 (8Bit wide) */
           uint32_t RESERVED5[644];                                  
      __O  uint32_t STIR;           /* 软中断触发寄存器(Software Trigger Interrupt Register),Offset: 0xE00 */
    }  NVIC_Type;

    编程中常用的是ISER、ICER和IP这三个寄存器。ISER和ICER分别用于enable、disable中断,IP用于控制中断优先级。
    同在core_cm3.h中,定义了对结构体成员的操作函数,这是针对Cortex-M3内核芯片都适用的函数:

    (1)设置优先级分组寄存器: NVIC_SetPriorityGrouping(uint32_t PriorityGroup)
    (2)从NVIC中断控制器得到优先级分组设置值: NVIC_GetPriorityGrouping(void)
    (3)使能中断: NVIC_EnableIRQ(IRQn_Type IRQn) 
    (4)失能中断: NVIC_DisableIRQ(IRQn_Type IRQn) 
    (5)获取挂起中断编号: NVIC_GetPendingIRQ(IRQn_Type IRQn) 
    (6)设置中断挂其位: NVIC_SetPendingIRQ(IRQn_Type IRQn) 
    (7)清除中断挂起位: NVIC_ClearPendingIRQ(IRQn_Type IRQn) 
    (8)NVIC_GetActive(IRQn_Type IRQn)
    (9)设置中断源的中断优先级: NVIC_SetPriority(IRQn_Type IRQn, uint32_t priority) 
    (10)获取中断源的中断优先级: NVIC_GetPriority(IRQn_Type IRQn) 
    (11)编码一个中断的优先级,不知道干嘛: NVIC_EncodePriority (uint32_t PriorityGroup, uint32_t PreemptPriority, uint32_t SubPriority)
    (12)解码一个中断的优先级,不知道干嘛: NVIC_DecodePriority (uint32_t Priority, uint32_t PriorityGroup, uint32_t* pPreemptPriority, uint32_t* pSubPriority) 
    (13)系统复位: NVIC_SystemReset(void)

    在外设库misc.h定义了针对STM32的NVIC的初始化描述结构体:

    typedef struct
    {
      uint8_t NVIC_IRQChannel;                    /* 中断源 */
      uint8_t NVIC_IRQChannelPreemptionPriority;  /* 抢占优先级 */
      uint8_t NVIC_IRQChannelSubPriority;         /* 子优先级 */
      FunctionalState NVIC_IRQChannelCmd;         /* 中断使能或者失能 */   
    } NVIC_InitTypeDef;

    misc.c也定义了针对STM32的NVIC的操作函数:

    (1)设置优先级分组寄存器: NVIC_PriorityGroupConfig(uint32_t NVIC_PriorityGroup)
    (2)初始化NVIC_InitTypeDef类的结构体: NVIC_Init(NVIC_InitTypeDef* NVIC_InitStruct)
    (3)设置中断向量表位置和偏移: NVIC_SetVectorTable(uint32_t NVIC_VectTab, uint32_t Offset)
    系统可以选择从SRAM启动,也可以选择从flash启动,对应的启动地址会映射到0地址处,而中断向量表是要被放在0地址处的,所以要将中      断向量表放在SRAM/flash的起始位置。函数参数一的取值为NVIC_VectTab_RAM/NVIC_VectTab_FLASH,参数二的取值必须是0x200的整数倍(STM32就是这么规定的)。
    (4)选择进入低功耗模式的条件: NVIC_SystemLPConfig(uint8_t LowPowerMode, FunctionalState NewState) 
    参数一取值NVIC_LP_SEVONPEND/NVIC_LP_SLEEPDEEP/NVIC_LP_SLEEPONEXIT,参数二取值ENABLE/DISABLE

    4. EXTI–外部中断和事件控制器

    EXTI有20个中断/事件线,每个GPIO都可以被设置为中断/事件的输入线,占用EXTI0~EXTI15,还有另外4根用于特定的外设事件的EXTI16~EXTI19:
    这里写图片描述
    注意,EXTIx与GPIOx的对应关系,EXTI0只能和P[x]0绑定(x = A、B、C、D…),
    这里写图片描述
    实现绑定操作的函数声明位于标准库Libraries\STM32F10x_StdPeriph_Driver\inc\stm32f10x_gpio.h中:

    GPIO_EXTILineConfig(uint8_t GPIO_PortSource, uint8_t GPIO_PinSource)
    参数一GPIO_PortSource的取值为GPIO_PortSourceGPIOx (x = A..G),
    参数二GPIO_PinSource的取值为GPIO_PinSourcex(x = 0..15)

    这个函数在一般初始化EXTI寄存器时候调用。因为外部中断是GPIO引脚的复用功能,所以同时要开启GPIO复用功能的时钟:

    RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE)

    5. EXTI描述结构体的初始化

    EXTI描述结构体声明在标准外设库Libraries\STM32F10x_StdPeriph_Driver\inc\stm32f10x_exti.h中:

    typedef struct
    {
      uint32_t EXTI_Line;               /* 中断事件线 */
      EXTIMode_TypeDef EXTI_Mode;       /* EXTI模式,事件/中断 */
      EXTITrigger_TypeDef EXTI_Trigger; /* 触发类型 */
      FunctionalState EXTI_LineCmd;     /* EXTI使能 */ 
    }EXTI_InitTypeDef;

    与EXTI操作相关的函数有:

    (1)去除EXTI_InitTypeDef结构体的初始化:EXTI_DeInit(void)
    (2)初始化EXTI_InitTypeDef结构体: EXTI_Init(EXTI_InitTypeDef* EXTI_InitStruct) 
    (3)默认初始化:EXTI_StructInit(EXTI_InitTypeDef* EXTI_InitStruct) 
    (4)EXTI_GenerateSWInterrupt(uint32_t EXTI_Line) 产生一个软件中断
    (5)获取产生中断的标志:EXTI_GetFlagStatus(uint32_t EXTI_Line) 
                       EXTI_GetITStatus(uint32_t EXTI_Line)
    (6)清除中断产生标志:EXTI_ClearFlag(uint32_t EXTI_Line) 
                      EXTI_ClearITPendingBit(uint32_t EXTI_Line)

    获取/清除产生中断的标志的实现是一样的,但是为什么要分成两组函数?
    也许这是STM32标准外设库设计者出自于为兼容性考虑吧。有的ARM芯片的中断体系分为两层,也就是说中断信号要抵达NVIC需要两层筛选,同理清除中断标志也需要清除两层。但是在CM3核的ARM只设计了一层,STM32为了兼容其他芯片,依旧还是将函数设计成两层,只不过这两层的实现体是一致的。

    6. 编程实现按键中断

    下来编程操作STM32的中断。未能免俗,还是以按键中断为例。
    实验采用正点原子miniSTM32硬件平台,
    这里写图片描述

    按键KEY0(PC5)和KEY1(PA15)的原理图:
    这里写图片描述
    这里写图片描述
    这里写图片描述

    LED0(PA8)和LED1(PD2)的原理图:
    这里写图片描述
    这里写图片描述

    实验实现按键产生外部中断,在中断处理函数中实现反向控制LED灯。编程的要点为:

    (1)初始化用来产生中断信号的GPIO
    (2)初始化中断/事件控制器EXTI
    (3)配置NVIC
    (4)编写中断服务函数

    EXTI用于设置中断源的触发方式、中断/事件类型和具体是哪一个中断源。
    中断信号产生后最终传递到NVIC,NVIC控制中断源优先级、中断线通道等,以便比对中断信号、根据优先级调用中断服务函数。
    实验采用MDK4集成开发环境,工程的目录结构如下:
    这里写图片描述

    exti_led.h声明实验中用到的函数:

    #ifndef __EXTI_LED_H__
    #define __EXTI_LED_H__
    
    #include "stm32f10x_conf.h"
    
    void TimeDelay(void);
    void Led_CfgInit(void);
    void Exti_CfgInit(void);
    void NVIC_CfgInit(void);
    void Key_CfgInit(void);
    
    #endif /* __EXTI_LED_H__ */

    mian.c实现各个功能模块:
    (1)初始化外接LED的GPIO引脚

    //PA8-->LED0,PD2-->LED1
    void Led_CfgInit(void)
    {
        GPIO_InitTypeDef GPIO_InitTypeStu;
    
        //开启PA和PD端口的时钟
        RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOD, ENABLE);
    
        //初始化PA8引脚为推挽输出
        GPIO_InitTypeStu.GPIO_Mode = GPIO_Mode_Out_PP;
        GPIO_InitTypeStu.GPIO_Pin = GPIO_Pin_8;
        GPIO_InitTypeStu.GPIO_Speed = GPIO_Speed_50MHz;
        GPIO_Init(GPIOA, &GPIO_InitTypeStu);
        GPIO_SetBits(GPIOA,GPIO_Pin_8);         //LED0初始状态为灭
    
        //初始化PD2引脚为推挽输出
        GPIO_InitTypeStu.GPIO_Pin = GPIO_Pin_2;
        GPIO_Init(GPIOD, &GPIO_InitTypeStu);
        GPIO_SetBits(GPIOD,GPIO_Pin_2);         //LED1初始状态为灭
    }

    (2)初始化外接按键的引脚

    void Key_CfgInit(void)
    {
        GPIO_InitTypeDef GPIO_InitTypeStu;
    
        //开启PC和PA的时钟
        RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC | RCC_APB2Periph_GPIOA, ENABLE);
    
        //初始化PC5为上拉输入
        GPIO_InitTypeStu.GPIO_Mode = GPIO_Mode_IPU; // GPIO_Mode_IN_FLOATING;   
        GPIO_InitTypeStu.GPIO_Pin = GPIO_Pin_5;
        GPIO_InitTypeStu.GPIO_Speed = GPIO_Speed_50MHz;
        GPIO_Init(GPIOC, &GPIO_InitTypeStu);
    
        //初始化PA15为上拉输入
        GPIO_InitTypeStu.GPIO_Mode = GPIO_Mode_IPU; // GPIO_Mode_IN_FLOATING;   
        GPIO_InitTypeStu.GPIO_Pin = GPIO_Pin_15;
        GPIO_InitTypeStu.GPIO_Speed = GPIO_Speed_50MHz;
        GPIO_Init(GPIOA, &GPIO_InitTypeStu);
    }

    一开始我是设置PC5和PA15为浮空输入的,因为只需要考虑到按键按下是低电平,但是实验表明,浮空输入并不能确定引脚状态,即使在按键按下以后也不能触发中断,所以还是要将它们设置为上拉/下拉输入。

    (3)初始化EXTI

    void Exti_CfgInit(void)
    {
        EXTI_InitTypeDef EXTI_InitStu;  
        RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
    
        //EXTI5
        GPIO_EXTILineConfig(GPIO_PortSourceGPIOC, GPIO_PinSource5); //将具体GPIO和外部中断事件线绑定
        EXTI_InitStu.EXTI_Line = EXTI_Line5;
        EXTI_InitStu.EXTI_Mode = EXTI_Mode_Interrupt;               //中断模式
        EXTI_InitStu.EXTI_Trigger = EXTI_Trigger_Rising;            //上升沿触发
        EXTI_InitStu.EXTI_LineCmd = ENABLE;
        EXTI_Init(&EXTI_InitStu);
    
        //EXTI15
        GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource15);    
        EXTI_InitStu.EXTI_Line = EXTI_Line15;
        EXTI_InitStu.EXTI_LineCmd = ENABLE;
        EXTI_Init(&EXTI_InitStu);
    }

    GPIO用于传输外部中断信号属于GPIO复用部分的功能,所以需要打开RCC_APB2Periph_AFIO的时钟。

    (4)初始化NVIC

    void NVIC_CfgInit(void)
    {
        NVIC_InitTypeDef NVIC_InitTypeStu;
    
        NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);         //设置中断分组
        NVIC_InitTypeStu.NVIC_IRQChannel = EXTI9_5_IRQn;        //外部中断线EXTI5属于共享中断
        NVIC_InitTypeStu.NVIC_IRQChannelPreemptionPriority = 1; //抢占优先级
        NVIC_InitTypeStu.NVIC_IRQChannelSubPriority = 1;        //子优先级
        NVIC_InitTypeStu.NVIC_IRQChannelCmd = ENABLE;   
        NVIC_Init(&NVIC_InitTypeStu);
    
        NVIC_InitTypeStu.NVIC_IRQChannel = EXTI15_10_IRQn;      //外部中断线EXTI15属于共享中断
        NVIC_InitTypeStu.NVIC_IRQChannelSubPriority = 2;
        NVIC_Init(&NVIC_InitTypeStu);
    }

    (5)延时函数,这里只是简单延时,并没有精准计算

    void TimeDelay(void)
    {
        int i, j;
    
        for (i = 0; i < 100; i++)
            for (j = 0; j < 1000; j++);
    }

    (6)main函数

    int main(void)
    {
        Led_CfgInit();
        Key_CfgInit();
        Exti_CfgInit();
        NVIC_CfgInit();
    
        while(1);
        return 0;
    }

    当用户按下KEY0时,会进入EXTI9_5_IRQHandler()处理函数中,按下KEY1则进入EXTI15_10_IRQHandler()处理函数。这两个函数名是在启动文件写的,详细可参考文章http://blog.csdn.net/qq_29344757/article/details/74932235。处理函数的实现如下:

    void EXTI9_5_IRQHandler(void)
    {   
        //int i = 50;
        TimeDelay();    //延时去抖动
    
        if (!GPIO_ReadInputDataBit(GPIOC, GPIO_Pin_5) && EXTI_GetFlagStatus(EXTI_Line5))
        {
            GPIO_WriteBit(GPIOA, GPIO_Pin_8, ((BitAction)!GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_8)));
    
            //清中断
            //EXTI_ClearFlag(EXTI_Line5);
            EXTI_ClearITPendingBit(EXTI_Line5);
        }
    }
    
    void EXTI15_10_IRQHandler(void)
    {   
        TimeDelay();    //延时去抖动
    
        if (!GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_15) && EXTI_GetFlagStatus(EXTI_Line15))
        {
            GPIO_WriteBit(GPIOD, GPIO_Pin_2, ((BitAction)!GPIO_ReadInputDataBit(GPIOD, GPIO_Pin_2)));
    
            //清中断
            //EXTI_ClearFlag(EXTI_Line15);
            EXTI_ClearITPendingBit(EXTI_Line15);
        }
    }

    函数可在用户自定义的文件实现,也可以在标准库提供的工程模板文件stm32f10x_it.c中实现。由于是共享中断,所以需要调用EXTI_GetFlagStatus()函数来判断是否为目的中断源。其实在其他很多非共享中断的场合也有使用EXTI_GetFlagStatus()函数判断目的中断源,无关紧要了。

    STM32的中断系统非常强大,每一个外设都可以产生中断。关于STM32中断/事件部分的学习就先告一段落,详细内容可参考STM32F10X-中文参考手册》、《 Cortex-M3 内核编程手册》的4.3章节。下一个学习任务–系统定时器SysTick。

    展开全文
  • TL0计数溢出时不仅使溢出中断标志位TF0置1,而且还自动把TH0中的内容重新装载到TL0中。TL0用作8位计数器,TH0用以保存初值。  (2)用于定时工作方式时间时,最大定时时间=256*1个机器周期;用于计数工作方式时,...

    (1)模式2把TL0(或TL1)配置成一个可以自动重装载的8位定时器/计数器。TL0计数溢出时不仅使溢出中断标志位TF0置1,而且还自动把TH0中的内容重新装载到TL0中。TL0用作8位计数器,TH0用以保存初值。 
    (2)用于定时工作方式时间时,最大定时时间=256*1个机器周期;用于计数工作方式时,最大计数长度(TH0初值=0)为2^8=256个外部脉冲。  
    这种工作方式可省去用户软件重装初值的语句,并可产生相当精确定时时间,特别适于作串行波特率发生器

    展开全文
  • 1 轮询与中断 外部设备与中央处理器交互一般有两种手段:轮询和中断。 (1)轮询(Polling) 很多I/O设备都有一个状态寄存器,用于描述设备当前的工作状态,每当设备状态发生改变时,设备将修改相应状态寄存器...

    1 轮询与中断

    外部设备与中央处理器交互一般有两种手段:轮询和中断。

    (1)轮询(Polling)

    很多I/O设备都有一个状态寄存器,用于描述设备当前的工作状态,每当设备状态发生改变时,设备将修改相应状态寄存器位。通过不断查询设备的状态寄存器,CPU就可以了解设备的状态,从而进行必要的I/O操作。为了节约CPU资源,查询工作往往不是连续的,而是定时进行。

    轮询方式具有简单、易实现、易控制等优势,在很多小型系统中有大量应用。对那些实时敏感性不高、具有大量CPU资源的系统来说,轮询方式有很广泛的应用。最典型的用途就是在那些任务比较单一的单片机上,嵌入式系统中也有应用。

    轮询的一种典型的实现可能是这样的:while(TRUE){/*…*/ select(,,timeout); /*…*/};当然这里的select()也可以使用poll()替换。

    轮询方式主要存在以下不足:

    <1>增加系统开销。无论是任务轮询还是定时器轮询都需要消耗对应的系统资源

    <2>无法及时感知设备状态变化。在轮询间隔内的设备状态变化只有在下次轮询时才能被发现,这将无法满足对实时性敏感的应用场合。

    <3>浪费CPU资源。无论设备是否发生状态改变,轮询总在进行。在实际情况中,大多数设备的状态改变通常不会那么频繁,轮询空转将白白浪费CPU时间片。

    (2)中断(Interrupt)

    中断,顾名思义,就是打断正在进行中的工作。中断不需要处理器轮询设备的状态,设备在自己发生状态改变时将主动发送一个信号给处理器(PIC),后者在接收到这一通知信号时,会挂起当前正在执行的任务转而去处理响应外设的中断请求。中断通知机制通过硬件信号异步唤起处理器的注意,解决了外部设备与处理器之间速度不匹配导致的资源浪费问题。

    现代设备绝大多数采用中断的方式与处理器进行沟通,因此设备驱动程序必须能够支持设备的中断特性。处理器在中断到达时会根据不同的中断号找到对应设备(IRR),并对中断请求进行响应处理。中断处理例程ISR(Interrupt Service Routine)由设备驱动程序提供,并在设备驱动模块初始化时注册到系统中断向量表中。从设备发出中断信号,到处理器最终调用ISR进行处理,期间会经过很多步骤,这个过程构成了中断处理框架。中断处理框架包括了进入ISR之前的很多进入路径(entry path),例如MIPS下要经历这样几个步骤:设置或屏蔽相关寄存器;进入异常入口点取指;现场保护;异常分类(MIPS下中断也是一种异常)处理;查找中断向量表路由ISR。不同的操作系统对中断处理框架的设计不尽相同,但是要达到的目的是一样的,那就是最终调用用户注册的设备ISR。

    (3)中断与轮询的折衷

    虽然轮询方式存在空转损耗导致名声不佳,但并非一无是处。中断模型也并非十全十美,其高优先级的VIP待遇和快速响应要求在极端条件下将造成“活锁”效应。有时候需要发挥粗暴中断和温和轮询各自的优势,根据实际应用情景,在两种模式之间切换。手机导航杆卡死情形的处理是个很好的案例。

    在过去的一些手机和PDA设备上安装有导航杆,它支持3种 动作(顺时针旋转、逆时针旋转和按键),可方便菜单导航。导航杆的三种动作都会向处理器发出中断。系统中通用的目的I/O(GPIO)端口和导航杆连接。 中断处理函数的工作就是查看GPIO数据寄存器解析出导航杆运动。假定导航杆由于存在运动部件(如旋轮偶尔被卡住)引起的固有的硬件问题,从而在GPIO 端口产生不同于方波的波形。被卡住的旋轮会不停地产生假的中断,并可能使系统冻结。为了解决这个问题,可以捕获波形分析,在卡住的情况下动态地从中断模式切换到轮询模式。如果旋轮恢复正常,再动态地从轮询模式切换到中断模式,软件也恢复正常模式。

    在本文的最后,将介绍Linux网络设备驱动模型中的NAPI机制 ,它采用“中断+轮询”的处理方式代替纯中断处理方式,是中断和轮询的完美合体。

    2 中断硬件框架

    中断的主动通知特性需要硬件设施支持。在数字逻辑电路层面,外部设备和处理器之间有一条专门的中断信号线(Interrupt Line),用于连接外设与CPU的中断引脚(Interrupt Pin)。当外部设备发生状态改变时,可以通过这条信号线向处理器发出一个中断请求(Interrupt Request,IRQ),其中外部设备通常被称作中断源(Interrupt Source)。

    处理器一般只有两根左右的中断引脚(例如8259A的INTR和INTA),而管理的外设却很多。为了解决这个问题,现代设备的中断信号线并不是与处理器直接相连,而是与一个称为中断控制器的设备相连接,后者才跟处理器的中断引脚连接。中断控制器一般可以通过处理器进行编程配置,所以常称为可编程中断控制器PIC(Programmable Interrupt Controller)。下图是一个典型的中断硬件连接的系统框架图:


    中断连接框图

    上图中,PIC的输出中断信号线连接到处理器的INT引脚 上,这是处理器专门用来接收中断信号的pin脚。外部设备的中断线连接到PIC的pin引脚上,这是PIC用来接收外设中断的pin脚。比如第一个设备的 中断线通过P0连到PIC上。在实际的硬件平台上,PIC有的在CPU外部,比如x86平台的8259中断控制器;有的被封装到CPU的内部,这广泛见于嵌入式领域。一颗SoC芯片内部集成了处理器和各种外部设备的控制器,其中包括PIC。

    IRQ相关信息管理的关键点是一个全局数组,每个数组项对应一个IRQ编号,软件中断号irq就是这个数组的索引,irq将一对一或多对一(共享)映射到硬件中断源编号。不同的操作系统相关数据结构的实现和映射策略实现可能有差别。

    3 中断向量表

    中断向量表其实是处理器内部的概念,因为处理器除了会被外部设备中断外,其内部也可能 产生异常等事件,例如在MIPS中,中断只是异常的一种。当这些事件发生时,CPU必须暂停手头上的工作,转而去处理中断或异常,因此处理器需要知道到哪 里去获得这些中断或异常的处理函数的目标地址。中断向量表就是用来解决这个问题,其中每一项都是一个中断或异常处理函数的入口地址,具体来说4个字节的函数指针将指向一段汇编微码(intConnectCode)执行跳转。

    外部设备的中断常常对应向量表中的某一项,这是通用框架的外部中断处理函数入口,因此在进入通用的中断处理函数之后,系统必须知道正在处理的中断是哪一个设备产生的,而这正是由软件中断号irq定的决。中断向量表的内容是由操作系统在初始化阶段来填写,对于外部中断,操作系统负责实现一个通用的外部中断处理函数,然后把这个函数的入口地址放到中断向量表中的对应位置。用户注册设备驱动ISR,实际上就是挂接到中断向量表中,覆盖某一项的默认处理实现特化。

    4 中断路由

    很多SoC芯片或设备提供了一个重要的寄存器——IRR(Interrupt Routing Register),例如PCI中的Interrupt Line Register,它用来配置中断源与CPU中断位图的映射关系。所谓CPU中断位图是指CPU中中断相关的控制寄存器(IE/IP)的比特位分布(bitmap),例如MIPS中的C0_SR:IM[7~2]/C0_CAUSE:IP[7~2]对应六路外设中断源。

    关于IRR,这里摘录网贴PCI Interrupt Routing》中的一段阐述:

    --------------------------------------------------------------

    PCIinterrupt routing consists of figuring out which platform-specific interrupt isasserted when a given PCI interrupt signal is asserted. On x86 machines, thisconsists of figuring out which input pin on an interrupt controller is assertedwhen a given PCI interrupt signal is asserted.

    --------------------------------------------------------------

    PCI interrupt routing所描述(figure out)的问题是:PCI作为中断源与PIC(interrupt controller)相连,当PCI有interrupt signal时,PIC哪个输入引脚(input pin)将收到通知(asserted)。

    需要明确的是,在中断硬件连接框图中,左边的中断源与PIC往往是通过硬连线连接的。PCI设备向PIC发出中断,PIC向CPU传递中断,这里需要确定的应该是CPU相关中断控制寄存器IP位图与中断源(PCI设备)的对应关系。这个映射关系是由中介位置的PIC向外提供的IRR寄存器配置的。

    可编程中断控制器8259A》 中提到“在80x86”系统中,8259A在中断响应周期的第二个总线中期内,从数据总线内向CPU送出8位中断类型码N的值。”当某一路中断源发起中断 时,PIC将根据引脚编号编码中断类型号N,CPU收到INTR信号后在稍后适当的时钟周期中从PIC相应端口读取中断类型码,紧接着CPU中断位图将置 位。例如在MIPS CPU中,外部设备7(Timer)发起中断,C0_CAUSE:IP[7]置位;外部设备6发起中断,C0_CAUSE:IP[6]置位;...。我们 可以通过配置IRR寄存器来改变这种映射关系,这里体现了PIC的可编程性。可以将PIC类比为一个路由器,左边各个中断源为接入PHY口的PC,IRR就相当于路由器为各个PC分配IP地址。有的SoC硬连线和位图映射关系已经固定(相当于ARP绑定),并未提供类似的IRR寄存器,此时就得参考datasheet或SDK指导开发。

    在MIPS  SoC中,外设中断引脚(线)与PIC连接,配置完IRR并配置C0_SR:IM[7~2]使能断源后,C0_CAUSE:IP[7~2]的相应位将反馈 对应设备的中断活跃状态。当有中断发生时,通用中断处理程序将C0_CAUSE:IP[7~2]和C0_SR:IM[7~2]执行逻辑“与”运算,其中为 1的IM&IP位对应的设备发出了活跃且使能的中断请求。经过现场保护处理和一系列的进入路径(entry path),如果是共享中断则需要先解复用,最终在中断向量表中查找当初用户为该设备注册的ISR并调用之。

    5 中断复用/解复用

    在编写设备驱动程序时,我们往往预期为每个设备都注册一个 IRQ(request_irq),但实际情况往往并不那么理想。8259A芯片最多支持8个中断源,IA-32/8256A最多支持16个中断 源,MIPS CPU的C0_SR:IM/C0_CAUSE:IP最多支持6路外设中断源。当SoC芯片上的设备IRQ多于中断槽位时,多个设备只能复用(共享)同一个 中断。例如,PCI中断是典型的复用中断,PCI规范所定义的中断源只包含了ABCD四种,所有的设备都是用这其中之一。

    中断复用(Interrupt Multiplexing)/中断共享(Interrupt Sharing)就是几个设备(devA,devB,…)接到了PIC的同一引脚上当做一个中断源。当它们有事件发生时,都向这个引脚发出中断信号。那么到底是哪个(些)设备发出的中断通知呢?这涉及中断解复用。中断解复用(Interrupt Demultiplexing)就是负责决断识别中断信号来自哪个(些)复用设备。

    中断发生后,我们首先需要确定是哪个设备触发的(who trigger),对于共享中断需要解复用,然后进入对应设备上挂接的ISR。ISR需要确认该设备上发生了哪些事情(what happened),然后对感兴趣的事情进行处理,或预处理然后交由中断处理程序的下半部。首先解复用是系统级别的(which device),其次的解复用是设备级别的(what happed to the device),可以说整个中断处理框架都是围绕中断路由和中断解复用展开分流。中断的解复用总是离不开全局的或局部的使能/状态寄存器,即到处可见的IE(Interrupt Enable flag)和IP(Interrupt Pending status)。

    (1)全局中断使能/状态寄存器——GIMR/GISR

    一般的SoC中会有GlobalInterrupt Mask Register/Global Interrupt Status Register这两组寄存器,分别简称为GIMR/GISR。GIMR为设备中断使能(IE)寄存器,GISR为设备中断挂起状态(IP)寄存器,类似 MIPS CPU中的C0_SR:IM与C0_CAUSE:IP的关系。设备在进行必要的初始化(包括中断挂接)完毕后,启用全局中断(例如MIPS中的C0_SR:IE),然后配置GIMR使能SoC各设备中断,整个SoC系统就可以正常运转了。

    (2)设备间中断解复用

    不同操作系统的中断处理框架中,对于中断向量表和中断复用/解复用的数据结构实现会有 一定的差异。对于普通非共享中断,通过CPU的IP位图和IRQ号可以索引中断向量表直接进入ISR调用。对于中断复用的情况,不同的操作系统处理有所不 同,但都是通过GIMR&GISR掩码运算确定具体中断设备的。

    在Linux中,对于相同irq号的irq_desc[irq]::irqaction::flags设置为IRQF_SHARED,进而调用irq_desc[irq]::irqaction操作链。在每个irqaction::handler中通过GIMR&GISR运算核对该设备是否发生了中断,如果是则进一步action后返回IRQ_HANDLED;否则返回IRQ_NONE,进行next irqaction。

    在VxWorks中,中断共享的多个外设中断源对应一个CPU中断位,但是每个设备都分配了一个中断向量(IRQ vector)并将ISR挂接到中断向量表。在解复用例程(Demux Routine)中通过GIMR&GISR运算核对有效使能中断挂起,从而识别出哪个(些)设备产生了中断,返回中断向量,然后根据中断向量索引中断向量表中的ISR进行处理。

    (3)设备内中断解复用

    通过直接位图映射或设备间中断解复用识别出了哪个设备发生 了中断并进入其ISR,ISR首先需要弄明白这个设备具体发生了什么事情。经常阅读嵌入式SoC芯片datasheet的人可能知道,每个设备本身就有自 己的中断使能/状态寄存器,用来区分设备内部粒度的事件。相对于SoC级别的GIMR/GISR,我们姑且将设备局部的Device Interrupt MaskRegister/Device Interrupt Status Register简称为DIMR/DISR。当然,我们在初始化设备时,也需要配置其DIMR,只接收我们感兴趣或需要处理的设备子事件。在设备ISR 中,需要进行类似设备间的解复用处理,通过DIMR&DISR运算,逐位核对哪些bit位处于pending状态,结合datasheet确定该设备发生了什么事件并作出相应处理。

    6 中断上下文

    当处理器检测到某一中断源对应的中断产生时,它将停止现在的工作,进入中断(异常)入 口点取指。在进入ISR之前,通用中断处理框架首先执行现场保护,将当前任务的上下文寄存器组保存一个特定的中断栈(Interrupt Stack)中,然后屏蔽处理器响应外部中断,最后路由中断向量表开始进入C函数ISR调用,例如Linux平台上定义的do_IRQ()。异步中断并不 与特定的进程(线程)关联,中断借用被中断的线程栈环境运行自己。此时,软件运行在中断上下文(Interrupt Context)中。为了对粗暴打断当前无辜线程的行为进行补偿,ISR不得不礼貌地执行于受限制的中断上下文中。

    通常,处理器在接收到外部的中断信号时,硬件逻辑会自动屏蔽处理器响应外部中断的能 力,因此如果操作系统实现的中断处理框架不主动打开中断的话,整个中断处理的流程是在中断关闭的情况下进行的。因为设备中断处理程序是由驱动程序实现的, 内核无法保证这些中断处理程序执行时间的长短。如果某一中断处理执行时间过长,则将会导致系统可能很长时间无法接收中断或执行任务调度,这可能使某些外部设备丢失数据或者操作系统响应时间变长。

    为了解决中断对系统调度的影响,Linux内核为驱动程序提供的中断处理机制分为两个部分:HARDIRQ顶半部(The top half)SOFTIRQ底半部(The bottom half)。 HARDIRQ顶半部短小精悍(Minimal Fast Handling),它在中断关闭的情况下执行,执行最关键的动作响应硬件交互后,将重大的工作负载丢给底半部,并对外宣称它已经服务了该中断。 SOFTIRQ底半部在中断开启的情况下运行,此时外部设备仍可以继续中断处理器,因此驱动程序往往将一些比较耗时的工作延迟到底半部执行。底半部是同步 的,因为内核决定了它什么时候会执行中断。

    软中断和工作队列常用于执行ISR中非时间关键部分的底半部,其代码一定不能在中断处理程序内调用,而是运行于(软)中断上下文进程(线程)上下文

    7 中断底半部延期机制

    (1)软中断

    软中断机制使得内核可以延期执行任务。它们的运作方式与硬件中断类似,但是完全是软件触发实现的,因此称为软中断(Software Interrupt,softirq)。典型的软中断如用于x86体系架构上的系统调用int 0x80指令,关于系统调用可参考《程序员的自我修养——链接、装载与库》的第12章<系统调用与API>。

    软中断是硬件IRQ的软件等价物。软中断只适用于少数场合,只有在一些对性能敏感的中枢子系统(如网络层、SCSI层和内核定时器)中才会使用softirq。

    许多软中断不仅可以同时运行,而且相同的软中断还可以在不 同的CPU上运行。对并发的唯一限制就是无论何时,在一个CPU上每个软中断都只能有一个实例在运行。同一种类型的软中断的不同实例可以同时在不同的 CPU上运行。因此软中断所执行的函数还是必须锁住共享的数据,以避免CPU之间的竞争。

    <1>softirq

    内核借助于软中断来获知异常情况的发生,在do_IRQ()末尾处理所有的待决软中断,因而可以确保软中断能够定期得到处理。

    raise_softirq(int nr)用于触发一个软中断,该参数对应CPU提供的软中断源位图(例如MIPS中的C0_CAUSE:IP[1~0]),此时软中断的延期处理将运行于软中断上下文中。如果不在中断上下文中调用raise_softirq,则可调用wakeup_softirqd来唤醒软中断守护进程ksoftirqd,此时软中断的延期处理运行于进程上下文中。当在中断上下文中处理软中断时,处理函数不能进行睡眠,如果睡眠,将导致无法唤醒。

    <2>tasklet

    软中断是将操作推迟到未来时刻执行的最有效方法,但该延期机制处理起来非常复杂。一些对可扩展性和速度要求很高的设备有自身的softirq下半部,有较强的加锁需求,大多数共享一个称之为小片任务(tasklet)的灵活系统。

    tasklet的实现是基于软中断(TASKLET_SOFTIRQ和HI_SOFTIRQ),但更容易使用。在任何时刻,每个tasklet都只有一个实例可以等待执行,无需考虑多CPU的支持。

    tasklet比softirq更易于使用,因而更适合于设备驱动程序。

    <3>其他

    内核定时器(timer_list)延期执行工作的机制,也是基于软中断(TIMER_SOFTIRQ)实现。在启用高分辨率定时器时,还需要一个软中断(HRTIMER_SOFTIRQ)。

    网络收发包的底半部处理也是基于软中断实现的,它们是NET_RX_SOFTIRQ和NET_TX_SOFTIRQ。

    (2) 工作队列(workqueue)

    工作队列是中断处理延期执行的第3种方式。对于每个工作队列,内核都会创建一个新的内核守护进程,因此延期任务是在守护进程的上下文中执行的。由于在进程上下文中执行,因此允许睡眠,可以使用互斥体这类可能导致睡眠的函数。

    Linux内核创建了一个标准的工作队列,称为 events。内核的各个函数中,凡是没有必要创建独立的工作队列者,均可以使用该队列。VxWorks提供了类似的工作队列机制:ISR做完简单的底层 操作后,调用netJobAdd()将底半部工作(netJob)排队加入(Add)网络任务tNetTask的服务队列 (netJobRing),tNetTask任务将会调度执行挂载到工作队列上延期工作。

    8 Linux网络设备驱动模型中的NAPI模型

    尽管现代绝大多数设备都支持中断特性,但是中断的高优先级 和快速响应在极端条件下,可能会带来麻烦。对于一些I/O频繁量大的外设(如net_device),必须及时响应中断,及时递交处理数据包,以防数据积 压(如DMA rxoverflow)。但中断处理上下文的切换需要系统开销,在数据量过载时中断频率过高,CPU疲于应付中断,上层应用程序无法得到调度。底半部因频 繁被鲁莽中断导致无法完成对数据包的处理(如做NAT),而中断程序依然在不断地往网络子系统的接收队列中灌数据,这将会导致数据队列溢出丢包和传输超 时。系统将自陷在中断响应这一环节,产生所谓的“活锁”。中断过度掠夺资源将造成系统响应变慢甚至半身不遂,底半部消化不良将造成吞吐量等性能指标下降。

    在Linux旧的网络设备驱动模型中,设备驱动会为其所接 收的每个帧都产生一个中断事件(int_events),通过旧函数netif_rx/netif_receive_skb通知内核帧已接收。在高流量负 载下,花在处理中断事件的时间会造成资源相当程度的浪费。针对旧的中断处理模式的缺点,一种被称为NAPI(New API)的处理模式被引入到了Linux内核中。

    虽然在设备驱动中,轮询方式存在空转损耗导致名声不佳,但并非一无是处。NAPI的设计思想其实是结合了中断与轮询的各自优势,是中断驱动程序所采用的“”模式和轮询驱动程序所采用的“”模式的混合。当有数据包到达时将会触发硬件中断,在中断处理中关闭中断,系统对硬件的掌控将进入轮询模式,直到所有的数据包接收完毕,再重新开启中断, 进入下一轮中断轮询周期。显然,在系统对硬件进行轮询期间,硬件可能接收到大量进入的数据包,但是它们不会产生中断。设备关闭中断期间仍然具备接受后续分 组的能力,否则轮询也就失去了意义。典型的如以太网芯片中,网络数据包经过PHY到达MAC,此时数据包保存在设备内存中(挂在DMA描述符环上的缓冲 区)。每当接收到数据包时,MAC将向CPU发出中断通知,但是如果关闭了中断,DMA通道传输接收数据仍在异步进行中。可以在下一次开启中断时,再收割DMA描述符环。

    一个支持NAPI的驱动程序,需要提供poll()函数, 它将在内核对当前设备轮询时调用。另外需要提供一个控制多个网络设备轮询公平度的相关权重参数weight,它赋予一个设备进行轮询处理的时间宽度。作为 中断和轮询的完美合体,NAPI在高流量负载下的性能比旧方法要出色。从内核的处理的观点来看,NAPI方法有两个优点:(1)减少了CPU的负载(因为 中断事件变少了);(2)设备的处理更为公平。

    尽管在其他操作系统的网络设备驱动模型中,并无NAPI概念直接对应,但大部分ISR的实现都在渗透NAPI理念,所谓英雄所见略同,殊途同归。


    参考:

    《深入Linux内核架构》

    《精通Linux设备驱动程序开发》

    《深入Linux设备驱动程序内核机制》


     

    中断和中断处理

    Linux中的中断处理分析

    PCI 中断路由机制

    linux内核研究-3-中断》

    IA32上Linux内核中断机制分析

    linux内核中断 - tasklet 分析

    Linux 内核软中断(softirq)执行分析

    基于VxWorks的多路高速串口的通信方法设计

    NAPI模式--中断和轮询的折中以及一个负载均衡的问题

    转载于:https://www.cnblogs.com/welhzh/p/5645425.html

    展开全文
  • 中断处理

    千次阅读 2013-05-20 23:11:57
    1 轮询与中断 外部设备与中央处理器交互一般有两种手段:轮询和中断。 (1)轮询(Polling) 很多I/O设备都有一个状态寄存器,用于描述设备当前的工作状态,每当设备状态发生改变时,设备将修改相应状态寄存器位。...
  • linux 中断 1

    2015-05-25 15:39:59
    在linux里,中断处理分为顶半(top half),底半(bottomhalf),在顶半里处理优先级比较高的事情,要求占用中断时间尽量的短,在处理完成后,就激活底半,有底半处理其余任务。底半的处理方式主要有soft_irq,tasklet,...
  • 抛弃中断bottom half,使用中断线程吧

    千次阅读 2013-03-07 09:27:48
    在linux里,中断处理分为顶半(top half),底半(bottomhalf),在顶半里处理优先级比较高的事情,要求占用中断时间尽量的短,在处理完成后,就激活底半,有底半处理其余任务。底半的处理方式主要有soft_irq,tasklet,...
  • 中断线程化简述

    2019-08-21 16:39:24
    在linux里,中断处理分为顶半(top half),底半(bottom half),在顶半里处理优先级比较高的事情,要求占用中断时间尽量的短,在处理完成后,就激活底半,有底半处理其余任务。底半的处理方式主要有soft_irq, ...
  • linux中断子系统

    千次阅读 2014-12-12 12:51:06
    Linux中断(interrupt)子系统之一:中断系统基本原理 http://blog.csdn.net/droidphone/article/details/7445825 这个中断系列文章主要针对移动设备中的Linux进行讨论,文中的例子基本都是基于ARM这一体系...
  • 抛弃中断bottom half,使用中断线程吧 (2011-02-24 10:39:54) 转载▼ 标签: 杂谈   在linux里,中断处理分为顶半(top half),底半(bottom half),在顶半里处理优先级比较高的事
  • linux 内核笔记--中断子系统之softirq

    千次阅读 2017-03-19 19:09:27
    linux把处理硬件中断的过程分为两部分。上半部简单快速,执行时禁止部分或全部中断。下半部稍后执行,并且执行期间可以响应所有的中断。...soft_irq用在对底半执行时间要求比较紧急或者非常重要的场合,在中断上下文
  • 中断通信

    千次阅读 2018-05-04 15:11:39
    编写一段C语言程序,使其实现进程的软中断通讯。 二、要求: 使用系统调用fork()创造二个进程,父进程睡眠2秒后,用系统调用Kill() 向两个子进程发出信号,子进程捕捉到信息后分别输出下列信息终止: Child Processl...
  • 可编程中断控制器8259A

    万次阅读 2015-12-06 13:11:38
     它将中断源优先级判优、中断源识别和中断屏蔽电路集于一体,不需要附加任何电路就可以对外部中断进行管理,单片可以管理8级外部中断,在多片级联方式下,可以管理多达64级的外部中断。8259A内部结构及其引脚功能可...
  • 【实验六】定时器中断实验

    千次阅读 2016-01-22 11:31:15
    适用于多种场合,包括测量输入信号的脉冲长度(输入捕获)或者产生输出波形(输出比较和 PWM)。使用定时器预分频器和RCC时钟控制器预分频器,脉冲长度和波形周期可以在几个微秒到几个毫秒间调整
  • 定时器中断实验

    2017-03-06 08:57:59
    定时器中断实验   STM32F1 的定时器功能十分强大,有TIME1 和 TIME8 等高级定时器,也有 TIME2~TIME5 等通用定时器,还有 TIME6 和 TIME7 等基本定时器。 高级控制定时器(TIM1和TIM8) 13.1 TIM1和TIM8简介 ...
  • 关于8259中断控制器

    千次阅读 2018-12-28 21:09:33
    可编程中断控制器(PIC - Programmable Interrupt Controller)是微机系统中管理设备中断请求的管理者。当PIC向处理器的INT引脚发出一个中断信号时,处理器会立刻停下当时所做的事情并询问PIC需要执行哪个中断服务...
  • linux定时中断的三种实现

    千次阅读 2018-08-04 22:25:46
    我们可以利用定时中断在linux应用层实现一些对时间频率要求不是很高的驱动,虽然有些不规范,但是也是有其适用场合的。因为应用层不涉及到硬件,不同平台可移植性更高。 本文涉及到的内容有: 多线程间信号的...
  • 组成原理---中断

    2020-05-05 10:58:44
    文章目录中断的基本概念中断请求与判优中断响应中断服务与返回 中断系统是计算机中实现中断功能的软、硬件总称。一般在 CPU 中配置中断机构,在外设接口中配置中断控制器,在软件上设计相应的中断初始化程序和中断...
  • 80386的中断和异常

    千次阅读 2014-11-25 10:55:18
    80386的中断和异常 8086/8088把中断分为内部中断和外部中断两大类。为了支持多任务和虚拟存储器等功能,80386把外部中断称为“中断”,把内部中断称为“异常”。与8086/8088一样,80386通常在两条指令之间响应...
  • 中断控制器8259A

    2020-11-22 00:59:51
    中断控制器8259A 8259A的外部信号和含义 D7D_7D7​ ~ D0D_0D0​:数据线 INT:中断请求信号 INTA‾\overline{\text{INTA}}INTA:中断应答信号 RD‾\overline{\text{RD}}RD:读出信号 WR‾\overline{\text{WR}}WR:...
  • MQX中如何使用中断

    千次阅读 2015-07-17 11:55:22
    飞思卡尔目前MQX最新版本为4.2,本文给大家介绍一下如何在MQX中使用中断功能。 实现的功能是:使用一个PIT定时器,在PIT中断里翻转GPIO,控制LED灯闪烁。 平台:FRDM_K22F+MQX4.2 直接在hello工程里修改,只改动...
  • STC51单片机中断与定时器配置参考

    千次阅读 2018-11-22 11:59:13
    *外中断INT0--------void intsvr0(void) interrupt 0 using 1 *定时/计数器T0-----void timer0(void) interrupt 1 using 1 *外中断INT1--------void intsvr0(void) interrupt 2 using 1 *定时/计数器T1-----void ...
  • 硬件外设产生硬件的电信号变化,这个电信号首先发送给中断控制器(能够打开,关闭中断,能够指定中断的优先级,还能够判断中断是否发生),中断控制器判断是否使能,判断优先级,最终决定是否给
  • 输入/输出与中断I/O接口I/O接口概述CPU与外设交换的信息I/O端口的编址CPU与外设之间数据传送方式1、程序传送方式2、中断传送方式3、直接存储器存取(DMA)传送方式中断技术1、中断源:分为硬件中断源和软件中断源2、...
  • 在linux里,中断处理分为顶半(top half),底半(bottom half),在顶半里处理优先级比较高的事情,要求占用中断时间尽量的短,在处理完成后,就激活底半,有底半处理其余任务。底半的处理方式主要有soft_irq, ...
  • 51单片机 中断与定时器

    千次阅读 2020-06-23 11:45:27
    51单片机,拥有两个定时器,用来中断计数,分别是T0和T1。而52单片机和51单片机的定时器是一样的,只是52比51多了一个定时器/计数器T2,它们的设置都大同小异 定时器T0与T1不同之处在于它们的工作方式3不同,方式0...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 13,789
精华内容 5,515
关键字:

中断适用的场合