段地址
处理器负责执行指令,内存里面存着CPU要执行的指令。
当CPU通电之后,CPU内部所有的寄存器会进行复位,比如初始化成0000H,CPU就从这个地址开始提取指令,当然这是举例。
为了做某件事而编写的指令,当这样几条,几十条,甚至几百万条,几千万条这样的程序组合在一起,形成了我们平时所说的程序。
程序总要操作大量的数据,这些数据也应该集中在一起,位于内存中的某个地方,形成一个段,叫做数据段。
假定我们有16个数要相加,这些数都是16位的二进制数,分别是0005H、00A0H、00FFH、…。
为了让处理器把它们加起来,我们应该先在内存中定义一个数据段,将这些数字写进去。数据段可以起始于内存中的任何位置,既然如此,我们将它定在0100H处。
这样一来,第一个要加的数位于地址0100H,第二个要加的数位于地址0102H,最后一个数的地址是011EH。
一旦定义了数据段,我们就知道了每个数的内存地址。然后,紧挨着数据段,我们从内存地址0120H处定义代码段。
严格地说,数据段和代码段是不需要连续的,但这里把它们挨在一起更自然一些。为了区别数据段和代码段,我们使用了不同的底色。
代码段是从内存地址0120H处开始的,第一条指令是A1
00 01,其功能是将内存单元0100H里的字传送到AX寄存器。指令执行后,AX的内容为0005H。
第二条指令是03 06 02
01,功能是将AX中的内容和内存单元0102H里的字相加,结果在AX中。由于AX的内容为0005H,而内存地址0102H里的数是00A0H,这条指令执行后,AX的内容为00A5H。
第三条指令是03
06 04
01,功能是将AX中的内容和内存单元0104H里的字相加,结果在AX中。此时,由于AX里的内容是00A5H,内存地址0104H里的数是00FFH,本指令执行后,AX的内容为01A4H。
图2-
我们的程序OK,搞定了。
但是平常我们使用计算机的时候,游戏,视频,操作系统已经把内存占用了。
当我们在操作系统里面,双击一个这样的程序运行的时候,它很可能压根不会运行成功。
因为操作系统,会考虑哪里的内存比较空闲,就把这些指令放到哪里去。
可能把我们这些数据段和代码段放到了1000H的内存地址。
而我们指令相加的内存地址却是0100……0104.。这肯定就出问题了。。
我们让指令运行的时候,定位数据用的是绝对的内存地址。
这样子程序真正执行的知道,被操作系统不知道分配到哪个内存位置了,就出问题了。
所以,我们只能使用相对地址。
当然,CPU的设计者们也为我们考虑到这些问题了,那么如何解决这个问题呢?
在8086
16位CPU中,除了我们刚才学习的几个通用寄存器,再介绍两个寄存器,这两个寄存器分别为:
代码段寄存器 CS [Code Segment]
数据段寄存器 DS [Data
Segment]
附加段几次器 ES [Extra Segment]
指令寄存器 IP
它和CS一起使用。
栈段寄存器
SS
当CPU要运行我们程序的时候,首先把CS和DS的位置确定下来,然后放到这两个寄存器里面。
CS和IP共用形成逻辑地址,并经过CPU的一个部件变成物理地址来取得指令。
访问内存则使用DS加偏移
按照套路8086应该最大只有64KB内存。
但是64KB太小了。
内存的大小取决于CPU可以定位的范围和物理内存本身的大小。
0000
0000 0000 0000
1111 1111 1111 1111 地址定位
FFFF = 65536 =64KB的内存
1111
1111 1111 1111 1111 地址定位。
这样子可以定位1MB的内存.
00000H到FFFFFH
而寄存器却都是16位的。
CS:0008
IP:
0003
CS:IP= 0008:0003 =000B 真实的内存物理地址。
但却是00080:0003=00083
所以,这个问题可以这样理解。
我有两个16位寄存器最大都可以表示
FFFF
现在我要通过这两个寄存器的组合,能够表示FFFFF
为了让大家能够理解这种工作模式,我们转换成十进制给大家理解。
A最大可以表示9999,最小为0000
B最大可以表示9999 , 最小为0000
C最大可以表示99999,最小为00000
我们怎么样可以用A和B来表示C呢?
假如A是1230,B是80
那么A x 10 + B= 12300+80 =12380
A和B | C | |
9999:0009 | 99999 | |
9999:0008 | 99998 | |
9999:0007 | 99997 | |
9999:0006 | 99996 | |
9999:0005 | 99995 | |
9999:0004 | 99994 | |
9999:0003 | 99993 | |
9999:0002 | 99992 | |
9999:0001 | 99991 | |
9999:0000 | 99990 | |
... | ... | |
... | ... | |
... | ... | |
0001:0009 | 00019 | |
0001:0008 | 00018 | |
0001:0007 | 00017 | |
0001:0006 | 00016 | |
0001:0005 | 00015 | |
0001:0004 | 00014 | |
0001:0003 | 00013 | |
0001:0002 | 00012 | |
0001:0001 | 00011 | |
0001:0000 | 00010 | |
0000:0009 | 00009 | |
0000:0008 | 00008 | |
0000:0007 | 00007 | |
0000:0006 | 00006 | |
0000:0005 | 00005 | |
0000:0004 | 00004 | |
0000:0003 | 00003 | |
0000:0002 | 00002 | |
0000:0001 | 00001 | |
0000:0000 | 00000 |
十六进制
A和B | C | |
FFFF:000F | FFFFF | |
FFFF:000E | FFFFE | |
FFFF:000D | FFFFD | |
FFFF:000C | FFFFC | |
FFFF:000B | FFFFB | |
FFFF:000A | FFFFA | |
FFFF:0009 | FFFF9 | |
FFFF:0008 | FFFF8 | |
FFFF:0007 | FFFF7 | |
FFFF:0006 | FFFF6 | |
FFFF:0005 | FFFF5 | |
FFFF:0004 | FFFF4 | |
FFFF:0003 | FFFF3 | |
FFFF:0002 | FFFF2 | |
FFFF:0001 | FFFF1 | |
FFFF:0000 | FFFF0 | |
... | ... | |
... | ... | |
... | ... | |
0001:000F | 0001F | |
0001:000E | 0001E | |
0001:000D | 0001D | |
0001:000C | 0001C | |
0001:000B | 0001B | |
0001:000A | 0001A | |
0001:0009 | 00019 | |
0001:0008 | 00018 | |
0001:0007 | 00017 | |
0001:0006 | 00016 | |
0001:0005 | 00015 | |
0001:0004 | 00014 | |
0001:0003 | 00013 | |
0001:0002 | 00012 | |
0001:0001 | 00011 | |
0001:0000 | 00010 | |
0000:000F | 0000F | |
0000:000E | 0000E | |
0000:000D | 0000D | |
0000:000C | 0000C | |
0000:000B | 0000B | |
0000:000A | 0000A | |
0000:0009 | 00009 | |
0000:0008 | 00008 | |
0000:0007 | 00007 | |
0000:0006 | 00006 | |
0000:0005 | 00005 | |
0000:0004 | 00004 | |
0000:0003 | 00003 | |
0000:0002 | 00002 | |
0000:0001 | 00001 | |
0000:0000 | 00000 |
0001:0000-1000:000F
0002:0000-0002:000F
FFFF:0000-FFFF:000F
把1M的内存分成了65536个段,每个段16个字节。
当然,这只是一种组合定位物理地址的方法。
我们还可以这么定位。
0000:0000 - 0000:FFFF
1000:0000 - 1000:FFFF
2000:0000 - 2000:FFFF
F000:0000 -F000:FFFF
把1M的内存分成了16个段,每个段64KB
这是两种不同的分法:
比如FFFF:000F和F000:FFFF其实是一个物理地址。
假设现在物理地址00000H-82251H都被其他程序占用了。
二后面82252H-FFFFFH,都是空闲的。
为了让我们的程序能够加载。
我们可以选择82260H这个位置。然后把DS:偏移设置成8226H:0000H
因为我们要让偏移的初值地址为0000