Cortex-M CPU架构基础

寄存器简介

  • Cortex-M 系列 CPU 的寄存器组里有 R0\~R15 共 16 个通用寄存器组和若干特殊功能寄存器,如下图所示。
  • 通用寄存器组里的 R13 作为堆栈指针寄存器 (Stack Pointer,SP);R14 作为连接寄存器 (Link Register,LR),用于在调用子程序时,存储返回地址;R15 作为程序计数器 (Program Counter,PC),其中堆栈指针寄存器可以是主堆栈指针(MSP),也可以是进程堆栈指针(PSP)。

  • 特殊功能寄存器包括程序状态字寄存器组(PSRs)、中断屏蔽寄存器组(PRIMASK, FAULTMASK, BASEPRI)、控制寄存器(CONTROL),可以通过 MSR/MRS 指令来访问特殊功能寄存器,例如:
  • MRS R0, CONTROL ; 读取 CONTROL 到 R0 中
    MSR CONTROL, R0 ; 写入 R0 到 CONTROL 寄存器中
  • 程序状态字寄存器里保存算术与逻辑标志,例如负数标志,零结果标志,溢出标志等等。中断屏蔽寄存器组控制 Cortex-M 的中断除能。控制寄存器用来定义特权级别和当前使用哪个堆栈指针。

  • 如果是具有浮点单元的 Cortex-M4 或者 Cortex-M7,控制寄存器也用来指示浮点单元当前是否在使用,浮点单元包含了 32 个浮点通用寄存器 S0\~S31 和特殊 FPSCR 寄存器(Floating point status and control register)。

操作模式和特权模式

  • Cortex-M 引入了操作模式和特权级别的概念,分别为线程模式和处理模式,如果进入异常或中断处理则进入处理模式,其他情况则为线程模式。

  • Cortex-M 有两个运行级别,分别为特权级和用户级,线程模式可以工作在特权级或者用户级,而处理模式总工作在特权级,可通过 CONTROL 特殊寄存器控制;
  • Cortex-M 的堆栈寄存器 SP 对应两个物理寄存器 MSP 和 PSP,MSP 为主堆栈,PSP 为进程堆栈,处理模式总是使用 MSP 作为堆栈,线程模式可以选择使用 MSP 或 PSP 作为堆栈,同样通过 CONTROL 特殊寄存器控制。复位后,Cortex-M 默认进入线程模式、特权级、使用 MSP 堆栈。

嵌套向量中断控制器

  • Cortex-M 中断控制器名为 NVIC(嵌套向量中断控制器),支持中断嵌套功能。当一个中断触发并且系统进行响应时,处理器硬件会将当前运行位置的上下文寄存器自动压入中断栈中,这部分的寄存器包括 PSR、PC、LR、R12、R3-R0 寄存器。

  • 当系统正在服务一个中断时,如果有一个更高优先级的中断触发,那么处理器同样会打断当前运行的中断服务程序,然后把这个中断服务程序上下文的 PSR、PC、LR、R12、R3-R0 寄存器自动保存到中断栈中。

PendSV系统调用

  • PendSV 也称为可悬起的系统调用,它是一种异常,可以像普通的中断一样被挂起,它是专门用来辅助操作系统进行上下文切换的;
  • PendSV 异常会被初始化为最低优先级的异常。每次需要进行上下文切换的时候,会手动触发 PendSV 异常,在 PendSV 异常处理函数中进行上下文切换;

RT-Thread中断工作机制

中断向量表

  • 中断向量表是所有中断处理程序的入口,如下图所示是 Cortex-M 系列的中断处理过程:把一个函数(用户中断服务程序)同一个虚拟中断向量表中的中断向量联系在一起。当中断向量对应中断发生的时候,被挂接的用户中断服务程序就会被调用执行。

  • 在 Cortex-M 内核上,所有中断都采用中断向量表的方式进行处理,即当一个中断触发时,处理器将直接判定是哪个中断源,然后直接跳转到相应的固定位置进行处理,每个中断服务程序必须排列在一起放在统一的地址上(这个地址必须要设置到 NVIC 的中断向量偏移寄存器中)。中断向量表一般由一个数组定义或在起始代码中给出,默认采用起始代码给出:

    __Vectors       DCD     __initial_sp               ; Top of Stack
    DCD Reset_Handler ; Reset Handler
    DCD NMI_Handler ; NMI Handler
    DCD HardFault_Handler ; Hard Fault Handler
    DCD MemManage_Handler ; MPU Fault Handler
    DCD BusFault_Handler ; Bus Fault Handler
    DCD UsageFault_Handler ; Usage Fault Handler
    DCD ; Reserved
    DCD ; Reserved
    DCD ; Reserved
    DCD ; Reserved
    DCD SVC_Handler ; SVCall Handler
    DCD DebugMon_Handler ; Debug Monitor Handler
    DCD ; Reserved
    DCD PendSV_Handler ; PendSV Handler
    DCD SysTick_Handler ; SysTick Handler ....... __Vectors_End __Vectors_Size EQU __Vectors_End - __Vectors AREA |.text|, CODE, READONLY ; Reset handler
    Reset_Handler PROC
    EXPORT Reset_Handler [WEAK]
    IMPORT SystemInit
    IMPORT __main LDR R0, =SystemInit
    BLX R0
    LDR R0, =__main
    BX R0
    ENDP ; Dummy Exception Handlers (infinite loops which can be modified) NMI_Handler PROC
    EXPORT NMI_Handler [WEAK]
    B .
    ENDP
    HardFault_Handler\
    PROC
    EXPORT HardFault_Handler [WEAK]
    B .
    ENDP
    MemManage_Handler\
    PROC
    EXPORT MemManage_Handler [WEAK]
    B .
    ENDP
    BusFault_Handler\
    PROC
    EXPORT BusFault_Handler [WEAK]
    B .
    ENDP
    UsageFault_Handler\
    PROC
    EXPORT UsageFault_Handler [WEAK]
    B .
    ENDP
    SVC_Handler PROC
    EXPORT SVC_Handler [WEAK]
    B .
    ENDP
    DebugMon_Handler\
    PROC
    EXPORT DebugMon_Handler [WEAK]
    B .
    ENDP
    PendSV_Handler PROC
    EXPORT PendSV_Handler [WEAK]
    B .
    ENDP
    SysTick_Handler PROC
    EXPORT SysTick_Handler [WEAK]
    B .
    ENDP

中断处理过程

  • void SysTick_Handler(void)
    {
    /* enter interrupt */
    rt_interrupt_enter(); rt_tick_increase(); /* leave interrupt */
    rt_interrupt_leave();
    }
  • RT-Thread 中断管理中,将中断处理程序分为中断前导程序、用户中断服务程序、中断后续程序三部分;

中断前导程序

  • 保存 CPU 中断现场,这部分跟 CPU 架构相关,不同 CPU 架构的实现方式有差异,对于 Cortex-M 来说,该工作由硬件自动完成,处理器硬件会将当前运行部分的上下文寄存器自动压入中断栈中,这部分的寄存器包括 PSR、PC、LR、R12、R3-R0 寄存器。
  • 通知内核进入中断状态,调用 rt_interrupt_enter() 函数,作用是把全局变量 rt_interrupt_nest 加 1,用它来记录中断嵌套的层数;
  • /**
    * This function will be invoked by BSP, when enter interrupt service routine
    *
    * @note please don't invoke this routine in application
    *
    * @see rt_interrupt_leave
    */
    void rt_interrupt_enter(void)
    {
    rt_base_t level; RT_DEBUG_LOG(RT_DEBUG_IRQ, ("irq coming..., irq nest:%d\n",
    rt_interrupt_nest)); level = rt_hw_interrupt_disable();
    rt_interrupt_nest ++;
    RT_OBJECT_HOOK_CALL(rt_interrupt_enter_hook,());
    rt_hw_interrupt_enable(level);
    }
    RTM_EXPORT(rt_interrupt_enter);
  • 用户中断服务程序

  • 中断服务程序分为两种情况:
    1. 不进行线程切换,这种情况下用户中断服务程序和中断后续程序运行完毕后退出中断模式,返回被中断的线程‘’
    2. 中断处理过程中需要进行线程切换,这种情况会调用 rt_hw_context_switch_interrupt() 函数进行上下文切换,该函数跟 CPU 架构相关,不同 CPU 架构的实现方式有差异,如下图所示:

  • 在 Cortex-M 架构中,rt_hw_context_switch_interrupt() 的函数实现流程如下图所示,它将设置需要切换的线程 rt_interrupt_to_thread 变量,然后触发 PendSV 异常(PendSV 异常是专门用来辅助上下文切换的,且被初始化为最低优先级的异常)。PendSV 异常被触发后,不会立即进行 PendSV 异常中断处理程序,因为此时还在中断处理中,只有当中断后续程序运行完毕,真正退出中断处理后,才进入 PendSV 异常中断处理程序。

中断后续程序

  • 中断后续程序主要完成的工作是:
    1. 通知内核离开中断状态,通过调用 rt_interrupt_leave() 函数,将全局变量 rt_interrupt_nest 减 1;
    2. /**
      * This function will be invoked by BSP, when leave interrupt service routine
      *
      * @note please don't invoke this routine in application
      *
      * @see rt_interrupt_enter
      */
      void rt_interrupt_leave(void)
      {
      rt_base_t level; RT_DEBUG_LOG(RT_DEBUG_IRQ, ("irq leave, irq nest:%d\n",
      rt_interrupt_nest)); level = rt_hw_interrupt_disable();
      rt_interrupt_nest --;
      RT_OBJECT_HOOK_CALL(rt_interrupt_leave_hook,());
      rt_hw_interrupt_enable(level);
      }
      RTM_EXPORT(rt_interrupt_leave);
    3. 恢复中断前的 CPU 上下文,如果在中断处理过程中未进行线程切换,那么恢复 from 线程的 CPU 上下文,如果在中断中进行了线程切换,那么恢复 to 线程的 CPU 上下文。这部分实现跟 CPU 架构相关;

中断嵌套

  • 在允许中断嵌套的情况下,在执行中断服务程序的过程中,如果出现高优先级的中断,当前中断服务程序的执行将被打断,以执行高优先级中断的中断服务程序,当高优先级中断的处理完成后,被打断的中断服务程序才又得到继续执行,如果需要进行线程调度,线程的上下文切换将在所有中断处理程序都运行结束时才发生,如下图所示。

中断栈

  • 在中断处理过程中,在系统响应中断前,软件代码(或处理器)需要把当前线程的上下文保存下来(通常保存在当前线程的线程栈中),再调用中断服务程序进行中断响应、处理;
  • 在进行中断处理时,中断处理函数中很可能会有自己的局部变量,这些都需要相应的栈空间来保存,所以中断响应依然需要一个栈空间来做为上下文,运行中断处理函数。中断栈可以保存在打断线程的栈中,当从中断中退出时,返回相应的线程继续执行;
  • RT-Thread 采用的方式是提供独立的中断栈,即中断发生时,中断的前期处理程序会将用户的栈指针更换到系统事先留出的中断栈空间中,等中断退出时再恢复用户的栈指针。这样中断就不会占用线程的栈空间,从而提高了内存空间的利用率,且随着线程的增加,这种减少内存占用的效果也越明显。
  • 在 Cortex-M 处理器内核里有两个堆栈指针,一个是主堆栈指针(MSP),是默认的堆栈指针,在运行第一个线程之前和在中断和异常服务程序里使用;另一个是线程堆栈指针(PSP),在线程里使用。在中断和异常服务程序退出时,修改 LR 寄存器的第 2 位的值为 1,线程的 SP 就由 MSP 切换到 PSP。

中断的底半处理

  • 对于另外一些中断,中断服务程序在取得硬件状态或数据以后,还需要进行一系列更耗时的处理过程,通常需要将该中断分割为两部分,即上半部分(Top Half)和底半部分(Bottom Half)。在上半部分中,取得硬件状态和数据后,打开被屏蔽的中断,给相关线程发送一条通知(可以是 RT-Thread 所提供的信号量、事件、邮箱或消息队列等方式),然后结束中断服务程序;而接下来,相关的线程在接收到通知后,接着对状态或数据进行进一步的处理,这一过程称之为底半处理。
  • 我们以一个虚拟的网络设备接收网络数据包作为范例,如下代码,并假设接收到数据报文后,系统对报文的分析、处理是一个相对耗时的,比外部中断源信号重要性小许多的,而且在不屏蔽中断源信号情况下也能处理的过程;
  • 创建了一个 nwt 线程,这个线程在启动运行后,将阻塞在 nw_bh_sem 信号上,一旦这个信号量被释放,将执行接下来的 nw_packet_parser 过程,开始 Bottom Half 的事件处理。
  • /*
    * 程序清单:中断底半处理例子
    */ /* 用于唤醒线程的信号量 */
    rt_sem_t nw_bh_sem; /* 数据读取、分析的线程 */
    void demo_nw_thread(void *param)
    {
    /* 首先对设备进行必要的初始化工作 */
    device_init_setting(); /*.. 其他的一些操作..*/ /* 创建一个 semaphore 来响应 Bottom Half 的事件 */
    nw_bh_sem = rt_sem_create("bh_sem", , RT_IPC_FLAG_FIFO); while()
    {
    /* 最后,让 demo_nw_thread 等待在 nw_bh_sem 上 */
    rt_sem_take(nw_bh_sem, RT_WAITING_FOREVER); /* 接收到 semaphore 信号后,开始真正的 Bottom Half 处理过程 */
    nw_packet_parser (packet_buffer);
    nw_packet_process(packet_buffer);
    }
    } int main(void)
    {
    rt_thread_t thread; /* 创建处理线程 */
    thread = rt_thread_create("nwt",demo_nw_thread, RT_NULL, , , ); if (thread != RT_NULL)
    rt_thread_startup(thread);
    } void demo_nw_isr(int vector, void *param)
    {
    /* 当 network 设备接收到数据后,陷入中断异常,开始执行此 ISR */
    /* 开始 Top Half 部分的处理,如读取硬件设备的状态以判断发生了何种中断 */
    nw_device_status_read(); /*.. 其他一些数据操作等..*/ /* 释放 nw_bh_sem,发送信号给 demo_nw_thread,准备开始 Bottom Half */
    rt_sem_release(nw_bh_sem); /* 然后退出中断的 Top Half 部分,结束 device 的 ISR */
    }
  • 从上面例子的代码片段可以看出,中断服务程序通过对一个信号量对象的等待和释放,来完成中断 Bottom Half 的起始和终结。由于将中断处理划分为 Top 和 Bottom 两个部分后,使得中断处理过程变为异步过程。这部分系统开销需要用户在使用 RT-Thread 时,必须认真考虑中断服务的处理时间是否大于给 Bottom Half 发送通知并处理的时间。

RT-Thread中断管理接口

  • 为了把操作系统和系统底层的异常、中断硬件隔离开来,RT-Thread 把中断和异常封装为一组抽象接口;

中断服务程序挂接

  • 系统把用户的中断服务程序 (handler) 和指定的中断号关联起来,可调用如下的接口挂载一个新的中断服务程序:
  • /**
    * 把中断服务程序和指定的中断号关联起来
    *
    * @param vector 是挂载的中断号
    * @param handler 新挂载的中断服务程序
    * @param param 会作为参数传递给中断服务程序
    * @param name 中断的名称
    * @return 挂载这个中断服务程序之前挂载的中断服务程序的句柄
    */
    rt_isr_handler_t rt_hw_interrupt_install(int vector,
    rt_isr_handler_t handler,
    void *param,
    const char *name);
  • 这个 API 并不会出现在每一个移植分支中,例如通常 Cortex-M0/M3/M4 的移植分支中就没有这个 API。

中断源管理

  • 通常在 ISR 准备处理某个中断信号之前,我们需要先屏蔽该中断源,在 ISR 处理完状态或数据以后,及时的打开之前被屏蔽的中断源,屏蔽中断源可以保证在接下来的处理过程中硬件状态或者数据不会受到干扰;
  • // ector    要屏蔽的中断号
    void rt_hw_interrupt_mask(int vector);
  • 这个 API 并不会出现在每一个移植分支中,例如通常 Cortex-M0/M3/M4 的移植分支中就没有这个 API。

  • 为了尽可能的不丢失硬件中断信号,可调用下面的函数接口打开被屏蔽的中断源:
  • //  vector   要打开屏蔽的中断号
    void rt_hw_interrupt_umask(int vector);
  • 这个 API 并不会出现在每一个移植分支中,例如通常 Cortex-M0/M3/M4 的移植分支中就没有这个 API。

全局中断开关

  • 局中断开关也称为中断锁,是禁止多线程访问临界区最简单的一种方式,即通过关闭中断的方式,来保证当前线程不会被其他事件打断(因为整个系统已经不再响应那些可以触发线程重新调度的外部事件),也就是当前线程不会被抢占,除非这个线程主动放弃了处理器控制权。当需要关闭整个系统的中断时,可调用下面的函数接口:
  • rt_base_t rt_hw_interrupt_disable(void);
  • 恢复中断也称开中断。rt_hw_interrupt_enable()这个函数用于 “使能” 中断,它恢复了调用 rt_hw_interrupt_disable()函数前的中断状态。

  • void rt_hw_interrupt_enable(rt_base_t level);
  • 使用中断锁来操作临界区的方法可以应用于任何场合,且其他几类同步方式都是依赖于中断锁而实现的,可以说中断锁是最强大的和最高效的同步方法。只是使用中断锁最主要的问题在于,在中断关闭期间系统将不再响应任何中断,也就不能响应外部的事件。所以中断锁对系统的实时性影响非常巨大,当使用不当的时候会导致系统完全无实时性可言;而使用得当,则会变成一种快速、高效的同步方式。

  • 例如,为了保证一行代码(例如赋值)的互斥运行,最快速的方法是使用中断锁而不是信号量或互斥量:
  •   /* 关闭中断 */
    level = rt_hw_interrupt_disable();
    a = a + value;
    /* 恢复中断 */
    rt_hw_interrupt_enable(level);
  • 在使用中断锁时,需要确保关闭中断的时间非常短,例如上面代码中的 a = a + value; 也可换成另外一种方式,例如使用信号量:.

  • rt_sem_take 、rt_sem_release 的实现中,已经存在使用中断锁保护信号量内部变量的行为,所以对于简单如 a = a + value; 的操作,使用中断锁将更为简洁快速。
  •     /* 获得信号量锁 */
    rt_sem_take(sem_lock, RT_WAITING_FOREVER);
    a = a + value;
    /* 释放信号量锁 */
    rt_sem_release(sem_lock);
  • 函数 rt_base_t rt_hw_interrupt_disable(void) 和函数 void rt_hw_interrupt_enable(rt_base_t level) 一般需要配对使用,从而保证正确的中断状态。

  • 在 RT-Thread 中,开关全局中断的 API 支持多级嵌套使用:
  • #include <rthw.h>
    
    void global_interrupt_demo(void)
    {
    rt_base_t level0;
    rt_base_t level1; /* 第一次关闭全局中断,关闭之前的全局中断状态可能是打开的,也可能是关闭的 */
    level0 = rt_hw_interrupt_disable();
    /* 第二次关闭全局中断,关闭之前的全局中断是关闭的,关闭之后全局中断还是关闭的 */
    level1 = rt_hw_interrupt_disable(); do_something(); /* 恢复全局中断到第二次关闭之前的状态,所以本次 enable 之后全局中断还是关闭的 */
    rt_hw_interrupt_enable(level1);
    /* 恢复全局中断到第一次关闭之前的状态,这时候的全局中断状态可能是打开的,也可能是关闭的 */
    rt_hw_interrupt_enable(level0);
    }

中断通知

  • 当整个系统被中断打断,进入中断处理函数时,需要通知内核当前已经进入到中断状态。可使用接口:
  • void rt_interrupt_enter(void);
    void rt_interrupt_leave(void);
  • 每当进入中断时,可以调用 rt_interrupt_enter() 函数,用于通知内核,当前已经进入了中断状态,并增加中断嵌套深度(执行 rt_interrupt_nest++);

  • 当退出中断时,可以调用 rt_interrupt_leave() 函数,用于通知内核,当前已经离开了中断状态,并减少中断嵌套深度(执行 rt_interrupt_nest --)。注意不要在应用程序中调用这两个接口函数;
  • 使用 rt_interrupt_enter/leave() 的作用是,在中断服务程序中,如果调用了内核相关的函数(如释放信号量等操作),则可以通过判断当前中断状态,让内核及时调整相应的行为。例如:在中断中释放了一个信号量,唤醒了某线程,但通过判断发现当前系统处于中断上下文环境中,那么在进行线程切换时应该采取中断中线程切换的策略,而不是立即进行切换。
  • 在上层应用中,在内核需要知道当前已经进入到中断状态或当前嵌套的中断深度时,可调用 rt_interrupt_get_nest() 接口:
  • /**
    * This function will return the nest of interrupt.
    *
    * User application can invoke this function to get whether current
    * context is interrupt context.
    *
    * @return the number of nested interrupts.
    */
    rt_uint8_t rt_interrupt_get_nest(void)
    {
    return rt_interrupt_nest;
    }

中断与轮训

  • 当驱动外设工作时,其编程模式到底采用中断模式触发还是轮询模式触发往往是驱动开发人员首先要考虑的问题,并且这个问题在实时操作系统与分时操作系统中差异还非常大。因为轮询模式本身采用顺序执行的方式:查询到相应的事件然后进行对应的处理。所以轮询模式从实现上来说,相对简单清晰。
  • 例如往串口中写入数据,仅当串口控制器写完一个数据时,程序代码才写入下一个数据(否则这个数据丢弃掉)。相应的代码可以是这样的:
  • /* 轮询模式向串口写入数据 */
    while (size)
    {
    /* 判断 UART 外设中数据是否发送完毕 */
    while (!(uart->uart_device->SR & USART_FLAG_TXE));
    /* 当所有数据发送完毕后,才发送下一个数据 */
    uart->uart_device->DR = (*ptr & 0x1FF); ++ptr; --size;
    }
  • 通常情况下,实时系统中更多采用的是中断模式来驱动外设。当数据达到时,由中断唤醒相关的处理线程,再继续进行后续的动作。例如一些携带 FIFO(包含一定数据量的先进先出队列)的串口外设,其写入过程可以是这样的,如下图所示:

  • 线程先向串口的 FIFO 中写入数据,当 FIFO 满时,线程主动挂起。串口控制器持续地从 FIFO 中取出数据并以配置的波特率(例如 115200bps)发送出去。当 FIFO 中所有数据都发送完成时,将向处理器触发一个中断;当中断服务程序得到执行时,可以唤醒这个线程。这里举例的是 FIFO 类型的设备,在现实中也有 DMA 类型的设备,原理类似。
  • 发送数据量越小,发送速度越快,对于数据吞吐量的影响也将越大。归根结底,取决于系统中产生中断的频度如何。当一个实时系统想要提升数据吞吐量时,可以考虑的几种方式:.
    1. 增加每次数据量发送的长度,每次尽量让外设尽量多地发送数据;

    2. 必要情况下更改中断模式为轮询模式。同时为了解决轮询方式一直抢占处理机,其他低优先级线程得不到运行的情况,可以把轮询线程的优先级适当降低。

全局终端开关使用示例

  • 在多线程访问同一个变量时,使用开关全局中断对该变量进行保护:
  • #include <rthw.h>
    #include <rtthread.h> #define THREAD_PRIORITY 20
    #define THREAD_STACK_SIZE 512
    #define THREAD_TIMESLICE 5 /* 同时访问的全局变量 */
    static rt_uint32_t cnt;
    void thread_entry(void *parameter)
    {
    rt_uint32_t no;
    rt_uint32_t level; no = (rt_uint32_t) parameter;
    while ()
    {
    /* 关闭全局中断 */
    level = rt_hw_interrupt_disable();
    cnt += no;
    /* 恢复全局中断 */
    rt_hw_interrupt_enable(level); rt_kprintf("protect thread[%d]'s counter is %d\n", no, cnt);
    rt_thread_mdelay(no * );
    }
    } /* 用户应用程序入口 */
    int interrupt_sample(void)
    {
    rt_thread_t thread; /* 创建 t1 线程 */
    thread = rt_thread_create("thread1", thread_entry, (void *),
    THREAD_STACK_SIZE,
    THREAD_PRIORITY, THREAD_TIMESLICE);
    if (thread != RT_NULL)
    rt_thread_startup(thread); /* 创建 t2 线程 */
    thread = rt_thread_create("thread2", thread_entry, (void *),
    THREAD_STACK_SIZE,
    THREAD_PRIORITY, THREAD_TIMESLICE);
    if (thread != RT_NULL)
    rt_thread_startup(thread); return ;
    } /* 导出到 msh 命令列表中 */
    MSH_CMD_EXPORT(interrupt_sample, interrupt sample);

    运行结果:

  •  \ | /
    - RT - Thread Operating System
    / | \ 3.1. build Aug
    - Copyright by rt-thread team
    msh >interrupt_sample
    msh >protect thread[]'s counter is 10
    protect thread[]'s counter is 30
    protect thread[]'s counter is 40
    protect thread[]'s counter is 60
    protect thread[]'s counter is 70
    protect thread[]'s counter is 80
    protect thread[]'s counter is 100
    protect thread[]'s counter is 110
    protect thread[]'s counter is 120
    protect thread[]'s counter is 140
  • 由于关闭全局中断会导致整个系统不能响应中断,所以在使用关闭全局中断做为互斥访问临界区的手段时,必须需要保证关闭全局中断的时间非常短,例如运行数条机器指令的时间。

参考

  • 《RT-Thread 编程指南》

RT-Thread--中断管理的更多相关文章

  1. Linux中断管理

    CPU和外设之间的交互,或CPU通过轮询机制查询,或外设通过中断机制主动上报. 对大部分外设中断比轮询效率高,但比如网卡驱动采取轮询比中断效率高. 这里重点关注ARM+Linux组合下中断管理,从底层 ...

  2. Linux中断管理 (1)Linux中断管理机制

    目录: <Linux中断管理> <Linux中断管理 (1)Linux中断管理机制> <Linux中断管理 (2)软中断和tasklet> <Linux中断管 ...

  3. Linux中断管理 (1)Linux中断管理机制【转】

    转自:https://www.cnblogs.com/arnoldlu/p/8659981.html 目录: <Linux中断管理> <Linux中断管理 (1)Linux中断管理机 ...

  4. STM32中断管理函数

    CM3 内核支持256 个中断,其中包含了16 个内核中断和240 个外部中断,并且具有256 级的可编程中断设置.但STM32 并没有使用CM3 内核的全部东西,而是只用了它的一部分. STM32 ...

  5. STM32-NVIC中断管理实现[直接操作寄存器]

    源:stm32 NVIC中断管理实现[直接操作寄存器]     cortex-m3支持256个中端,其中包含了16个内核中断,240个外部中断.stm32只有84个中断,包括16个内核中断和68个可屏 ...

  6. Linux中断管理 (2)软中断和tasklet

    目录: <Linux中断管理> <Linux中断管理 (1)Linux中断管理机制> <Linux中断管理 (2)软中断和tasklet> <Linux中断管 ...

  7. Linux中断管理 (3)workqueue工作队列

    目录: <Linux中断管理> <Linux中断管理 (1)Linux中断管理机制> <Linux中断管理 (2)软中断和tasklet> <Linux中断管 ...

  8. Linux中断管理 (3)workqueue工作队列【转】

    转自:https://www.cnblogs.com/arnoldlu/p/8659988.html 目录: <Linux中断管理> <Linux中断管理 (1)Linux中断管理机 ...

  9. STM32F103ZET6的中断管理

    1.STM32的中断 STM32的中断管理是属于内核部分的,所以中断管理的寄存器也是属于内核组,不属于芯片外设,在查看相关资料的时候,需要查看相对应的内核手册. STM32F103ZET6是Corte ...

  10. RT Thread 通过ENV来配置SFUD,操作SPI Flash

    本实验基于正点原子stm32f4探索者板子 请移步我的RT Thread论坛帖子. https://www.rt-thread.org/qa/forum.php?mod=viewthread& ...

随机推荐

  1. PBU流速权

    PBU概念 PBU是“Participant Business Unit”的英文缩写,中文全称是“参与者交易业务单元”. 在新版交易规则中对此有定义:“参与者交易业务单元”是指交易参与人据此参与本所证 ...

  2. MySQL普通索引性能试验

    首先使用如下node.js脚本创建两张表,并为这两张表各自生成10000条数据: var fs = require('fs'); var nameS = "赵钱孙李周吴郑王冯陈褚卫蒋沈韩杨朱 ...

  3. Docker快速入门——Docker-Compose

    一.Docker-Compose简介 1.Docker-Compose简介 Docker-Compose项目是Docker官方的开源项目,负责实现对Docker容器集群的快速编排.Docker-Com ...

  4. 导入数据到数据库表,报错[Err] [Row1] [Imp] 1153 - Got a packet bigger than 'max_allowed_packet' bytes

    # 在数据库新增查询,执行如下命令即可: show VARIABLES like '%max_allowed_packet%'; set global max_allowed_packet = 2*1 ...

  5. ConfigMap介绍

    来源 ConfigMap API资源用来保存key-value pair配置数据,这个数据可以在pods里使用,或者被用来为像controller一样的系统组件存储配置数据.虽然ConfigMap跟S ...

  6. 微信小程序中的事件绑定

    前言: 微信小程序中的事件绑定和Vue中的事件绑定其实有很多的相似之处,所以如果有过Vue相关的经验,学起来的话还是比较容易的. js代码: // 页面级的js文件必须调用Page函数来注册页面, / ...

  7. 洛谷 题解 UVA12661 【有趣的赛车比赛 Funny Car Racing】

    [题意] 在一个赛车比赛中,赛道有\(n(n<=300)\)个交叉点和\(m(m<=50000)\)条单向道路.有趣的是,每条道路都是周期性关闭的.每条道路用5个整数\(u,v,a,b,t ...

  8. Ubuntu bashrc profile environment 区别

    在 Ubuntu 中有如下几个文件可以设置环境变量: /etc/profile:在登录时,操作系统定制用户环境时使用的第一个文件,此文件为系统的每个用户设置环境信息,当用户第一次登录时,该文件被执行. ...

  9. 共阳极RGB LED二极管

    1)RGB LED二极管有四个引脚,它把3个普通led被封装在其内部,这三个led颜色分别为红.绿.蓝三种颜色,通过控制各个LED的亮度,你可以混合出几乎任何你想要的颜色,如下图: 2)RGB LED ...

  10. 在论坛中出现的比较难的sql问题:13(循环替换问题 过滤各种标点符号)

    原文:在论坛中出现的比较难的sql问题:13(循环替换问题 过滤各种标点符号) 所以,觉得有必要记录下来,这样以后再次碰到这类问题,也能从中获取解答的思路. 去掉一个字段中的标点符号的SQL语句怎么写 ...