3.1 内存中字的存储
CPU中,用16位寄存器来存储一个字。高8位存放高位字节,低8位存放低位字节。在内存中存储时,由于内存单元是字节单元(一个单元存放一个字节),则一个字要用两个地址连续的内存单元来存放,这个字的低位字节存放在低地址单元中,高位字节存放在高地址单元中。
这一章中没有介绍大端法,小端法。其实上面讲的就是小端法。一个合法的数据,不可能总是单个字节,当一个多字节数据要存储到内存中时,你从哪一端开始存储呢?如果从低位字节开始存,就是低位字节存低地址,高位字节存高地址,那就是小端法;如果从高位字节开始存,就是高位字节存低地址,低位字节存高地址,那就是大端法。据说现在处理器大端法、小端法都支持,看你自己怎么配置,我没具体配置过。我比较习惯小端法。处理网络数据时,要注意以下大小端,因为你不知道发送者的计算机配置是否跟你的一样。
3.2 DS和【address】
mov al,【0】,指令中“【...】”表示一个内存单元,“【...】”中的0表示内存单元的偏移地址。定位内存单元还需要段地址,指令执行时,8086CPU自动取ds中的数据为内存单元的段地址。
8086CPU不支持将数据直接送入段寄存器的操作,ds是一个段寄存器,所以mov ds,10000H这条指令是非法的。通过一个寄存器来中转,先将10000H送入一个通用寄存器,mov ax,10000H;再将ax送入ds中mov ds,ax
3.3 字的传送
3.4 mov、add、sub指令
由于我之前学习了保护模式下的寄存器,对段寄存器有滤镜。因为保护模式下,段寄存器有96位,分为可见部分和不可见部分,可见部分16位,不可见部分80位,来自于段描述符。而汇编语言中讲的段寄存器是实模式下的段寄存器,功能跟通用寄存器差不多,需要注意的就是不能直接将数据传送给段寄存器,需要通用寄存器中转以下;CS、ip的改变要通过转移指令,mov指令不能改变CS、IP;
3.5 数据段
3.6 栈
栈也是一段存储空间,只不过这段空间有特殊的访问方式:出栈和入栈。入栈就是将一个新的元素放到栈顶,出栈就是从栈顶取出一个元素。栈顶的元素总是最后入栈,需要出栈时,又最先被从栈中取出。
3.7 CPU提供的栈机制
8086CPU提供入栈和出栈指令,最基本的两个是PUSH(入栈)和POP(出栈)。入栈和出栈都是以字为单位进行的。
任意时刻,SS和SP指向栈顶元素,当栈为空时,栈中没有元素,也就不存在栈顶元素,所以SS:SP只能指向栈的最底部单元下面的单元,该单元偏移地址为栈最底部的字单元的偏移地址+2
结合前面的内容,内存中存放的都是二进制信息0和1,CPU如何知道内存中的二进制信息,哪些是指令、哪些是数据、哪些是栈中的数据?其实都是通过CPU中的段寄存器。CS段寄存器中的段地址所指示的段就是指令段,再加上偏移地址IP就找到相应的指令;DS段寄存器中段地址指向的段就是数据段,配合指令中[...]偏移地址,找到相应数据;SS段寄存器中的段地址指向的就是栈,配合寄存器SP指向栈顶,通过PUSH和POP两个指令读写栈段。
3.8 栈顶超界的问题
8086CPU不保证我们对栈的操作不会超界。我们再编程的时候要自己操心栈顶超界的问题,要根据可能用到的最大栈空间,来安排栈的大小,防止入栈的数据太多而导致的超界;执行出栈操作的时候也要注意,以防栈空的时候继续出栈而导致超界。
3.9 push、pop指令
栈空间也是内存空间的一部分,它只是一段可以以一种特殊的方式进行访问的内存空间。
push、pop实质上就是一种内存传送指令,可以在寄存器和内存之间传送数据,与mov指令不同的是,push和pop指令访问的内存单元的地址不是在指令中给出,而是由SS:SP指出的,同时push和pop指令还要改变SP中的内容。
CPU执行mov指令只需要一步操作,就是传送,而执行push、pop指令却需要两步操作。执行push时,CPU的两步操作是:先改变SP,后向SS:SP处传送。执行pop时,CPU的两步操作是:先读取SS:SP处的数据,后改变SP。
push、pop也可以在内存单元和内存单元之间传送数据。
用栈来暂存以后需要恢复的寄存器内容时,寄存器出栈的顺序要和入栈的顺序相反。
这一章我反复的写一句话“栈空间也是内容空间的一部分,只是它可以用特殊的方式访问”,因为我之前看了《深入理解操作系统》这本书的部分内容,对这本书中讲函数调用,栈的作用这部分不是很理解,反复看了很多次都感觉有点糊涂。对书上栈的内容都有点死记硬背的感觉。但是现在看了《汇编语言》书上栈的内容,两个结合理解的很透彻。之前之所以不理解栈,就是忽略了,栈也是一个内存空间的事实,所以就在这反复强调,希望不要再忽略。
3.10 栈段
段的综述
对于数据段,将它的段地址放在DS中,用mov、add、sub等访问内存单元的指令时,CPU就将我们定义的数据段中的内容当作数据来访问;
对于代码段,将它的段地址放在CS中,将段中第一条指令的偏移地址放在IP中,这样CPU就将执行我们定义的代码段中的指令;
对于栈段,将它的段地址放在SS中,将栈顶单元的偏移地址放在SP中,这样CPU在需要进行栈操作的时候,比如执行push、pop指令等,就将我们定义的栈段当作栈空间来用。
一段内存,可以既是代码的存储空间,又是数据的存储空间,还可以是栈空间,也可以什么都不是。关键就在于CPU中寄存器的设置,即CS、IP,SS、IP,DS的指向。
CPU将内存中某段内容当作代码,是因为CS:IP指向了那里;CPU将某段内存当作栈,是因为SS:SP指向了那里。我们要清楚什么是我们安排的,以及如何让CPU按我们的安排行事。
实验2 用机器指令和汇编指令编程
实验任务2中栈内容为什么会变化,我也没想通,百度之后才知道这个涉及到后面要讲中断机制的一些处理,学到后面自然就懂了。在学习保护模式时,跨段提权会涉及一些寄存器内容要保存到栈里,这里也应该类似,之前我学习保护模式对这方面的知识就理解的不彻底,等学到这本书后面讲中断的内容,应该能补充我之前对保护模式跨段提权和中断时栈的操作的理解。这里就不过多纠结这个问题了
补充:此刻这本书我已经看到第7章了,每一章的程序我都认真在写,然后用debug调试,debug用的多了,在第7章思考问题7.9时,突然想明白了一些问题,回头过来对这个实验进行补充。
计算机中程序的运行,都要先加载可执行文件进入内存,谁来加载呢?通过书中后面的内容,你会知道通常命令行界面是由shell程序来加载(windows系统是command程序),debug作为调试程序,也能加载可执行文件,而且shell程序加载可执行文件后就放弃CPU的控制权,交给加载的可执行文件;debug加载可执行文件后,不会放弃控制权,放弃了就无法调试程序了。
debug的-t命令,就是控制加载的程序,执行一条指令,然后就中断。以前在学习CPU的段页机制实现的保护模式时,了解过,当发生中断时,CS:IP、SS:IP、EFLAGS要入栈保存。这里实模式下发生中断时肯定也要入栈保存,也就是栈中原来是全0,为什么通过debug的-t命令执行一条指令后,栈中出现数据的原因。至于DOS实模式下中断入栈保存哪些数据,我测试下来,保存了EFLAGS、CS、IP、BP、AX,不保存BX、CX、DX、SI、DI。