從0到1寫RT_Thread內核 ——— 臨界段保護的實現

  臨界段通俗的解釋就是一段不能被打斷執行的代碼,比如說再對內部FLASH進行寫入時,可以加上臨界段的保護,多線程對一個全局變量的操作時,加上臨界段的保護可以避免一些意外的情況發生。比如這個線程在進行對此全局變量加操作,其他線程又在減操作。

  對臨界段的打斷都是中斷的產生,無論是外部中斷,還是線程切換(PenSV中斷)。所以對臨界段的保護一般都是在這段時間內進行關中斷操作。

  以下爲Cortex-M內核的快速關中斷指令。

CPSID I ;PRIMASK=1 ;關中斷
CPSIE I ;PRIMASK=0 ;開中斷
CPSID F ;FAULTMASK=1 ;關異常
CPSIE F ;FAULTMASK=0 ;開異常

 Cortex-M內核之中有3箇中斷屏蔽寄存器,如下圖所示

中斷屏蔽寄存器 功能描述
PRIMASK 大小1bit。置位爲1,關掉所有可屏蔽的異常,只有NMI(不可屏蔽中斷)和硬件FAULT可以響應。默認爲0
FAULIMASK 大小1bit。置位爲1,只有NMI可以響應。默認爲0
BASEPRI 定義了被屏蔽優先級的閾值。設定好以後,只有優先級比閾值高的可以響應。(優先級的數字越小,優先級越高)。0爲默認值,不關閉任何中斷。

以下爲實現開關中斷的彙編語言的實現

;/* 關中斷
; * rt_base_t rt_hw_interrupt_disable();
; */
rt_hw_interrupt_disable    PROC
    EXPORT  rt_hw_interrupt_disable ;EXPORT關鍵字導出,讓C文件調用此函數
    MRS     r0, PRIMASK    ;MRS 將特殊寄存器PRIMASK的值存儲到r0
    CPSID   I
    BX      LR
    ENDP

;/* 開中斷
; * void rt_hw_interrupt_enable(rt_base_t level);
; */
rt_hw_interrupt_enable    PROC
    EXPORT  rt_hw_interrupt_enable
    MSR     PRIMASK, r0  ;MRS 將通用寄存器r0的值存儲到特殊寄存器PRIMASK
    BX      LR           ;子程序返回
    ENDP

這裏面有一個小的技巧,代碼中出現了MRS指令和MSR指令。通常按常理的情況下直接開關中斷就可以了,爲什麼每次關中斷的時候要將PRIMASK寄存器的值存儲到r0,開中斷時需要將r0寄存器的值存儲到PRIMASK。這個是爲臨界段段嵌套而設計的一種做法。比如下面程序(示意程序)臨界段嵌套:

; 開關中斷函數的實現
;/*
; * void rt_hw_interrupt_disable();
; */
rt_hw_interrupt_disable PROC
EXPORT rt_hw_interrupt_disable
CPSID I
BX LR
ENDP

;/*
; * void rt_hw_interrupt_enable(void);
; */
rt_hw_interrupt_enable PROC
EXPORT rt_hw_interrupt_enable
CPSIE I
BX LR
ENDP

PRIMASK = 0; /* PRIMASK 初始值爲 0,表示沒有關中斷 */

/* 臨界段代碼 */
{
    /* 臨界段 1 開始 */
    rt_hw_interrupt_disable(); /* 關中斷,PRIMASK = 1 */
    {
        /* 臨界段 2 */
        rt_hw_interrupt_disable(); /* 關中斷,PRIMASK = 1 */
       
        rt_hw_interrupt_enable(); /* 開中斷,PRIMASK = 0 */ (注意)
    }
    /* 臨界段 1 結束 */
    rt_hw_interrupt_enable(); /* 開中斷,PRIMASK = 0 */
}

 分析以上程序,當第一次關閉中斷時,PRIMASK=1,確實關閉中斷了。此時有進行嵌套臨界段保護,再一次關閉中斷,PRIMASK=1。前面一切正常,但是當進行到開啓內嵌中斷時,此時PRIMASK = 0。開啓中斷成功,可是這只是開啓的內嵌,卻把全部中斷都打開了,我們臨界段1還沒結束,所有的中斷就已經打開了。注意:在開關中斷中,沒有保存特殊寄存器和讀取特殊寄存器。

 

由此,採用了以下的方法來避免了這個臨界段嵌套的問題,在中斷開關中使用了MRS指令和MSR指令。

理解下面的程序時,需要注意一個基本的知識點: 當一個彙編函數在 C 文件中調用的時候,如果有一個形參,則執行的時候會將這個 形參傳入到 CPU 寄存器 r0,如果有兩個形參第二個則傳入到 r1 。

;/* 關中斷
; * rt_base_t rt_hw_interrupt_disable();
; */
rt_hw_interrupt_disable PROC
EXPORT rt_hw_interrupt_disable
MRS r0, PRIMASK ;將PRIMASK的值存儲到R0
CPSID I
BX LR  ;這裏會返回r0的值作爲函數的返回值
ENDP

;/* 開中斷
; * void rt_hw_interrupt_enable(rt_base_t level);
; */
rt_hw_interrupt_enable PROC
EXPORT rt_hw_interrupt_enable
MSR PRIMASK, r0  ;函數形參的值傳入後自動保存到r0
BX LR
ENDP

PRIMASK = 0; /* PRIMASK 初始值爲 0,表示沒有關中斷 */ 
rt_base_t level1; 
rt_base_t level2;

/* 臨界段代碼 */
{
    /* 臨界段 1 開始  用level1 來接收返回值*/
    level1 = rt_hw_interrupt_disable(); /* 關中斷,level1=0,PRIMASK=1 */ 
    {
        /* 臨界段 2 */
        level2 = rt_hw_interrupt_disable(); /* 關中斷,level2=1,PRIMASK=1 */ 
        {
        }
        rt_hw_interrupt_enable(level2); /* 開中斷,level2=1,PRIMASK=1 */  
    }
    /* 臨界段 1 結束 */
    rt_hw_interrupt_enable(level1); /* 開中斷,level1=0,PRIMASK=0 */ 
}

                                                                                                以上內容來自於野火的<<RT_thread內核實現>>的學習與實踐筆記記錄。

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章