【ARMv8 编程】A64 内存访问其他指令

news2025/2/26 22:05:43

A64 内存访问其他指令包括浮点和 NEON 标量加载存储指令、访问多个内存位置指令、非特权访问指令、预取内存指令、非临时加载存储对指令、内存屏障和栅栏指令、同步原语等。

一、浮点和 NEON 标量加载存储指令

加载和存储指令也可以访问浮点/NEON 寄存器。这里,大小仅由加载或存储的寄存器决定,可以是 B、H、S、D 或 Q 寄存器中的任何一个。

加载指令

在这里插入图片描述

存储指令

在这里插入图片描述

没有符号扩展选项可用于加载到 FP/SIMD 寄存器。此类加载的地址仍使用通用寄存器指定。

例如:

LDR D0, [X0, X1] —— 将 X0 加 X1 指向的内存地址处的双字加载到寄存器 D0。

下面是的实例是实际执行 LDR D0, [%x[a], X0] 指令的效果。

    long long int len = 2;

    auto *arr = new long long int[len]{0};

    LOGD("+++++++++++++++++++++++++++++");
    for (int i = 0; i < len; i++) {
        arr[i] = 0x0102030410203040 * (i + 1);
        LOGD("arr[%d]=0x%llx", i, arr[i]);
    }

    char *a = (char *) arr;

    asm volatile(
        "MOV X0, #8\n"
        "LDR D0, [%x[a], X0]\n"
    :[a] "+r"(a)
    :
    : "cc", "memory");

    LOGD("+++++++++++++++++++++++++++++");

    delete[] arr;

我们看到 V0 寄存器在断点 1 处时,内部都为 0,当运行到断点 2 处时,已经被修改为了 arr[1]=0x204060820406080 的值了。因为 D0 实际上就是 V0 寄存器的下半部分。

在这里插入图片描述

浮点和标量 NEON 加载和存储使用与整数加载和存储相同的寻址模式。

二、访问多个内存位置指令

A64 不包括可用于 A32 和 T32 代码的多重加载 (LDM) 或存储 (STM) 指令。在 A64 代码中,有加载对 (LDP) 和存储对 (STP) 指令。与 A32 LDRD 和 STRD 指令不同,可以读取或写入任意两个整数寄存器。数据从相邻的内存位置读取或写入。为这些指令提供的寻址模式选项比其他内存访问指令更受限制。LDP 和 STP 指令只能使用具有缩放的 7 位带符号立即值的基址寄存器,并具有可选的前置或后置增量。与 32 位 LDRD 和 STRD 不同,LDP 和 STP 可以进行未对齐访问。

加载和存储对指令描述
LDP W3, W7, [X0]将地址 X0 处的字加载到 W3,并将地址 X0 + 4 处的字加载到 W7。
LDP X8, X2, [X0, #0x10]!将地址 X0 + 0x10 处的双字加载到 X8,将地址 X0 + 0x10 + 8 处的双字加载到 X2,并将 0x10 加到 X0。
LDPSW X3, X4, [X0]将地址 X0 处的字加载到 X3 并将地址 X0 + 4 处的字加载到 X4,并将两者符号扩展到双字大小。
LDP D8, D2, [X11], #0x10将地址 X11 处的双字加载到 D8,将地址 X11 + 8 处的双字加载到 D2,并将 0x10 加到 X11。
STP X9, X8, [X4]将 X9 中的双字存储到地址 X4,并将 X8 中的双字存储到地址 X4 + 8。

LDP W3, W7, [X0] 指令图示:

在这里插入图片描述

LDP X8, X2, [X0, #0x10]! 指令图示:

在这里插入图片描述

2.1 LDP

加载寄存器对指令根据基址寄存器值和立即偏移计算地址,从内存加载两个 32 位字或两个 64 位双字,并将它们写入两个寄存器。

Post-index

在这里插入图片描述

32-bit (opc == 00)

LDP <Wt1>, <Wt2>, [<Xn|SP>], #<imm>

64-bit (opc == 10)

LDP <Xt1>, <Xt2>, [<Xn|SP>], #<imm>

Pre-index

在这里插入图片描述

32-bit (opc == 00)

LDP <Wt1>, <Wt2>, [<Xn|SP>, #<imm>]!

64-bit (opc == 10)

LDP <Xt1>, <Xt2>, [<Xn|SP>, #<imm>]!

Signed offset

在这里插入图片描述

32-bit (opc == 00)

LDP <Wt1>, <Wt2>, [<Xn|SP>{, #<imm>}]

64-bit (opc == 10)

LDP <Xt1>, <Xt2>, [<Xn|SP>{, #<imm>}]

<Wt1> 是要传输的第一个通用寄存器的 32 位名称,在“Rt”字段中编码。

<Wt2> 是要传输的第二个通用寄存器的 32 位名称,在“Rt2”字段中编码。

<Xt1> 是要传输的第一个通用寄存器的 64 位名称,在“Rt”字段中编码。

<Xt2> 是要传输的第二个通用寄存器的 64 位名称,在“Rt2”字段中编码。

<Xn|SP> 是通用基址寄存器或堆栈指针的 64 位名称,在“Rn”字段中编码。

<imm> 对于 32 位后索引和 32 位前索引变体:是带符号的立即字节偏移量,是 -256 到 252 范围内的 4 的倍数,在“imm7”字段中编码为 <imm>/4。对于 32 位有符号偏移变体:是可选的有符号立即字节偏移,是 -256 到 252 范围内的 4 的倍数,默认为 0 并在“imm7”字段中编码为 <imm>/4

对于 64 位后索引和 64 位前索引变体:是带符号的立即字节偏移量,是 -512 到 504 范围内 8 的倍数,在“imm7”字段中编码为 <imm>/8。对于 64 位有符号偏移变体:是可选的有符号立即字节偏移,是 -512 到 504 范围内 8 的倍数,默认为 0 并在“imm7”字段中编码为 <imm>/8

LDP Post-index

下面是使用 LDP Post-index 指令的例子。

    long long int len = 3;

    long long int x = 0;
    long long int y = 0;
    long long int z = 0;
    long long int w = 0;

    auto *arr = new long long int[len]{0};

    LOGD("+++++++++++++++++++++++++++++");
    LOGD("x=0x%llx y=0x%llx z=0x%llx w=0x%llx", x, y, z, w);
    for (int i = 0; i < len; i++) {
        arr[i] = 0x0102030410203040 * (i + 1);
        LOGD("arr[%d]=0x%llx", i, arr[i]);
    }

    char *a = (char *) arr;

    asm volatile(
        "LDP %x[x], %x[y], [%x[a]], #8\n"
        "LDP %x[z], %x[w], [%x[a]], #8\n"
    :[a] "+r"(a),
     [x] "+r"(x),
     [y] "+r"(y),
     [z] "+r"(z),
     [w] "+r"(w)
    :
    : "cc", "memory");

    LOGD("-----------------------------");
    LOGD("x=0x%llx y=0x%llx z=0x%llx w=0x%llx", x, y, z, w);

    delete[] arr;

观察结果不难发现,LDP %x[x], %x[y], [%x[a]], #8 指令先将 arr[0] 加载到 %x[x] 寄存器,接着将 arr + 8 处的值(也就是 arr[1])加载到 %x[y] 寄存器。LDP %x[z], %x[w], [%x[a]], #8 指令接着从 arr + 8 处的值(也就是 arr[1])加载到 %x[z] 寄存器,arr + 16 加载到 %x[w]。我们看到 LDP %x[x], %x[y], [%x[a]], #8 将 arr + 8 同时存储在了 %x[a] 中,地址只加了一次,而不是两次!

运行结果如下:

2023-05-02 07:40:26.204 24915-24915/com.example.myapplication D/native-armv8a: +++++++++++++++++++++++++++++
2023-05-02 07:40:26.204 24915-24915/com.example.myapplication D/native-armv8a: x=0x0 y=0x0 z=0x0 w=0x0
2023-05-02 07:40:26.204 24915-24915/com.example.myapplication D/native-armv8a: arr[0]=0x102030410203040
2023-05-02 07:40:26.204 24915-24915/com.example.myapplication D/native-armv8a: arr[1]=0x204060820406080
2023-05-02 07:40:26.204 24915-24915/com.example.myapplication D/native-armv8a: arr[2]=0x306090c306090c0
2023-05-02 07:40:26.204 24915-24915/com.example.myapplication D/native-armv8a: -----------------------------
2023-05-02 07:40:26.204 24915-24915/com.example.myapplication D/native-armv8a: x=0x102030410203040 y=0x204060820406080 z=0x204060820406080 w=0x306090c306090c0

LDP Pre-index

下面是使用 LDP Pre-index 指令的例子。将 LDP Post-index 的例子稍做修改(len 改为 4,并把 LDP 指令改为 Pre-index 的格式)。

    long long int len = 4;

    long long int x = 0;
    long long int y = 0;
    long long int z = 0;
    long long int w = 0;

    auto *arr = new long long int[len]{0};

    LOGD("+++++++++++++++++++++++++++++");
    LOGD("x=0x%llx y=0x%llx z=0x%llx w=0x%llx", x, y, z, w);
    for (int i = 0; i < len; i++) {
        arr[i] = 0x0102030410203040 * (i + 1);
        LOGD("arr[%d]=0x%llx", i, arr[i]);
    }

    char *a = (char *) arr;

    asm volatile(
        "LDP %x[x], %x[y], [%x[a], #8]!\n"
        "LDP %x[z], %x[w], [%x[a], #8]!\n"
    :[a] "+r"(a),
     [x] "+r"(x),
     [y] "+r"(y),
     [z] "+r"(z),
     [w] "+r"(w)
    :
    : "cc", "memory");

    LOGD("-----------------------------");
    LOGD("x=0x%llx y=0x%llx z=0x%llx w=0x%llx", x, y, z, w);

    delete[] arr;

观察结果不难发现,LDP Pre-index 首先会将地址加 8 并保存,再去加载数据到寄存器。

运行结果如下:

2023-05-02 07:47:37.285 26563-26563/com.example.myapplication D/native-armv8a: +++++++++++++++++++++++++++++
2023-05-02 07:47:37.285 26563-26563/com.example.myapplication D/native-armv8a: x=0x0 y=0x0 z=0x0 w=0x0
2023-05-02 07:47:37.285 26563-26563/com.example.myapplication D/native-armv8a: arr[0]=0x102030410203040
2023-05-02 07:47:37.285 26563-26563/com.example.myapplication D/native-armv8a: arr[1]=0x204060820406080
2023-05-02 07:47:37.285 26563-26563/com.example.myapplication D/native-armv8a: arr[2]=0x306090c306090c0
2023-05-02 07:47:37.285 26563-26563/com.example.myapplication D/native-armv8a: arr[3]=0x4080c104080c100
2023-05-02 07:47:37.285 26563-26563/com.example.myapplication D/native-armv8a: -----------------------------
2023-05-02 07:47:37.285 26563-26563/com.example.myapplication D/native-armv8a: x=0x204060820406080 y=0x306090c306090c0 z=0x306090c306090c0 w=0x4080c104080c100

LDP Signed offset

下面是使用 LDP Signed offset 指令的例子。两次 LDP 指令加载的位置都是固定的。

    long long int len = 4;

    long long int x = 0;
    long long int y = 0;
    long long int z = 0;
    long long int w = 0;

    auto *arr = new long long int[len]{0};

    LOGD("+++++++++++++++++++++++++++++");
    LOGD("x=0x%llx y=0x%llx z=0x%llx w=0x%llx", x, y, z, w);
    for (int i = 0; i < len; i++) {
        arr[i] = 0x0102030410203040 * (i + 1);
        LOGD("arr[%d]=0x%llx", i, arr[i]);
    }

    char *a = (char *) arr;

    asm volatile(
        "LDP %x[x], %x[y], [%x[a], #8]\n"
        "LDP %x[z], %x[w], [%x[a], #8]\n"
    :[a] "+r"(a),
     [x] "+r"(x),
     [y] "+r"(y),
     [z] "+r"(z),
     [w] "+r"(w)
    :
    : "cc", "memory");

    LOGD("-----------------------------");
    LOGD("x=0x%llx y=0x%llx z=0x%llx w=0x%llx", x, y, z, w);

    delete[] arr;

运行结果如下:

2023-05-02 07:59:42.287 19495-19495/com.example.myapplication D/native-armv8a: +++++++++++++++++++++++++++++
2023-05-02 07:59:42.287 19495-19495/com.example.myapplication D/native-armv8a: x=0x0 y=0x0 z=0x0 w=0x0
2023-05-02 07:59:42.287 19495-19495/com.example.myapplication D/native-armv8a: arr[0]=0x102030410203040
2023-05-02 07:59:42.287 19495-19495/com.example.myapplication D/native-armv8a: arr[1]=0x204060820406080
2023-05-02 07:59:42.287 19495-19495/com.example.myapplication D/native-armv8a: arr[2]=0x306090c306090c0
2023-05-02 07:59:42.287 19495-19495/com.example.myapplication D/native-armv8a: arr[3]=0x4080c104080c100
2023-05-02 07:59:42.287 19495-19495/com.example.myapplication D/native-armv8a: -----------------------------
2023-05-02 07:59:42.287 19495-19495/com.example.myapplication D/native-armv8a: x=0x204060820406080 y=0x306090c306090c0 z=0x204060820406080 w=0x306090c306090c0

2.2 LDPSW

Load Pair of Registers Signed Word 指令根据基址寄存器值和立即偏移量计算地址,从内存中加载两个 32 位字,对它们进行符号扩展,并将它们写入两个寄存器。

Post-index

在这里插入图片描述

LDPSW <Xt1>, <Xt2>, [<Xn|SP>], #<imm>

Pre-index

在这里插入图片描述

LDPSW <Xt1>, <Xt2>, [<Xn|SP>, #<imm>]!

Signed offset

在这里插入图片描述

LDPSW <Xt1>, <Xt2>, [<Xn|SP>{, #<imm>}]

<Xt1> 是要传输的第一个通用寄存器的 64 位名称,在“Rt”字段中编码。

<Xt2> 是要传输的第二个通用寄存器的 64 位名称,在“Rt2”字段中编码。

<Xn|SP> 是通用基址寄存器或堆栈指针的 64 位名称,在“Rn”字段中编码。

<imm> 对于后索引和前索引变体:是带符号的立即字节偏移量,是 -256 到 252 范围内的 4 的倍数,在“imm7”字段中编码为 <imm>/4。对于带符号的偏移量变体:是可选的带符号立即字节偏移量,是 -256 到 252 范围内的 4 的倍数,默认为 0 并在“imm7”字段中编码为 <imm>/4

LDPSW Post-index

下面是使用 LDPSW Post-index 指令的例子。

    long long int len = 4;

    long long int x = 0;
    long long int y = 0;
    long long int z = 0;
    long long int w = 0;

    auto *arr = new long long int[len]{0};

    LOGD("+++++++++++++++++++++++++++++");
    LOGD("x=0x%llx y=0x%llx z=0x%llx w=0x%llx", x, y, z, w);
    for (int i = 0; i < len; i++) {
        arr[i] = 0x0102030450203040 * (i + 1);
        LOGD("arr[%d]=0x%llx", i, arr[i]);
    }

    char *a = (char *) arr;

    asm volatile(
        "LDPSW %x[x], %x[y], [%x[a]], #8\n"
        "LDPSW %x[z], %x[w], [%x[a]], #8\n"
    :[a] "+r"(a),
     [x] "+r"(x),
     [y] "+r"(y),
     [z] "+r"(z),
     [w] "+r"(w)
    :
    : "cc", "memory");

    LOGD("-----------------------------");
    LOGD("x=0x%llx y=0x%llx z=0x%llx w=0x%llx", x, y, z, w);

    delete[] arr;

LDPSW %x[x], %x[y], [%x[a]], #8 从 arr[0] 处加载 4 个字节(一个字长)到 %x[x],接着 arr + 4 处加载 4 个字节到 %x[y],最后要将 arr + 8 地址保存起来。LDPSW %x[z], %x[w], [%x[a]], #8 类似的,先从 arr + 8 处直接加载,但因为 0xa0406080 符号位为 1,因此要扩展符号位(均为 1),所以 z 的值最终为 0xffffffffa0406080。

运行结果如下:

2023-05-02 16:30:20.260 14000-14000/com.example.myapplication D/native-armv8a: +++++++++++++++++++++++++++++
2023-05-02 16:30:20.260 14000-14000/com.example.myapplication D/native-armv8a: x=0x0 y=0x0 z=0x0 w=0x0
2023-05-02 16:30:20.260 14000-14000/com.example.myapplication D/native-armv8a: arr[0]=0x102030450203040
2023-05-02 16:30:20.260 14000-14000/com.example.myapplication D/native-armv8a: arr[1]=0x2040608a0406080
2023-05-02 16:30:20.260 14000-14000/com.example.myapplication D/native-armv8a: arr[2]=0x306090cf06090c0
2023-05-02 16:30:20.260 14000-14000/com.example.myapplication D/native-armv8a: arr[3]=0x4080c114080c100
2023-05-02 16:30:20.260 14000-14000/com.example.myapplication D/native-armv8a: -----------------------------
2023-05-02 16:30:20.260 14000-14000/com.example.myapplication D/native-armv8a: x=0x50203040 y=0x1020304 z=0xffffffffa0406080 w=0x2040608

LDPSW Pre-index

下面是使用 LDPSW Pre-index 指令的例子。LDPSW Pre-index 和 LDPSW Post-index 区别在于先要将地址加上立即数后再去加载数据到寄存器。

    long long int len = 4;

    long long int x = 0;
    long long int y = 0;
    long long int z = 0;
    long long int w = 0;

    auto *arr = new long long int[len]{0};

    LOGD("+++++++++++++++++++++++++++++");
    LOGD("x=0x%llx y=0x%llx z=0x%llx w=0x%llx", x, y, z, w);
    for (int i = 0; i < len; i++) {
        arr[i] = 0x0102030450203040 * (i + 1);
        LOGD("arr[%d]=0x%llx", i, arr[i]);
    }

    char *a = (char *) arr;

    asm volatile(
        "LDPSW %x[x], %x[y], [%x[a], #8]!\n"
        "LDPSW %x[z], %x[w], [%x[a], #8]!\n"
    :[a] "+r"(a),
     [x] "+r"(x),
     [y] "+r"(y),
     [z] "+r"(z),
     [w] "+r"(w)
    :
    : "cc", "memory");

    LOGD("-----------------------------");
    LOGD("x=0x%llx y=0x%llx z=0x%llx w=0x%llx", x, y, z, w);

    delete[] arr;

运行结果如下:

2023-05-02 16:39:17.475 16127-16127/com.example.myapplication D/native-armv8a: +++++++++++++++++++++++++++++
2023-05-02 16:39:17.475 16127-16127/com.example.myapplication D/native-armv8a: x=0x0 y=0x0 z=0x0 w=0x0
2023-05-02 16:39:17.475 16127-16127/com.example.myapplication D/native-armv8a: arr[0]=0x102030450203040
2023-05-02 16:39:17.475 16127-16127/com.example.myapplication D/native-armv8a: arr[1]=0x2040608a0406080
2023-05-02 16:39:17.475 16127-16127/com.example.myapplication D/native-armv8a: arr[2]=0x306090cf06090c0
2023-05-02 16:39:17.475 16127-16127/com.example.myapplication D/native-armv8a: arr[3]=0x4080c114080c100
2023-05-02 16:39:17.475 16127-16127/com.example.myapplication D/native-armv8a: -----------------------------
2023-05-02 16:39:17.475 16127-16127/com.example.myapplication D/native-armv8a: x=0xffffffffa0406080 y=0x2040608 z=0xfffffffff06090c0 w=0x306090c

LDPSW Signed offset

下面是使用 LDPSW Signed offset 指令的例子。把 LDPSW Pre-index 例子中的 LDPSW 指令感叹号去掉。每次都是从固定的 arr + 8 处开始加载,也就是 arr[1]。

    long long int len = 4;

    long long int x = 0;
    long long int y = 0;
    long long int z = 0;
    long long int w = 0;

    auto *arr = new long long int[len]{0};

    LOGD("+++++++++++++++++++++++++++++");
    LOGD("x=0x%llx y=0x%llx z=0x%llx w=0x%llx", x, y, z, w);
    for (int i = 0; i < len; i++) {
        arr[i] = 0x0102030450203040 * (i + 1);
        LOGD("arr[%d]=0x%llx", i, arr[i]);
    }

    char *a = (char *) arr;

    asm volatile(
        "LDPSW %x[x], %x[y], [%x[a], #8]\n"
        "LDPSW %x[z], %x[w], [%x[a], #8]\n"
    :[a] "+r"(a),
     [x] "+r"(x),
     [y] "+r"(y),
     [z] "+r"(z),
     [w] "+r"(w)
    :
    : "cc", "memory");

    LOGD("-----------------------------");
    LOGD("x=0x%llx y=0x%llx z=0x%llx w=0x%llx", x, y, z, w);

    delete[] arr;

运行结果如下:

2023-05-02 16:43:12.228 17276-17276/com.example.myapplication D/native-armv8a: +++++++++++++++++++++++++++++
2023-05-02 16:43:12.228 17276-17276/com.example.myapplication D/native-armv8a: x=0x0 y=0x0 z=0x0 w=0x0
2023-05-02 16:43:12.228 17276-17276/com.example.myapplication D/native-armv8a: arr[0]=0x102030450203040
2023-05-02 16:43:12.228 17276-17276/com.example.myapplication D/native-armv8a: arr[1]=0x2040608a0406080
2023-05-02 16:43:12.228 17276-17276/com.example.myapplication D/native-armv8a: arr[2]=0x306090cf06090c0
2023-05-02 16:43:12.228 17276-17276/com.example.myapplication D/native-armv8a: arr[3]=0x4080c114080c100
2023-05-02 16:43:12.228 17276-17276/com.example.myapplication D/native-armv8a: -----------------------------
2023-05-02 16:43:12.228 17276-17276/com.example.myapplication D/native-armv8a: x=0xffffffffa0406080 y=0x2040608 z=0xffffffffa0406080 w=0x2040608

2.3 STP

Store Pair of Registers 指令根据基址寄存器值和立即偏移量计算地址,并将两个 32 位字或两个 64 位双字从两个寄存器存储到计算出的地址。

Post-index

在这里插入图片描述

32-bit (opc == 00)

STP <Wt1>, <Wt2>, [<Xn|SP>], #<imm>

64-bit (opc == 10)

STP <Xt1>, <Xt2>, [<Xn|SP>], #<imm>

Pre-index

在这里插入图片描述

32-bit (opc == 00)

STP <Wt1>, <Wt2>, [<Xn|SP>, #<imm>]!

64-bit (opc == 10)

STP <Xt1>, <Xt2>, [<Xn|SP>, #<imm>]!

Signed offset

在这里插入图片描述

32-bit (opc == 00)

STP <Wt1>, <Wt2>, [<Xn|SP>{, #<imm>}]

64-bit (opc == 10)

STP <Xt1>, <Xt2>, [<Xn|SP>{, #<imm>}]

<Wt1> 是要传输的第一个通用寄存器的 32 位名称,在“Rt”字段中编码。

<Wt2> 是要传输的第二个通用寄存器的 32 位名称,在“Rt2”字段中编码。

<Xt1> 是要传输的第一个通用寄存器的 64 位名称,在“Rt”字段中编码。

<Xt2> 是要传输的第二个通用寄存器的 64 位名称,在“Rt2”字段中编码。

<Xn|SP> 是通用基址寄存器或堆栈指针的 64 位名称,在“Rn”字段中编码。

<imm> 对于 32 位后索引和 32 位前索引变体:是带符号的立即字节偏移量,是 -256 到 252 范围内的 4 的倍数,在“imm7”字段中编码为 <imm>/4。对于 32 位有符号偏移变体:是可选的有符号立即字节偏移,是 -256 到 252 范围内的 4 的倍数,默认为 0 并在“imm7”字段中编码为 <imm>/4

对于 64 位后索引和 64 位前索引变体:是带符号的立即字节偏移量,是 -512 到 504 范围内 8 的倍数,在“imm7”字段中编码为 <imm>/8。对于 64 位有符号偏移变体:是可选的有符号立即字节偏移,是 -512 到 504 范围内的 8 的倍数,默认为 0 并在“imm7”字段中编码为 <imm>/8

STP Post-index

下面是使用 STP Post-index 指令的例子。

    long long int len = 4;

    long long int x = 0x102030450203040;
    long long int y = 0x2040608a0406080;
    long long int z = 0x306090cf06090c0;
    long long int w = 0x4080c114080c100;

    auto *arr = new long long int[len]{0};

    LOGD("+++++++++++++++++++++++++++++");
    LOGD("x=0x%llx y=0x%llx z=0x%llx w=0x%llx", x, y, z, w);

    char *a = (char *) arr;

    asm volatile(
        "STP %x[x], %x[y], [%x[a]], #8\n"
        "STP %x[z], %x[w], [%x[a]], #8\n"
    :[a] "+r"(a),
     [x] "+r"(x),
     [y] "+r"(y),
     [z] "+r"(z),
     [w] "+r"(w)
    :
    : "cc", "memory");

    LOGD("-----------------------------");
    for (int i = 0; i < len; i++) {
        LOGD("arr[%d]=0x%llx", i, arr[i]);
    }

    delete[] arr;

STP %x[x], %x[y], [%x[a]], #8 首先将 %x[x] 存储到 arr 处,%x[y] 存储到 arr + 8 处,并将 arr + 8 地址保存。同样 STP %x[z], %x[w], [%x[a]], #8 先将 %x[z] 存储到 arr + 8 处,%x[w] 存储到 arr + 16 处,,并将 arr + 16 地址保存。

运行结果如下:

2023-05-02 17:19:09.208 1431-1431/com.example.myapplication D/native-armv8a: +++++++++++++++++++++++++++++
2023-05-02 17:19:09.208 1431-1431/com.example.myapplication D/native-armv8a: x=0x102030450203040 y=0x2040608a0406080 z=0x306090cf06090c0 w=0x4080c114080c100
2023-05-02 17:19:09.208 1431-1431/com.example.myapplication D/native-armv8a: -----------------------------
2023-05-02 17:19:09.208 1431-1431/com.example.myapplication D/native-armv8a: arr[0]=0x102030450203040
2023-05-02 17:19:09.208 1431-1431/com.example.myapplication D/native-armv8a: arr[1]=0x306090cf06090c0
2023-05-02 17:19:09.208 1431-1431/com.example.myapplication D/native-armv8a: arr[2]=0x4080c114080c100
2023-05-02 17:19:09.208 1431-1431/com.example.myapplication D/native-armv8a: arr[3]=0x0

STP Pre-index

下面是使用 STP Pre-index 指令的例子。

    long long int len = 4;

    long long int x = 0x102030450203040;
    long long int y = 0x2040608a0406080;
    long long int z = 0x306090cf06090c0;
    long long int w = 0x4080c114080c100;

    auto *arr = new long long int[len]{0};

    LOGD("+++++++++++++++++++++++++++++");
    LOGD("x=0x%llx y=0x%llx z=0x%llx w=0x%llx", x, y, z, w);

    char *a = (char *) arr;

    asm volatile(
        "STP %x[x], %x[y], [%x[a], #8]!\n"
        "STP %x[z], %x[w], [%x[a], #8]!\n"
    :[a] "+r"(a),
     [x] "+r"(x),
     [y] "+r"(y),
     [z] "+r"(z),
     [w] "+r"(w)
    :
    : "cc", "memory");

    LOGD("-----------------------------");
    for (int i = 0; i < len; i++) {
        LOGD("arr[%d]=0x%llx", i, arr[i]);
    }

    delete[] arr;

和 STP Post-index 指令的唯一区别在于地址先增加。

运行结果如下:

2023-05-02 17:24:00.376 15988-15988/com.example.myapplication D/native-armv8a: +++++++++++++++++++++++++++++
2023-05-02 17:24:00.376 15988-15988/com.example.myapplication D/native-armv8a: x=0x102030450203040 y=0x2040608a0406080 z=0x306090cf06090c0 w=0x4080c114080c100
2023-05-02 17:24:00.376 15988-15988/com.example.myapplication D/native-armv8a: -----------------------------
2023-05-02 17:24:00.376 15988-15988/com.example.myapplication D/native-armv8a: arr[0]=0x0
2023-05-02 17:24:00.376 15988-15988/com.example.myapplication D/native-armv8a: arr[1]=0x102030450203040
2023-05-02 17:24:00.376 15988-15988/com.example.myapplication D/native-armv8a: arr[2]=0x306090cf06090c0
2023-05-02 17:24:00.376 15988-15988/com.example.myapplication D/native-armv8a: arr[3]=0x4080c114080c100

STP Signed offset

下面是使用 STP Signed offset 指令的例子,去除上面 STP Pre-index 指令中的感叹号,每次都从固定的地址处存储。

    long long int len = 4;

    long long int x = 0x102030450203040;
    long long int y = 0x2040608a0406080;
    long long int z = 0x306090cf06090c0;
    long long int w = 0x4080c114080c100;

    auto *arr = new long long int[len]{0};

    LOGD("+++++++++++++++++++++++++++++");
    LOGD("x=0x%llx y=0x%llx z=0x%llx w=0x%llx", x, y, z, w);

    char *a = (char *) arr;

    asm volatile(
        "STP %x[x], %x[y], [%x[a], #8]\n"
        "STP %x[z], %x[w], [%x[a], #8]\n"
    :[a] "+r"(a),
     [x] "+r"(x),
     [y] "+r"(y),
     [z] "+r"(z),
     [w] "+r"(w)
    :
    : "cc", "memory");

    LOGD("-----------------------------");
    for (int i = 0; i < len; i++) {
        LOGD("arr[%d]=0x%llx", i, arr[i]);
    }

    delete[] arr;

运行结果如下:

2023-05-02 17:24:49.807 16666-16666/com.example.myapplication D/native-armv8a: +++++++++++++++++++++++++++++
2023-05-02 17:24:49.807 16666-16666/com.example.myapplication D/native-armv8a: x=0x102030450203040 y=0x2040608a0406080 z=0x306090cf06090c0 w=0x4080c114080c100
2023-05-02 17:24:49.807 16666-16666/com.example.myapplication D/native-armv8a: -----------------------------
2023-05-02 17:24:49.807 16666-16666/com.example.myapplication D/native-armv8a: arr[0]=0x0
2023-05-02 17:24:49.807 16666-16666/com.example.myapplication D/native-armv8a: arr[1]=0x306090cf06090c0
2023-05-02 17:24:49.807 16666-16666/com.example.myapplication D/native-armv8a: arr[2]=0x4080c114080c100
2023-05-02 17:24:49.807 16666-16666/com.example.myapplication D/native-armv8a: arr[3]=0x0

三、非特权访问指令

A64 LDTR 和 STTR 指令执行非特权加载或存储:

  • 在 EL0(普通用户应用程序)、EL2(Hypervisor) 或 EL3(底层固件,包括安全监视器),它们的行为与正常的加载或存储相同。
  • 当在 EL1(操作系统内核通常被描述为具有特权) 执行时,它们的行为就好像它们是在特权级别 EL0 上执行的一样。这些指令等同于 A32 LDRT 和 STRT 指令。

3.1 LDTR

加载寄存器(非特权)指令从内存中加载一个字或双字,并将其写入寄存器。用于加载的地址是根据基址寄存器和立即偏移量计算得出的。

如果 PSTATE.UAO 的 Effective 值为 0 并且满足以下任一条件,则指令进行的内存访问就像指令在 EL0 处执行一样:

  • 指令在 EL1 处执行。
  • 当 HCR_EL2.{E2H, TGE} 的 Effective 值为 {1, 1} 时,指令在 EL2 处执行。

否则,内存访问将根据执行指令的异常级别确定的限制进行操作。

在这里插入图片描述

32-bit (size == 10)

LDTR <Wt>, [<Xn|SP>{, #<simm>}]

64-bit (size == 11)

LDTR <Xt>, [<Xn|SP>{, #<simm>}]

<Wt> 是要传输的通用寄存器的 32 位名称,在“Rt”字段中编码。

<Xt> 是要传输的通用寄存器的 64 位名称,在“Rt”字段中编码。

<Xn|SP> 是通用基址寄存器或堆栈指针的 64 位名称,在“Rn”字段中编码。

<simm> 是可选的有符号立即字节偏移量,范围为 -256 到 255,默认为 0 并在“imm9”字段中编码。

下面是使用 LDTR 指令的例子。

    long long int len = 4;

    long long int x = 0;
    long long int y = 0;

    auto *arr = new long long int[len]{0};

    LOGD("+++++++++++++++++++++++++++++");
    for (int i = 0; i < len; i++) {
        arr[i] = 0x0102030405060708 * (i + 1);
        LOGD("arr[%d]=0x%llx", i, arr[i]);
    }

    char *a = (char *) arr;

    asm volatile(
        "LDTR %x[x], [%x[a], #8]\n"
        "LDTR %x[y], [%x[a], #8]\n"
    :[a] "+r"(a),
     [x] "+r"(x),
     [y] "+r"(y)
    :
    : "cc", "memory");

    LOGD("-----------------------------");
    LOGD("x=0x%llx y=0x%llx", x, y);

    delete[] arr;

LDTR 每次从 a + 8 处读取数据加载到想要的寄存器。

运行结果如下:

2023-05-03 06:41:19.391 2097-2097/com.example.myapplication D/native-armv8a: +++++++++++++++++++++++++++++
2023-05-03 06:41:19.391 2097-2097/com.example.myapplication D/native-armv8a: arr[0]=0x102030405060708
2023-05-03 06:41:19.391 2097-2097/com.example.myapplication D/native-armv8a: arr[1]=0x20406080a0c0e10
2023-05-03 06:41:19.391 2097-2097/com.example.myapplication D/native-armv8a: arr[2]=0x306090c0f121518
2023-05-03 06:41:19.391 2097-2097/com.example.myapplication D/native-armv8a: arr[3]=0x4080c1014181c20
2023-05-03 06:41:19.391 2097-2097/com.example.myapplication D/native-armv8a: -----------------------------
2023-05-03 06:41:19.391 2097-2097/com.example.myapplication D/native-armv8a: x=0x20406080a0c0e10 y=0x20406080a0c0e10

3.2 LDTRB

加载寄存器字节(非特权)指令从内存中加载一个字节,对其进行零扩展,并将结果写入寄存器。用于加载的地址是根据基址寄存器和立即偏移量计算得出的。

如果 PSTATE.UAO 的 Effective 值为 0 并且满足以下任一条件,则指令进行的内存访问就像指令在 EL0 处执行一样:

  • 指令在 EL1 处执行。
  • 当 HCR_EL2.{E2H, TGE} 的 Effective 值为 {1, 1} 时,指令在 EL2 处执行。

否则,内存访问将根据执行指令的异常级别确定的限制进行操作。

在这里插入图片描述

LDTRB <Wt>, [<Xn|SP>{, #<simm>}]

<Wt> 是要传输的通用寄存器的 32 位名称,在“Rt”字段中编码。

<Xn|SP> 是通用基址寄存器或堆栈指针的 64 位名称,在“Rn”字段中编码。

<simm> 是可选的有符号立即字节偏移量,范围为 -256 到 255,默认为 0 并在“imm9”字段中编码。

下面是使用 LDTRB 指令的例子。

    long long int len = 2;

    long long int x = 0;
    long long int y = 0;

    auto *arr = new long long int[len]{0};

    LOGD("+++++++++++++++++++++++++++++");
    for (int i = 0; i < len; i++) {
        arr[i] = 0x0102030450607080 * (i + 1);
        LOGD("arr[%d]=0x%llx", i, arr[i]);
    }

    char *a = (char *) arr;

    asm volatile(
        "LDTRB %w[x], [%x[a], #0]\n"
        "LDTRB %w[y], [%x[a], #0]\n"
    :[a] "+r"(a),
     [x] "+r"(x),
     [y] "+r"(y)
    :
    : "cc", "memory");

    LOGD("-----------------------------");
    LOGD("x=0x%llx y=0x%llx", x, y);

    delete[] arr;

LDTRB 的目的寄存器是 w 寄存器,另外将对应字节进行 0 扩展!

运行结果如下:

2023-05-03 06:51:13.186 7353-7353/com.example.myapplication D/native-armv8a: +++++++++++++++++++++++++++++
2023-05-03 06:51:13.186 7353-7353/com.example.myapplication D/native-armv8a: arr[0]=0x102030450607080
2023-05-03 06:51:13.186 7353-7353/com.example.myapplication D/native-armv8a: arr[1]=0x2040608a0c0e100
2023-05-03 06:51:13.186 7353-7353/com.example.myapplication D/native-armv8a: -----------------------------
2023-05-03 06:51:13.186 7353-7353/com.example.myapplication D/native-armv8a: x=0x80 y=0x80

3.3 LDTRH

加载寄存器半字(非特权)指令从内存中加载一个半字,对其进行零扩展,并将结果写入寄存器。用于加载的地址是根据基址寄存器和立即偏移量计算得出的。

如果 PSTATE.UAO 的 Effective 值为 0 并且满足以下任一条件,则指令进行的内存访问就像指令在 EL0 处执行一样:

  • 指令在 EL1 处执行。
    -当 HCR_EL2.{E2H, TGE} 的 Effective 值为 {1, 1} 时,指令在 EL2 处执行。

否则,内存访问将根据执行指令的异常级别确定的限制进行操作。

在这里插入图片描述

LDTRH <Wt>, [<Xn|SP>{, #<simm>}]

<Wt> 是要传输的通用寄存器的 32 位名称,在“Rt”字段中编码。

<Xn|SP> 是通用基址寄存器或堆栈指针的 64 位名称,在“Rn”字段中编码。

<simm> 是可选的有符号立即字节偏移量,范围为 -256 到 255,默认为 0 并在“imm9”字段中编码。

下面是使用 LDTRH 指令的例子。

    long long int len = 2;

    long long int x = 0;
    long long int y = 0;

    auto *arr = new long long int[len]{0};

    LOGD("+++++++++++++++++++++++++++++");
    for (int i = 0; i < len; i++) {
        arr[i] = 0x0102030450608070 * (i + 1);
        LOGD("arr[%d]=0x%llx", i, arr[i]);
    }

    char *a = (char *) arr;

    asm volatile(
        "LDTRH %w[x], [%x[a], #0]\n"
        "LDTRH %w[y], [%x[a], #0]\n"
    :[a] "+r"(a),
     [x] "+r"(x),
     [y] "+r"(y)
    :
    : "cc", "memory");

    LOGD("-----------------------------");
    LOGD("x=0x%llx y=0x%llx", x, y);

    delete[] arr;

LDTRB 的目的寄存器是 w 寄存器,另外将对应半字进行 0 扩展!

运行结果如下:

2023-05-03 06:58:49.589 10072-10072/com.example.myapplication D/native-armv8a: +++++++++++++++++++++++++++++
2023-05-03 06:58:49.589 10072-10072/com.example.myapplication D/native-armv8a: arr[0]=0x102030450608070
2023-05-03 06:58:49.589 10072-10072/com.example.myapplication D/native-armv8a: arr[1]=0x2040608a0c100e0
2023-05-03 06:58:49.589 10072-10072/com.example.myapplication D/native-armv8a: -----------------------------
2023-05-03 06:58:49.589 10072-10072/com.example.myapplication D/native-armv8a: x=0x8070 y=0x8070

3.4 LDTRSB

Load Register Signed Byte(非特权)指令从内存中加载一个字节,将其符号扩展为 32 位或 64 位,并将结果写入寄存器。 用于加载的地址是根据基址寄存器和立即偏移量计算得出的。

如果 PSTATE.UAO 的 Effective 值为 0 并且满足以下任一条件,则指令进行的内存访问就像指令在 EL0 处执行一样:

  • 指令在 EL1 处执行。
  • 当 HCR_EL2.{E2H, TGE} 的 Effective 值为 {1, 1} 时,指令在 EL2 处执行。

否则,内存访问将根据执行指令的异常级别确定的限制进行操作。

在这里插入图片描述

32-bit (opc == 11)

LDTRSB <Wt>, [<Xn|SP>{, #<simm>}]

64-bit (opc == 10)

LDTRSB <Xt>, [<Xn|SP>{, #<simm>}]

<Wt> 是要传输的通用寄存器的 32 位名称,在“Rt”字段中编码。

<Xt> 是要传输的通用寄存器的 64 位名称,在“Rt”字段中编码。

<Xn|SP> 是通用基址寄存器或堆栈指针的 64 位名称,在“Rn”字段中编码。

<simm> 是可选的有符号立即字节偏移量,范围为 -256 到 255,默认为 0 并在“imm9”字段中编码。

下面是使用 LDTRSB 指令的例子。

    long long int len = 2;

    long long int x = 0;
    long long int y = 0;

    auto *arr = new long long int[len]{0};

    LOGD("+++++++++++++++++++++++++++++");
    for (int i = 0; i < len; i++) {
        arr[i] = 0x0102030450608070 * (i + 1);
        LOGD("arr[%d]=0x%llx", i, arr[i]);
    }

    char *a = (char *) arr;

    asm volatile(
        "LDTRSB %x[x], [%x[a], #1]\n"
        "LDTRSB %x[y], [%x[a], #1]\n"
    :[a] "+r"(a),
     [x] "+r"(x),
     [y] "+r"(y)
    :
    : "cc", "memory");

    LOGD("-----------------------------");
    LOGD("x=0x%llx y=0x%llx", x, y);

    delete[] arr;

LDTRSB 加载地址的对应字节,并进行了符号位扩展!

运行结果如下:

2023-05-03 07:06:41.981 12820-12820/com.example.myapplication D/native-armv8a: +++++++++++++++++++++++++++++
2023-05-03 07:06:41.981 12820-12820/com.example.myapplication D/native-armv8a: arr[0]=0x102030450608070
2023-05-03 07:06:41.981 12820-12820/com.example.myapplication D/native-armv8a: arr[1]=0x2040608a0c100e0
2023-05-03 07:06:41.981 12820-12820/com.example.myapplication D/native-armv8a: -----------------------------
2023-05-03 07:06:41.981 12820-12820/com.example.myapplication D/native-armv8a: x=0xffffffffffffff80 y=0xffffffffffffff80

3.5 LDTRSH

加载寄存器带符号半字(非特权)指令从内存中加载半字,将其符号扩展为 32 位或 64 位,并将结果写入寄存器。用于加载的地址是根据基址寄存器和立即偏移量计算得出的。

如果 PSTATE.UAO 的 Effective 值为 0 并且满足以下任一条件,则指令进行的内存访问就像指令在 EL0 处执行一样:

  • 指令在 EL1 处执行。
  • 当 HCR_EL2.{E2H, TGE} 的 Effective 值为 {1, 1} 时,指令在 EL2 处执行。

否则,内存访问将根据执行指令的异常级别确定的限制进行操作。

在这里插入图片描述

32-bit (opc == 11)

LDTRSH <Wt>, [<Xn|SP>{, #<simm>}]

64-bit (opc == 10)

LDTRSH <Xt>, [<Xn|SP>{, #<simm>}]

<Wt> 是要传输的通用寄存器的 32 位名称,在“Rt”字段中编码。

<Xt> 是要传输的通用寄存器的 64 位名称,在“Rt”字段中编码。

<Xn|SP> 是通用基址寄存器或堆栈指针的 64 位名称,在“Rn”字段中编码。

<simm> 是可选的有符号立即字节偏移量,范围为 -256 到 255,默认为 0 并在“imm9”字段中编码。

下面是使用 LDTRSH 指令的例子。

    long long int len = 2;

    long long int x = 0;
    long long int y = 0;

    auto *arr = new long long int[len]{0};

    LOGD("+++++++++++++++++++++++++++++");
    for (int i = 0; i < len; i++) {
        arr[i] = 0x0102030450608070 * (i + 1);
        LOGD("arr[%d]=0x%llx", i, arr[i]);
    }

    char *a = (char *) arr;

    asm volatile(
        "LDTRSH %x[x], [%x[a], #0]\n"
        "LDTRSH %x[y], [%x[a], #0]\n"
    :[a] "+r"(a),
     [x] "+r"(x),
     [y] "+r"(y)
    :
    : "cc", "memory");

    LOGD("-----------------------------");
    LOGD("x=0x%llx y=0x%llx", x, y);

    delete[] arr;

LDTRSH 加载地址的对应半字,并进行了符号位扩展!

运行结果如下:

2023-05-03 07:11:41.995 14111-14111/com.example.myapplication D/native-armv8a: +++++++++++++++++++++++++++++
2023-05-03 07:11:41.995 14111-14111/com.example.myapplication D/native-armv8a: arr[0]=0x102030450608070
2023-05-03 07:11:41.995 14111-14111/com.example.myapplication D/native-armv8a: arr[1]=0x2040608a0c100e0
2023-05-03 07:11:41.995 14111-14111/com.example.myapplication D/native-armv8a: -----------------------------
2023-05-03 07:11:41.995 14111-14111/com.example.myapplication D/native-armv8a: x=0xffffffffffff8070 y=0xffffffffffff8070

3.6 LDTRSW

加载寄存器有符号字(非特权)指令从内存中加载一个字,将其符号扩展为 64 位,并将结果写入寄存器。用于加载的地址是根据基址寄存器和立即偏移量计算得出的。

如果 PSTATE.UAO 的 Effective 值为 0 并且满足以下任一条件,则指令进行的内存访问就像指令在 EL0 处执行一样:

  • 指令在 EL1 处执行。
  • 当 HCR_EL2.{E2H, TGE} 的 Effective 值为 {1, 1} 时,指令在 EL2 处执行。

否则,内存访问将根据执行指令的异常级别确定的限制进行操作。

在这里插入图片描述

LDTRSW <Xt>, [<Xn|SP>{, #<simm>}]

<Xt> 是要传输的通用寄存器的 64 位名称,在“Rt”字段中编码。

<Xn|SP> 是通用基址寄存器或堆栈指针的 64 位名称,在“Rn”字段中编码。

<simm> 是可选的有符号立即字节偏移量,范围为 -256 到 255,默认为 0 并在“imm9”字段中编码。

下面是使用 LDTRSW 指令的例子。

    long long int len = 2;

    long long int x = 0;
    long long int y = 0;

    auto *arr = new long long int[len]{0};

    LOGD("+++++++++++++++++++++++++++++");
    for (int i = 0; i < len; i++) {
        arr[i] = 0x0102030450608070 * (i + 1);
        LOGD("arr[%d]=0x%llx", i, arr[i]);
    }

    char *a = (char *) arr;

    asm volatile(
        "LDTRSW %x[x], [%x[a], #8]\n"
        "LDTRSW %x[y], [%x[a], #8]\n"
    :[a] "+r"(a),
     [x] "+r"(x),
     [y] "+r"(y)
    :
    : "cc", "memory");

    LOGD("-----------------------------");
    LOGD("x=0x%llx y=0x%llx", x, y);

    delete[] arr;

LDTRSW 加载地址(arr + 8)的对应字,并进行了符号位扩展!

运行结果如下:

2023-05-03 07:18:33.563 16309-16309/com.example.myapplication D/native-armv8a: +++++++++++++++++++++++++++++
2023-05-03 07:18:33.563 16309-16309/com.example.myapplication D/native-armv8a: arr[0]=0x102030450608070
2023-05-03 07:18:33.563 16309-16309/com.example.myapplication D/native-armv8a: arr[1]=0x2040608a0c100e0
2023-05-03 07:18:33.563 16309-16309/com.example.myapplication D/native-armv8a: -----------------------------
2023-05-03 07:18:33.563 16309-16309/com.example.myapplication D/native-armv8a: x=0xffffffffa0c100e0 y=0xffffffffa0c100e0

3.7 STTR

存储寄存器(非特权)指令将一个字或双字从寄存器存储到内存中。用于存储的地址是根据基址寄存器和立即偏移量计算得出的。

如果 PSTATE.UAO 的 Effective 值为 0 并且满足以下任一条件,则指令进行的内存访问就像指令在 EL0 处执行一样:

  • 指令在 EL1 处执行。
  • 当 HCR_EL2.{E2H, TGE} 的 Effective 值为 {1, 1} 时,指令在 EL2 处执行。

否则,内存访问将根据执行指令的异常级别确定的限制进行操作。

在这里插入图片描述

32-bit (size == 10)

STTR <Wt>, [<Xn|SP>{, #<simm>}]

64-bit (size == 11)

STTR <Xt>, [<Xn|SP>{, #<simm>}]

<Wt> 是要传输的通用寄存器的 32 位名称,在“Rt”字段中编码。

<Xt> 是要传输的通用寄存器的 64 位名称,在“Rt”字段中编码。

<Xn|SP> 是通用基址寄存器或堆栈指针的 64 位名称,在“Rn”字段中编码。

<simm> 是可选的有符号立即字节偏移量,范围为 -256 到 255,默认为 0 并在“imm9”字段中编码。

下面是使用 STTR 指令的例子。

    long long int len = 2;

    long long int x = 0x102030450608070;
    long long int y = 0x2040608a0c100e0;

    auto *arr = new long long int[len]{0};

    LOGD("+++++++++++++++++++++++++++++");
    LOGD("x=0x%llx y=0x%llx", x, y);

    char *a = (char *) arr;

    asm volatile(
        "STTR %x[x], [%x[a], #0]\n"
        "STTR %x[y], [%x[a], #8]\n"
    :[a] "+r"(a),
     [x] "+r"(x),
     [y] "+r"(y)
    :
    : "cc", "memory");

    LOGD("-----------------------------");
    for (int i = 0; i < len; i++) {
        LOGD("arr[%d]=0x%llx", i, arr[i]);
    }

    delete[] arr;

运行结果如下:

2023-05-03 07:26:24.480 17849-17849/com.example.myapplication D/native-armv8a: +++++++++++++++++++++++++++++
2023-05-03 07:26:24.480 17849-17849/com.example.myapplication D/native-armv8a: x=0x102030450608070 y=0x2040608a0c100e0
2023-05-03 07:26:24.480 17849-17849/com.example.myapplication D/native-armv8a: -----------------------------
2023-05-03 07:26:24.480 17849-17849/com.example.myapplication D/native-armv8a: arr[0]=0x102030450608070
2023-05-03 07:26:24.480 17849-17849/com.example.myapplication D/native-armv8a: arr[1]=0x2040608a0c100e0

3.8 STTRB

Store Register Byte(非特权)指令将一个字节从 32 位寄存器存储到内存中。用于存储的地址是根据基址寄存器和立即偏移量计算得出的。

如果 PSTATE.UAO 的 Effective 值为 0 并且满足以下任一条件,则指令进行的内存访问就像指令在 EL0 处执行一样:

  • 指令在 EL1 处执行。
  • 当 HCR_EL2.{E2H, TGE} 的 Effective 值为 {1, 1} 时,指令在 EL2 处执行。

否则,内存访问将根据执行指令的异常级别确定的限制进行操作。

在这里插入图片描述

STTRB <Wt>, [<Xn|SP>{, #<simm>}]

<Wt> 是要传输的通用寄存器的 32 位名称,在“Rt”字段中编码。

<Xn|SP> 是通用基址寄存器或堆栈指针的 64 位名称,在“Rn”字段中编码。

<simm> 是可选的有符号立即字节偏移量,范围为 -256 到 255,默认为 0 并在“imm9”字段中编码。

下面是使用 STTRB 指令的例子。

    long long int len = 2;

    long long int x = 0x102030450608070;
    long long int y = 0x2040608a0c100e0;

    auto *arr = new long long int[len]{0};

    LOGD("+++++++++++++++++++++++++++++");
    LOGD("x=0x%llx y=0x%llx", x, y);
    for (int i = 0; i < len; i++) {
        arr[i] = 0xffffffffffffffff;
        LOGD("arr[%d]=0x%llx", i, arr[i]);
    }

    char *a = (char *) arr;

    asm volatile(
        "STTRB %w[x], [%x[a], #1]\n"
        "STTRB %w[y], [%x[a], #9]\n"
    :[a] "+r"(a),
     [x] "+r"(x),
     [y] "+r"(y)
    :
    : "cc", "memory");

    LOGD("-----------------------------");
    for (int i = 0; i < len; i++) {
        LOGD("arr[%d]=0x%llx", i, arr[i]);
    }

    delete[] arr;

从结果可以看出要将 w 寄存器的最低有效字节写入相应的地址!

运行结果如下:

2023-05-03 14:42:39.016 29839-29839/com.example.myapplication D/native-armv8a: +++++++++++++++++++++++++++++
2023-05-03 14:42:39.016 29839-29839/com.example.myapplication D/native-armv8a: x=0x102030450608070 y=0x2040608a0c100e0
2023-05-03 14:42:39.016 29839-29839/com.example.myapplication D/native-armv8a: arr[0]=0xffffffffffffffff
2023-05-03 14:42:39.016 29839-29839/com.example.myapplication D/native-armv8a: arr[1]=0xffffffffffffffff
2023-05-03 14:42:39.016 29839-29839/com.example.myapplication D/native-armv8a: -----------------------------
2023-05-03 14:42:39.016 29839-29839/com.example.myapplication D/native-armv8a: arr[0]=0xffffffffffff70ff
2023-05-03 14:42:39.016 29839-29839/com.example.myapplication D/native-armv8a: arr[1]=0xffffffffffffe0ff

3.9 STTRH

存储寄存器半字(非特权)指令将半字从 32 位寄存器存储到内存。用于存储的地址是根据基址寄存器和立即偏移量计算得出的。

如果 PSTATE.UAO 的 Effective 值为 0 并且满足以下任一条件,则指令进行的内存访问就像指令在 EL0 处执行一样:

  • 指令在 EL1 处执行。
  • 当 HCR_EL2.{E2H, TGE} 的 Effective 值为 {1, 1} 时,指令在 EL2 处执行。

否则,内存访问将根据执行指令的异常级别确定的限制进行操作。

在这里插入图片描述

STTRH <Wt>, [<Xn|SP>{, #<simm>}]

<Wt> 是要传输的通用寄存器的 32 位名称,在“Rt”字段中编码。

<Xn|SP> 是通用基址寄存器或堆栈指针的 64 位名称,在“Rn”字段中编码。

<simm> 是可选的有符号立即字节偏移量,范围为 -256 到 255,默认为 0 并在“imm9”字段中编码。

下面是使用 STTRH 指令的例子。

    long long int len = 2;

    long long int x = 0x102030450608070;
    long long int y = 0x2040608a0c100e0;

    auto *arr = new long long int[len]{0};

    LOGD("+++++++++++++++++++++++++++++");
    LOGD("x=0x%llx y=0x%llx", x, y);
    for (int i = 0; i < len; i++) {
        arr[i] = 0xffffffffffffffff;
        LOGD("arr[%d]=0x%llx", i, arr[i]);
    }

    char *a = (char *) arr;

    asm volatile(
        "STTRH %w[x], [%x[a], #1]\n"
        "STTRH %w[y], [%x[a], #9]\n"
    :[a] "+r"(a),
     [x] "+r"(x),
     [y] "+r"(y)
    :
    : "cc", "memory");

    LOGD("-----------------------------");
    for (int i = 0; i < len; i++) {
        LOGD("arr[%d]=0x%llx", i, arr[i]);
    }

    delete[] arr;

从结果可以看出要将 w 寄存器的最低有效半字写入相应的地址!

运行结果如下:

2023-05-03 15:57:01.285 10565-10565/com.example.myapplication D/native-armv8a: +++++++++++++++++++++++++++++
2023-05-03 15:57:01.285 10565-10565/com.example.myapplication D/native-armv8a: x=0x102030450608070 y=0x2040608a0c100e0
2023-05-03 15:57:01.285 10565-10565/com.example.myapplication D/native-armv8a: arr[0]=0xffffffffffffffff
2023-05-03 15:57:01.285 10565-10565/com.example.myapplication D/native-armv8a: arr[1]=0xffffffffffffffff
2023-05-03 15:57:01.285 10565-10565/com.example.myapplication D/native-armv8a: -----------------------------
2023-05-03 15:57:01.285 10565-10565/com.example.myapplication D/native-armv8a: arr[0]=0xffffffffff8070ff
2023-05-03 15:57:01.285 10565-10565/com.example.myapplication D/native-armv8a: arr[1]=0xffffffffff00e0ff

四、预取内存指令

从内存中预取 (PRFM) 使代码能够向内存系统提供提示,表明来自特定地址的数据将很快被程序使用。此提示的效果是实现定义的,但通常,它会导致数据或指令被加载到其中一个缓存中。

指令语法是:

PRFM <prfop>, <addr> | label

其中 prfop 是以下选项的串联:

Type PLD 或 PST(加载或存储的预取)。

Target L1、L2 或 L3(目标缓存)。

Policy KEEP 或 STRM(保留在缓存中或流数据)。

例如,PLDL1KEEP。在 libyuv 这个库内可以看到 PRFM 指令的使用,prfm pldl1keep, [%[src_y], 448] 指示内存系统预加载 src_y + 448 地址处 Y 数据到 L1 缓存行上,448 立即数代表字节偏移量(8 的倍数)。

这些指令类似于 A32 PLD 和 PLI 指令。

PRFM(立即数)

预取内存(立即数)指令向内存系统发出信号,表明在不久的将来可能会从指定地址访问数据内存。当内存访问确实发生时,内存系统可以通过采取预期加速内存访问的操作来响应,例如将包含指定地址的缓存行预加载到一个或多个缓存中。PRFM 指令的效果是实现定义的。

在这里插入图片描述

PRFM (<prfop>|#<imm5>), [<Xn|SP>{, #<pimm>}]

<prfop> 是预取操作,定义为<type><target><policy>

<type> 是以下之一:

PLD 用于加载的预取,在Rt<4:3>字段中编码为 0b00。

PLI 用于预加载指令,在Rt<4:3>字段中编码为 0b01。

PST 用于存储的预取,在Rt<4:3>字段中编码为 0b10。

<target> 是以下之一:

L1 一级缓存,在Rt<2:1>字段中编码为 0b00。

L2 二级缓存,在Rt<2:1>字段中编码为 0b01。

L3 三级缓存,在Rt<2:1>字段中编码为 0b10。

<policy> 是以下之一:

KEEP 保留或临时预取,通常在缓存中分配。在Rt<0>字段中编码为 0。

STRM 流式或非临时预取,用于仅使用一次的数据。在Rt<0>字段中编码为 1。

<imm5> 是预取操作编码为立即数,范围为 0 到 31,编码在“Rt”字段中。此语法仅适用于无法使用 <prfop> 访问的编码。

<Xn|SP> 是通用基址寄存器或堆栈指针的 64 位名称,在“Rn”字段中编码。

<pimm> 是可选的正立即字节偏移量,在 0 到 32760 范围内是 8 的倍数,默认为 0 并在“imm12”字段中编码为 <pimm>/8

下面是使用 PRFM(立即数)指令的例子。

    long long int len = 2;

    long long int x = 0;
    long long int y = 0;

    auto *arr = new long long int[len]{0};

    LOGD("+++++++++++++++++++++++++++++");
    for (int i = 0; i < len; i++) {
        arr[i] = 0x1020304050607080 * (i + 1);
        LOGD("arr[%d]=0x%llx", i, arr[i]);
    }

    char *a = (char *) arr;

    asm volatile(
        "PRFM PLDL1KEEP, [%x[a], #0]\n"
        "LDTR %x[x], [%x[a], #0]\n"
        "LDTR %x[y], [%x[a], #8]\n"
    :[a] "+r"(a),
     [x] "+r"(x),
     [y] "+r"(y)
    :
    : "cc", "memory");

    LOGD("-----------------------------");
    LOGD("x=0x%llx y=0x%llx", x, y);

    delete[] arr;

运行结果如下:

2023-05-03 16:27:11.967 12292-12292/com.example.myapplication D/native-armv8a: +++++++++++++++++++++++++++++
2023-05-03 16:27:11.967 12292-12292/com.example.myapplication D/native-armv8a: arr[0]=0x1020304050607080
2023-05-03 16:27:11.967 12292-12292/com.example.myapplication D/native-armv8a: arr[1]=0x20406080a0c0e100
2023-05-03 16:27:11.967 12292-12292/com.example.myapplication D/native-armv8a: -----------------------------
2023-05-03 16:27:11.967 12292-12292/com.example.myapplication D/native-armv8a: x=0x1020304050607080 y=0x20406080a0c0e100

另外还有 PRFM (literal)PRFM (register) 指令,只是预取地址提供的方式有所差异。

五、非临时加载存储对指令

ARMv8 中的一个新概念是非临时加载和存储。这些是执行一对寄存器值的读取或写入的 LDNP 和 STNP 指令。它们还向内存系统提示缓存对这些数据没有用。该提示不禁止内存系统活动,例如地址缓存、预加载或收集。但是,这表明缓存不太可能提高性能。一个典型的用例可能是流数据,但请注意,有效使用这些指令需要一种特定于微体系结构的方法。

非临时加载和存储放宽了内存排序要求。在上述情况下,可能会在前面的 LDR 指令之前观察到 LDNP 指令,这可能导致从 X0 中的不确定地址读取。

例如:

LDR X0, [X3]
LDNP X2, X1, [X0] // 指令执行时可能没有加载 X0

要纠正上述问题,需要一个明确的加载屏障:

LDR X0, [X3]
DMB nshld
LDNP X2, X1, [X0]

5.1 LDNP

Load Pair of Registers,带有非临时提示,根据基址寄存器值和立即偏移量计算地址,从内存中加载两个 32 位字或两个 64 位双字,并将它们写入两个寄存器。

在这里插入图片描述

32-bit (opc == 00)

LDNP <Wt1>, <Wt2>, [<Xn|SP>{, #<imm>}]

64-bit (opc == 10)

LDNP <Xt1>, <Xt2>, [<Xn|SP>{, #<imm>}]

<Wt1> 是要传输的第一个通用寄存器的 32 位名称,在“Rt”字段中编码。

<Wt2> 是要传输的第二个通用寄存器的 32 位名称,在“Rt2”字段中编码。

<Xt1> 是要传输的第一个通用寄存器的 64 位名称,在“Rt”字段中编码。

<Xt2> 是要传输的第二个通用寄存器的 64 位名称,在“Rt2”字段中编码。

<Xn|SP> 是通用基址寄存器或堆栈指针的 64 位名称,在“Rn”字段中编码。

<imm> 是可选的有符号立即字节偏移量。对于 32 位变体:在 -256 到 252 范围内是 4 的倍数,默认为 0 并在“imm7”字段中编码为 <imm>/4。对于 64 位变体:是 -512 到 504 范围内的 8 的倍数,默认为 0 并在“imm7”字段中编码为 <imm>/8

下面是使用 LDNP 指令的例子。

    long long int len = 2;

    long long int x = 0;
    long long int y = 0;
    auto *addr = new long long int[1]{0};
    auto *arr = new long long int[len]{0};

    LOGD("+++++++++++++++++++++++++++++");
    for (int i = 0; i < len; i++) {
        arr[i] = 0x1020304050607080 * (i + 1);
        LOGD("arr[%d]=0x%llx", i, arr[i]);
    }

    char *a = (char *) arr;
    char *z = (char *) addr;

    asm(
        "STR %x[a], [%x[z]]\n"
        "LDR X0, [%x[z]]\n"
        "LDNP %x[x], %x[y], [X0, #0]\n"
    :[a] "+r"(a),
     [x] "+r"(x),
     [y] "+r"(y),
     [z] "+r"(z)
    :
    : "cc", "memory");

    LOGD("-----------------------------");
    LOGD("x=0x%llx y=0x%llx addr[0]=0x%llx", x, y, addr[0]);

    delete[] addr;
    delete[] arr;

例子中可能会在前面的 LDR 指令之前观察到 LDNP 指令,这可能导致从 X0 中的不确定地址读取。内联汇编去掉了 volatile 关键字,表示编译器可以优化,但目前在笔者环境运行还是正常的,没有出现加载错乱的情况发生。

运行结果如下:

2023-05-03 17:31:32.246 13498-13498/com.example.myapplication D/native-armv8a: +++++++++++++++++++++++++++++
2023-05-03 17:31:32.246 13498-13498/com.example.myapplication D/native-armv8a: arr[0]=0x1020304050607080
2023-05-03 17:31:32.246 13498-13498/com.example.myapplication D/native-armv8a: arr[1]=0x20406080a0c0e100
2023-05-03 17:31:32.246 13498-13498/com.example.myapplication D/native-armv8a: -----------------------------
2023-05-03 17:31:32.246 13498-13498/com.example.myapplication D/native-armv8a: x=0x1020304050607080 y=0x20406080a0c0e100 addr[0]=0xb400007356acf240

5.2 STNP

Store Pair of Registers,带非临时提示,根据基址寄存器值和立即偏移量计算地址,并将两个 32 位字或两个 64 位双字从两个寄存器存储到计算出的地址。

在这里插入图片描述

32-bit (opc == 00)

STNP <Wt1>, <Wt2>, [<Xn|SP>{, #<imm>}]

64-bit (opc == 10)

STNP <Xt1>, <Xt2>, [<Xn|SP>{, #<imm>}]

<Wt1> 是要传输的第一个通用寄存器的 32 位名称,在“Rt”字段中编码。

<Wt2> 是要传输的第二个通用寄存器的 32 位名称,在“Rt2”字段中编码。

<Xt1> 是要传输的第一个通用寄存器的 64 位名称,在“Rt”字段中编码。

<Xt2> 是要传输的第二个通用寄存器的 64 位名称,在“Rt2”字段中编码。

<Xn|SP> 是通用基址寄存器或堆栈指针的 64 位名称,在“Rn”字段中编码。

<imm> 是可选的有符号立即字节偏移量。对于 32 位变体:在 -256 到 252 范围内是 4 的倍数,默认为 0 并在“imm7”字段中编码为 <imm>/4。对于 64 位变体:是 -512 到 504 范围内的 8 的倍数,默认为 0 并在“imm7”字段中编码为 <imm>/8

六、内存屏障和栅栏指令

ARMv7 和 ARMv8 都支持不同的屏障操作。

  • 数据内存屏障(DMB)。这会强制所有程序顺序较早的内存访问在任何后续访问之前变得全局可见。

  • 数据同步屏障(DSB)。所有挂起的加载和存储、高速缓存维护指令和所有 TLB 维护指令都在程序继续执行之前完成。DSB 的行为类似于 DMB,但具有其他属性。

  • 指令同步屏障(ISB)。该指令刷新 CPU 流水线和预取缓冲区,导致 ISB 之后的指令从缓存或内存中获取(或重新获取)。

ARMv8 引入了与发布一致性模型相关联的单边栅栏。这些称为加载-获取(LDAR)和存储-发布(STLR),并且是基于地址的同步原语。这两个操作可以配对以形成完整的栅栏。这些指令仅支持基址寄存器寻址,不提供偏移量或其他类型的索引寻址。

七、同步原语

ARMv7-A 和 ARMv8-A 架构都支持独占内存访问。在 A64 中,这是加载/存储独占(LDXR/STXR)对。

LDXR 指令从内存地址加载一个值,并尝试静默地声明对该地址的独占锁。只有在成功获得并持有锁的情况下,Store-Exclusive 指令才会将新值写入该位置。 LDXR/STXR 配对用于构造标准同步原语,例如自旋锁。 提供了一组成对的 LDXRP 和 STXRP 指令,以允许代码自动更新跨越两个寄存器的位置。字节、半字、字和双字选项可用。与加载获取/存储释放配对一样,仅支持基址寄存器寻址,不支持任何偏移量。

CLREX 指令清除监视器,但与 ARMv7 不同,异常进入或返回也会清除监视器。监视器也可能被虚假清除,例如缓存逐出或其他与应用程序不直接相关的原因。软件必须避免在配对的 LDXR 和 STXR 指令之间有任何显式内存访问、系统控制寄存器更新或缓存维护指令。

还有一对专用的加载获取/存储释放指令,称为 LDAXR 和 STLXR。

参考资料

1.《ARMv8-A-Programmer-Guide》
2.《Arm® A64 Instruction Set Architecture Armv8, for Armv8-A architecture profile》

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/520667.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

k8基础知识

总述 在构成扁平化网络的基础上实现Pod编排&#xff08;控制、管理&#xff09;、调度&#xff0c;再构成服务&#xff1b;对服务的管理有所欠缺&#xff1b;可以说k8s重点解决资源的问题 服务管理、应用管理&#xff1b;istio重点解决服务的问题 功能 开源、动态伸缩、负载…

爱奇艺2020校招Java方向笔试题(第一场)

1.计算下列程序的时间复杂度&#xff08;&#xff09; for (i1;i<n;i)for(j1;j<m;j){a1,a2,a3,a4};A.O(n) B.O(mn) C.O(m) D.O(1) 答案:B 2.求递归方程T(n)4T(n/2)n 的解 ( ) A.O(n) B.O(n^2) C.O(n^3) D.O(logn) 答案:B 用Mater公式计算 3.下列关于动态规划算法说法错…

RCWA包:光学模拟的强大工具——用于计算光子 晶体R/T 光谱 的严格耦合波分析

目录 RCWA包&#xff1a;光学模拟的强大工具 RCWA包的功能 如何开始使用RCWA包 RCWA包的特性 RCWA包的应用示例 示例代码 基本的光学概念介绍。 1. 反射率、透射率和散射率 2. 衍射和衍射光栅 3. 光子晶体 4. 布拉格镜 5. 垂直腔面发射激光器&#xff08;VCSEL&…

芒果改进YOLOv8系列:改进特征融合网络 BiFPN 结构,融合更多有效特征

芒果改进YOLOv8系列:改进特征融合网络 BiFPN 结构,融合更多有效特征 在这篇文章中,将 BiFPN 思想加入到 YOLOv8 结构中 该版本为高效简洁版,涨点多、还速度快(实际效果反馈) 本篇博客 不占用 高阶专栏的总篇数计划中 文章目录 一、BiFPN 论文理论二、效果反馈(涨点)…

华为OD机试真题 Java 实现【任务总执行时长】【2023Q1 100分】

一、题目描述 任务编排服务负责对任务进行组合调度。 参与编排的任务有两种类型&#xff0c;其中一种执行时长为taskA&#xff0c;另一种执行时长为taskB。 任务一旦开始执行不能被打断&#xff0c;且任务可连续执行。服务每次可以编排num个任务。请编写一个方法&#xff0c…

Git常用操作:基础命令、生成公钥、webhook同步

这里写目录标题 下载安装GIT基础命令克隆初始化与仓库建立连接下拉代码提交代码清空本地缓存 本地生成GIT公钥webhook&#xff08;本地-码云-服务器代码同步&#xff09; 下载安装GIT 此处只介绍windows系统下的安装&#xff0c;linux一般都是自带git&#xff08;自行百度&…

linux跑python控制台输出日志无内容或者断掉

问题描述 训练时候的输出日志要么没有&#xff0c;要么就是输出了一点点就没有了&#xff08;突然不输出内容了&#xff09;&#xff0c;记得之前也出现过训练中途突然日志不打印了&#xff0c;当时以为服务器原因可能被挤掉了。但这次是还没开始并且试过很多次了。 所用语句…

85. `if __name__ == “__main__“`的作用和原理(适合小白)

85. if __name__ "__main__"的作用和原理&#xff08;适合小白&#xff09; 文章目录 85. if __name__ "__main__"的作用和原理&#xff08;适合小白&#xff09;1. 代码体验2. __name__的作用2.1 模块知识回顾2.2 __name__的作用 3. if __name__ "…

水果手机SJ流程

注册教程 第一步:注册新邮箱。 首先&#xff0c;你需要一个邮箱账号&#xff0c;国内126.QQ.163等都可以&#xff0c;只要你没有用它注册过AppleID。 第二步:注册。 复制链接到浏览器&#xff0c;打开进入appleID注册界面:https://appleid.apple.com/account#&#xff01;&…

采用UWB(超宽频)技术开发的java版智慧工厂定位系统源码

室内定位系统源码&#xff0c;采用UWB定位技术开发的智慧工厂定位系统源码 技术架构&#xff1a;单体服务 硬件&#xff08;UWB定位基站、卡牌&#xff09; 开发语言&#xff1a;java 开发工具&#xff1a;idea 、VS Code 前端框架&#xff1a;vue 后端框架&#xff1a;s…

Linux内核(十七)Input 子系统详解 IV —— 配对的input设备与input事件处理器 input_register_handle

文章目录 input_handle结构体详解配对的input设备与input事件处理器实例input核心层对驱动层和事件层之间的框架建立流程图 本文章中与input子系统相关的结构体可参考input子系统结构体解析 input函数路径&#xff1a;drivers/input/input.c input_handle结构体详解 input_ha…

算法之美:探究左右元素和的差值

本篇博客会讲解力扣“2574. 左右元素和的差值”的解题思路&#xff0c;这是题目链接。 先来审题&#xff1a; 以下是输出示例&#xff1a; 以下是提示&#xff1a; 本题的关键在于&#xff0c;“左和”和“右和”是如何变化的。下面我通过代码来演示。 一上来&#xff0c;…

唐朔飞计算机组成原理常见的寄存器(时不时就更新)

EINT中断允许触发器 如果是1代表开中断就是可以中断 如果0表示关中断 不能触发中断程序&#xff0c;1.响应中断的条件是当设备有请求并且 中断允许触发器为1的时候 时间是指令执行周期结束的时候。2.实现多重中断的条件是 中断允许触发器置为1并且 有更高的服务程序才能除法多重…

【数据结构】- 几个步骤教你认识并实现一个链表之带头(哨兵位)双向循环链表(上)

文章目录 前言&#x1f31f;一、带头双向循环链表&#x1f31f;二、带头双向循环链表结构图&#xff1a;&#x1f31f;三、带头双向循环链表代码实现&#xff1a;&#x1f30f;3.1头插&#xff1a;&#x1f4ab;3.1.1头插流程图&#x1f4ab;3.1.2 相较于无头单向非循环链表&am…

高边输出和低边输出

文章目录 高边和低边高边驱动&#xff08;HSD&#xff0c;High Side Drive&#xff09;和低边驱动&#xff08;LSD&#xff0c;Low Side Drive&#xff09;应用示例举例高/低边驱动芯片 按照开关闭合时开关输出的电压高低&#xff0c;可以分为高边输出和低边输出开关。 高边和低…

ubuntu 20.04 通过 sshfs 共享文件夹到 windows

功能需求 最近需要 让 ubuntu 共享文件夹&#xff0c;windows 来映射成磁盘或者共享目录的方式使用 windows 的文件夹可以通过 VMware Workstation Pro 虚拟机【共享文件夹】设置&#xff0c;轻松的实现 文件共享&#xff0c;不过这里的共享是 单向的&#xff0c;也就是 文件夹…

Windows服务器加固

文章目录 一、 账户安全要求二、 权限安全要求三、 通用安全管理四、 日志审计安全五、网络协议安全 一、 账户安全要求 1、 账户错误登录锁定 次数&#xff08;8&#xff09; 开始->运行->输入“gpedit.msc”打开本地组策略编辑器&#xff0c;浏览 路径&#xff1a;“…

快来学习SQL数据库,MySQL的保姆级安装,MySQL的入门使用以及DBeaver软件的安装

SQL数据库 无处不在的SQL&#xff0c;不管是何种开发语言&#xff0c;亦或是何种开发方向&#xff0c;SQL都是开发人员无法绕开的话题。除了一门趁手的编程语言外&#xff0c;SQL语言也是开发人员人人必备的开发技能。 数据库有三个层级&#xff1a;库——表——数据 市面上的…

路径规划算法:基于斑点鬣狗优化的路径规划算法- 附代码

路径规划算法&#xff1a;基于斑点鬣狗优化的路径规划算法- 附代码 文章目录 路径规划算法&#xff1a;基于斑点鬣狗优化的路径规划算法- 附代码1.算法原理1.1 环境设定1.2 约束条件1.3 适应度函数 2.算法结果3.MATLAB代码4.参考文献 摘要&#xff1a;本文主要介绍利用智能优化…

第五十八章 Unity 发布PC平台

本章节我们介绍一些如何打包游戏到PC平台&#xff0c;这里重点介绍如何制作Windows操作系统下的游戏包。首先&#xff0c;我们创建一个“PcDemo”工程&#xff0c;然后简单布置一下场景内容&#xff0c;如下 想要打包发布Unity项目&#xff0c;我们可以在菜单栏选择“File”→ …