3.1 实模式
实模式是最古老的模式。这种模式没有虚拟内存的概念;操作系统直接在物理内存上进行寻址,通用寄存器只有 16 位长。
因此,没有什么 rax,eax,只有 ax,al 和 ah。
这些寄存器可以保存从 0 到 65535 大小的值,所以我们使用这样的寄存器也就只有 65535 字节的寻址能力。这样的内存区域被称为段(segment)。不要把这里的段和 ELF(Executable and Linkable Format)文件中的段(section)搞混了。
下面是在实模式中使用到的寄存器:
- ip,flags;
- ax,bx,cx,dx,sp,bp,si,di;
- 段寄存器:cs,ds,ss,es,(还有 gs 和 fs)
因为 16 位的寄存器不能简单地定位大于 64 KB 的内存,工程师们想出了一个办法来解决这个问题。使用一种特殊的段寄存器,像下面这样:
-
物理内存地址由 20 位组成(5 个十六进制数)
-
每个逻辑地址都由两部分组成。一部分从段寄存器中取,用来标识段的起始位置。另一部分记录的是地址在段内的偏移量。硬件用这两个寄存器来计算物理地址:
物理地址 = 段基址 * 16 + 偏移量
你可能经常看到以段地址加偏移量来表示的地址,例如:4a40:0002, ds:0001,7bd3:ah。
我们已经提到过,程序员希望将代码和数据(以及栈)分离开,比如他们希望能为代码中的内容使用不同的代码段(section)。段(Segment)寄存器 cs 就是为这个目的打造的。cs 中存储了代码段的起始地址,ds 则对应数据,ss 对应栈段。其它的段寄存器用来存储其它的数据段。
严格意义上说,段寄存器并不是存储的段的起始地址,而只是地址的一部分(四个主要的十六进制数)。给这个数值乘以 16 然后再加上一个 0 才能拿到真正的段起始地址。
每一条汇编指令都隐式地假定了会使用一个段寄存器来组成地址的一部分。在文档中会对每条指令默认使用了哪些寄存器进行说明。不过常识已经可以帮助我们进行判断了。例如 mov 指令是操作数据的,所以地址一定是和数据段(ds)有关系。
mov al, [0004] ; === mov al, ds:0004
显式地修改默认段也是可以的:
mov al, cs:[0004]
程序被加载时,加载器会设置 ip,cs,ss 和 sp 寄存器。这样 cs:ip 就指向了程序的入口,ss:sp 指向栈的顶部。
CPU 总是从实模式开始运行,然后主加载器执行代码显式地切换到保护模式,然后再切换到长模式。
实模式有很多缺点:
- 多任务非常困难。所有程序共享内存地址空间,所以这些程序都需要被加载到不同的内存地址去。而它们的相对排列又需要在编译期指定。
- 同样因为共享地址空间,程序可以随意修改它人代码甚至是操作系统的代码
- 程序可以执行任意的任意指令,包括设置处理器状态的指令。而一些指令本应只由操作系统执行(例如设置虚拟内存,管理电源的指令等等),如果指令被错误地使用,会导致整个系统崩溃。
保护模式就是用来解决上面这些问题的。