註:本文僅針對Cortex-M3/4 系列進行講述。 在傳統的ARM處理器架構中,常使用SWP指令來實現鎖的讀/寫原子操作,但從ARM v6開始,讀/寫訪問在獨立的兩條匯流排上進行,SWP指令已無法在此架構下保證讀/寫訪問的原子操作,因此互斥訪問指令應運而生。本文結合項目中運用的相關方法,總結Cort ...
註:本文僅針對Cortex-M3/4 系列進行講述。
在傳統的ARM處理器架構中,常使用SWP指令來實現鎖的讀/寫原子操作,但從ARM v6開始,讀/寫訪問在獨立的兩條匯流排上進行,SWP指令已無法在此架構下保證讀/寫訪問的原子操作,因此互斥訪問指令應運而生。本文結合項目中運用的相關方法,總結Cortex-M晶元常用的互斥訪問方法。
互斥訪問方式1--LDREX/STREX指令
ARM支持的互斥指令對有LDREX/STREX、LDREXB/STREXB 及 LDREXH/STREXH(專有的寄存器載入/存儲指令),其分別支持字/位元組/半字訪問,本節以LDREX/STREX為例.
語法格式
LDREX{cond} Rt, [Rn {, #offset}]
STREX{cond} Rd, Rt, [Rn {, #offset}]
其中
cond: 可選狀態碼-若指令包含此狀態碼,則只有當APSR寄存器中的狀態位滿足狀態碼條件時,指令才會執行
Rd: 目的寄存器-指令執行後的返回狀態,0執行成功,1執行失敗
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/B先後訪問Mem,則
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()函數,因此各自只需要嘗試一次原子操作即可成功。
情況2.A/B同時訪問Mem,A在原子操作過程中被B搶占,則
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() 執行原子操作後,通過判斷函數返回值可知,本次互斥操作是否搶占資源成功。
互斥訪問方式2--Bit-Band操作
在支持 “locked transfers”或僅有單個匯流排主機的記憶體系統中,使用位帶操作也可實現信號量操作。要實現互斥訪問某個資源,操作過程中需遵循以下幾點:
1. 系統為每個需互斥訪問的任務分配一個位帶bit位,
2. 任務僅能對自己的bit位進行讀-修改-寫操作。
2. 不能以常規的寫方式來直接修改位帶區域值,否則可能丟失已鎖定的位信息
具體操作過程直接上圖:
優點:可使用C代碼直接實現上述互斥訪問邏輯。
互斥訪問方式3--關中斷
最為簡單粗暴的互斥訪問方法,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