8086 cpu是一个完全16位的处理器,也就是说它的寄存器、内部和外部数据总线都是16位的。它有8个16位的通用寄存器AX,BX,CX,DX,SI,DI,BP,SP。
通常计算机都是处理单字节(8位)的数据,如果每次都用16位的寄存器来存储就显得有些浪费,因此为了减少这些浪费,AX,BX,CX,DX这个4个寄存器可以拆成一个高8位和一个低8位的寄存器来用,使用时互不干扰,即:
AX:AH,AL
BX: BH,BL
CX: CH,CL
DX: DH,DL
8086 CPU扩展了4根地址线,共有20根地址线,所以它的寻址空间是2的20次方,恰好有1M个地址,即:0x00000~0xFFFFF,常被表达成的地址空间是1M或1M的地址空间。
我们要理解好地址空间并不等于内存空间。并不是所有地址空间都会分配给内存来使用,还会分配给显存,BIOS等都来使用。那么这些地址都是怎么分配的呢?具体分配如下:
内存:0x00000~0x9FFFF,共640KB
图形:0xA0000~0xBFFFF,共128KB,这里的地址分配给了包括文字模式、图像模式在内的显存地址。
(1)EGA/VGA/XGA/XVGA彩色图形:0xA0000~0xAFFFF,共64KB
(2)黑白文本:0xB0000~0xB7FFF,共32KB
(3)彩色文本:0xB8000~0xBFFFF,共32KB
BIOS:0xC0000~0xFFFFF,共256KB,注意这里不仅包括系统ROM芯片里的BIOS,还包括显卡,硬盘等上面的BIOS。
综上所述,我们可以看出,显存和BIOS地址并不是映射到内存的,而是把CPU的地址空间中的一部分分配给了它们。因此超过0x9FFFF的地址对应的物理内存块是不存在的,因为它们都分配给了其他设备(或者说即使有,也无法被访问到)。我们可以将这些包括显存,BIOS,内存等在内的看成一个“大内存”。
迷一般的存在:0x7c00
在BIOS上电自检完之后就会到硬盘、U盘、CD等设备的MBR分区上寻找BootLoader,然后将BootLoader加载到内存0x7c00的位置上。为什么一定是这个位置,而不是其他位置呢,如0x0000。其实这与CPU没有任何关系,因为你给CPU什么地址,CPU就去哪里加载指令执行。0x7c00这个地址首次出现是在IBM PC Model 5150上,这个数字是存储在它的ROM BIOS里的。IBM PC Model 5150支持的最小内存是32KB,即0x0000~0x7FFF。
(1)当CPU收到RST信号(重置信号),指令寄存器会被强制定位到ROM,然后从ROM中读取指令开始执行
(2)在BIOS执行过程中,会产生中断向量表,放在内存最开始的地方,同时也会用内存来存在BIOS的数据,这部分数据会紧跟在中断向量表之后。
(3)在BIOS上电自检完后(即POST后),会到硬盘、U盘、CD等设备的MBR分区上寻找BootLoader,那么BootLoader要加载在什么地方比较合适呢?是加载到BIOS数据之后吗?最好的位置是内存的末端。因为BootLoader运行完成后,会把操作系统运行起来,它的任务也就完成了,它占用的内存空间也就应该被释放并重新利用起来。MBR分区(也就是Boot扇区)占用512字节,它需要使用的栈/数据也要占用512字节,加起来共1024字节。所以将BootLoader放在32KB内存的末端的1024字节内,它的起始位置就是0x7c00(刚好等于32KB-1024B,即0x7FFF-0x0400+1 = 0x7c00,0x7FFF即32KB内存的最后一个地址,0x400即1024,加1是因为地址是从0x0000开始的),虽然后来的内存大小发生了巨大的变化,但是这个位置一直没有改变。就是为什么BootLoader会被加载到内存0x7c00这个位置的历史原因了。
目前,显卡的内存(即显存)都比内存都要大,那么如果分配给显存的地址不够用怎么办?其实通过切换到相应的地址空间的显存即可。如显卡内存大小是4G,分配给显卡的地址只有256M,解决的办法就是一开始,将显存开始的256MB物理内存映射到CPU的寻址空间,如果不够用就切换到下一个256MB,也就是将分配的地址映射到显卡内存的下一个256MB,如此来回切换就能把整个4GB大小的显卡内存空间给映射完。因为显卡有自己的汇编指令,因此它能够完成这些切换映射工作。
显卡在初始化后,会默认进入彩色文字模式。当向这些地址写入数据就是把数据写入显卡的内存中,然后显示器会不断地把这些数据取走显示在屏幕上。文字模式下,它会把显示器整个区域划分为25行,每行80个字符,所以屏幕最多可以显示2000个字符。为了简化显示字符的工作,早期的工程师将每个可以显示的字符的像素定义好,然后发送给显卡一个字符代号,显卡就知道如何显示这些字符了。代号与字符的对应关系反映在我们常说的编码表中,如ASCII码表。如要显示‘A’,在ASCII码表中的代码为0x41,那么我们可以发送0x41和一个紧跟其后的属性字节给显卡,屏幕就会把它显示出来了。
属性字节:
7 6 5 4 3 2 1 0
K R G B I R G B
K:表示闪烁,
6,5,4位分别代表背景色的红、绿、蓝
I:表示高亮
2,1,0位分别代表前景色的红、绿、蓝
如果要让屏幕显示一个黑底白字的‘A’,就只需要发送0x4107到显卡的内存中即可。显示器默认就是显示黑底白字的。所以可以只发送0x41,但是切记要空出一个字节(那个字节默认就是0x07),要隔一个字节来发送,不能连续发送:
mov byte [0x00],’A’
mov byte [0x02],0x41
mov byte [0x04],41h
1
2
3
上面三条语句都是发送“A”。正如所见地址0x01,0x03都是要空出来的,里面默认就是0x07。随便介绍一下这条指令,mov是传送指令,byte是指定传送数据大小为一字节,下一个为传送目标,如0x00,传送内容为‘A’
什么是地址分段和偏移地址?
随着计算机的发展,我们的内存管理也变化了很多次。目前,在现代的操作系统中打开的程序会被加载到内存的某个随机的位置。这个位置就是程序的起始地址,然后其他指令的地址就以这个起始地址为基准,依次向后排开。我们的程序一般包括指令与数据两部分。CPU执行指令就是单纯地顺序执行,因此程序的指令一般都是放在一起的,占用一段连续的物理地址,我们称其为代码段。程序的数据会放在一起,占用一段连续的物理地址,我们称其为数据段。CS寄存器会记录程序代码段的起始地址,IP指令寄存器则存放相对于CS的偏移,CPU访问代码段是通过 段地址:偏移地址 来进行的。 而DS寄存器则会记录程序数据段的起始地址,CPU通过增加相应的偏移地址来访问数据。
8086 CPU扩展了4根地址线后就有了20根地址线,共1MB的地址空间,分配给内存的地址空间只有640KB。8086的寄存器都是16位的,CS段寄存器与IP指令寄存器想回还是16位,8086 CPU对于这个问题的解决方案是在把逻辑地址转成物理地址前,先把CS段地址寄存器左移4位,就有了20位了,然后再加上16位偏移地址,就可以访问所有的640KB的内存空间了。
我们来阅读一下这段汇编:
jmp $ // 标定当前位置
times 510-(\$-\$\$) db 0 // 分区标志位与代码之间填充0
db 0x55,0xaa // MBR 分区标志位
1
2
3
IP是指令寄存器,我们不能直接修改它,可以通过其他指令间接修改它的值,如jmp。
jmp $ // 就是跳到jmp所在的位置,就是将IP的偏移地址设置为jmp所在的位置。
$:表示jmp标定的位置
$$:表示程序的起始位置
$-$$:表示从jmp标定的位置到程序的起始位置共有多少个字节
因为MBR一共512字节,最后两个字节放了0x55,0xaa作为MBR分区标志位,因此还有510,510-($-$$) 等到了应该填充0的个数。
温馨提示:MBR分区里,如果没有分区信息,运行也会有问题的。
————————————————
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/weixin_40763897/article/details/118276792