性无码一区二区三区在线观看,少妇被爽到高潮在线观看,午夜精品一区二区三区,无码中文字幕人妻在线一区二区三区,无码精品国产一区二区三区免费

一口Linux
認證:優(yōu)質(zhì)創(chuàng)作者
所在專題目錄 查看專題
1. 從0開始學(xué)ARM-安裝Keil MDK uVision集成開發(fā)環(huán)境
2. 從0開始學(xué)ARM-CPU原理,基于ARM的SOC講解
3. 從0開始學(xué)ARM-ARM模式、寄存器、流水線
4. 從0開始學(xué)ARM-ARM匯編指令其實很簡單
5. 從0開始學(xué)ARM-MRS、MSR、尋址操作、原子操作原理
6【從0學(xué)ARM】ARM如何處理異常
作者動態(tài) 更多
linux系統(tǒng)監(jiān)控工具小神器:btop
2天前
有沒有權(quán)貴開后門讓子女做軟件開發(fā)人員?
1星期前
一文包你學(xué)會網(wǎng)絡(luò)數(shù)據(jù)抓包
03-15 09:26
C語言初學(xué)者編程水平上不來?不妨嘗試這10個C語言例子
03-14 20:31
Linux rootfs:如何開機就自動添加某個用戶?
03-09 22:18

5. 從0開始學(xué)ARM-MRS、MSR、尋址操作、原子操作原理

一、程序狀態(tài)寄存器訪問指令

ARM微處理器支持程序狀態(tài)寄存器訪問指令,用于在程序狀態(tài)寄存器和通用寄存器之間傳送數(shù)據(jù)。

MRS

MRS{條件} 通用寄存器,程序狀態(tài)寄存器(CPSR或SPSR)

MRS指令用于將程序狀態(tài)寄存器的內(nèi)容傳送到通用寄存器中。該指令一般用在以下幾種情況:

  1. 當(dāng)需要改變程序狀態(tài)寄存器的內(nèi)容時,可用MRS將程序狀態(tài)寄存器的內(nèi)容讀入通用寄存器,修改后再寫回程序狀態(tài)寄存器。
  2. 當(dāng)在異常處理或進程切換時,需要保存程序狀態(tài)寄存器的值,可先用該指令讀出程序狀態(tài)寄存器的值,然后保存。如:
MRS R0,CPSR   ;傳送CPSR的內(nèi)容到R0MRS R0,SPSR   ;傳送SPSR的內(nèi)容到R0

MSR

MSR{條件} 程序狀態(tài)寄存器(CPSR或SPSR)_<域>,操作數(shù)

MSR指令用于將操作數(shù)的內(nèi)容傳送到程序狀態(tài)寄存器的特定域中。其中,操作數(shù)可以為通用寄存器或立即數(shù)。<域>用于設(shè)置程序狀態(tài)寄存器中需要操作的位,32位的程序狀態(tài)寄存器可分為4個域:

位[31:24]為條件標志位域,用f表示;位[23:16]為狀態(tài)位域,用s表示;位[15:8]為擴展位域,用x表示;位[7:0]為控制位域,用c表示;

該指令通常用于恢復(fù)或改變程序狀態(tài)寄存器的內(nèi)容,在使用時,一般要在MSR指令中指明將要操作的域。如:

MSR CPSR,R0   ;傳送R0的內(nèi)容到CPSRMSR SPSR,R0   ;傳送R0的內(nèi)容到SPSRMSR CPSR_c,R0  ;傳送R0的內(nèi)容到SPSR,但僅僅修改CPSR中的控制位域

應(yīng)用舉例

  1. 使能中斷

要是能中斷,必須將寄存器CPSR的bit[7]設(shè)置為0

要將寄存器CPSR的bit[7]設(shè)置為0,但是不能影響其他位,所以必須先用msr讀取出cpsr的值到通用寄存器Rn(n取值0~8),然后修改bit[7]設(shè)置為0,再將該寄存器的值設(shè)置到CPSR中。

代碼如下:

 area reset,code
 code32
 entry
start
 bl enale_irq
enale_irq
 mrs r0,cpsr
 bic r0,r0,#0x80
 msr cpsr_c,r0
 mov pc,lr

執(zhí)行結(jié)果:

  1. 第8行【其實第8行還沒有執(zhí)行】:

  • 當(dāng)前模式時SVC ,因為開機商店屬于reset異常,而該異常會自動進入svc模式
  • CPSR的值是0X000000D3
  1. 9行

  • mrs r0,cpsr 將cpsr的內(nèi)容讀取到寄存器r0中
  • R0的值為0X000000D3
  1. 10行

  • bic r0,r0,#0x80 將r0的第7個bit位置設(shè)置為0(從低往高數(shù),0開始計數(shù))
  • 寄存器R0的值變成0x00000053
  1. 11行

  • msr cpsr_c,r0 將構(gòu)造好的值寫回CPSR,
  • 此時CPSR的I 位已經(jīng)為0從而實現(xiàn)了中斷使能
  1. 禁止中斷 同理,我們要關(guān)閉中斷,只需要將CPSR的I位設(shè)置為1即可。
 area reset,code
 code32
 entry
start
 bl diable_irq
diable_irq
 mrs r0,cpsr
 orr r0,r0,#0x80
 msr cpsr_c,r0
 mov pc,lr
 end
  1. 設(shè)置各模式的棧地址 要想初始化各個模式的棧地址,必須首先切換到對應(yīng)的模式,然后再將棧地址設(shè)置到寄存器sp即可。

代碼:

 area reset,code
 code32
 entry
start
  bl stack_init
stack_init                      ; 棧指針初始化函數(shù)
;    @undefine_stack                                       
    msr cpsr_c,#0xdb             ; 切換到未定義異常
    ldr sp,=0x34000000      ; 棧指針為內(nèi)存最高地址,棧為倒生的棧
                             ; ??臻g的最后1M 0x34000000~0x33f00000
;    @abort_stack                                                      
    msr cpsr_c,#0xd7                ; 切換到終止異常模式
    ldr        sp,=0x33f00000       ; ??臻g為1M,0x33f00000~0x33e00000
 ;   @irq_stack                                                
    msr      cpsr_c,#0xd2            ; 切換到中斷模式
    ldr        sp,=0x33e00000        ; ??臻g為1M,0x33e00000~0x33d00000
 ;   @ sys_stack                                               
    msr  cpsr_c,#0xdf              ; 切換到系統(tǒng)模式
    ldr  sp,=0x33d00000           ; ??臻g為1M,0x33d00000~0x33c00000
    msr  cpsr_c,#0xd3           ; 切換回管理模式
    mov pc,lr 
 end

「結(jié)果分析:」我們只分析undef棧的初始化。

  1. 8行

  • 模式切換前,當(dāng)前模式時svc模式,CPSR的值是0x000000D3
  • 注意看下SVC和undef模式的SP值都是0
  1. 9行

  • msr cpsr_c ,# 0xdb 直接對CPSR進行賦值,將當(dāng)前模式設(shè)置為undef模式
  • Current模式看到的LR寄存器值變成了0,因為模式切換成了undef模式,該模式下有自己的LR、SP寄存器
  • SVC模式的私有寄存器SP和LR沒有改變
  1. 12行

12行

  • ldr sp,=0x34000000 將常數(shù)裝載到寄存器sp中,(=表示這是一條偽指令)
  • 注意觀察,SVC模式的sp沒有變化,undef模式的SP被設(shè)置為 0x34000000

其他模式的棧初始化以此類推。

二、尋址方式

處理器根據(jù)指令中給出的地址信息來尋找物理地址的方式。

在講解尋址方式之前,我們首先來看下LDR、STR指令。

1. 加載存儲指令

ARM微處理器支持加載/存儲指令用于在寄存器和存儲器之間傳送數(shù)據(jù),加載指令用于將存儲器中的數(shù)據(jù)傳送到寄存器,存儲指令則完成相反的操作。

我們之前講的尋址方式都是直接對立即數(shù)或者寄存器尋址,如果我們想訪問外部存儲器的某個內(nèi)存地址或者一些外設(shè)的控制器寄存器該如何操作呢?

那就需要進行寄存器間接尋址。如下圖所示,訪問外存需要通過AHB、APB總線,所以往往需要幾個指令周期才能實現(xiàn)1個數(shù)據(jù)的讀寫。

訪問外存

LDR指令

LDR指令的格式為:

LDR{條件} 目的寄存器,<存儲器地址>LDR指令用于從存儲器中將一個32位的字數(shù)據(jù)傳送到目的寄存器中。

1) 用于從存儲器中讀取32位的字數(shù)據(jù)到通用寄存器,然后對數(shù)據(jù)進行處理。2) 當(dāng)程序計數(shù)器PC作為目的寄存器時,指令從存儲器中讀取的字數(shù)據(jù)被當(dāng)作目的地址,從而可以實現(xiàn)程序流程的跳轉(zhuǎn)。如:

 area reset,code
 code32
 entry
start
  bl stack_init
stack_init                      ; 棧指針初始化函數(shù)
;    @undefine_stack                                       
    msr cpsr_c,#0xdb             ; 切換到未定義異常
    ldr sp,=0x34000000      ; 棧指針為內(nèi)存最高地址,棧為倒生的棧
                             ; 棧空間的最后1M 0x34000000~0x33f00000
;    @abort_stack                                                      
    msr cpsr_c,#0xd7                ; 切換到終止異常模式
    ldr        sp,=0x33f00000       ; ??臻g為1M,0x33f00000~0x33e00000
 ;   @irq_stack                                                
    msr      cpsr_c,#0xd2            ; 切換到中斷模式
    ldr        sp,=0x33e00000        ; 棧空間為1M,0x33e00000~0x33d00000
 ;   @ sys_stack                                               
    msr  cpsr_c,#0xdf              ; 切換到系統(tǒng)模式
    ldr  sp,=0x33d00000           ; ??臻g為1M,0x33d00000~0x33c00000
    msr  cpsr_c,#0xd3           ; 切換回管理模式
    mov pc,lr 
 end

STR指令

STR指令的格式為:STR{條件} 源寄存器,<存儲器地址> STR指令用于從源寄存器中將一個32位的字數(shù)據(jù)傳送到存儲器中。該指令在程序設(shè)計中比較常用,且尋址方式靈活多樣,使用方式可參考指令LDR。如:

STR R0,[R1],#8 ;將R0中的字數(shù)據(jù)寫入以R1為地址的存儲器中,并將新地址R1+8寫入R1。STR R0,[R1,#8] ;將R0中的字數(shù)據(jù)寫入以R1+8為地址的存儲器中。

LDR/STR指令都可以加B、H、SB、SH的后綴,分別表示加載/存儲字節(jié)、半字、帶符號的字節(jié)、帶符號的半字。如LDRB指令表示從存儲器加載一個字節(jié)進寄存器。當(dāng)使用這些后綴時,要注意所使用的存儲器要支持訪問的數(shù)據(jù)寬度。

LDRB指令

LDRB指令的格式為:

LDR{條件}B 目的寄存器,<存儲器地址>

LDRB指令用于從存儲器中將一個8位的字節(jié)數(shù)據(jù)傳送到目的寄存器中,同時將寄存器的高24位清零。該指令通常用于從存儲器中讀取8位的字節(jié)數(shù)據(jù)到通用寄存器,然后對數(shù)據(jù)進行處理。

「指令示例:」

LDRB   R0,[R1]   ;將存儲器地址為R1的字節(jié)數(shù)據(jù)讀入寄存器R0,并將R0的高24位清零。LDRB   R0,[R1,#8];將存儲器地址為R1+8的字節(jié)數(shù)據(jù)讀入寄存器R0,并將R0的高24位清零。

LDRH指令

LDRH指令的格式為:

LDR{條件}H 目的寄存器,<存儲器地址>

LDRH指令用于從存儲器中將一個16位的半字數(shù)據(jù)傳送到目的寄存器中,同時將寄存器的高16位清零。該指令通常用于從存儲器中讀取16位的半字數(shù)據(jù)到通用寄存器,然后對數(shù)據(jù)進行處理。

「指令示例:」

LDRH   R0,[R1]  ;將存儲器地址為R1的半字數(shù)據(jù)讀入寄存器R0,并將R0的高16位清零。LDRH  R0,[R1,R2];將存儲器地址為R1+R2的半字數(shù)據(jù)讀入寄存器R0,并將R0的高16位清零。 

舉例

1) STR r0,[r1,#12]

如上圖所示:

  1. 寄存器r0中的值是0x5,r1中的值是0x200
  2. 將r1的值加上#12,得到地址0x20c
  3. 將r0寄存器里的值發(fā)送給該地址對應(yīng)的內(nèi)存,即向地址0x20c中賦值0x5

2) STR r0,[r1],#12

如上圖所示:

  1. 寄存器r0的值是0x5,r1中的值是0x200
  2. 將r0寄存器里的值發(fā)送給該r1中的值對應(yīng)的內(nèi)存,即向地址0x200中賦值0x5
  3. 將r1的值加上#12并賦值給r1,r1的值就變成了0x20c

「擴展:」比如有以下c代碼

int *ptr;x = *ptr++;

經(jīng)過編譯器編譯,可以將這兩行代碼編譯為一條單指令:

LDR r0, [r1], #4

2. 立即尋址

立即尋址也叫立即數(shù)尋址,這是一種特殊的尋址方式,操作數(shù)本身就在指令中給出,只要取出指令也就取到了操作數(shù)。這個操作數(shù)被稱為立即數(shù),對應(yīng)的尋址方式也就叫做立即尋址。例如以下指令:

Add r0,r0,#1                ;R0=R0+1

在以上兩條指令中,第二個源操作數(shù)即為立即數(shù),要求以“#”為前綴,對于以十六進制表示的立即數(shù),還要求在“#”后加上“0x”或“&”。

3. 寄存器尋址

利用寄存器中的數(shù)值作為操作數(shù),這種尋址方式是各類微處理器經(jīng)常采用的一種方式,也是一種執(zhí)行效率較高的尋址方式。

Add R0 , R1,R2         ;R0=R1+R2

該指令的執(zhí)行效果是將寄存器R1和R2的內(nèi)容相加,其結(jié)果存放在寄存器R0中。

4. 寄存器間接尋址

以寄存器中的值作為操作數(shù)的地址,而操作數(shù)本身存放在存儲器中。例如以下指令:

Add R0,R1,[R2]     ; R0=R1+[R2]LDR R0,[R1]        ; R0=[R1]

在第一條指令中,以寄存器R2的值作為操作數(shù)的地址,在存儲器中取得一個操作數(shù)后與R1相加,結(jié)果存入寄存器R0中。第二條指令將以R1的值為地址的存儲器中的數(shù)據(jù)傳送到R0中。

5. 基址變址尋址

將寄存器(該寄存器一般稱作基址寄存器)的內(nèi)容與指令中給出的地址偏移量相加,從而得到一個操作數(shù)的有效地址:

LDR R0,[R1,#4]       ;R0=[R1+4]
LDR R0,[R1,#4] !      ;R0=[R1+4]、R1=R1+4
LDR R0,[R1],#4       ;R0=[R1]    、R1=R1+4
LDR R0,[R1,R2]       ;R0=[R1+R2]

6. 多寄存器尋址

采用多寄存器尋址方式,一條指令可以完成多個寄存器值的傳送。這尋址方式可以用一條指令完成傳送最多16個通用寄存器的值。以下指令:

LDMIA  R0,{R1,R2,R3,R4}       ;R1=[R0]    R2=[R0+4]    R3=[R0+8]  R4=[R0+12]

該指令的后綴IA表示在每次執(zhí)行完加載/存儲操作后,R0按字長度增加,因此,指令可將連續(xù)存儲單元的值傳送到R1~R4。

7. 相對尋址

與基址變址尋址方式相類似,相對尋址以程序計數(shù)器PC的當(dāng)前值為基地址,指令中的地址標號作為偏移量,將兩者相加之后得到操作數(shù)的有效地址。以下程序段完成子程序的調(diào)用和返回,跳轉(zhuǎn)指令BL采用了相對尋址方式:

BL NEXT     ;跳轉(zhuǎn)到子程序NEXT處執(zhí)行
……
NEXT
……
MOV PC,LR   ;從子程序返回

8. 堆棧尋址、批量加載/存儲指令

堆棧是一種數(shù)據(jù)結(jié)構(gòu),按先進后出(First In Last Out,F(xiàn)ILO)的方式工作,使用一個稱作堆棧指針的專用寄存器指示當(dāng)前的操作位置,堆棧指針總是指向棧頂。

批量數(shù)據(jù)加載/存儲指令可以一次在一片連續(xù)的存儲器單元和多個寄存器之間傳送數(shù)據(jù)。常用的加載存儲指令如下:

LDM批量數(shù)據(jù)加載指令STM批量數(shù)據(jù)存儲指令

LDM(或STM)指令的格式為:

LDM(或STM){條件}{類型} 基址寄存器{!},寄存器列表{∧}

LDM(或STM)指令用于從由基址寄存器所指示的一片連續(xù)存儲器到寄存器列表所指示的多個寄存器之間傳送數(shù)據(jù),該指令的常見用途是將多個寄存器的內(nèi)容入棧或出棧。其中,{類型}為以下幾種情況:

   IA 每次傳送后地址加1;
    IB 每次傳送前地址加1;
    DA 每次傳送后地址減1;
    DB 每次傳送前地址減1;
    FD 滿遞減堆棧;              向低地址方向生長
    ED 空遞減堆棧;
    FA 滿遞增堆棧;              向高地址方向生長
 EA 空遞增堆棧;
【滿堆?!浚憾褩V羔楽P指向最后壓入堆棧的有效數(shù)據(jù)項
【空堆棧】:堆棧指針指向下一個要放入數(shù)據(jù)的空位置

「【特別注意】」

{!}為可選后綴,若選用該后綴,則當(dāng)數(shù)據(jù)傳送完畢之后,將最后的地址寫入基址寄存器,否則基址寄存器的內(nèi)容不改變。

基址寄存器不允許為R15,寄存器列表可以為R0~R15的任意組合。

{∧}為可選后綴,當(dāng)指令為LDM且寄存器列表中包含R15,選用該后綴時表示:除了正常的數(shù)據(jù)傳送之外,還將SPSR復(fù)制到CPSR。同時,該后綴還表示傳入或傳出的是用戶模式下的寄存器,而不是當(dāng)前模式下的寄存器。

如:

STMFD  R13!,{R0,R4-R12,LR}  ;將寄存器列表中的寄存器(R0,R4到R12,LR)存入堆棧,向低地址方向生長。
LDMFD  R13!,{R0,R4-R12,PC}  ;將堆棧內(nèi)容恢復(fù)到寄存器(R0,R4到R12,LR)。

【注意】 要壓棧的寄存器順序可以亂序,但是實際壓棧和出棧仍然會將寄存器順序調(diào)整后再操作。

9. 舉例

例1 數(shù)組求和

編寫一個ARM匯編程序,累加一個“數(shù)組”的所有元素,碰上0時停止。結(jié)果放入 r4。

「實在步驟如下:」1) 在源文件末尾按如下方式聲明“數(shù)組”:

array:
 .word 0x11
 .word 0x22
 .word 0

2) 用r0指向“數(shù)組”的入口

LDR r0,=array 

3) 使用LDR r1,[r0],#4從“數(shù)組”中裝載數(shù)據(jù) 4) 累加并放入r4 5) 循環(huán),直到r1為0 6) 停止,進入死循環(huán)

代碼:

 area first, code, readonly                                
 code32
 entry
start
 ldr r0,=array
; adr r0,array   ;ADR為小范圍的地址讀取偽指令
loop
 ldr r1,[r0],#4
 cmp r1,#0
 addne r4,r4,r1
 bne loop
stop
 b stop
 ; DCD 偽操作  數(shù)據(jù)緩沖池技術(shù)   
 ; dcd  機器碼
array
 dcd 0x11
 dcd 0x22
 dcd 0

我們看一下最終執(zhí)行代碼在內(nèi)存中的機器碼對比圖

由上圖可知:

  1. ldr r0,=array,編譯器會計算出array標號的地址0x0018,注意該值是偏移當(dāng)前指令所在內(nèi)存位置的偏移量,所以該指令最終被翻譯成
ldr r0,[pc,#0x001c]

為什么是0x001c而不是0x0018呢?剛上電時此時pc的值是-4,因為下一條要執(zhí)行的指令的0x0000這個地址的指令

  1. 數(shù)組元素的3個值依次存放在0x0018、0x001c、0x001c這3個地址中
  2. ldr r1,[r0],#4每次取出r0指向的內(nèi)存的值并寫入到r1,同時將r0值自加4
  3. bne loop 的loop被編譯器計算為地址0x0004

例2 內(nèi)存數(shù)據(jù)讀寫

將某個整型值寫入到內(nèi)存0x40000000 中然后再將其讀出。

代碼:

  area first, code, readonly
 code32
 entry
start
    mov r0, #0x10000003
 mov r1, #0x40000000  ; SAMSUNG 2410 , 2410 = > sram 0x40000000  0x3fffff00
 str r0, [r1]             ;內(nèi)存單元的地址r1寄存器的內(nèi)容指示
 ldr r2,[r1]
stop 
 b stop  
 end

做這個實驗之前需要做以下設(shè)置。IRAM地址為0x40000000,size設(shè)置0x1000,就是我們測試用的IRAM地址范圍是0x40000000-0x40001000

注意,該內(nèi)存地址不是隨意設(shè)置的,查看S3C2440A用戶手冊【因為我們模擬的是S3C2440A這個soc】,從下圖可以清楚看到ram地址空間。

「數(shù)據(jù)寫入內(nèi)存:」

「從內(nèi)存讀取數(shù)據(jù):」

例3 數(shù)據(jù)壓棧退棧

先將棧地址設(shè)置為將要壓棧的數(shù)據(jù)存入寄存器r1-r5中,然后

area first, code, readonly                               
 code32
 entry
Start
   ;mov r0, #0x40000000
 ldr sp, =0x40001000  ;注意地址

 mov r1, #0x11
 mov r2, #0x22
 mov r3, #0x33
 mov r5, #0x55
 ; 壓棧
 stmfd sp!, {r1-r3, r5}
 ;stmia r0!, {r1-r3, r5} ; 加感嘆號是自動修改基地址
    mov r1, #0 
 mov r2, #0
 mov r3, #0
 mov r5, #0

 ldmfd sp!, {r1-r3, r5}
 ;ldmdb r0!, {r2,r1,r3, r5} ; 寄存列表書寫順序無所謂, 低地址內(nèi)容對應(yīng)低編號寄存器 
stop 
 b stop 
 end

在壓棧前,內(nèi)存0x40001000地址全為0。sp的值為0x40001000。

執(zhí)行命令ldmfd sp!, {r1-r3, r5}壓棧后,因為我們是滿遞減堆棧,并且SP后又!,所以內(nèi)存0x40000ff0地址開始的數(shù)據(jù)是0x11、0x22、0x33、0x44,sp的值修改為為0x40000ff0。以下是壓棧后內(nèi)存的數(shù)據(jù):

例4 函數(shù)嵌套調(diào)用

當(dāng)有多級函數(shù)嵌套,函數(shù)返回值我們不可能都存儲在通用寄存器中,必須利用ldm將程序跳轉(zhuǎn)前的寄存器值以及函數(shù)的返回地址壓棧。

area first, code, readonly                   
 code32
 entry
start
 ldr sp, =0x40002000
 mov r1, #0x11
 mov r2, #0x22
 mov r3, #0x33
 mov r5, #0x55
 bl child_func1        ; 【先寫跳轉(zhuǎn)到 child_func1,再寫跳轉(zhuǎn)到child_func】 
 add  r0, r1,r2
stop 
 b stop 
; 非葉子函數(shù) 
child_func
 stmfd sp!, {r1-r3,r5,lr} ;;;在子函數(shù)里首先將所有寄存器值壓棧保存,
                    ;;防止在子函數(shù)里篡改原本在主函數(shù)里運算需要的值,
                    ;;通常需要把r0-r12全都保存,為了安全和程序通用性應(yīng)該這么做
 mov r1, #10       ;;在這里子函數(shù)想怎么做自己的事情就可以做自己的事情
    bl child_func1
 ldmfd sp!, {r1-r3,r5,lr};;;;; 放在主函數(shù)bl之后的第一句行嗎?
 mov pc, lr
child_func1
  stmfd sp!, {r1-r3,r5};;;不論嵌套多少層子函數(shù),都是先壓棧,
   mov r1, #11
 ldmfd sp!, {r1-r3,r5};;對應(yīng)的,在返回到自己的父函數(shù)之前將自己出棧
 mov pc, lr
 end

讀者可以自己debug,查看內(nèi)存的內(nèi)容變化

四、ldrex 和 strex

1. LDREX

LDREX可從內(nèi)存加載數(shù)據(jù)。

如果物理地址有共享TLB屬性,則「LDREX會將該物理地址標記為由當(dāng)前處理器獨占訪問」,并且會「清除該處理器對其他任何物理地址的任何獨占訪問標記」。

否則,會標記:執(zhí)行處理器已經(jīng)標記了一個物理地址,但訪問尚未完畢。

2. STREX

STREX可在一定條件下向內(nèi)存存儲數(shù)據(jù)。

條件具體如下:

如果物理地址沒有共享TLB屬性,且執(zhí)行處理器有一個已標記但尚未訪問完畢的物理地址,那么將會進行存儲,清除該標記,并在Rd中返回值0。

如果物理地址沒有共享TLB屬性,且執(zhí)行處理器也沒有已標記但尚未訪問完畢的物理地址,那么將不會進行存儲,而會在Rd中返回值1。

如果物理地址有共享TLB屬性,且已被標記為由執(zhí)行處理器獨占訪問,那么將進行存儲,清除該標記,并在Rd中返回值0。

如果物理地址有共享TLB屬性,但沒有標記為由執(zhí)行處理器獨占訪問,那么不會進行存儲,且會在Rd中返回值1。

3. 語法

LDREX{cond} Rt, [Rn {, #offset}]
STREX{cond} Rd, Rt, [Rn {, #offset}]
LDREXB{cond} Rt, [Rn]        字節(jié)加載
STREXB{cond} Rd, Rt, [Rn]      字節(jié)存儲
LDREXH{cond} Rt, [Rn]        半字加載
STREXH{cond} Rd, Rt, [Rn]      半字存儲
LDREXD{cond} Rt, Rt2, [Rn]      雙字加載
STREXD{cond} Rd, Rt, Rt2, [Rn]    雙字存儲

其中:

cond
 是一個可選的條件代碼(請參閱條件執(zhí)行)。
Rd
 是存放返回狀態(tài)的目標寄存器。
Rt
 是要加載或存儲的寄存器。
Rt2
 為進行雙字加載或存儲時要用到的第二個寄存器。
Rn
 是內(nèi)存地址所基于的寄存器。
offset
 為應(yīng)用于 Rn 中的值的可選偏移量。offset 只可用于 Thumb-2 指令中。如果省略 offset,則認為偏移量為 0。

實現(xiàn)原子操作

利用 LDREX 和 STREX 可在多個處理器和共享內(nèi)存系統(tǒng)之前實現(xiàn)進程間通信。

原理

將對一個內(nèi)存地址的原子操作拆分成兩個步驟,一起完成對內(nèi)存的原子操作??梢岳斫鉃閳?zhí)行LDREX Rd [Rs]指令會標記對[Rs]這個內(nèi)存地址的訪問是獨占狀態(tài)(exclusive state)。而執(zhí)行STREX R0 Rd [Rs]指令會讓先前處于獨占狀態(tài)的內(nèi)存地址[Rs]轉(zhuǎn)變?yōu)檎顟B(tài),并且設(shè)置R0為0。若執(zhí)行STREX R0 Rd [Rs]指令時,內(nèi)存地址[Rs]是正常狀態(tài),則指令的存儲動作會失敗,并且R0置為1。

在 linux 中原子操作對應(yīng)的數(shù)據(jù)結(jié)構(gòu)為 atomic_t,定義如下:

typedef struct {
 int counter;
} atomic_t;

本質(zhì)上就是一個整型變量。

比如我們要對原子變量實行加操作,使用獨占指令完成累加操作。

 #if __LINUX_ARM_ARCH__ >= 6 ----(1)
static inline void atomic_add(int i, atomic_t *v)
{
    unsigned long tmp;
    int result;
// 使用獨占指令讀取,然后執(zhí)行加操作,獨占寫失敗時就重新執(zhí)行 
    prefetchw(&v->counter); ----(2)
    __asm__ __volatile__(
        "@ atomic_add\n" ----(3)
  "1:    ldrex    %0, [%3]\n" ----(4)
  "    add    %0, %0, %4\n" ----(5)
  "    strex    %1, %0, [%3]\n" ----(6)
  "    teq    %1, #0\n" ----(7)
  "    bne    1b"
    : "=&r" (result), "=&r" (tmp), "+Qo" (v->counter) ---對應(yīng)%0,%1,%2
    : "r" (&v->counter), "Ir" (i) ----對應(yīng)%3,%4
    : "cc");
}
#else
#ifdef CONFIG_SMP

原理:

(1)ARMv6之前的CPU并不支持SMP,之后的ARM架構(gòu)都是支持SMP的(例如我們熟悉的ARMv7-A)。因此,對于ARM處理,其原子操作分成了兩個陣營,一個是支持SMP的ARMv6之后的CPU,另外一個就是ARMv6之前的,只有單核架構(gòu)的CPU。對于UP,原子操作就是通過關(guān)閉CPU中斷來完成的。

(2)這里的代碼和preloading cache相關(guān)。在strex指令之前將要操作的memory內(nèi)容加載到cache中可以顯著提高性能。

(3)其中%3就是input operand list中的”r” (&v->counter),r是限制符(constraint),用來告訴編譯器gcc,你看著辦吧,你幫我選擇一個通用寄存器保存該操作數(shù)吧。

%0 對應(yīng)output openrand list中的”=&r” (result), = 表示該操作數(shù)是write only的, & 表示該操作數(shù)是一個earlyclobber operand,

編譯器在處理嵌入式匯編的時候,傾向使用盡可能少的寄存器,如果output operand沒有&修飾的話,匯編指令中的input和output操作數(shù)會使用同樣一個寄存器。因此,&確保了%3和%0使用不同的寄存器。

(5)完成步驟(4)后,%0這個output操作數(shù)已經(jīng)被賦值為atomic_t變量的old value,這里的操作是要給old value加上i。

這里%4對應(yīng)”Ir” (i),這里“I”這個限制符對應(yīng)ARM平臺,表示這是一個有特定限制的立即數(shù),該數(shù)必須是0~255之間的一個整數(shù)通過rotation的操作得到的一個32bit的立即數(shù)。

這是和ARM的data-processing instructions如何解析立即數(shù)有關(guān)的。每個指令32個bit,其中12個bit被用來表示立即數(shù),其中8個bit是真正的數(shù)據(jù),4個bit用來表示如何rotation。

(6)這一步將修改后的new value保存在atomic_t變量中。是否能夠正確的操作的狀態(tài)標記保存在%1操作數(shù)中,也就是”=&r” (tmp)。

(7)檢查memory update的操作是否正確完成,如果OK,皆大歡喜,如果發(fā)生了問題(有其他的內(nèi)核路徑插入),那么需要跳轉(zhuǎn)到lable 1那里,從新進行一次read-modify-write的操作。

聲明:本內(nèi)容為作者獨立觀點,不代表電子星球立場。未經(jīng)允許不得轉(zhuǎn)載。授權(quán)事宜與稿件投訴,請聯(lián)系:editor@netbroad.com
覺得內(nèi)容不錯的朋友,別忘了一鍵三連哦!
贊 2
收藏 3
關(guān)注 181
成為作者 賺取收益
全部留言
0/200
成為第一個和作者交流的人吧