曲径通幽论坛

标题: 内存复制 | __memcpy() [打印本页]

作者: beyes    时间: 2011-3-10 14:05
标题: 内存复制 | __memcpy()
内核:2.6.24

__memcpy() 是内存复制函数,它在 asm-x86/string_32.h 中定义,用汇编语言实现,代码如下:
static __always_inline void * __memcpy(void * to, const void * from, size_t n)
{
int d0, d1, d2;
__asm__ __volatile__(
    "rep ; movsl\n\t"
    "movl %4,%%ecx\n\t"
    "andl $3,%%ecx\n\t"
    "jz 1f\n\t"
    "rep ; movsb\n\t"
    "1:"
    : "=&c" (d0), "=&D" (d1), "=&S" (d2)
    : "0" (n/4), "g" (n), "1" ((long) to), "2" ((long) from)
    : "memory");
return (to);
}


内存复制,一般使将 ESI 指向的内存区里的数据复制到 EDI 指向的内存区里,复制时使用 REP 指令前缀 MOV 数据到目的地 (rep ; movsb)并以 ECX 用作循环计数器(直到 ECX 为 0)进行复制。


ESI 寄存器的设置
"=&S" (d2) 表示 d2 这个局部变量(用占位符表示是 %2)使用 ESI 寄存器;而  "2" ((long) from 表示 from 源地址同样使用 ESI ("2" 表示强制使用和 d2 一样的寄存器)。另外,d2 局部变量前面的 & 约束符说明了在输入部分中,它所使用的寄存器会被修改,这里就是被 from 参数使用了。


EDI 寄存器的设置
和 ESI 的设置同理, to 参数被强制和 d1 局部变量使用同样的寄存器 EDI 。


循环计数器的设置
循环计数器放在 ECX 中,"=&c" (d0) 表示 d0 这个局部变量使用 ECX 寄存器,而 所以 "0" (n/4) 表示 n/4 (以 4 为 模)  被强制使用 ECX 寄存器来装载。


复制字节数设置
复制字节数参数 n 存放在通用寄存器或者内存中 (g 约束符),n 的占位符为 4% 。


设置完相关的变量和参数后,回到汇编语句中:

rep ; movsl 表示每次从 ESI 指向的内存区复制 4 个字节到 EDI 指向的内存区,知道 ECX 为 0 时结束(如果 ECX 中的字节数不为 4 个整数倍时,那么就循环 n/4 次)。
movl %4,%%ecx andl $3,%%ecx 表示是不是还有剩余字节还没复制(小于 4 个字节),没有则直接退出,有则 rep; mosb 复制完它。


问题一
3 个局部变量 d0, d1, d2 好像没什么用? 而且 ECX, EDI, ESI 都被使用了,如果在本函数之前也被使用了,这里难道不是被破坏了吗?那为什么不在内嵌指令的破坏部分(第 3 个冒号后)添加这几个寄存器呢?


答案是 d0, d1, d2 这 3 个寄存器正是用来恢复 ECX, EDI, ESI 的!因为 "0" , "1" , "2" 强制 (n/4) ,to, from 也使用了 ECX, EDI, ESI 寄存器,所以如果在调用本函数之前,ECX, EDI, ESI 这 3 个函数是被使用中的话,那么 gcc 会插入汇编指令,保存这  3 个寄存器的内容到 d0, d1, d2 中,这样在代码的最后,也就是输出部分,gcc 同样会插入指令来恢复它们。


问题二
这里的 memory 表示的是什么?


memcry 表示在这些汇编指令中,会对内存修改,而这个修改可能会破坏了之前的某些变量存储,通过此约束告知编译器要对相关变量的保护。比如,在调用 __memory() 函数的代码中,EAX 被分配给某个变量 var, 而 var 也正好位于 to 指向的内存块中。在调用__memcpy() 之后,to 指向的内存块会被破坏,所以我们要用 memcpy 来告知 gcc 说内存被修改了,这样 gcc 会插入指令保护变量 var,在 __memcpy() 执行完后重新刷新寄存器的分配关系,新 var 重新加载回 EAX 寄存器中。




欢迎光临 曲径通幽论坛 (http://www.groad.net/bbs/) Powered by Discuz! X3.2