自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

鴻蒙內(nèi)核源碼分析(原子操作篇) | 是誰在為原子操作保駕護航?

開發(fā)
文章由鴻蒙社區(qū)產(chǎn)出,想要了解更多內(nèi)容請前往:51CTO和華為官方戰(zhàn)略合作共建的鴻蒙技術(shù)社區(qū)https://harmonyos.51cto.com

[[390980]]

想了解更多內(nèi)容,請訪問:

51CTO和華為官方合作共建的鴻蒙技術(shù)社區(qū)

https://harmonyos.51cto.com

本篇說清楚原子操作

讀本篇之前建議先讀鴻蒙內(nèi)核源碼分析(總目錄)系列篇.

基本概念

在支持多任務(wù)的操作系統(tǒng)中,修改一塊內(nèi)存區(qū)域的數(shù)據(jù)需要“讀取-修改-寫入”三個步驟。然而同一內(nèi)存區(qū)域的數(shù)據(jù)可能同時被多個任務(wù)訪問,如果在修改數(shù)據(jù)的過程中被其他任務(wù)打斷,就會造成該操作的執(zhí)行結(jié)果無法預(yù)知。

使用開關(guān)中斷的方法固然可以保證多任務(wù)執(zhí)行結(jié)果符合預(yù)期,但這種方法顯然會影響系統(tǒng)性能。

ARMv6架構(gòu)引入了LDREX和STREX指令,以支持對共享存儲器更縝密的非阻塞同步。由此實現(xiàn)的原子操作能確保對同一數(shù)據(jù)的“讀取-修改-寫入”操作在它的執(zhí)行期間不會被打斷,即操作的原子性。

有多個任務(wù)對同一個內(nèi)存數(shù)據(jù)進行加減或交換操作時,使用原子操作保證結(jié)果的可預(yù)知性。

看過鴻蒙內(nèi)核源碼分析(總目錄)自旋鎖篇的應(yīng)該對LDREX和STREX指令不陌生的,自旋鎖的本質(zhì)就是對某個變量的原子操作,而且一定要通過匯編代碼實現(xiàn),也就是說LDREX和STREX指令保證了原子操作的底層實現(xiàn). 回顧下自旋鎖申請和釋放鎖的匯編代碼.

ArchSpinLock 申請鎖代碼

  1. FUNCTION(ArchSpinLock)  @死守,非要拿到鎖 
  2.     mov     r1, #1      @r1=1 
  3. 1:                      @循環(huán)的作用,因SEV是廣播事件.不一定lock->rawLock的值已經(jīng)改變了 
  4.     ldrex   r2, [r0]    @r0 = &lock->rawLock, 即 r2 = lock->rawLock 
  5.     cmp     r2, #0      @r2和0比較 
  6.     wfene               @不相等時,說明資源被占用,CPU核進入睡眠狀態(tài) 
  7.     strexeq r2, r1, [r0]@此時CPU被重新喚醒,嘗試令lock->rawLock=1,成功寫入則r2=0 
  8.     cmpeq   r2, #0      @再來比較r2是否等于0,如果相等則獲取到了鎖 
  9.     bne     1b          @如果不相等,繼續(xù)進入循環(huán) 
  10.     dmb                 @用DMB指令來隔離,以保證緩沖中的數(shù)據(jù)已經(jīng)落實到RAM中 
  11.     bx      lr          @此時是一定拿到鎖了,跳回調(diào)用ArchSpinLock函數(shù) 

ArchSpinUnlock 釋放鎖代碼

  1. FUNCTION(ArchSpinUnlock)    @釋放鎖 
  2.      mov     r1, #0          @r1=0                
  3.      dmb                     @數(shù)據(jù)存儲隔離,以保證緩沖中的數(shù)據(jù)已經(jīng)落實到RAM中 
  4.      str     r1, [r0]        @令lock->rawLock = 0 
  5.      dsb                     @數(shù)據(jù)同步隔離 
  6.      sev                     @給各CPU廣播事件,喚醒沉睡的CPU們 
  7.      bx      lr              @跳回調(diào)用ArchSpinLock函數(shù) 

運作機制

鴻蒙通過對ARMv6架構(gòu)中的LDREX和STREX進行封裝,向用戶提供了一套原子操作接口。

● LDREX Rx, [Ry] 讀取內(nèi)存中的值,并標記對該段內(nèi)存為獨占訪問:

◊ 讀取寄存器Ry指向的4字節(jié)內(nèi)存數(shù)據(jù),保存到Rx寄存器中。

◊ 對Ry指向的內(nèi)存區(qū)域添加獨占訪問標記。

● STREX Rf, Rx, [Ry] 檢查內(nèi)存是否有獨占訪問標記,如果有則更新內(nèi)存值并清空標記,否則不更新內(nèi)存:

◊ 有獨占訪問標記

◊ 將寄存器Rx中的值更新到寄存器Ry指向的內(nèi)存。

◊ 標志寄存器Rf置為0。

◊ 沒有獨占訪問標記

◊ 不更新內(nèi)存。

◊ 標志寄存器Rf置為1。

● 判斷標志寄存器 標志寄存器為0時,退出循環(huán),原子操作結(jié)束。 標志寄存器為1時,繼續(xù)循環(huán),重新進行原子操作。

功能列表

原子數(shù)據(jù)包含兩種類型Atomic(有符號32位數(shù))與 Atomic64(有符號64位數(shù))。原子操作模塊為用戶提供下面幾種功能,接口詳細信息可以查看源碼。

此處講述 LOS_AtomicAdd , LOS_AtomicSub,LOS_AtomicRead,LOS_AtomicSet 理解了函數(shù)的匯編代碼是理解的原子操作的關(guān)鍵.

LOS_AtomicAdd

  1. //對內(nèi)存數(shù)據(jù)做加法 
  2. STATIC INLINE INT32 LOS_AtomicAdd(Atomic *v, INT32 addVal)   
  3.     INT32 val; 
  4.     UINT32 status; 
  5.  
  6.     do { 
  7.         __asm__ __volatile__("ldrex   %1, [%2]\n" 
  8.                              "add   %1, %1, %3\n"  
  9.                              "strex   %0, %1, [%2]" 
  10.                              : "=&r"(status), "=&r"(val) 
  11.                              : "r"(v), "r"(addVal) 
  12.                              : "cc"); 
  13.     } while (__builtin_expect(status != 0, 0)); 
  14.  
  15.     return val; 

這是一段C語言內(nèi)嵌匯編,逐一解讀

● 1.先將 val status v addVal的值交由通用寄存器(R0~R3)接管.

● 2.%2代表了入?yún),[%2]代表的是參數(shù)v指向地址的值,也就是 *v ,函數(shù)要獨占的就是它

● 3.%0 ~ %3 對應(yīng) val status v addVal

● 4.ldrex %1, [%2] 表示 val = *v ;

● 5.add %1, %1, %3 表示 val = val + addVal;

● 6.strex %0, %1, [%2] 表示 *v = val;

● 7.status 表示是否更新成功,成功了置0,不成功則為 1

● 8.__builtin_expect是結(jié)束循環(huán)的判斷語句,將最有可能執(zhí)行的分支告訴編譯器。 這個指令的寫法為:

__builtin_expect(EXP, N)。

意思是:EXP==N 的概率很大。

綜合理解__builtin_expect(status != 0, 0)

說的是status = 1失敗的可能性很大,不成功就重新來一遍,直到strex更新成(status == 0)為止.

● 9."=&r"(val) 被修飾的操作符作為輸出,即將寄存器的值回給val,val為函數(shù)的返回值

● 10."cc"向GCC編譯器聲明以上信息.

LOS_AtomicSub

  1. //對內(nèi)存數(shù)據(jù)做減法 
  2. STATIC INLINE INT32 LOS_AtomicSub(Atomic *v, INT32 subVal)   
  3.     INT32 val; 
  4.     UINT32 status; 
  5.  
  6.     do { 
  7.         __asm__ __volatile__("ldrex   %1, [%2]\n" 
  8.                              "sub   %1, %1, %3\n" 
  9.                              "strex   %0, %1, [%2]" 
  10.                              : "=&r"(status), "=&r"(val) 
  11.                              : "r"(v), "r"(subVal) 
  12.                              : "cc"); 
  13.     } while (__builtin_expect(status != 0, 0)); 
  14.  
  15.     return val; 

解讀

● 同 LOS_AtomicAdd解讀

volatile

這里要重點說下volatile,volatile 提醒編譯器它后面所定義的變量隨時都有可能改變,因此編譯后的程序每次需要存儲或讀取這個變量的時候,都要直接從變量地址中讀取數(shù)據(jù)。如果沒有volatile關(guān)鍵字,則編譯器可能優(yōu)化讀取和存儲,可能暫時使用寄存器中的值,如果這個變量由別的程序更新了的話,將出現(xiàn)不一致的現(xiàn)象。

  1. //讀取內(nèi)存數(shù)據(jù) 
  2. STATIC INLINE INT32 LOS_AtomicRead(const Atomic *v)  
  3.     return *(volatile INT32 *)v; 
  4. //寫入內(nèi)存數(shù)據(jù) 
  5. STATIC INLINE VOID LOS_AtomicSet(Atomic *v, INT32 setVal)    
  6.     *(volatile INT32 *)v = setVal; 

編程實例

調(diào)用原子操作相關(guān)接口,觀察結(jié)果:

1.創(chuàng)建兩個任務(wù)

● 任務(wù)一用LOS_AtomicAdd對全局變量加100次。

● 任務(wù)二用LOS_AtomicSub對全局變量減100次。

2.子任務(wù)結(jié)束后在主任務(wù)中打印全局變量的值。

  1. #include "los_hwi.h" 
  2. #include "los_atomic.h" 
  3. #include "los_task.h" 
  4.  
  5. UINT32 g_testTaskId01; 
  6. UINT32 g_testTaskId02; 
  7. Atomic g_sum; 
  8. Atomic g_count; 
  9.  
  10. UINT32 Example_Atomic01(VOID) 
  11.     int i = 0; 
  12.     for(i = 0; i < 100; ++i) { 
  13.         LOS_AtomicAdd(&g_sum,1); 
  14.     } 
  15.  
  16.     LOS_AtomicAdd(&g_count,1); 
  17.     return LOS_OK; 
  18.  
  19. UINT32 Example_Atomic02(VOID) 
  20.     int i = 0; 
  21.     for(i = 0; i < 100; ++i) { 
  22.         LOS_AtomicSub(&g_sum,1); 
  23.     } 
  24.  
  25.     LOS_AtomicAdd(&g_count,1); 
  26.     return LOS_OK; 
  27.  
  28. UINT32 Example_TaskEntry(VOID) 
  29.     TSK_INIT_PARAM_S stTask1={0}; 
  30.     stTask1.pfnTaskEntry = (TSK_ENTRY_FUNC)Example_Atomic01; 
  31.     stTask1.pcName       = "TestAtomicTsk1"
  32.     stTask1.uwStackSize  = LOSCFG_BASE_CORE_TSK_DEFAULT_STACK_SIZE; 
  33.     stTask1.usTaskPrio   = 4; 
  34.     stTask1.uwResved     = LOS_TASK_STATUS_DETACHED; 
  35.  
  36.     TSK_INIT_PARAM_S stTask2={0}; 
  37.     stTask2.pfnTaskEntry = (TSK_ENTRY_FUNC)Example_Atomic02; 
  38.     stTask2.pcName       = "TestAtomicTsk2"
  39.     stTask2.uwStackSize  = LOSCFG_BASE_CORE_TSK_DEFAULT_STACK_SIZE; 
  40.     stTask2.usTaskPrio   = 4; 
  41.     stTask2.uwResved     = LOS_TASK_STATUS_DETACHED; 
  42.  
  43.     LOS_TaskLock(); 
  44.     LOS_TaskCreate(&g_testTaskId01, &stTask1); 
  45.     LOS_TaskCreate(&g_testTaskId02, &stTask2); 
  46.     LOS_TaskUnlock(); 
  47.  
  48.     while(LOS_AtomicRead(&g_count) != 2); 
  49.     dprintf("g_sum = %d\n", g_sum); 
  50.  
  51.     return LOS_OK; 

結(jié)果驗證

  1. g_sum = 0 

想了解更多內(nèi)容,請訪問:

51CTO和華為官方合作共建的鴻蒙技術(shù)社區(qū)

https://harmonyos.51cto.com

 

責任編輯:jianghua 來源: 鴻蒙社區(qū)
相關(guān)推薦

2014-01-09 09:45:41

原子飛原子

2021-01-26 11:54:10

Mysql 操作日志

2020-11-27 06:44:22

原子加鎖x86

2023-09-19 23:26:15

++i原子操作C++

2013-01-10 11:32:12

阿里云雙十一云計算

2015-08-19 10:06:21

2012-09-12 09:40:36

云服務(wù)GIS技術(shù)彈性云計算

2021-06-30 09:11:17

KubernetesDevtronK8S

2021-06-29 10:07:24

Javalong原子操作

2021-04-02 09:42:54

鴻蒙HarmonyOS應(yīng)用

2021-04-08 09:32:17

鴻蒙HarmonyOS應(yīng)用

2011-12-16 11:11:24

戴爾

2014-03-13 14:55:45

云計算新媒體虛擬化

2010-06-14 23:32:04

綜合布線機場西蒙

2009-10-29 10:22:22

2013-12-09 16:16:29

初志科技數(shù)據(jù)動車

2014-07-01 10:07:56

2012-06-25 16:57:07

2021-07-14 13:30:44

KubernetesLinux文件
點贊
收藏

51CTO技術(shù)棧公眾號