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

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

最近买了王爽的汇编语言和Linux内核完全注释,准备开始好好学习一下汇编语言,并看看早期的Linux(0.11版本)源代码实现。

之前舍友面试TX是被问过memcpy什么时候不能用,这种问题如何解决?

答:当destsrc都指向同一个数组且dest>src,那么当n大于absdest > src,则这个时候最后m=n - abs(dest > src))个字节会被覆盖。可以用memmove来规避这种问题,因为memmove有对destsrc大小进行判断,根据不同的结果进行升序拷贝(dest < src)和逆序拷贝(dest >= src)。

memcpy Linux0.11版本)源代码实现如下:

extern inline void * memcpy(void * dest, const void * src, int n)

{

__asm__ ("cld\n\t"

 "rep\n\t"

 "movsb"

 ::"c"(n),"S"(src),"D"(dest)

 :"cx","si","di");

return dest;

}

我们可以发现memcpy是从源地址直接到目的地址的逐字节的升序拷贝。因为是逐字节的升序拷贝,所以但拷贝的指针是指向同一个数值时可能会出现问题。

让我们再来看一下memmoveLinux0.11版本)源代码实现如下:

extern inline void * memmove(void * dest, const void * src, int n)

{

if (dest < src)

{

__asm__ ("cld\n\t"

 "rep\n\t"

 "movsb"

 ::"c"(n),"S"(src),"D"(dest)

 :"cx","si","di");

}

else

{

__asm__ ("std\n\t"

 "rep\n\t"

 "movsb"

 ::"c"(n),"S"(src + n - 1),"D"(dest + n - 1)

 :"cx","si","di");

}

return dest;

}

我们发现memmove并没有判断destsrc是否指向同一数组(实际上也无法判断),而是判断destsrc之间的大小关系,并根据大小比较结果采取不同的拷贝策略,当dest小于src采用升序拷贝,否则采用逆序拷贝。

到这里思考就结束了?其实并没有,我们可以看看一下这两个实现对应的汇编代码。这里我只看了memcpy的汇编代码,下面我截取了memcpy 对应的汇编代码的实现(gcc -S test.c来获取test.c对应的汇编代码):

 5 memcpy:

     6 pushl %ebp

     7 movl %esp, %ebp

     8 pushl %edi

     9 pushl %esi

    10 pushl %ebx

    11 movl 16(%ebp), %eax

    12 movl 12(%ebp), %edx

    13 movl 8(%ebp), %ebx

    14 movl %eax, %ecx

    15 movl %edx, %esi

    16 movl %ebx, %edi

    17 #APP

    18 # 5 "t.c" 1

    19 cld

    20 rep movsb

    21 # 0 "" 2

    22 #NO_APP

    23 movl 8(%ebp), %eax

    24 popl %ebx

    25 popl %esi

    26 popl %edi

    27 popl %ebp

    28 ret

最为关键的几行汇编代码如下:

   14 movl %eax, %ecx

    15 movl %edx, %esi

    16 movl %ebx, %edi

    17 #APP

    18 # 5 "t.c" 1

    19 cld

    20 rep movsb

17,18行为gcc嵌入的注释,可以无视,学过8086汇编的人都知道对内存地址的访问需要知道段地址和偏移地址,可是对应memcpy的汇编代码并没有显示地对esds这连个寄存器进行赋值,刚开始以为段地址是在调用memcpy前调用,为了证实这个猜想写了个demo程序,在main函数中调用memcpy函数,并用gcc把源代码转换成汇编代码,查看了源代码发现,在调用memcpy前并没有设置esds这两个段寄存器。后面我陷入了困惑,问题一直没有解开。

后面在看《深入理解计算机系统书》的第三章时,发现了这样的描述“最初的8086的存储器模型和它在80286中的扩展都已经过时了,作为替代,Linux使用了平面寻址方式(flat addressing),在这种寻址方式中,程序员将整个存储空间看做一个大的字节数组”,心想难道段寄存器不用设置了,也不用段寄存器了?

在好奇心的驱动下搜索了“平面寻址 不需要段地址?”看了第一篇文章(http://www.cnblogs.com/awpatp/archive/2009/11/03/1595380.html),最后的几行描述是“Windows操作系统为用户程序安排好了一切。具体表现在为用户程序的代码段、数据段和堆栈段全部预定义好了段描述符。这些段的起始地址为0,限长为ffffffff,所以用它们可以直接寻址全部的4 GB地址空间。程序开始执行的时候,CSDSESSS都已经指向了正确的描述符,在整个程序的生命周期内,程序员不必改动这些段寄存器,也不必关心它们的值究竟是多少(实际上,想改也改不了)。 

答案终于揭晓:“程序开始执行的时候,CSDSESSS都已经指向了正确的描述符,在整个程序的生命周期内,程序员不必改动这些段寄存器,也不必关心它们的值究竟是多少(实际上,想改也改不了)。

总结:我学的8086是早期的汇编,gcc 转换而成的汇编是基于IA32Intel Architecture 32-bit)的汇编,寻址方式已经发生了改变。要猜想,并去验证。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值