臨界段通俗的解釋就是一段不能被打斷執行的代碼,比如說再對內部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內核實現>>的學習與實踐筆記記錄。