简介:
我们之前使用内嵌汇编基于GCC编写了memcpy 函数,标准库里面的memcpy 函数是如何实现的呢,以下是GCC 工具链中memcpy 函数对应的汇编代码。
lib_a-memcpy.o: file format elf32-littleriscv Disassembly of section .text.memcpy: 00000000 <memcpy>: 0: 00a5c7b3 xor a5, a1, a0 4: 8b8d andi a5, a5, 3 6: 00c50733 add a4, a0, a2 a: e781 bnez a5, 12 <.L2> c: 478d li a5, 3 e: 00c7ee63 bltu a5, a2, 2a <.L3> 00000012 <.L2>: 12: 87aa mv a5, a0 14: 0ae57063 bgeu a0, a4, b4 <.L19> 00000018 <.L5>: 18: 0005c683 lbu a3, 0(a1) 0000001c <.LVL3>: 1c: 0785 addi a5, a5, 1 1e: 0585 addi a1, a1, 1 20: fed78fa3 sb a3, -1(a5) 00000024 <.LBE2>: 24: fee7eae3 bltu a5, a4, 18 <.L5> 28: 8082 ret 0000002a <.L3>: 2a: 00357693 andi a3, a0, 3 2e: 87aa mv a5, a0 00000030 <.L20>: 30: ca91 beqz a3, 44 <.L7> 00000032 <.LBB3>: 32: 0005c683 lbu a3, 0(a1) 00000036 <.LVL8>: 36: 0785 addi a5, a5, 1 38: 0585 addi a1, a1, 1 3a: fed78fa3 sb a3, -1(a5) 0000003e <.LBE3>: 3e: 0037f693 andi a3, a5, 3 00000042 <.LVL11>: 42: b7fd j 30 <.L20> 00000044 <.L7>: 44: ffc77693 andi a3, a4, -4 00000048 <.LVL13>: 48: fe068613 addi a2, a3, -32 0000004c <.L21>: 4c: 06c7f063 bgeu a5, a2, ac <.L11> 00000050 <.LBB4>: 50: 0005a383 lw t2, 0(a1) 00000054 <.LVL16>: 54: 0045a283 lw t0, 4(a1) 00000058 <.LVL17>: 58: 0085af83 lw t6, 8(a1) 0000005c <.LVL18>: 5c: 00c5af03 lw t5, 12(a1) 00000060 <.LVL19>: 60: 0105ae83 lw t4, 16(a1) 00000064 <.LVL20>: 64: 0145ae03 lw t3, 20(a1) 00000068 <.LVL21>: 68: 0185a303 lw t1, 24(a1) 0000006c <.LVL22>: 6c: 01c5a883 lw a7, 28(a1) 00000070 <.LVL23>: 70: 02458593 addi a1, a1, 36 00000074 <.LVL24>: 74: 0077a023 sw t2, 0(a5) 78: ffc5a803 lw a6, -4(a1) 0000007c <.LVL25>: 7c: 0057a223 sw t0, 4(a5) 00000080 <.LVL26>: 80: 01f7a423 sw t6, 8(a5) 00000084 <.LVL27>: 84: 01e7a623 sw t5, 12(a5) 00000088 <.LVL28>: 88: 01d7a823 sw t4, 16(a5) 0000008c <.LVL29>: 8c: 01c7aa23 sw t3, 20(a5) 00000090 <.LVL30>: 90: 0067ac23 sw t1, 24(a5) 00000094 <.LVL31>: 94: 0117ae23 sw a7, 28(a5) 98: 02478793 addi a5, a5, 36 0000009c <.LVL32>: 9c: ff07ae23 sw a6, -4(a5) a0: b775 j 4c <.L21> 000000a2 <.L12>: a2: 4190 lw a2, 0(a1) 000000a4 <.LVL34>: a4: 0791 addi a5, a5, 4 a6: 0591 addi a1, a1, 4 a8: fec7ae23 sw a2, -4(a5) 000000ac <.L11>: ac: fed7ebe3 bltu a5, a3, a2 <.L12> 000000b0 <.LVL38>: b0: f6e7e4e3 bltu a5, a4, 18 <.L5> 000000b4 <.L19>: b4: 8082 ret
上述汇编代码涉及的汇编指令并不多只是存储、加载 跳转指令,对上述代码的流程添加了注释,对应说明如下
memcpy: # void *memcpy(void *destin, void *source, unsigned n) xor a5, a1, a0 #判断src 和 dst 地址的低两位地址是否相同,如果相同则地址为拷贝源地址和目的地址是对齐的 andi a5, a5, 3 add a4, a0, a2 #a4 保存拷贝数据的结束地址 bnez a5, .L2 #a5 != 0 跳转至L2 按照地址非同步对齐方式访问 li a5, 3 # 地址对齐时更新a5 = 3 bltu a5, a2, .L3 # a5(3) < a2(len) len > 3 跳转至L3 .L2: # src 和 dst 地址非对齐 及 len <= 3 会运行至此 mv a5, a0 bgeu a0, a4, .L19 # a0 > a4 函数结束,a4 = a0 + len,该条件成立说明拷贝地址发生了无符号的overflow事件 .L5: lbu a3, 0(a1) # a3 作为拷贝中间寄存器,将src[0] 数据加载到a3 .LVL3: addi a5, a5, 1 # destin++ addi a1, a1, 1 # src++ sb a3, -1(a5) # 将a3数据写入destin .LBE2: bltu a5, a4, .L5 # a5 < a4 跳转到 L5 继续拷贝一个字节,否则结束拷贝 ret .L3: # 拷贝的长度大于等于4,并且src 和 destin 地址对齐时会跳转到此处 andi a3, a0, 3 # 获取destin 地址的低两位地址保存至a3 mv a5, a0 # a5 = 获取destin .L20: beqz a3, .L7 # destin 和 src 地址是4字节对齐,跳转至 L7 处理 .LBB3: lbu a3, 0(a1) # 读取src[] 至 a3 .LVL8: addi a5, a5, 1 # destin++ addi a1, a1, 1 # src++ sb a3, -1(a5) # dest[] = a3 .LBE3: andi a3, a5, 3 # 更新 destin 地址的低两位地址保存至a3 .LVL11: j .L20 # 跳转至L20,处理非四字节对齐部分的拷贝 .L7: andi a3, a4, -4 # 将destin + len 地址向下4字节对齐保存至a3 .LVL13: addi a2, a3, -32 # a2 = a3 - 32,作为循环结束条件判断 .L21: bgeu a5, a2, .L11 #a5 > a2 说明要拷贝的字节数小于32 跳转到L11 处理 .LBB4: lw t2, 0(a1) # 拷贝32字节 .LVL16: lw t0, 4(a1) .LVL17: lw t6, 8(a1) .LVL18: lw t5, 12(a1) .LVL19: lw t4, 16(a1) .LVL20: lw t3, 20(a1) .LVL21: lw t1, 24(a1) .LVL22: lw a7, 28(a1) .LVL23: addi a1, a1, 36 .LVL24: sw t2, 0(a5) lw a6, -4(a1) .LVL25: sw t0, 4(a5) .LVL26: sw t6, 8(a5) .LVL27: sw t5, 12(a5) .LVL28: sw t4, 16(a5) .LVL29: sw t3, 20(a5) .LVL30: sw t1, 24(a5) .LVL31: sw a7, 28(a5) .LVL32: addi a5, a5, 36 .LVL33: sw a6, -4(a5) j .L21 .L12: #按照4字节为代为进行拷贝 lw a2, 0(a1) .LVL34: addi a5, a5, 4 addi a1, a1, 4 sw a2, -4(a5) .L11: bltu a5, a3, .L12 .LVL38: # 拷贝剩余非四字节对齐的数据,按照字节拷贝 bltu a5, a4, .L5 .L19: ret
对应汇编代码的实现流程如下:
1. 如果src 及 destin 的地址是非对齐,或者拷贝数据 < 4 字节 则按照字节进行拷贝,拷贝结束返回
==》 因为地址是非对齐的,以src 地址为1 dest 地址为2 为例,这两个地址同时加一,永远不会两地址同时为4的倍数,使用sl/sb 字节就进行拷贝,避免处理器使用lw/sw地址非4字节对齐访问出发的硬件异常
2. 如果 src 及 destin 的地址是对齐 并且拷贝的长度大于4,先按照字节拷贝非4字节对齐部分。
3.按照字节拷贝完非四字对齐部分后,剩余长度> 32 则按照32字节为单位进行拷贝。
==>这么拷贝都是使用LW/SW指令进行拷贝和4字节拷贝使用的指令是一致的,连续拷贝8次,这么拷贝的目的代码会减少判断拷贝快结束的次数提高代码执行效率
4.拷贝完32字节后,如果剩余长度 >4 ,剩余部分如果大于四字节按照4字节为单位进行拷贝
5.按照四字节拷贝后剩余长度> 0,按照字节为单位拷贝剩余内容
以下以destin = 0x100100c1 ,src = 0x10010001, 拷贝64 字节为例说明整个拷贝流程
1. 按照1字节为单位拷贝0x10010001~0x10010003 3字节至0x100100c1 ~ 0x100100c3 地址,处理后destin 和 src 地址已经按照四字节对齐
2. 按照32字节为单位拷贝0x10010004 ~ 0x10010023 至 0x100100C4~ 0x100100E3 空间,拷贝32字节数据
3.按照4字节为单位拷贝28字节数据
3.按照1字节为单位拷贝1字节数据
整个复制过程分为四段进行拷贝,从中可以看出库函数实现上充考虑了地址对齐访问的场景,同时最多拷贝32字节,以数据块的方式来拷贝减少判断次数提高代码的执行效率。