注:本文僅針對Cortex-M3/4 系列進行講述。架構
在傳統的ARM處理器架構中,常使用SWP指令來實現鎖的讀/寫原子操做,但從ARM v6開始,讀/寫訪問在獨立的兩條總線上進行,SWP指令已沒法在此架構下保證讀/寫訪問的原子操做,所以互斥訪問指令應運而生。本文結合項目中運用的相關方法,總結Cortex-M芯片經常使用的互斥訪問方法。函數
ARM支持的互斥指令對有LDREX/STREX、LDREXB/STREXB 及 LDREXH/STREXH(專有的寄存器加載/存儲指令),其分別支持字/字節/半字訪問,本節以LDREX/STREX爲例.ui
LDREX{cond} Rt, [Rn {, #offset}]atom
STREX{cond} Rd, Rt, [Rn {, #offset}] spa
其中3d
cond: 可選狀態碼-若指令包含此狀態碼,則只有當APSR寄存器中的狀態位知足狀態碼條件時,指令纔會執行指針
Rd: 目的寄存器-指令執行後的返回狀態,0執行成功,1執行失敗blog
Rt: 待加載/存儲的寄存器事件
Rn: 寄存器地址內存
offset: 可選的地址偏移
使用互斥訪問指令時,需知足如下基本要求,以防不可預期的結果出現。
1. LDREX/STREX必須成對出現
2. LDREX/STREX的Rn寄存器地址必須一致,操做的寄存器長度必須一致
3. LDREX/STREX之間不得使用PC指針,操做的寄存器不使用SP指針
4. LDREX/STREX之間的指令要儘量的簡短,offset需4字節對齊,範圍在0~1020之間(不一樣的廠商設置範圍不一樣)
在知足基本要求後,互斥寫不必定成功,如互斥操做中途遇到如下狀況:
1. 調用CLREX指令清除互斥狀態
2. 發生上下午切換(如中斷)
3. 以前未執行過LDREX
4. 總線反饋的互斥錯誤
以nRF52源碼中的 nrf_atomic_internal_orr() 函數爲例,該函數實現了或運算的原子操做,其中p_ptr爲初始值,value爲或運算因子,p_new爲運算後的值,函數返回原爲子操做以前的p_ptr的值。
先簡單描述上述各行代碼:
89: r0/r1/r2分別存儲的p_ptr/value/p_new的值
94:將p_ptr地址付給r4
97:將r4所指向的值賦給r0,r0得到了p_ptr此時的值
98:對r0存儲的值進行或運算,運算值賦給r5
99:將r5的值存儲給r4指向的地址,即更新p_ptr的值,同時將本條指令的執行結果賦給r3
100/101:判斷返回值r3,若不爲0,重試 97~99的操做
103/104/105:將運算值賦給r2指向的值,即獲得新值
代碼的關鍵在97行,需注意的是,當函數執行結束返回時,r0存儲函數的返回值,所以此函數的返回值爲原子操做以前的p_ptr值,而不是調用此函數時傳入的p_ptr值(中途可能有變)
以實際場景爲例,倘若存在兩個任務A和B,以及一個共享內存Mem,互斥變量Flag標記Mem是否正在被佔用(0:空閒中,1:佔用中),要如何實現呢?
1. A首先調用 nrf_atomic_internal_orr() 函數(Flag=0),嘗試原子操做,此時R0=0,執行結束後,由返回值R0可知,Flag成功由0->1,A佔用Mem成功
2. 此時發生任務切換
3. B調用 nrf_atomic_internal_orr() 函數(Flag=1),嘗試原子操做,此時R0=1,執行結束後,由返回值R0可知,Flag在置位以前已是1,B佔用Mem失敗
注:由於只有A/B前後訪問nrf_atomic_internal_orr()函數,所以各自只須要嘗試一次原子操做便可成功。
1. A首先調用 nrf_atomic_internal_orr() 函數(Flag=0),嘗試第一次原子操做,此時R0=0,此時發生任務切換
2. A被搶佔,上下文切換退出
3. B調用 nrf_atomic_internal_orr() 函數(Flag=0),嘗試第一次原子操做,此時R0=0,執行結束後,由返回值R0可知,Flag成功由0->1,B佔用Mem成功
4. 此時發生任務切換
5. A繼續執行第一次原子操做,因在LDREX/STREX之間已發生上下文切換,這次原子操做STREX返回 1,執行失敗
6. A繼續執行第二次原子操做,注意:此時R0重載,R0=1,執行結束後,由返回值R0可知,Flag在置位以前已是1,A佔用Mem失敗
所以本例中,調用nrf_atomic_internal_orr() 執行原子操做後,經過判斷函數返回值可知,本次互斥操做是否搶佔資源成功。
在支持 「locked transfers」或僅有單個總線主機的內存系統中,使用位帶操做也可實現信號量操做。要實現互斥訪問某個資源,操做過程當中需遵循如下幾點:
1. 系統爲每一個需互斥訪問的任務分配一個位帶bit位,
2. 任務僅能對本身的bit位進行讀-修改-寫操做。
2. 不能以常規的寫方式來直接修改位帶區域值,不然可能丟失已鎖定的位信息
具體操做過程直接上圖:
優勢:可以使用C代碼直接實現上述互斥訪問邏輯。
最爲簡單粗暴的互斥訪問方法,FreeRTOS的信號量獲取/釋放操做便採用此方式進入臨界區。
關中斷實現起來雖然簡單,但也需根據具體場景來選擇關總中斷仍是外設中斷,不然可能下降系統的實時性甚至形成數據丟失。
舉例來講,在以前經歷的一個項目中,有一款MCU既須要負責USB數據的收發,同時還得處理無線數據的轉發,如在處理USB臨界區數據時選擇關總中斷,則可能致使無線數據沒法及時處理甚至致使丟包,在該場景下,若選擇只關閉USB中斷,則MCU依然可以在實現局部互斥操做的同時實時響應優先級更高的事件。
TI 《Cortex-M3/M4F Instruction Set》
宋巖 《Cortex -M3 權威指南》
《The Definitive guild to the ARM Cortex-M3》Second Edition》
《The Definitive guild to the ARM Cortex-M3 and Cortex-M4 Processors》Third Edition