理解计算机系统_程序的机器级表示(二):寄存器,操作数,数据传送,程序栈

前言

        以<深入理解计算机系统>(以下称“本书”)内容为基础,对程序的整个过程进行梳理。本书内容对整个计算机系统做了系统性导引,每部分内容都是单独的一门课.学习深度根据自己需要来定

引入

        本书第三章:程序的机器级表示内容的理解,这一章内容以汇编语言为主.汇编语言偏底层,用于系统级别的程序编写.如果不是做系统的,可以不用深入学习.理解汇编语言的关键是理解gcc指令,数据,寄存器,地址等概念.这些概念在C语言中也是很重要的,而且从汇编语言的角度会发现更多细节.

        这章可以作为学习C语言的辅助,明白C语言的运行机制.其中重点关注能优化代码的部分.

        本帖内容为3.4节访问信息

一.寄存器

        本书P119讲了寄存器的演化历史,和指令集历史同步.

        1>寄存器是干什么的?

         大致推导代码执行过程:源代码经编译汇编链接后,成为可执行文件并加载到内存中.可执行文件是十六进制组成的机器码,实际上是一条条的指令,交给CPU执行.

        每条指令有指令码和操作数.例如

movl $0x4050,%eax

         指令码由PC(程序计数器Program Counter)管理,存放着指令地址,执行完一条指令后,自动指向下一条指令的地址.CPU执行相应的指令,作为软件开发者可以不管CPU是怎么完成指令的,因为指令码提供了机器活动的抽象,比如movl这条指令,程序员调用后,将立即数0x4050送到寄存器%eax中.

        指令码的实现由芯片开发人员完成,我们可以设想一下:把一个十六进制数0x4050,送到%eax中,按照二进制0B0100 0000 0101 0000,CPU接到这条指令,把%eax(32位寄存器)中对应的值等于1的位打开,对应值等于0的位关闭.------------这段属于超纲内容,知道他的结果就行了.

        CPU有个特点:只能处理来自寄存器的数据,指令中的操作数必须经寄存器传给CPU.寄存器的作用是把内存的数据调入交给CPU处理;再将CPU处理好的数据交给内存.简而言之,寄存器是用来中转数据的.

        2>认识寄存器

        本书P120画出了x86-64的整数寄存器图,如果想使用汇编语言,这张图挺重要,最好能熟悉.

         简单认识一下寄存器,有64位,32位,16位,8位之分,分别对应四字,双字,字和字节.各自特点如下:

                 64位寄存器特点:以"r"开头,非"d"结尾

                 32位寄存器特点:以"e"开头或者"d"结尾

                 8位寄存器特点:以"l"或者"b"结尾

                  其余是16位寄存器 

         在P120倒数第二段,有关于寄存器指令的特别规则:当这些指令以寄存器为目标时,生成4字节数的指令会把高位4个字节置0.

        说明:指令有以下2个特点:

        1)指令有单个操作数或者两个操作数(3个以上的暂时还没见过)

        2)指令末尾要加上表示操作数字节长度的标识(字节加b,字加w,双字加l,四字加q)

        当第2个操作数为寄存器,且指令以"l"结尾时,高位4个字节将置0.

        3>每个寄存器的作用   

         本书P120最后一段,强调了栈指针%rsp,知名运行时栈的结束位置.P121第一段:有一组标准的编程规范控制着如何使用寄存器来管理栈、传递函数参数、从函数的返回值,以及存储局部和临时数据.(黑体字是原话).这段话表明了寄存器有使用规则,具体怎样使用在3.7节

二.操作数指示符

          操作数的类型有三种:立即数,直接寻址(寄存器寻址和内存寻址),间接寻址(内存地址).为了方便下面的理解:操作数其实是两个类型,立即数和地址

          操作数的抽象模型是这样的:

        以下是直接寻址和间接寻址的示意图 

        

        直接寻址:当标识是寄存器名称或者内存时,实际上表示的是他的数值

        间接寻址:当标识用()括起来的寄存器名称或者内存时,表示以他的数值为地址内的数值.

        由此引出一个非常重要!非常重要的概念

                 内存中的数据都是以地址形式出现(立即数除外)

        从这个结论对前面所学内容做个回顾和对比:

        1.首先是指针的重要性,在C语言等高级语言中,有"变量"的存在.变量是为了方便编写和使用程序的人理解而设置的.CPU不认识变量,他是通过数据地址来运算的.所以变量是地址的代名词

        2.立即数的用途不多,在高级语言中,一般使用枚举来表示常量,避开硬编码,侧面加强了这个概念,突出地址的重要性.

        3.地址传递数据这一机制,由接收方直接寻址或间接寻址来表达传的是值或者指针,非常好用.这句话很拗口,用代码来说明

//以下标记为代码段1
movew $0x1040,0xff00        //把0x1040写入地址0xff00
movew $0x1000,0x1040        //把0x1000写入地址0x1040 
movew 0xff00,%ax            //把0xff00的值0x1040传给寄存器%ax
movew %ax,0x40              //把寄存器%ax里的值传给地址0x40,此时0x40的值等于0x1040
movew (%ax),%rdx            //把寄存器%ax值表示的地址的值传给%rdx,此时%rdx值等于0x1000

         由%ax接收数据后,%ax表示接收到的值,由(%ax)表示值的地址里的值.因此可以忽略数据类型是"变量"还是"指针".这一点细微的差别,能感觉得出比起高级语言中需声明指针,再间接访问数据这种写法来得更好.-------第3点属于思路发散,实际意义没多少,个人感觉创造出"间接寻址"这个概念的人很厉害

        关于操作数指示符的其他内容,可以参看本书P121和P122,并做一做练习题.

三.数据传送指令

        本书P122说了数据传送指令是最频繁使用的指令.回到编程的根本目的---用数据来表示结果.传送指令相当于直接写入结果. 运行程序中各种参数传递,结果回传等,都会用到move指令,所以使用很多.

        笔者把数据传送指令的规则做个归纳和说明

        1>源操作数可以是立即数,寄存器或者内存地址;目的操作数必须是地址

                和上面操作数讲的一样,源操作数用寄存器名称或者内存地址,表示他的数值.目的操作数必须是地址,不能是立即数.以下写法错误

//错误写法
movew 0x40 $0xff00        //目的操作数是立即数

           2>源操作数和目的操作数不能都是地址,如果把一个地址的数据传到另一个地址中,需要经过寄存器,两步转换.

movew 0xff00,%ax            //把0xff00的值传给寄存器%ax
movew %ax,0x40              //把寄存器%ax里的值传给地址0x40

这样写是错的

//以下为错误写法
movew 0xff00,0x40         //不能将一个内存的值直接传给另一个内存地址
//以下是正确写法
movew $0x40 0xff00        //立即数写入内存

                3>寄存器部分的大小必须与指令最后一个字符(b,w,l,q)指定大小匹配(本书P123原话).move指令对于内存地址没有限制

moveb %ax,0xff00    //错误,b表示字节,%ax表示字,大小不匹配
moveb %al,%bl       //正确,寄存器大小匹配

                4>特殊的movl指令:movl指令用于替代movzlq指令.和寄存器特殊运算规则一样,当以寄存器为目的操作数,指令使用movl时,它会将该寄存器的高位4字节设置为0. 

movl %eax,%rdx    //错误,%rdx高4字节位被置0(不可预料后果)

                5>某些寄存器不能被作为目的操作数                

movb $0xf,(%ebx)    //错误,被调用者保留位,不能被写入

====================================内容分割线============================ 

         笔者在看完了move指令的用法,做了相关习题后,确实有些"嫌"汇编语言.他好不好学,好不好用笔者不知道,但就只是一个指令,就有这么多规则要遵守,在写代码段1的时候,还很小心地对地址进行了计算(每个数据占8个字节,因此地址末尾应该是0x00,0x08),与此对应的32位机,地址相差4,地址末尾0x00,0x04,0x08,0x0c

        回到理解汇编语言的初衷,加深对C语言的认识,以及优化代码的基础上去学汇编语言是很好的.但如果可以用高级语言替代,又没有那么大的兴趣去研究的话,相关内容可以考虑不看.

====================================内容分割线============================ 

          P125的3.4.3数据传送示例,有一个地方需要注意,在程序传参的过程中,实际上还发生了一些代码调用.具体要结合程序中上面的代码.

//将xp地址传给寄存器%rdi(第一个参数),这里xp是形参,实际上是实参地址
//将y地址传给寄存器%rsi(第二个参数),这里y是形参,实际上是实参地址
//xp in %rdi的伪代码实现,y in %rsi的伪代码实现
moveq xp地址 %rdi    
moveq y地址 %rsi

 四.程序栈的初识

        本书P127有几句重要的内容(黑体字是原话)

        1>程序栈存放在内存某个区域

        2>栈向下增长

        3>栈指针%rsp保存着栈顶元素的地址

        这里程序栈内容并没有讲栈内内容如何与寄存器产生数据交互.

        重点:栈指针始终指向栈顶,保存着栈顶元素的地址.每当数据压入栈中,栈顶地址值加8(8个字节),每当数据弹出栈,栈顶地址值减8,汇编代码如下

subq $8,%rsp        //数据压栈,栈向下增长,栈顶元素地址减8
addq $8,%rsp        //数据出栈,栈向上增长,栈顶元素地址加8

        栈指针%rsp的作用,就和指向链表首个元素的指针一样(可以访问链表中任意一个元素),获得栈指针可以访问栈内任意元素(每次移动8个字节.),并将其传给寄存器,以使CPU获得数据后执行指令.

小结

        3.4节的内容对理解程序的执行过程还是非常重要的

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

jllws1

你的鼓励是我创作的动力,谢谢

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值