RT-Thread--中斷管理


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     0                          ; Reserved
                    DCD     0                          ; Reserved
                    DCD     0                          ; Reserved
                    DCD     0                          ; Reserved
                    DCD     SVC_Handler                ; SVCall Handler
                    DCD     DebugMon_Handler           ; Debug Monitor Handler
                    DCD     0                          ; 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", 0, RT_IPC_FLAG_FIFO);
    
        while(1)
        {
            /* 最后,讓 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, 1024, 20, 5);
    
        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 (1)
        {
            /* 關閉全局中斷 */
            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 * 10);
        }
    }
    
    /* 用戶應用程序入口 */
    int interrupt_sample(void)
    {
        rt_thread_t thread;
    
        /* 創建 t1 線程 */
        thread = rt_thread_create("thread1", thread_entry, (void *)10,
                                  THREAD_STACK_SIZE,
                                  THREAD_PRIORITY, THREAD_TIMESLICE);
        if (thread != RT_NULL)
            rt_thread_startup(thread);
    
    
        /* 創建 t2 線程 */
        thread = rt_thread_create("thread2", thread_entry, (void *)20,
                                  THREAD_STACK_SIZE,
                                  THREAD_PRIORITY, THREAD_TIMESLICE);
        if (thread != RT_NULL)
            rt_thread_startup(thread);
    
        return 0;
    }
    
    /* 導出到 msh 命令列表中 */
    MSH_CMD_EXPORT(interrupt_sample, interrupt sample);

    運行結果:

  •  \ | /
    - RT -     Thread Operating System
     / | \     3.1.0 build Aug 27 2018
     2006 - 2018 Copyright by rt-thread team
    msh >interrupt_sample
    msh >protect thread[10]'s counter is 10
    protect thread[20]'s counter is 30
    protect thread[10]'s counter is 40
    protect thread[20]'s counter is 60
    protect thread[10]'s counter is 70
    protect thread[10]'s counter is 80
    protect thread[20]'s counter is 100
    protect thread[10]'s counter is 110
    protect thread[10]'s counter is 120
    protect thread[20]'s counter is 140

     

  • 由於關閉全局中斷會導致整個系統不能響應中斷,所以在使用關閉全局中斷做為互斥訪問臨界區的手段時,必須需要保證關閉全局中斷的時間非常短,例如運行數條機器指令的時間。

參考

  • 《RT-Thread 編程指南》

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM