包含多个段的程序

  在操作系统环境中,合法地通过操作系统取得的空间都是安全的,因为操作系统不会让一个程序所用的空间和其他程序以及系统自己的空间相冲突。在操作系统允许的情况下,程序可以取得任意容量的空间。

  程序取得所需空间的方法有两种,一是在加载程序的时候为程序分配,再就是在程序执行的过程中向系统申请。我们若要一个程序在被加载的时候就取得所需的空间,则必须要在源程序中做出说明。我们通过在源程序中定义段来进行内存空间的获取。

 

6.1 在代码段中使用数据

  考虑这样一个问题,编程计算以下 8 个数的和,结果存在 ax 寄存器中:

  0123h、0456h、0789h、0abch、0defh、0fedh、0cbah、0987h

  在之前,我们都是累加某些内存单元中的数据,并不关系数据本身。可现在要累加的就是已经给定的数据。我们可以将它们一个个地加到 ax 寄存器中,但是我们希望可以用循环的方法来进行累加,所以在累加前,要将这些数据存储在一组地址连续的内存单元中。如何将这些数据存储在一组地址连续的内存单元中呢?我们可以用指令一个一个地将它们送入地址连续的内存单元中,可是这样又有一个问题,到哪里去找这段内存空间呢?

  从规范的角度来讲,我们是不能自己随便决定哪段空间可以使用,应该让系统来为我们分配。我们可以在程序中,定义我们希望处理的数据,这些数据就会被编译、连接程序作为程序的一部分写到可执行文件中。当可执行文件中的程序被加载入内存的时候,这些数据也同时被加载入内存中。与此同时,我们要处理的数据也就自然而然地获得了存储空间。

  具体如下:

assume cs:code

code segment

	dw 0123h,0456h,0789h,0abch,0defh,0fedh,0cbah,0987h
	
	mov bx,0
	mov ax,0
	
	mov cx,8
s:	add ax,cs:[bx]
	add bx,2
	loop s
	
	mov ax,4c00h
	int 21h

code ends

end

  解释一下,程序第一行中的“dw”的含义是定义字型数据。dw 即“define word”,在这里,使用 dw 定义了 8 个字型数据(数据之间以逗号分隔),它们所占的内存大小为 16 个字节。

  程序中的指令就是对这 8 个数据进行累加,我们知道生成的 exe 运行的时候 CS 指向了 代码段第一条指令处,在上面的例子中,也就是指向了第一位字型数据,我们可以通过 cs:[xx] 来获取当前要读取的字型数据,然后通过偏移地址 +2 读取下一个数据,总共 8 个。

  我们用 debug 查看编译连接生成的内容:

 

  我们可以看到,对应的汇编指令并不是我们所写的。实际上,用 u 命令查看的时候,debug 把 我们定义的 dw 那一行也当作指令了(而不是我们期待的字型数据)。实际上,在程序中,有一个代码段,在代码段中,前面的 16 个字节是用“dw”定义的数据,从第 16 个字节开始才是汇编指令所对应的机器码。

  我们可以用 u 0770:0010 查看程序中要执行的机器指令,如下:

 

  由以上两图我们可以看到程序加载到内存后,所占内存空间的前 16 个单元存放着源程序中用“dw”定义的数据,后面的单元存放源程序中汇编指令对应的机器指令。

  怎样执行指令呢?用 Debug 加载后,可以将 IP 设置为 10h,从而使 CS:IP 指向程序中的第一条指令。然后用 t 命令、p命令,或者是 g 命令执行。

  可是这样一来,我们就必须用 Debug 来执行程序。

 

  其实,我们可以在源程序中指明程序的入口所在,具体做法如下:

assume cs:code

code segment

	dw 0123h,0456h,0789h,0abch,0defh,0fedh,0cbah,0987h
	
start:	mov bx,0
		mov ax,0
		
		mov cx,8
	s:	add ax,cs:[bx]
		add bx,2
		loop s
		
		mov ax,4c00h
		int 21h

code ends

end start

  在上面的源程序中,在程序的第一条指令的前面加上了一个标号 start,而这个标号在伪指令 end 的后面也出现了。end 除了通知编译器程序结束外,还可以通知编译器程序的入口在什么地方。在上面的程序中,我们用 end 指令指明了程序的入口在标号 start 处,也就是说,“mov bx,0”是程序的第一条指令。

 

6.2 在代码段中使用栈

  完成下面的程序,利用栈,将程序中定义的数据逆序存放。

assume cs:codesg

codesg segment
    dw 0123h,0456h,0789h,0abch,0defh,0fedh,0cbah,0987h
    ?
codesg ends

end

  程序的思路:定义的数据放在 cs:0~cs:f 单元中,共 8 个单元。依次将这 8 个字单元数据入栈,然后再依次出栈到这 8 个字单元中,从而实现数据的逆序存放。

  我们首先需要一段同样大小的内存作为栈空间。因此我们在程序段第二行使用数值 0 定义一段长度为 8 个字的单元。

assume cs:code

code segment
	
	dw 0123h,0456h,0789h,0abch,0defh,0fedh,0cbah,0987h
	
	dw 0,0,0,0, 0,0,0,0
		;用 dw 定义 16 个字型数据,在程序加载后,将取得 16 个字的
		;内存空间,存放这 16 个数据。在后面的程序中将这段空间
		;当作栈来使用

start:
	mov ax,cs
	mov ss,cx
	mov sp,30h	;设置栈顶 ss:sp 指向 cs:30
	
	mov bx,0
	mov cx,8
	
	s:	push cs:[bx]
		add bx,2
		loop s			;这段循环把第一段 dw 逆序送入第二行 dw 内存
	
	mov bx,0
	mov cx,8
	s0:	pop cs:[bx]
		add bx,2
		loop s0

	mov 4c00h
	int 21h

code ends

end start

  我们要将 cs:10~cs:2f 的 8 个字的内存空间当作栈来使用,初始状态下栈为空,所以 ss:sp 要指向栈底,则设置 ss:sp 指向 cs:30。

  

6.3 将数据、代码、栈放入不同的段

  在前面的程序中,我们用到了数据和栈,将数据、栈和代码都放到了一个段里面。我们在编程的时候要注意何处是数据,何处是栈,何处是代码。这样做显然有两个问题:

  (1) 把它们放到一个段中使程序显得混乱;

  (2) 前面程序中处理的数据很少,用到的栈空间也小,加上没有多长的代码,放到一个段里面没有问题。但如果数据、栈和代码需要的空间超过 64KB,就不能放在一个段中(这个限制限于我们讨论的 8086PC)。

  所以,我们可以考虑使用多个段来分开存放代码、栈和数据。

  我们可以用和定义代码段一样的方法来定义多个段,然后在这些段里面 定义需要的数据或者栈。具体如下:

assume cs:code,ds:data,ss:stack

data segment
	dw 0123h,0456h,0789h,0abch,0defh,0fedh,0cbah,0987h
data ends

stack segment
	dw 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
stack ends

code segment
	
start:	mov ax,stack
		mov ss,ax
		mov sp,20h	;设置栈顶 ss:sp 指向 stack:20
		
		mov ax,data
		mov ds,ax	;ds 指向 data 段
		
		mov bx,0	;ds:bx 指向 data 段中的第一个单元
		
		mov cx,8
	s:	push [bx]
		add bx,2
		loop s		;以上将 data 段中的 0~15 单元中的 8 个字型数据依次入栈
		
		mov bx,0
		
		mov cx,8
	s0:	pop [bx]
		add bx,2
		loop s0		;以上依次出栈 8 个字型数据到 data 段的 0~15 单元中
		
		mov ax,4c00h
		int 21h
	
code ends

end start

  下面对这段代码做出说明:

  (1)定义多个段的方法:

  对于不同的段,使用不同的命名。

  (2)对段地址的引用

  在程序中,段名就代码了该段的起始地址。注意:8086CPU 不允许将一个数值直接送入段寄存器,需要通过其他寄存器中转。

  (3)“代码段”、“数据段”、“栈段”完全是我们的安排

  ① 我们在源程序中为这 3 个段起了具有含义的名称,用来放数据的段我们将其命名为“data”,用来存放代码的段我们将其命名为“code”,用作栈空间的段我们将其命名为“stack”。

  这样命名了之后,CPU 是否就去执行“code”段中的内容?处理“data”段中的数据,将“stack”当作栈了呢?

  当然不是,我们这样命名,仅仅是为了使程序便于阅读。这些名称同“start”、“s”等标号一样,仅在源程序中存在,CPU 并不知道它们。

  ② 我们在源程序中用伪指令“assume cs:code,ds:data,ss:stack”将cs、ds和 ss 分别和code、data、stack段相连。这样做了之后,CPU是否就会将 cs 指向 code,ds 指向 data,ss 指向 stack,从而按照我们的意图来处理这些段呢?

  当然也不是,要知道 assume 是伪指令,是由编译器执行的,也是仅在源程序中存在的信息,CPU 并不知道它们。我们不必深究 assume 的作用,只要知道需要用它将你定义的具有一定用途的段和相关的寄存器联系起来就可以了。

  ③ 若要 CPU 按照我们的安排行事,就要用机器指令控制它,源程序中的汇编指令是 CPU 要执行的内容。我们在源程序的最后用“end start”指明了程序的入口,这个入口将被写入可执行文件的描述信息,可执行文件中的程序被加载入内存后,CPU 的 CS:IP 被设置指向这个入口,从而开始指向程序中的第一条指令。

 

  总之,CPU 到底如何处理我们定义的段中的内容,是当作指令执行,当作数据访问,还是当作栈空间,完全是靠程序中具体的汇编指令,和汇编指令对 CS:IP、SS:SP、DS等寄存器的设置来决定的。

 

posted @ 2017-10-08 16:03  佚名000  阅读(174)  评论(0)    收藏  举报