ARM 学习总结
发布时间:2022-11-23 11:13:27
归档分类:

二、寻址方式

每种寻址方式可能还有其他的变形,但是在这一章中不做过多说明,会在下面对应的章节中给出。

寄存器寻址

MOV R1,R2 ;R2 -> R1

立即寻址

MOV R0,#0x123 ;0x123 -> R0

寄存器偏移寻址

MOV R0,R1,LSL #2 ;R1 的值左移 2 位,结果送给R0,即 R2 * 4 -> R0

可采用的移位操作如下:

  • LSL:逻辑左移(Logical Shift Left),寄存器中字的低端空出的位补 0
  • LSR:逻辑右移(Logical Shift Right),寄存器中字的高端空出的位补 0
  • ASR:算术右移(Arithmetic Shift Right),移位过程中保持符号位不变,即如 果源操作数为正数,则字的高端空出的位补 0,否则补 1
  • ROR:循环右移(Rotate Right),由字的低端移出的位填入字的高端空出的位
  • RRX:带扩展的循环右移(Rotate Right eXtended by 1place),操作数右移一位, 高端空出的位用原 C 标志值填充。

寄存器间接寻址

LDR R0,[R1] ;将 R1 中的数值作为地址,将这个地址的值取出给R0

基址寻址

LDR R2,[R3,#0x0F] ;将 R3 中的数值加 0x0F 作为地址,取出此地址的数值保存在 R2 中

多寄存器寻址

堆栈寻址

二、数据处理指令

快速查阅表

| 编号 | 助记符号 | 说明 | 操作 | | :--: | :-------------------: | :-----------------: | :---------------------------: | | 0 | MOV Rd ,operand2 | 数据转送 | Rd←operand2 | | 1 | MVN Rd ,operand2 | 数据非转送 | Rd←(~operand2) | | 2 | ADD Rd,Rn operand2 | 加法运算指令 | Rd←Rn+operand2 | | 3 | SUB Rd,Rn operand2 | 减法运算指令 | Rd←Rn-operand2 | | 4 | RSB Rd,Rn operand2 | 逆向减法指令 | Rd←operand2-Rn | | 5 | ADC Rd,Rn operand2 | 带进位加法 | Rd←Rn+operand2+carry | | 6 | SBC Rd,Rn operand2 | 带进位减法指令 | Rd←Rn-operand2-(NOT)Carry | | 7 | RSC Rd,Rn operand2 | 带进位逆向减法指令 | Rd←operand2-Rn-(NOT)Carry | | 8 | AND Rd,Rn operand2 | 逻辑与操作指令 | Rd←Rn&operand2 | | 9 | ORR Rd,Rn operand2 | 逻辑或操作指令 | Rd←Rn|operand2 | | 10 | EOR Rd,Rn operand2 | 逻辑异或操作指令 | Rd←Rn^operand2 | | 11 | BIC Rd,Rn operand2 | 位清除指令 | Rd←Rn&(~operand2) | | 12 | CMP Rn,operand2 | 比较指令 | 标志 N、Z、C、V←Rn-operand2 C | | 13 | CMN Rn,operand2 | 负数比较指令 | N、Z、C、V←Rn+operand2 | | 14 | TST Rn,operand2 | 位测试指令 | 标志 N、Z、C、V←Rn&operand2 | | 15 | TEQ Rn,operand2 | 相等测试指令 | 标志 N、Z、C、V←Rn^operand2 | | 16 | MUL Rd,Rm,Rs | 32 位乘法指令 | Rd←RmRs (Rd≠Rm) | | 17 | MLA Rd,Rm,Rs,Rn | 32 位乘加指令 | Rd←RmRs+Rn (Rd≠Rm) | | 18 | UMULL RdLo,RdHi,Rm,Rs | 64 位无符号乘法指令 | (RdLo,RdHi)←RmRs | | 19 | UMLAL RdLo,RdHi,Rm,Rs | 64 位无符号乘加指令 | (RdLo,RdHi)←RmRs+(RdLo,RdHi) | | 20 | SMULL RdLo,RdHi,Rm,Rs | 64 位有符号乘法指令 | (RdLo,RdHi)←RmRs | | 21 | SMLAL RdLo,RdHi,Rm,Rs | 64 位有符号乘加指令 | (RdLo,RdHi)←RmRs+(RdLo,RdHi) |

在介绍指令之前,我们首先先来介绍影响CPSR中的一些标志位

  • V 溢出标志位

  • C 进位或借位标志位

    • 对于加法指令(ADDS 和 CMN)如果产生进位,则C = 1
    • 对于减法指令 (SUBS 和 CMP )如果产生借位,则C = 0
  • Z 结果为0标志位

    • Z = 1 表示运算结果是 0
    • 同理
  • N 符号标志位

    • N=1 表示运算结果为负数
    • 同理

[0] MOV 数据转送指令

MOV{cond}{S} Rd,operand2

MOV R1,#0x12 ;R1=0x12
MOV R2,R1,LSL #2 ;R2=R1 << 2
MOVS R3,R2,LSL #4 ;R3=R2 << 4,并影响标志位

[1] MVN 数据非转送指令

这个命令和MOV很像,只不过在传送之前,把操作数先取反了。

在使用这个命令的时候,请不要忘记 ARM 的寄存器是 32位的

MVN{cond}{S} Rd,operand2

MVN R1,#0xFF ;R1=0xFFFFFF00,这里的 0xFF 实际上是 0x000000FF

[2] ADD 加法运算指令

ADD R1,R1,#0x13 ;R1 = R1 + 0x13
ADDS R2,R1,#0x1 ;影响标志位

[3] SUB 减法运算指令指令

SUB R0,R1,#0x12 ;R0=R1-0x12

[4] RSB 逆向减法指令

RSB R3,R1,#0x12 ;R3=0x12-R1

[5] ADC 带进位加法

带进位加法指令.将 operand2 的数据与 Rn 的值相加,再加上 CPSR 中的 C 条件标志位.结果保存到 Rd 寄存器.

由于寄存器是32位的,所以这个指令常用于计算64位加法。

这里需要注意的是,在进行 ADDS 运算的时候,如果出现了进位,CPSR中的 C=1,否则 C=0

例如有这样的两个64位数:

假设R0和R1构成一个64位数,R0存放低32位,R1存放高32位;R2,R3构成一个64位数,R2存放低32位,R3存放高32位.

计算的方法就是,先让两个低32位寄存器相加,为了得到可能的进位,需要用到ADDS,它能影响标志位

接着使用ADC,两个高32位相加,再加上标志位中的C 进位

LDR R0, =0XFFFFFFFF
LDR R1, =0X12
LDR R2, =0X1
LDR R3, =0X2

ADDS R0,R0,R2 ;R0 = R0 + R1 也就是0xFFFFFFFF + 0x1 这得到的是 0x00000000 ,因为溢出了,但是有进位 C = 1
ADC R1,R1,R3 ; R1 = R1 + R3 也就是 0x12 + 0x2 + 1 得到 0x15

[6] SBC 带进位减法指令

带进位减法指令。用寄存器 Rn 减去 operand2,再减去 CPSR 中的 C 条件标志位的反码

这里需要注意的是,在进行 SUBS 运算的时候,如果出现了借位,CPSR中的 C=0,否则 C=1

SBC与ADC指令类似,常用于计算64位的减法。

例如有这样的两个64位数:

假设R0和R1构成一个64位数,R0存放低32位,R1存放高32位;R2,R3构成一个64位数,R2存放低32位,R3存放高32位.

LDR R0, =0X12
LDR R1, =0X9
LDR R2, =0X32
LDR R3, =0X2

SUBS R0,R0,R2 ;R0 = R0 - R2 也就是 0x12 - 0x32 这将得到 0xFFFFFFE0 ,因为不够减,CPSR 中的 N=1,C=0
SBC R1,R1,R3 ;R1 = R1 - R3 - !C 也就是 0x9 - 0x2 - !0 ,得到0x6

[7] RSC 带进位逆向减法指令

用寄存器 operand2 减去 Rn,再减去 CPSR 中的 C 条件标志位的反码

例如有这样的两个64位数:

假设R0和R1构成一个64位数,R0存放低32位,R1存放高32位;R2,R3构成一个64位数,R2存放低32位,R3存放高32位.

LDR R0, =0X12
LDR R1, =0X9
LDR R2, =0X32
LDR R3, =0X2

RSBS R0,R0,R2 ;R0 = R2-R0 也就是 0x32 - 0X12 这将得到 0x20 ,没有借位 ,CPSR 中的 N=1,C=1
RBC R1,R1,R3 ;R1 = R3 - R2 - !C 也就是 0x2 - 0x9 - !1 ,得到0xFFFFFFF9

这里值得注意一下,在计算机中负数是用补码保存的。

2 - 9 = -7

这个 -7 的原码在八位寄存器中是 10000111 , 反码是 11111000,补码是 11111001 ,也就是0xF9 ,同理,在32位寄存器中就是 0xFFFFFFFF9

所以,在了解了SUBS 和 SBC 之后,我们同样可以求出 64 位的负数,和上面的例子是一样的

假设R0和R1构成一个64位数,R0存放低32位,R1存放高32位 ,求它的负数

LDR R0, =0X12
LDR R1, =0X9

RSBS R0,R0,#0 ;R0 = 0-R0 也就是 0 - 0X12 这将得到 0xFFFFFFEE ,有借位 ,CPSR 中的 N=1,C=0
RBC R1,R1,#0 ;R1 = 0- R1 - !C 也就是 0 - 0x9 - !0 ,得到0xFFFFFFF6

这里还是算一下,-10 的 补码

在八位寄存器中,-10 的原码表示是 1000 1010 ,反码表示是 1111 0101,补码表示是 1111 0110 ,也就是0xF6

在32位寄存器中的表示就是,0xFFFF FFF6

[8] AND 逻辑与操作指令

AND R0,R1,R2 ;R0=R1&R2

[9] ORR 逻辑或操作指令

ORR R0,R1,R2 ;R0=R1|R2

[10] EOR 逻辑异或操作指令

[11] BIC 位清除指令

位清除指令.将寄存器Rn的值与operand2的值的反码按位作逻辑与操作,结果保存 到 Rd 中

[12] CMP 比较指令

本质是 做减法 ,结果一定影响标志位

CMP R1,R2 ;R1-R2

[13] CMN 负数比较指令

本质是 做加法 ,结果一定影响标志位

CMN R1,R2 ;R1+R2

[14] TST 位测试指令

指令将寄存器Rn的值与operand2的值按位作逻辑与操作,根据操作的 结果理新 CPSR 中相应的条件标志位

TST R0,#0x1 ;判断 R0 的最低位是否为 0

[15] TEQ 位相等测试指令

指令寄存器Rn的值与operand2的值按位作逻辑异或操作,根据操作 的结果理新 CPSR 中相应条件标志位

TEQ R0,R1 ;比较 R0 与 R1 是否相等 ,与用cmp命令对比,它不影响(不影响 V 位和 C 位)

[16] MUL 32 位乘法指令

指令将 Rm 和 Rs 中的值相乘,结果的低 32 位保存到 Rd 中

MUL{cond}{S} Rd,Rm,Rs

MUL R1,R2,R3 ;R1=R2×R3
MULS R1,R2,R3 ;R0=R2×R3,同时设置 CPSR 中的 N 位和 Z 位

[17] MLA 32 位乘加指令

指令将 Rm 和 Rs 中的值相乘,再将乘积加上第 3 个操作数,结果的低 32 位保存到 Rd 中

MLA{cond}{S} Rd,Rm,Rs,Rn

MLA R1,R2,R3,R4 ;R1=R2×R3+R4

[18] UMULL 64 位无符号乘法指令

U即 Unsigned 无符号

指令将 Rm 和 Rs 中的值作无符号数相乘,结果的低 32 位保存 到 RsLo 中,而高 32 位保存到 RdHi 中

UMULL{cond}{S} RdLo,RdHi,Rm,Rs

UMULL R0,R1,R2,R3 ;(R1:R0)=R2×R3 ;相当于 R0 = (R2*R3) 的低32位,R1 = (R2*R3) 的高32位

[19] UMLAL 64 位无符号乘加指令

U即 Unsigned 无符号

指令将 Rm 和 Rs 中的值作无符号数相乘,64 位乘积与 RdHi,RdLo 相加,结果的低 32 位保存到 RdLo 中,而高 32 位保存到 RdHi 中.

UMLAL{cond}{S} RdLo,RdHi,Rm,Rs

UMLAL R0,R1,R2,R3 ;(R1,R0)=R2×R3+(R1,R0) ;相当于 R0 = (R2*R3) 的低32位 + R0,R1 = (R2*R3) 的高32位+ R1

[20] SMULL 64 位有符号乘法指令

S即 Signed 有符号

指令将 Rm 和 Rs 中的值作有符号数相乘,结果的低 32 位保存 到 RdLo 中,而高 32 位保存到 RdHi 中

SMULL R0,R1,R2,R3 ;(R1:R0)=R2×R3 ;相当于 R0 = (R2*R3) 的低32位,R1 = (R2*R3) 的高32位

[21] SMLAL 64 位有符号乘加指令

指令将 Rm 和 Rs 中的值作有符号数相乘,64 位乘积与RdHi,RdLo,相加,结果的低 32 位保存到 RdLo 中,而高 32 位保存到 RdHi 中.

SMLAL R0,R1,R2,R3 ;(R1,R0)=R2×R3+(R1,R0) ;相当于 R0 = (R2*R3) 的低32位 + R0,R1 = (R2*R3) 的高32位+ R1

三、ARM分支指令

在了解分支指令之前,我们首先得去了解一下条件码,否则我们就会没办法正确使用分支指令

在此给出条件码表格

| 条件码助记符 | 英文含义,助记符来源 | 查看的标志 | 中文含义 | | :----------: | :--------------------------------------------------------: | :--------: | :------------------------: | | EQ | Equal | Z=1 | 相等 | | NE | Not equal. | Z=0 | 不相等 | | CS/HS | Unsigned higher or same (or carry set). | C=1 | 无符号数大于或等于/C位设置 | | CC/LO | Unsigned lower (or carry clear). | C=0 | 无符号数小于/C位清除 | | MI | Negative. The mnemonic stands for "minus". | N=1 | 负数 | | PL | Positive or zero. The mnemonic stands for "plus". | N=0 | 正数或零 | | VS | Signed overflow. The mnemonic stands for "V set". | V=1 | 溢出/V位设置 | | VC | No signed overflow. The mnemonic stands for "V clear". | V=0 | 没有溢出 /V位清除 | | HI | Unsigned higher. | C=1,Z=0 | 无符号数大于 | | LS | Unsigned lower or same. | C=0,Z=1 | 无符号数小于或等于 | | GE | Signed greater than or equal. | N=V | 带符号数大于或等于 | | LT | Signed less than. | N!=V | 带符号数小于 | | GT | Signed greater than. | Z=0,N=V | 带符号数大于 | | LE | Signed less than or equal. | Z=1,N!=V | 带符号数小于或等于 | | AL | Always executed. | 任何 | 无条件执行(指令默认条件) |

快速记忆方法:

我们必须得结合英文才能快速地记住这些**“助记符”**

尤其是无符号和有符号之间的比较。

无符号一般会使用 Lower 和 HigherSame,有符号一般会使用 Greater thanLess thanEqual

所以,无符号的大于等于 HS = Higher + Same ,无符号小于等于 LS = Lower + Same ,无符号大于 HI = Higher(前两个字母),无符号小于 LO = Lower

同理的,有符号大于等于 GE = Greater + Equal ,有符号小于等于 LE = Less + Equal, 有符号大于就是 GT = Greater + Than ,有符号小于就是 LT = Less + Than

所以,通过这个英文可以很快速地就记住。

跳转分支指令

接下来,我们来说一下跳转指令,同样的,给出跳转指令快速查阅表:

| 助记符 | 说明 | 操作 | | :------: | :------------------: | :-------------------: | | B label | 跳转指令 | PC←label | | BL label | 带链接的跳转指令 | LR←PC-4, PC←label | | BX Rm | 带状态切换的跳转指令 | PC←label,切换处理状态 |

[1] B 跳转指令

B{cond} label

B LOOP_Y1 ;跳转到 LOOP_Y1 标号处

[2] BL 带链接的跳转指令

BL{cond} label

这个跳转的操作是:LR←PC-4, PC←label,由于将PC地址保持到了LR寄存器里面,所以之后还能跳转回来

[3] BX 带状态切换的跳转指令

四、加载和存储指令

Load and Store with register offset.

他们最基础的指令是 LDR 和 STR,以下先给出这两个基础指令的用法:

| 助记符 | 说明 | 操作 | | ------------------ | ---------- | --------------- | | LDR Rd, addressing | 加载字数据 | Rd←[addressing] | | STR Rd, addressing | 存储字数据 | [addressing]←Rd | | | | |

寄存器间接寻址

LDR R0,[R1]	;R0 <- [R1]
STR R0,[R1] ;[R1] <- R0

基址加变址寻址

这里有几种方式,前变址法、后变址法、自动变址

  • 前变址法,也就是先变化地址,再根据这个地址 存取。

    LDR R0,[R1,#4]	;R0 <- [R1 + 4]
    
  • 后变址,也就是先存取,再变化地址

    LDR R0,[R1],#4	;R0 <- [R1] 然后 R1<-R1+4
    
  • 自动变址,综合上面两种,加一个 感叹号 !

    LDR R0,[R1,#4]!	;R0 <- [R1 + 4] 然后 R1<-R1+4
    

STR 指令也是同理的,这里不再赘述。

在理解了基础指令之后,我们可以尝试去看看这两个指令的更多用法:

以下依旧给出速查表:

| 助记符 | 说明 | 操作 | | -------------------- | -------------------------- | --------------- | | LDR Rd, addressing | 加载字数据 | Rd←[addressing] | | LDRB Rd,addressing | 加载无符字节数据 | Rd←[addressing] | | LDRT Rd,addressing | 以用户模式加载字数据 | Rd←[addressing] | | LDRBT Rd,addressing | 以用户模式加载无符号字数据 | Rd←[addressing] | | LDRH Rd,addressing | 加载无符半字数据 | Rd←[addressing] | | LDRSB Rd,addressing | 加载有符字节数据 | Rd←[addressing] | | LDRSH Rd,addressing | 加载有符半字数据 | Rd←[addressing] | | | | | | STR Rd,addressing | 存储字数据 | [addressing]←Rd | | STRB Rd,addressing | 存储字节数据 | [addressing]←Rd | | STRT Rd,addressing | 以用户模式存储字数据 | [addressing]←Rd | | SRTBT Rd,addressing | 以用户模式存储字节数据 | [addressing]←Rd | | STRH Rd,addressing | 存储半字数据 | [addressing]←Rd |

虽然看起来蛮多的,但主要就是

  • 后缀带有B的,是无符字节数据
  • 后缀带有H的,是无符半字数据
  • 后缀带有SB的,是有符号字节数据
  • 后缀带有SH的,是有符号半字数据

因为 字节是Byte ,半字是Half Word,有符号是 Signed

五、加载和存储指令LDM 和 STM 批量加载和批量存储分析

这一段内容来自 http://blog.chinaunix.net/uid-29401328-id-5059312.html

这里是简单地进行搬运。

普通用法和堆栈用法

当LDM/STM没有被用于堆栈,而只是简单地表示地址前向增加,后向增加,前向减少,后向减少时,由IA,IB,DA,DB控制。

  • IA ----> Increment After 每次传送后地址加4
  • IB ----> Increment Before 每次传送前地址加4
  • DA ----> Decrement After 每次传送后地址减4
  • DB ----> Decrement Before 每次传送前地址减4

堆栈请求格式,FD,ED,FA,EA定义了前/后向索引和上/下位

F,E表示堆栈满或者空。 A 和 D 定义堆栈是递增还是递减,如果递增,STM将向上,LDM向下,如果递减,则相反。

  • FA ----> Full Ascending 满递增堆栈
  • FD ----> Full Descending 满递减堆栈
  • EA ----> Empty Ascending 空递增堆栈
  • ED ----> Empty Descending 空递减堆栈

普通用法

STMIA R0!,{R1,R3,R5}
LDMDB R0!,{R1-R3}

保存的时候使用了 IA 后增加的方式,取的时候就得用 DB 先减少 的方式

这个例子的R0指向一段基地址

堆栈用法

  1. Full descending 满递减堆栈——FD 堆栈首部是高地址,堆栈向低地址增长。栈指针总是指向堆栈最后一个元素(最后 一个元素是最后压入的数据)。ARM-Thumb过程调用标准和ARM、Thumb C/C++ 编译器总是使用Full descending 类型堆栈。

  2. Full ascending 满递增堆栈——FA 堆栈首部是低地址,堆栈向高地址增长。栈指针总是指向堆栈最后一个元素(最后 一个元素是最后压入的数据)。

  3. Empty descending 空递减堆栈——ED 堆栈首部是高地址,堆栈向低地址增长。栈指针总是指向下一个将要放入数据的空位置

  4. Empty ascending 空递增堆栈——EA 堆栈首部是低地址,堆栈向高地址增长。栈指针总是指向下一个将要放入数据的空位置

A 和D 定义堆栈是递增还是递减,如果递增,STM将向上,LDM向下,如果递减,则相反。

所以,LDMFD和STMFD是成对使用,因为堆栈方式和出栈方式要是相同的

六、ARM 伪指令

| 伪指令助记符 | 说明 | 操作 | | ----------------------------------- | ------------------------ | ------------------------------------------ | | ADR{cond} register,exper | 小范围的地址读取伪指令 | register<-expr 指向的地址 | | ADRL {cond} register,exper | 中等范围的地址读取伪指令 | register<-expr 指向的地址 | | LDR{cond} register,=expr/label_expr | 大范围的地址读取伪指令 | register<-expr/label-expr 指定 的数据/地址 | | NOP | 空操作伪指令 | |

七、简单介绍数据定义伪指令

1、DCB 分配一段字节的内存单元

{label} DCB expr{,expr}{,expr}…

A
	DCB 0x11,0x22,0x33,0x44
	DCB 0x55,0x66,0x77,0x88
	DCB "Hello World"
	DCB "ABCDEFGHIJKLMN",0

2 、DCW 和 DCWU 分配一段半字的内存单元

DCWU 需要半字对齐

B
	DCW 0x1122,0x3344,0x5566,0x7788

3、 DCD 和 DCDU 分配一段字内存单元

DCD 需要字对齐

C
	DCW 0x11223344,0x55667788,0x99aabbcc,0xddeeff00

4、SPACE 分配一片连续的字节内存单元,并初始化为0

{label} SPACE expr

D
	Space 500 ;分配 500 字节空间,并初始化为0

八、汇编程序设计

程序 1 、使用跳转完成函数功能

首先,我们先来一个简单的跳转指令:

	...					;之前的一些操作
	BL ADD_FUNCTION		;带连接的跳转,LR <- PC -4 ,PC <- ADD_FUNCTION
	...					;完成ADD_FUNCTION 这个函数的操作
	...
ADD_FUNCTION
	...					;一些操作
    MOV PC,LR			;函数返回,相当于RET、Return,总之就是 PC <- LR

下面这个例子是老师给的:

设计一个函数,计算R0 和 R1 的值

	AREA Example1,CODE,READONLY
	ENTRY
start
	LDR R0,=0X66
	LDR R1,=0X88
	BL ADD_FUNCTION		;带链接跳转
	B RETURN			;跳到结束位置
ADD_FUNCTION
	ADD R0,R0,R1
	MOV PC,LR
RETURN
	END

程序 2 、计算数组第1项和第5项之和,并将结果保存在第9项中

	AREA Example1,CODE,READONLY
	ENTRY
start
	LDR R0,=ARRAY
	LDR R1,[R0]
	LDR R2,[R0,#16]
	ADD R1,R1,R2

	STR R1,[R0,#32]


ARRAY
	DCD 0X11,0X22,0X33,0X44
	DCD 0X55,0X66,0X77,0X88
	DCD 0X00,0X00,0X00,0X00
	END

程序 3、编写一个分支程序段,如果R5中的值等于10,就把R5中的数据存入R1,否则就把R5中的数据分别存入寄存器R0和R1

	AREA Example1,CODE,READONLY
	ENTRY
start
	MOV R5,#9
	CMP R5,#10
	MOVNE R0,R5
	MOV R1,R5
	END

程序 4、编写一个程序段,当R1中的数据大于R2中的数据时,将R2中的数据加10存入R1中,否则将R2中的数据加5存入R1中

	AREA Example1,CODE,READONLY
	ENTRY
start
	MOV R1,#10
	MOV R2,#5
	CMP R1,R2
	ADDHI R1,R2,#10
	ADDLS R1,R2,#5
	END

还记得吗?

无符号一般会使用 Lower 和 HigherSame,有符号一般会使用 Greater thanLess thanEqual

所以,无符号的大于等于 HS = Higher + Same ,无符号小于等于 LS = Lower + Same ,无符号大于 HI = Higher(前两个字母),无符号小于 LO = Lower

同理的,有符号大于等于 GE = Greater + Equal ,有符号小于等于 LE = Less + Equal, 有符号大于就是 GT = Greater + Than ,有符号小于就是 LT = Less + Than

程序 5、循环,将 src 中的10个字节的数据,传送到 dst 开始的区域

	AREA init,CODE,READONLY
	ENTRY
start
	LDR R0,=src
	LDR R1,=dst
	MOV R2,#0

LOOP
	LDRB R3,[R0,R2]
	STRB R3,[R1,R2]

	ADD R2,R2,#1
	CMP R2,#10
	BLO LOOP

src
	DCB "0123456789"
dst
	DCB "aaaaaaaaaa"

	END

程序 6、循环,将src中的所有小写字母变成大写字母,其他的ASCII码不变

我们需要知道 ascii 码中,

A的十六进制是41,能够推出Z的十六进制是5A

a的十六进制是61,能够推出z的十六进制是7A

	AREA init,CODE,READONLY
	ENTRY
start
	LDR R0,=src
	MOV R1,#0

LOOP
	LDRB R2,[R0,R1]
	CMP R2,#0X61
	BLO NEXT
	CMP R2,#0X7A
	SUBLS R2,R2,#0X20
	STRBLS R2,[R0,R1]

NEXT
	ADD R1,R1,#1
	CMP R1,#10
	BNE LOOP

src
	DCB "AabCdEfghI"
	END

程序 7、循环,将src中的所有大写字母变成小写字母,其他的ASCII码不变

和上一题同理

	AREA init,CODE,READONLY
	ENTRY
start
	LDR R0,=src
	MOV R1,#0

LOOP
	LDRB R2,[R0,R1]
	CMP R2,#0X41
	BLO NEXT
	CMP R2,#0X5A
	ADDLS R2,R2,#0X20
	STRBLS R2,[R0,R1]

NEXT
	ADD R1,R1,#1
	CMP R1,#10
	BNE LOOP

src
	DCB "AabCdEfghI"
	END
  • ARM
  • 笔记
  • 记录一首歌
    发布时间:2022-11-21 19:11:34
    归档分类:

    原唱是:B站UP主 糖醋蛋黄

    月昢昢兮 尘莫莫 几时归矩州啊 风萧萧兮 寒漠漠 远隔离思多啊 夜行千里做远客 半生半将魂魄赊 山离离 水漓漓 荒唐事历历

  • 糖醋蛋黄
  • 给 GIT 和 SSH 配置代理
    发布时间:2022-11-10 20:38:27
    归档分类:

    今天 push 不了项目,估计又是网络的问题,决定还是给git和ssh配置一下代理,不然太难受了。 配置记录如下: 1.使用socks5代理,我这边的端口是1089

    git config --global http.https://github.com.proxy socks5://127.0.0.1:1089
    

    2.修改 ~/.ssh/config 文件

    Host github.com
        User git
        ProxyCommand nc -v -x 127.0.0.1:1089 %h %p
    
  • git
  • ssh
  • Linux 进程学习 二
    发布时间:2022-10-16 16:13:33
    归档分类:

    创建进程

    在 Linux 中每个进程都是父进程创建的,Linux 启动时会创建init 进程,这是系统的第一个进程,其 PID 为 1。 在 C 语言中,我们可以用fork函数创建新的进程:

    #include <unistd.h>
    pid_t fork(void);
    
    • If fork() returns a negative value, the creation of a child process was unsuccessful.
    • fork() returns a zero to the newly created child process.
    • fork() returns a positive value, the process ID of the child process, to the parent. The returned process ID is of type pid_t defined in sys/types.h. Normally, the process ID is an integer. Moreover, a process can use function getpid() to retrieve the process ID assigned to this process.

    也就是:

    On success, the PID of the child process is returned in the parent, and 0 is returned in the child. On failure, -1 is returned in the parent, no child process is created, and errno is set to indicate the error.

    也就是:

    • 返回负数,创建失败
    • 返回 0 ,就是子进程,它永远返回 0
    • 返回大于 0 的整数,这是父进程,它会返回子进程的pid

    终止进程

  • Linux
  • 进程
  • Linux 进程学习 一
    发布时间:2022-10-16 15:54:11
    归档分类:

    什么是进程?

    在了解进程的时候,首先得知道什么是程序。 操作系统其实学过,程序被执行时,操作系统将可执行文件复制到内存中,这就是程序,而进程则是程序的实例,是系统资源分配的基本单位,它被唯一标识于 PCB 之中,也就是进程控制块。

    ps 命令 查看进程

    Note that ps -aux is distinct from ps aux. The POSIX and UNIX standards require that ps -aux print all processes owned by a user named x, as well as printing all processes that would be selected by the -a option. If the user named x does not exist, this ps may interpret the command as ps aux instead and print a warning. This behavior is intended to aid in transitioning old scripts and habits. It is fragile, subject to change, and thus should not be relied upon.

    To see every process on the system using BSD syntax: ps ax ps axu

    也就是说,我们可以通过 ps axu 来查看系统中的进程

    top 命令 查看进程

    The top program provides a dynamic real-time view of a running system.

    这个命令显示的进程状态是动态更新的。 以下是常用命令:

    • q:退出top命令
    • :立即刷新
    • s:设置刷新时间间隔
    • t:显示或隐藏进程和CPU状态信息
    • m:显示或隐藏内存状态信息
    • P:按%CPU使用率排行
    • M:按%MEM排行
    • u:指定显示用户进程
    • k:kill进程
    • i:只显示正在运行的进程
    • h:帮助命令。
  • Linux
  • 进程
  • git 学习
    发布时间:2022-10-14 21:06:13
    归档分类:

    git 虽说也用了蛮久的了,但是其实来来去去也是一些基础操作,更深入的倒也没有怎么去学,趁着这段时间稍微有些时间,把以前的笔记整理到这里来,也方便之后来查看。

    git commit

    这个命令用于提交记录

    git commit
    

    git branch newImage

    这个命令用于创建新的分支。

    Git 的分支也非常轻量。它们只是简单地指向某个提交纪录 —— 仅此而已。所以许多 Git 爱好者传颂。

    早建分支!多用分支!

    这是因为即使创建再多的分支也不会造成储存或内存上的开销,并且按逻辑分解工作到不同的分支要比维护那些特别臃肿的分支简单多了。

    在将分支和提交记录结合起来后,我们会看到两者如何协作。现在只要记住使用分支其实就相当于在说:“我想基于这个提交以及它所有的父提交进行新的工作。”

    创建分支

    git branch newImage
    

    切换分支

    git checkout <name>
    

    融合分支

    git merge <name>
    

    重基

    git rebase <name>
    

    ^ 和 ~number

    可以通过 ^ 符号向上找到父节点
    ~ 则是多个
    

    Git Reset

    git reset 通过把分支记录回退几个提交记录来实现撤销改动。你可以将这想象成“改写历史”。git reset 向上移动分支,原来指向的提交记录就跟从来没有提交过一样。

    Git Revert

    虽然在你的本地分支中使用 git reset 很方便,但是这种“改写历史”的方法对大家一起使用的远程分支是无效的哦!

    奇怪!在我们要撤销的提交记录后面居然多了一个新提交!这是因为新提交记录 C2' 引入了更改 —— 这些更改刚好是用来撤销 C2 这个提交的。也就是说 C2' 的状态与 C1 是相同的。

    revert 之后就可以把你的更改推送到远程仓库与别人分享啦。

  • git
  • 学习
  • React 前端加载优化
    发布时间:2022-10-11 18:13:47
    归档分类:

    主要问题

    这次的 December 项目采用的是前后端分离的技术,交互使用的是 Axios ,但目前遇到的问题是,首次加载网站的时间过于久了,根据 report 的分析,我认为可能存在的问题是 react-router-dom 一次性加载了所有的组件,导致了加载变慢。

    所以,主要的解决策略集中在对 router 的懒加载上,让它分批次地加载组件。

    当然我还怀疑 React-Mui 组件库也拖慢了网站的加载,毕竟它是有些大的,但是应该还好,毕竟加载时是分开导入的,先解决 Router 的问题吧

    解决方案

    导入 lazy,Suspense 包

    import { lazy,Suspense } from 'react';
    

    lazy 用于懒加载 , Suspense 用于加载 loading

    const MyComponent = lazy(() => import('./MyComponent'))
    
    <Suspense fallback={<div>Loading...</div>}>
    	<RouterProvider router={router} />
    </Suspense>
    
  • React
  • 开发记录
  • Rust 即将成为 linux 内核的第二语言
    发布时间:2022-10-11 13:04:48
    归档分类:

    今天又看到了一则消息,2022年 Rust 将成为 Linux 内核第二官方语言。

    我认为这绝对是一个好消息。

  • Rust