glibc--memcpy源码分析

来源:互联网 发布:淘宝客会推新店铺吗 编辑:程序博客网 时间:2024/05/21 04:39

以下是glibc-2.10.1中memcpy函数的源码

#include <string.h>#include <memcopy.h>#include <pagecopy.h>#undef memcpyvoid *memcpy (dstpp, srcpp, len)     void *dstpp;     const void *srcpp;     size_t len;{  unsigned long int dstp = (long int) dstpp;  unsigned long int srcp = (long int) srcpp;  /* Copy from the beginning to the end.  */  /* If there not too few bytes to copy, use word copy.  */  if (len >= OP_T_THRES)    {      /* Copy just a few bytes to make DSTP aligned.  */      len -= (-dstp) % OPSIZ;      BYTE_COPY_FWD (dstp, srcp, (-dstp) % OPSIZ);      /* Copy whole pages from SRCP to DSTP by virtual address manipulation, as much as possible.  */      PAGE_COPY_FWD_MAYBE (dstp, srcp, len, len);      /* Copy from SRCP to DSTP taking advantage of the known alignment of DSTP.  Number of bytes remaining is put in the third argument, i.e. in LEN.  This number may vary from machine to machine.  */      WORD_COPY_FWD (dstp, srcp, len, len);      /* Fall out and copy the tail.  */    }  /* There are just a few bytes to copy.  Use byte memory operations.  */  BYTE_COPY_FWD (dstp, srcp, len);  return dstpp;}libc_hidden_builtin_def (memcpy)


现在让我们来一步步分析源码。

首先要明确一点的是glibc中的库函数都是经过反复优化过的,所以memcpy的实现不可能是通过one byte by one byte实现的,如果真是那样,肯定会笑掉大牙的^-^。

其次我们还应该考虑到字节对齐问题,因为在大多数平台下,从内存对齐边界开始拷贝会有许多的优化方案可以使用,glibc中memcpy正是利用了这一点!

1. 判断需要拷贝的字节数是否大于某一临界值。如果大于临界值,则可以使用更加强大的优化手段进行拷贝;否则,直接按照一般的方法,即one byte by one byte来拷贝,

  /* There are just a few bytes to copy.  Use byte memory operations.  */  BYTE_COPY_FWD (dstp, srcp, len);

if判断中的OP_T_THRES就是我们所说的临界值,根据不同平台情况,OP_T_THRES的值是16或者8。

2.假设要拷贝的目的地址如下所示:

                                              

其中start为拷贝目的地的起始地址 ,end为拷贝目的地的结束地址,align border为内存中的对齐边界。

现在要计算startalign border的距离,此处使用了一个非常聪明的小技巧。

      /* Copy just a few bytes to make DSTP aligned.  */      len -= (-dstp) % OPSIZ;      BYTE_COPY_FWD (dstp, srcp, (-dstp) % OPSIZ);

是不是很奇怪,为什么(-dstp) % OPSIZ就是start到align border的距离?dstp是unsigned long int型的数据,现在前面加了一个负号,会发什么有趣的事情呢?
我们假设n是unsigned long int数据类型,值为9,十六进制为0x00000009,那么负n的十六进制为0xfffffff7,但是,因为unsigned long int类型是不可能出现负值的,所以此时会把0xfffffff7当作一个无符号数,这是一个很大的数值了。所以(-n) % 3就相当于这个很大的数与3相余。

3.对于特殊平台,可以使用page copy的方法。由于限制条件太多,一般x86平台下不会使用。

4.使用word copy的方法进行one word by one word进行拷贝,此处是memcpy的优化关键,优化的条件是拷贝地址处于对齐边界。

5.剩余的不能采用word copy的尾部使用one byte by one byte进行拷贝。

现在来分析实现内存拷贝的宏。

以下是memcopy.h中的代码

#define BYTE_COPY_FWD(dst_bp, src_bp, nbytes)                                 \  do {                                                                        \    int __d0;                                                                 \    asm volatile(/* Clear the direction flag, so copying goes forward.  */    \                 "cld\n"                                                      \                 /* Copy bytes.  */                                           \                 "rep\n"                                                      \                 "movsb" :                                                    \                 "=D" (dst_bp), "=S" (src_bp), "=c" (__d0) :                  \                 "0" (dst_bp), "1" (src_bp), "2" (nbytes) :                   \                 "memory");                                                   \  } while (0)

利用x86的movsb指令实现字节拷贝。首先使用cld指令将DF标志清零(如果DF标志被清零,那么每条movs指令执行之后ESI和EDI寄存器中的数值就会递增。如果DF标志通过STD指令被设置,那么每条movs指令执行之后ESI和EDI寄存器中的数值就会递减。) 接着使用"rep movsb"指令实现one byte by one byte的拷贝。

#define WORD_COPY_FWD(dst_bp, src_bp, nbytes_left, nbytes)                    \  do                                                                          \    {                                                                         \      int __d0;                                                               \      asm volatile(/* Clear the direction flag, so copying goes forward.  */  \                   "cld\n"                                                    \                   /* Copy longwords.  */                                     \                   "rep\n"                                                    \                   "movsl" :                                                  \                   "=D" (dst_bp), "=S" (src_bp), "=c" (__d0) :                \                   "0" (dst_bp), "1" (src_bp), "2" ((nbytes) / 4) :           \                   "memory");                                                 \      (nbytes_left) = (nbytes) % 4;                                           \    } while (0)
利用x86的movsl指令实现四字节拷贝。如果movsl和movsb花费相同的cpu时钟周期,那优化后的拷贝时间是原来的四分之一,相当可观啊^_^


 参考链接: 

《大并发服务器内存转换的灵活运用,memcpy的思考》

《iOS 常见 Crash 及解决方案》

《strcpy以及memcpy的实现》

《memcpy Linux内核实现引发的思考:为什么嵌入式汇编中不用指定段寄存器》

《我的memcpy还是不如标准库的厉害》


原创粉丝点击