2015.03.25-26
读《汇编语言》—王爽、《x86汇编语言:由实模式到保护模式》— 李忠、《30天自制操作系统》—川合秀实
整理笔记。
在实模式下,处理器的内存寻址方式和8086处理器相同。
任何一台使用Intel 系列CPU的PC机只要一开机,CPU就工作在实模式下。CPU从地址FFFF0H处开始执行指令,这是一条跳转指令,这条跳转指令会跳到系统BIOS真正的启动代码处(进行硬件检测和初始化)。启动代码执行完毕后,调用int19h进行操作系统的引导,将计算机交由操作系统控制。
如果你的机器装的是DOS,那么在DOS加载后CPU仍以实模式工作。如果你的机器装的是Windows,那么Windows加载后,将由Windows将CPU切换到保护模式下工作,因为Windows是多任务系统,它必须在保护模式下运行。Windows启动完成后,如果在Windows中运行一个DOS下的程序,那么Windows将CPU切换到虚拟8086下运行该程序。或者是这样,你点击开始菜单在程序项中进入MS-DOS方式,这时,Windows也将CPU切换到虚拟8086模式下运行。《汇编语言》第三版– 王爽。
也就是说,将CPU由实模式到保护模式的切换是由操作系统完成的。
8086处理器有20根地址线,寻址范围为0x00000~ 0xFFFFF(1M)。
实模式下的内存地址空间是各个存储器件的地址的逻辑组合,各个存储器中的存储单元的地址与内存地址空间中的地址一一对应。
[1]处1KB内容是系统BIOS程序加载的(实际占用可能小于1KB);[2]处512字节内容是BIOS从磁盘/硬盘的第1柱面第1磁道的第1扇区读来的程序(此程序刚好512字节);[3]处128KB是显卡存储器的地址;[4]处256KB是存储BIOS程序的ROM存储器的地址。其中,最后64KB是存储系统BIOS程序的ROM存储器的地址,前192KB是其它存储BIOS程序的ROM存储器的地址。]
在系统BIOS执行完毕后,可用的RAM地址空间为0x00400~ 0x07bff以及0x0800 ~ 0x9ffff。
8086处理器(CPU)内的寄存器都是16位的,地址线上的地址由CPU提供。CPU用两个16位的值合成一个20位的值:将一个16位的值乘以16(相当于这个16位的值保存在20位寄存器的低16位中,然后将低16位往高位移动了4位)再加上另一个16位的值。将这个由2个16位合成的值提供给20根地址线作为访问内存的地址值。被乘以16的那个值被称为段基址,没有被乘以16的那个值被称为偏移地址。
CPU在实模式下,段寄存器中的值就是段基址,如果想要通过汇编程序访问某个内存单元,那么就需要指定一个段寄存器和一个偏移地址或以“段基址值:偏移地址值”的格式。CPU会将段寄存器的值乘以16再与偏移地址相加后,才将这个结果提供给地址线;用段基址和偏移地址的形式给出的表达式,CPU也会给段基址乘以16与偏移地址相加后,才将这个结果提供给地址线。
Figure 2. 实模式下访问内存(得出内存地址)的方式
这就是说,在汇编程序中,得用段基址:偏移地址的方式访问内存,因为CPU形成内存地址的方式是“段基址* 16 + 偏移地址”。
按照“段:偏移地址”的方式访问内存是CPU对内存的一种管理方式。
实模式下的内存的段基址低4位都为0(段基址左移4位),段基址至少要是16的倍数。所以,内存中连续的两个段之间至少也会相差16字节,也就是说内存段之间以16字节对齐。
汇编编译器将汇编程序中的“段名segment … 段名ends”伪指令对中的段名解释成一个程序段,程序加载器能够根据汇编编译器的解释将程序段加载到一个空闲的内存段中。
assume cs:code, ds:data, ss:stack data segment dw 0123h, 0456h, 0789h, 0abch, 0defh, 0fedfh, 0cbah, 0987h data ends stack segment dw 0, 0, 0, 0, 0, 0, 0, 0 stack ends code segment start: mov ax, stack mov ss, ax mov sp, 16 mov ax, data mov ds, ax push ds:[0] push ds:[2] pop ds:[2] pop ds:[0] mov ax, 4c00h int 21h code ends end start //告诉汇编编译器start标号处为程序的入口地址
编译、连接此汇编程序,用debug调试器调试此程序,得到各个程序段对应的内存内存段:
code段的段地址为X,则data段的段地址为X-2,stack段的段地址为X-1。(因为每个段的内容都未超过16字节)
ORG是由编译器处理的伪指令。它告知加载器,后续的程序应被加载至本内存段的哪个偏移地址处。它对段无效。
assume cs:code code segment jmp my_label ;org 5h my_label: mov ax, 0 mov ax,4c00h int 21h code ends end
将此段汇编程序编译、连接,用debug调试器观看生成的指令:
将程序中的注释去掉,再将程序编译、连接,用debug观看生成的指令:
程序加载器能够根据汇编编译器对org伪指令的处理信息而将org后面的指令装载到org指定的偏移地址处。同时修改某些指令的参数。
BIOS读软盘第一个扇区时,磁盘内不同地方的内容有不同的含义。以下是FAT12格式软盘第一扇区各内容的含义:
表格 1. Windows (开机)读软盘第一个扇区的读法
偏移 |
长度/字节 |
内容/含义 |
(参考)值 |
0 |
3 |
跳转到软盘指令开始处的跳转指令 |
jmp l_start(l_start为软盘内第一条指令处的标号) |
3 |
8 |
第一个扇区的名字 |
"xnlosipl" |
11 |
2 |
软盘每个扇区的大小(字节为单位) |
必须为512 |
13 |
1 |
每簇的扇区数 |
必须为1 |
14 |
2 |
保留扇区数/FAT的起始位置/Boot记录占用扇区 |
一般为1 |
16 |
1 |
FAT的个数 |
必须为2 |
17 |
2 |
根目录文件数的最大值 |
一般为224 |
19 |
2 |
扇区总数 |
(必须为)2880 |
21 |
1 |
介质描述符 |
必须为0xf0 |
22 |
2 |
每FAT扇区数 |
必须为9 |
24 |
2 |
每磁道扇区数 |
必须为18 |
26 |
2 |
磁头数 |
必须为2 |
28 |
4 |
隐藏/不使用的扇区数 |
必须为0 |
32 |
4 |
如果偏移19处值为0,由这个值表示总扇区数 |
2880 |
36 |
1 |
中断13h的驱动器号 |
固定为0 |
37 |
1 |
未使用 |
固定为0 |
38 |
1 |
扩展引导标记 |
固定为0x29 |
39 |
4 |
卷列序号 |
0xffffffff |
43 |
11 |
软盘名称 |
"xnlhello-os" |
54 |
8 |
软盘格式名称 |
"FAT12 "(8字节,空格填充) |
62 |
448 |
(引导)代码、数据及其它填充字符 |
…… |
510 |
2 |
标明软盘有无所需的启动代码(在第一个扇区内) |
0x55aa(为0x55aa表明有) |
这些规定都是指电平信号(在二进制编辑器中可直接书写就可得到它们)。在软盘内每个偏移内组织的数据要符合“表格 1”中的要求。这样,软盘内的数据才会得到计算机的正确解释。
启动区外,除下图有2个地方有规定(非必须)内容外,其它地方的内容无规定。
[x86OS] Note Over.
[2015.04.01]