深入理解计算机系统(3.3)---数据传送(或者说复制)指令详解

本文转载地址:http://www.cnblogs.com/zuoxiaolong/p/computer15.html

引言

  上一章我们已经介绍了汇编语言的基础部分,包括数据格式、寄存器以及操作数的标识方式,接下来我们就应该去认识一下汇编语言当中的各个指令了。这些指令大多数都非常简单,但是组合在一起却能模拟出我们程序当中想要的任何效果,确实是十分神奇的一件事。

  

数据传送指令

  数据传送指令的目的是为了将一个数据从一个位置复制到另外一个位置。既然如此,那么数据传送指令就会包含一个源操作数和一个目的操作数,指令会将原操作数的值复制到目的操作数并覆盖。

  数据传送指令一共可分为五种,分别是mov、movs、movz、push以及pop,下面LZ依次介绍一下这五个指令的作用。

  

mov指令

  mov指令的作用是将源操作数S中的数据复制到目的操作数D中,mov指令有一个数据格式和两个操作数,因此一般的形式为[movx S D]。其中x为数据格式,S为源操作数,D为目的操作数。

  这里举一个简单的例子,比如我们有一条指令为movl %edx %eax。那么它的执行过程就如下图所示。

  可以看到,在指令执行之后,%edx寄存器当中的内容会被复制到%eax寄存器。需要一提的是,mov指令可以在后面加上任何数据格式,比如上面这一过程中,数据格式则为四个字节,也就是双字。因此不难推断出,我们还可以使用movb和movw去复制一个字节或者两个字节。

movs指令

  movs指令的作用是将源操作数S中的数据做符号扩展后,再复制到目的操作数D中,movs指令有两个数据格式和两个操作数,因此一般的形式为[movsxy S D]。其中x、y为数据格式,S为源操作数,D为目的操作数。其中x、y的组合一共有三种,分别是bw、bl、wl,这三个组合代表的意思分别是单字节到双字节,单字节到双字以及双字节到双字。

  这里LZ依然举一个例子,对于指令movswl %dx %eax来讲,它的作用如下图所示。

  这里为了可以看出符号位的扩展,因此LZ这里使用了十六进制的整数表示方式。可以看到,movs指令将0x8FFF扩展以后存入%eax寄存器,其中%dx为寄存器%edx的后16位表示。

  

movz指令

  movz指令的作用是将源操作数S做零扩展后,再复制到目的操作数中。它与movs指令十分相似,也有两个数据格式和两个操作数,因此一般的形式为[movzxy S D]。其中x、y为数据格式,S为源操作数,D为目的操作数。其中x、y的组合一共有三种,分别是bw、bl、wl,这三个组合代表的意思分别是单字节到双字节,单字节到双字以及双字节到双字。

  这里依然采用相似的示例,我们来看看对于指令movzwl %dx %eax来讲,它的作用与上面的movs有何不同。

  可以看出,movz与movs指令是十分相似的,只是这里扩展后,目标寄存器%eax的前16位为0而不再是1。

push指令

  push指令与上面的mov族指令有着不同,它的目的操作数被固定为栈顶,因此它的指令当中没有目的操作数。另外有一点需要注意的是,它在进行复制操作之前,需要移动栈顶指针(-4)。push指令的一般形式为[pushl S],其中l代表数据格式为双字,S为源操作数,目的操作数默认为栈顶。

  这里LZ举一个简单的例子,比如pushl %edx这条命令,它的任务是将%edx寄存器的值复制到栈顶。我们首先来看一下命令执行前,寄存器以及存储器的状态。

  可以看到,寄存器%ebp和%esp分别指向帧指针和栈指针,而%esp实际上就是指向的栈顶。由于现在栈顶位于-16的位置,因此若要将%edx压入栈,则先需要将栈顶移动到-20的位置,然后再进行复制,移动后的状态如下图所示。

  可以看到,这里栈指针的位置已经发生了变化,向下移动了四位,并且将%edx寄存器的值放入新的栈顶,因此pushl %edx指令就相当于下面两条指令。

                  subl $4,%esp

                  movl %edx,(%esp)

  这里可以看出来,其实pushl指令做了一个隐藏操作,就是移动栈指针(-4),这一点希望各位猿友们注意。

pop指令

  pop指令与push指令是做的相反的操作,一个是入栈一个是出栈。对于pop指令来讲,它的源操作数被固定为栈顶,相反,它会先进行复制操作,然后再移动栈指针。pop指令的一般形式为[popl D],其中l代表数据格式为双字,D为目的操作数,源操作数默认为栈顶。

  接下来我们举一个例子,与上面的例子类似,我们考虑popl %edx这条指令的效果,它会将栈顶的值弹出到寄存器%edx。首先来看执行之前,寄存器以及存储器的状态。

  接下来执行pop指令时,会先将栈顶的值复制到%edx,然后再将栈指针移动(+4)。我们来看一下它执行后的状态。

  可以看到,之前栈顶的内容已经被弹出到%edx寄存器,并且当前栈顶已经移动到了-16的位置,也就是进行了+4操作。因此popl %edx指令就相当于下面两条指令。

                  movl (%esp),%edx

                  addl $4,%esp     

  这里可以看出来,其实popl指令也同样做了一个隐藏操作,就是移动栈指针(+4)。

数据复制示例

  上面我们已经了解了几乎所有的数据复制指令,接下来我们写一小段程序,来看下这些数据复制指令,如何完成我们的程序操作。

simple(int *xp,int y){
    int t = *xp;
    *xp=y;
    return t;
}

  上面是一个简单的C程序sum.c,它其中包含了一些赋值操作,我们来看看它的汇编代码。使用GCC -O1 -S sum.c来获取我们的汇编代码,并使用cat sum.s来查看一下。

    .file    "sum.c"
    .text
.globl simple
    .type    simple, @function
simple:
    pushl    %ebp
    movl    %esp, %ebp    //以上为栈的建立部分
    movl    8(%ebp), %edx
    movl    (%edx), %eax
    movl    12(%ebp), %ecx
    movl    %ecx, (%edx)    //以下为栈的完成部分
    popl    %ebp
    ret
    .size    simple, .-simple
    .ident    "GCC: (Ubuntu 4.4.3-4ubuntu5.1) 4.4.3"
    .section    .note.GNU-stack,"",@progbits

  分析这段汇编代码的时候,我们应该分为三个部分来看待,首先是栈的建立、然后是使用、最后是完成部分。可以看到,里面几乎全是数据复制指令,我们先来看看栈的建立部分。

  其实对于一开始pushl和movl指令来讲,它主要做了两件事。第一个是将原来的帧指针备份到栈顶,然后再将帧指针和栈指针统一指向这个新的栈顶,也就是完成了一个新栈的建立。它在完成后,栈的状态如下所示。

  可以看到,寄存器%ebp和寄存器%esp都指向当前帧指针的位置,其中变量xp位于+8的位置,而y位于+12的位置。由于xp是一个指针变量,因此它会指向一个内存中的区域,其中的值为*xp。

  了解完寄存器和存储器的状态,此时栈已经建立完毕,接下来我们看紧接着的一句汇编代码的作用。

    movl    8(%ebp), %edx

  这一句将内存地址为%ebp+8的值复制到%edx,很明显,从上面的图中可以看出,%ebp+8这个位置存储着xp变量。这一句指令做了一个简单的操作,就是将xp提取到%edx寄存器,如下所示。

  此时已经将%edx的值改为了变量xp,看接下来的一句操作。

movl (%edx), %eax

  这一句将内存地址为%edx的值赋给寄存器%eax,并准备返回值。此时%edx寄存器的值已经改为了xp变量,因此(%edx)其实就是*xp,而%eax寄存器一般会作为函数的返回值,因此它其实替代了临时变量t。执行后的状态如下所示。

  此时其实已经完成了程序中的int t = *xp以及为return t准备好了返回值,接下来的一句汇编代码作用也非常简单,如下。

    movl    12(%ebp), %ecx

  它的作用是将地址为%ebp+12的值复制到寄存器%ecx,从图中可以看出,%ebp+12就是存储的变量y。因此它的作用就是将y复制到寄存器%ecx,如下所示。

  上面这一步挺简单,我们来看最后一步操作,如下。

    movl    %ecx, (%edx)

  它的作用是将%ecx寄存器的值复制到内存中%edx的位置。此时%ecx的值为y,而%edx中为xp,因此目的操作数则为xp指向的位置,也就是*xp。这一句话执行的就是程序代码当中,*xp=y这个操作,它执行后的状态如下所示。

  可以看到,在执行了*xp=y以后,xp指针所指向的位置,其值已经变为了y。此时程序其实已经基本运行完毕,剩下的工作也就是栈的完成操作了,也就是popl指令。在栈完成之后,也就是pop指令执行之后,当前帧会恢复到调用者的帧上面去,如下所示。

  此时当前帧已经恢复到了调用者的帧,最后ret指令会改变程序计数器(PC)的值,然后跳出子函数,继续执行调用者当中的代码。到此,我们的数据复制示例就结束了,尽管这个例子并不难,但是麻雀虽小五脏俱全,如果理解了这个过程,相信就算是再复杂一些的汇编指令,也只是分析的时间长一点罢了。

文章小结

  本章内容比较长,LZ为了便于理解,插入了不少图,希望对各位有帮助,不过这些图是真不好画,浪费了LZ不少时间。本次主要介绍了一些数据复制指令,都是复制来复制去的,下一章我们将讨论一下有关计算的指令,那里会有一些加减乘除、移位、取地址等操作。

时间: 2024-11-10 11:22:58

深入理解计算机系统(3.3)---数据传送(或者说复制)指令详解的相关文章

数据传送指令详解

数据传送指令详解 前言 上一章我们说了汇编语言的基础,包括数据格式,寄存器以及操作数的标识方式,接下来我们就应该去认识一下hiU币按语言当红真难过的格各个指令了.这些指令大部署很简单,但是组合在一起却能模拟出我们程序当中香烟的任何效果,确实很神奇. 数据传送指令 数据传送指令的目的是我了将一个数据从一个位置复制到另一个位置.既然如此,那么数据传送至零就会包含一个源操作数和一个目的操作数,指令会将源操作数的值复制到目的操作数并覆盖. 数据传送指令一共可以分为5种,分别是mov,movs,movz,

ActiveMQ讯息传送机制以及ACK机制详解

[http://www.ylzx8.cn/ruanjiangongcheng/software-architecture-design/11922.html] AcitveMQ:消息存储和分发组件,涉及到client与broker端数据交互的方方面面,它不仅要担保消息的存储安全性,还要提供额外的手段来确保消息的分发是可靠的. [ActiveMQ消息传送机制]Producer客户端用来发送消息的, Consumer客户端用来消费消息:它们的协同中心就是ActiveMQ broker,broker也

《深入理解mybatis原理6》 MyBatis的一级缓存实现详解 及使用注意事项

<深入理解mybatis原理> MyBatis的一级缓存实现详解 及使用注意事项 0.写在前面   MyBatis是一个简单,小巧但功能非常强大的ORM开源框架,它的功能强大也体现在它的缓存机制上.MyBatis提供了一级缓存.二级缓存 这两个缓存机制,能够很好地处理和维护缓存,以提高系统的性能.本文的目的则是向读者详细介绍MyBatis的一级缓存,深入源码,解析MyBatis一级缓存的实现原理,并且针对一级缓存的特点提出了在实际使用过程中应该注意的事项. 读完本文,你将会学到: 1.什么是一

Android数据存储(二)----PreferenceFragment详解

?[声明] 欢迎转载,但请保留文章原始出处→_→ 生命壹号:http://www.cnblogs.com/smyhvae/ 文章来源:http://www.cnblogs.com/smyhvae/p/4020469.html 联系方式:[email protected] [正文] 一.PreferenceFragment的引入: PreferenceActivity是一个非常有用的基类,当我们开发Android项目时避免不了选项设置,这些设置习惯用Preference来保存.Android专门为

Android数据存储之GreenDao 3.0 详解(一)

前言: 今天一大早收到GreenDao 3.0 正式发布的消息,自从2014年接触GreenDao至今,项目中一直使用GreenDao框架处理数据库操作,本人使用数据库路线 Sqlite---->OrmLite---->GreenDao.今天白天一直在忙着公司的项目需求,只能晚上加班来学习最新的GreenDao 3.0使用方式了. GreenDao 介绍: greenDAO是一个对象关系映射(ORM)的框架,能够提供一个接口通过操作对象的方式去操作关系型数据库,它能够让你操作数据库时更简单.更

【R】数据导入读取read.table函数详解,如何读取不规则的数据(fill=T)

函数 read.table 是读取矩形格子状数据最为便利的方式.因为实际可能遇到的情况比较多,所以预设了一些函数.这些函数调用了 read.table 但改变了它的一些默认参数.  注意,read.table 不是一种有效地读大数值矩阵的方法:见下面的 scan 函数. 一些需要考虑到问题是: 编码问题 如果文件中包含非-ASCII字符字段,要确保以正确的编码方式读取.这是在UTF-8的本地系统里面读取Latin-1文件的一个主要问题.此时,可以如下处理 read.table(file("fil

MySQL数据切分的相关概念和原理详解

对于数据切分,我们可能还不是很熟悉,但是它对于MySQL数据库来说也是相当重要的一门技术,本文我们就详细介绍一下MySQL数据库的数据切分的相关知识,接下来就让我们一起来了解一下这部分内容. 什么是数据切分 "Shard" 这个词英文的意思是"碎片",而作为数据库相关的技术用语,似乎最早见于大型多人在线角色扮演游戏中."Sharding" 姑且称之为"分片".Sharding 不是一门新技术,而是一个相对简朴的软件理念.众所周

Android数据存储(一)----SharedPreferences详解

一.Android数据的存储方式: Android系统一共提供了四种数据存储方式.分别是:SharePreference.SQLite.Content Provider和File:此外还有一种网络存储.由于Android系统中,数据基本都是私有的,都是存放于“data/data/程序包名”目录下,所以要实现数据共享,正确方式是使用Content Provider. 在Android中,可以使用几种方式实现数据持久化: Shared Preferences:除SQLite数据库外,另一种常用的数据

OpenLayers 之 地图图层数据来源(ol.source)详解

source 是 Layer 的重要组成部分,表示图层的来源,也就是服务地址.除了在构造函数中制定外,可以使用 layer.setSource(source) 稍后指定. 一.包含的类型 ol.source.BingMaps ,必应地图的切片数据,继承自ol.source.TileImage: ol.source.Cluster,聚簇矢量数据,继承自ol.source.Vector: ol.source.ImageCanvas,数据来源是一个 canvas 元素,其中的数据是图片,继承自 ol.