目录
什么叫做程序模块?
一个较大的程序一般分为若干个程序模块,每一个程序模块都可以实现一个特定的功能。相较于汇编语言这种低级语言而言,一些高级语言中都有子程序的概念,在C语言中子程序就是函数块。一个大型的程序须由许多个程序模块组成,这些程序模块可以在同一块内存上分时运行,也可以在不同的内存位置上同时运行:
内存的物理分段以及物理分段的弊端
上述在存储器中的物理空间中进行的分段操作被称为内存的物理分段,其弊端就在于资源的浪费,这种浪费可以从“物理分段会固定每个段的存储空间造成内存分配的不灵活”方面得出:
假设我们的程序中有2个程序模块正在运行并且两个程序模块使用的代码量很多,但是堆栈和数据占用的内存很小,这就使得代码段内存不够用,但是如果使用逻辑分段就可以解决这个问题:
与内存地址相关的寄存器
我们也看到了物理分段的弊端,因此内存的分段采用逻辑分段的方式进行。内存中的逻辑段也简称为逻辑段,我们前面了解过8086CPU内部的14个寄存器,其中除了4个段基地址寄存器外与内存的地址有关的还有4个,分别为:
基址指针寄存器BP | 存放数据段内存单元的偏移地址 |
源变址寄存器SI | 数据操作的源地址 |
目标变址寄存器DI | 数据操作的目标地址 |
栈顶指针寄存器SP | 指向堆栈的栈顶 |
内存的逻辑分段
内存的逻辑分段由操作系统进行,因此逻辑段的段基地址是我们人为不可控的,操作系统采用的是“见缝插针”的内存分配模式,高效利用内存空间,因此我们也不知道在某一时刻哪里的内存是空闲着的,哪里的空闲内存可以容纳某个程序模块的某个逻辑段。
一个程序模块中最多有4种逻辑段(数据段、代码段、附加段、堆栈段)并且每种逻辑段的个数最多有1个,我们回忆一下“16位的段基地址寄存器最多可以产生64K个地址编码,那说明通过指令指针寄存器IP段基地址寄存器最多可以寻址64K个内存单元”,这就使得每一个程序模块中逻辑段有以下限制:
1. 一个程序模块中最多有4种逻辑段(数据段、代码段、附加段、堆栈段);
2. 一个程序模块中每种逻辑段的个数最多有1个;
3. 一个程序模块中每个逻辑段的长度最大为64K;
逻辑段所用的寄存器如下所示:
代码段只需要 一个代码段的基址寄存器CS和指令指针IP就可以实现程序自动一条一条的执行,指令指针IP中存放的偏移地址指向下一条需要访问的代码段内存单元,直至访问到程序截至的指令为止;
堆栈段中SP存放着栈顶指针的偏移地址,BP则存放着内存单元相较于段首的偏移地址,SP和BP虽然都存放着偏移地址,但是SP专用于堆栈而BP通用;
数据段和附加段都存放着数据,涉及到数据的转移等操作则需要变址寄存器SI和DI有时也需要一个存放数据段基地址的寄存器BX(后面以批量转移内存单元中的数据为例进行了说明)。
内存的地址编码
8086CPU要管理/访问1MB内存就必须需要有1MB个地址才可以,这就需要有20位的物理地址(物理地址和物理分段无关,物理地址指的是内存单元在内存条中的具体位置,就和大楼内部各个房间的门牌号码类似,门牌号码是固定的不依据外部变换而变化)。
20位地址依靠地址加法器计算得到:
地址加法器的运算逻辑:16位段基地址<<4+偏移地址=20位物理地址
逻辑段必须以节为结尾(每个逻辑段的段尾地址必须可以被16整除,即逻辑段段尾地址必须是0000B)才可以,这是为了保证16位的体系结构中所存储的数据都是以16位长度存储的。
逻辑段最多可以有多少?
虽然一个程序模块最多有4个不同种类的逻辑段,但是一个大的项目工程包含众多逻辑段,我们可以想象一下一个项目工程最多有多少个逻辑段,答案是“1MB/16B=64K个逻辑段”,即当该项目工程占用总空间为1MB且其中每个逻辑段长度为最小长度16B,那么此时逻辑段数目最多,达到64K个。
逻辑段在内存空间中的分布
一个内存单元在内存中的分布可以有多种形式:
1. 在不同时刻一个内存单元分属于相同/不同逻辑段(分时复用)
2. 在同一时刻一个内存单元属于不同逻辑段(仅有数据段和附加段可以重叠)
我们可以看到T1时刻地址为5F00H:1009H的内存单元不仅属于数据段也属于附加段。
数据段和附加段重叠的情形
要将内存某区间的一组数据复制到内存的另一区间:
在进行数据的转移操作时,我们常常是“数据段和附加段结合在一起使用”:
用DS:[SI]表示源数据区数据存储单元的地址,用 ES:[DI]表示目的数据区数据存储单元的地址。假设 DS=250AH,ES=2EF0H,执行以下程序段,可将源数据区的100个字(Word)数据复制到目的数据区:
MOV CX, 100 ;在计数寄存器CX中设置循环次数
MOV SI,1 ;操作对象的源地址(相对地址)
MOV DI,1 ;操作对象的目标地址(相对地址)
LP1:
MOV DX, DS:[SI] ;将地址(段基地址+偏移地址)中存放的内容copy入DX数据寄存器中
MOV ES:[DI], DX ;将DX内容copy入地址为ES+DI的存储单元中
INC SI
INC DI
LOOP LP1 ;循环直至CX中数值为0
程序执行逻辑如下所示:
注意:附加段是数据段的扩展段,我们也可以称附加段为“额外的数据段”,这说明附加段和数据段都是存储数据用的,此外,这种批量的数据操作也被称之为“串操作”。
前面提及过在操作数据段时,BX基址寄存器、SI源变址寄存器、DI目标变址寄存器三者再结合数据段基址寄存器DS后也可以实现同上述类似的对数据段数据转移操作:
假设 DS=250AH,BX=2EF0H,执行以下程序段,可将源数据区的100个字(Word)数据复制到目的数据区:
MOV CX, 100 ;在计数寄存器CX中设置循环次数
MOV SI,1 ;操作对象的源地址(相对地址)
MOV DI,1 ;操作对象的目标地址(相对地址)
LP1:
MOV DX, DS:[SI] ;将“段基地址+偏移地址”copy入DX数据寄存器中
MOV BX:[DI], DX ;将DX内容copy入地址为BX+DI的存储单元中
INC SI
INC DI
LOOP LP1 ;循环直至CX中数值为0
其实此处BX充当了ES的作用,BX存放了数据转移的目标地址,我们使用循环不断地将数据copy至目标地址。
一个物理单元可以有多个逻辑地址
前面提到过“数据段和附加段可以重叠”,也就是说“一个内存单元在同一时刻可以既属于数据段又属于附加段”:
堆栈段
当栈顶指针=栈底指针时,空栈;当栈顶指针=堆栈段基地址时,满栈。同样,堆栈段属于逻辑段拥有逻辑段在数量(一个程序模块中最多出现一个)和长度(MAX=64KB)上的限制。
堆栈段中存放数据的特殊之处
堆栈段存放着一些“非常重要需要保存并且当前可能用不到的数据”,比如函数的返回地址(函数体执行与函数体的返回地址无关,但是函数体执行完后返回何处却由函数的返回地址决定)等。函数的返回地址和变量的存放地址就是我们常见的函数名称和变量名称,这些名称符号表征着数据存放的相对位置(即偏移地址),前面提到过段基地址由操作系统随机分配我们人为决定不了。