carloscn / blog

My blog
Apache License 2.0
126 stars 36 forks source link

06_ARMv8_指令集_一些重要的指令 #12

Open carloscn opened 2 years ago

carloscn commented 2 years ago

06_ARMv8_指令集_一些重要的指令

1. PC相对地址加载指令

1.1 指令ADR

相对于PC地址加一个立即数写入目标寄存器,Xd = PC + imm,得到偏移imm的PC地址的地址。实际上,执行的ADD/SUB 对PC地址的指令^1

这里一直强调一个相对PC,这个相对这个词用的十分有讲究,具体参考,[1.4 ADR和LDR的陷阱](#1.4 ADR和LDR的陷阱).

1.2 指令ADRP

ADRP首先找到PC向下4K对齐的位置(寻找4K对齐的基地址)^1,然后加上给定的

1.3 Example

1.3.1 对比LDR和ADR指令

新建一个汇编文件,在汇编代码中定义一个my_test_data的标签

.align 3
.global my_test_data
my_test_data:
    .dword 0x12345678abcdabcd

【分析】:ADR和ADRP指令读取.my_test_data的地址,函数的地址势必是PC执行的地址,因此地址必须和PC关联,因此,标签自身的值+PC的值就应该是.my_test_data的地址,ADR x1, my_test_datal, 接着使用LDR x2, [x1]把x1寄存器地址里面的值加载到X2寄存器。x2的值应该是.dword的值

.global test_adr
.align 3
.global my_test_data

my_test_data:
    .dword 0x12345678abcdabcd

test_adr:
    adr x1, my_test_data
    adrp x2, my_test_data
    // read back offset
    add x2, x2, #:lo12:my_test_data
    ldr x3, [x1]

    // using ldr read label
    // my_test_data -> x4
    ldr x4, =my_test_data
    // *my_test_data -> x4
    ldr x5, my_test_data

    ret

1.3.2 页地址加载

修改链接文件linker.ld,在树莓派的4MB内存地址上分配一个4096大小的页面init_pg_dir,用来存储页表。请使用adrp和ldr指令来加载init_pg_dir的地址到通用寄存器。

1.4 ADRP和LDR的陷阱

从上面的[example2](#1.3.2 页地址加载),似乎可以得到ADR和LDR可以通用的结论,LDR可以访问64bit整个地址空间的加载,但是ADR可以访问±4GB的地址空间,ADR为什么还有存在的必要呢?实际上这里涉及ELF文件的VMA和LMA的一个知识(在 03_ELF文件_静态链接的2.2 两步链接(Two-pass Linking),提到了VMA和LMA的概念,里面虚拟地址和物理地址在某些嵌入式系统里面可能会不一样),我们在树莓派的BOOTROM场景下,若把程序加载到0x8_0000的地址外运行,此时就会出现一个问题。

image-20220324131142609

我们现在制造一个LMA和VMA不同的情况,以研究ADRP和LDR的差别,基于上面的[example2](#1.3.2 页地址加载):

image-20220324135231019

2. 内存独占加载和存储指令

在介绍内存独占加载和存储指令之前,先科普一下ARMv8架构里面的一个机制-独占监视器(Exclusive monitor)[^4] ,虽然这个这个是ARMv6比较老的架构上面的文章,但是这个原理是不变的。ARM里面有两个独占监视器,一个本地的独占监视器,还有一个是全局的独占监视器。本地的独占监视器用于监视non-shareble/shareble的地址访问,全局的独占监视器用于监视shareble的地址访问(多核,如图Cortex-A8/Cortex-R4)。LDXR指令会让监视器进入到独占状态,STXR存储只有当独占监视器还处于独占状态的时候才可以存储成功。

image-20220324142522170

实际上内存独占和加载指令为操作系统的一些原子操作提供底层的技术支持,Linux内核一些atomic的访问,比如atomic_write(), atomic_set_bit()的这些原子操作在底层的指令都有涉及到内存独占。这里有个文章可以参考,spinlock上面如何应用LDXR, STXR[^5].

2.1 指令LDXR

内存独占加载指令。以内存中独占exclusive的方式加载内存地址到通用寄存器。

2.2 指令STXR

内存独占存储指令。

2.3 Example

2.3.1 实现atomic_write函数

使用汇编实现atomic_write函数,在汇编定义数据my_data,初始化为0,然后使用atomic_write来写入my_data的这个数据atomic_write(0x34),使用C语言调用这个函数测试。

这个C语言代码:

int  my_data = 0;
int atomic_write(int a)
{
  my_data = a;
  return a;
}

ASM:

.section .data
.align 3
.global my_test_data
my_test_data:
    .dword 0
.section .text
.global my_atomic_write
my_atomic_write:
    // get my_test_data addr atomicl
    ldr x2, =my_test_data
1:
    ldxr x1, [x2]
    orr x1, x1, x0
    // save x0 to x2, the result on w0
    stxr w0, x1, [x2]
    cbnz w0, 1b
    mov x0, x2
    ret

Note, 汇编里面要映射.data和.text区域,否则在某些环境会报段错误。

3. 系统寄存器访问指令

对于系统寄存器的访问不能像是通用寄存器一样,系统寄存器非常特殊,所以就需要特殊的指令进行访问。

4. 内存屏障指令

内存屏障指令DMB, DSB还有ISB指令

Ref

[^2]: test_bits.c: test_4k_align_using_the_clear [^3]: GDB Maunal - 18.1 Commands to Specify Files [^4]: ARM Synchronization Primitives Development Article - Exclusive monitors [^5]:Exclusive monitor在spinlock中的应用 - Cache One [^6]:Arm Armv8-A A32/T32 Instruction Set Architecture - DMB [^7]:Arm Armv8-A A32/T32 Instruction Set Architecture - DSB [^8]:Arm Armv8-A A32/T32 Instruction Set Architecture - ISB

carloscn commented 2 years ago

https://github.com/carloscn/armv8-train/tree/lab06 https://github.com/carloscn/armv8-train/tree/lab07