部分转载,不能说明,谅解!
操作系统又实模式进入保护模式之前要打开A20地址线。
那么什么是A20地址线呢?经过一番查证理解如下:
在8086、8088中,有20跟地址线。所以寻址范围是2^20 = 1M,但8086/8088是16位的地址模式,即只能表示FFFFH(64K)的范围。为了能访问1M的内存采取了分段的模式。
16位段基址:16位偏移。
0XFFFF:0XFFFF达到了0X10FFEF
但是8086/8088的内存不可能超过1MB,所以当时的程序超过1MB时会自动回卷。
但是到了80286地址线达到24跟。而386达到32根。芯片也达到32-bit。寻址能力达到4GB。但是为了向后兼容IBM采用了一个控制方法。用键盘控制器上的一个剩余的控制线来控制(注意是键盘控制器上的控制线,而不是地址线的第20跟)。即A20控制线。当A20控制线打开时可以使用20-31的地址线。而当A20关闭时20-31的地址线全部为0.
所以,如果A20被禁止,可访问的内存只能是奇数段(2N+1)M,只有当A20被打开的时候才能访问连续的内存。
所以,如果A20被禁止,可访问的内存只能是奇数段(2N+1)M,只有当A20被打开的时候才能访问连续的内存。
只有A20打开才能进入保护模式。
下面讨论一下如何打开A20地址线:
从理论上讲,打开A20 Gate的方法是通过设置8042芯片输出端口(64h)的2nd-bit,但事实上,当你向8042芯片输出端口进行写操作的时候,在键盘缓冲区中,或许还有别的数据尚未处理,因此你必须首先处理这些数据。
所以,激活A20地址线的流程为:
1.关闭中断;
2.等待8042 Input buffer为空;
3.发送禁止键盘操作命令;
4.等待8042 Input buffer为空;
5.发送读取8042 Output Port命令;
6.等待8042 Output buffer有数据;
7.读取8042 Output buffer,并保存得到的字节;
8.等待8042 Input buffer为空;
9.发送Write 8042 Output Port命令到8042 Input buffer;
10.等待8042 Input buffer为空;
11.将从8042 Output Port得到的字节的第2位置1(或清0),然后写入8042 Input buffer;
12.等待,直到8042 Input buffer为空为止;
13.发送允许键盘操作命令到8042 Input buffer;
14. 打开中断。
下面讨论一下如何打开A20地址线:
从理论上讲,打开A20 Gate的方法是通过设置8042芯片输出端口(64h)的2nd-bit,但事实上,当你向8042芯片输出端口进行写操作的时候,在键盘缓冲区中,或许还有别的数据尚未处理,因此你必须首先处理这些数据。
所以,激活A20地址线的流程为:
1.关闭中断;
2.等待8042 Input buffer为空;
3.发送禁止键盘操作命令;
4.等待8042 Input buffer为空;
5.发送读取8042 Output Port命令;
6.等待8042 Output buffer有数据;
7.读取8042 Output buffer,并保存得到的字节;
8.等待8042 Input buffer为空;
9.发送Write 8042 Output Port命令到8042 Input buffer;
10.等待8042 Input buffer为空;
11.将从8042 Output Port得到的字节的第2位置1(或清0),然后写入8042 Input buffer;
12.等待,直到8042 Input buffer为空为止;
13.发送允许键盘操作命令到8042 Input buffer;
14. 打开中断。
下面是完成打开A20 Gate的代码:
A20Enable:
cli ;1.关闭中断
call WaitInbufEmpty ;2.等待8042 Input buffer为空;
mov al, 0adh
mov dx, 64h
out dx, al ;3.发送禁止键盘操作命令
call WaitInbufEmpty ;4.等待8042 Input buffer为空;
mov al, 0d0h
mov dx, 64h
out dx, al ;5.发送读取8042 Output Port命令;
call WaitOutbufFull ;6.等待8042 Output buffer有数据;
mov dx, 60h
in al, dx ;7.读取8042 Output buffer
push ax ;保存读取的数据
call WaitInbufEmpty ;8.等待8042 Input buffer为空;
mov al, 0d1h
mov dx, 64h
out dx, al ;9.发送写 8042 Output Port命令
call WaitInbufEmpty ;10.等待8042 Input buffer为空
pop ax
or al, 02h ;11.将从8042 Output Port得到的字节的bit 1置1
mov dx, 60h
out dx, al ;写入Output Port
call WaitInbufEmpty ;12.等待8042 Input buffer为空
mov al, 0aeh
mov dx, 64h
out dx, al ;13.发送允许键盘操作命令
sti ;开中断
ret
cli ;1.关闭中断
call WaitInbufEmpty ;2.等待8042 Input buffer为空;
mov al, 0adh
mov dx, 64h
out dx, al ;3.发送禁止键盘操作命令
call WaitInbufEmpty ;4.等待8042 Input buffer为空;
mov al, 0d0h
mov dx, 64h
out dx, al ;5.发送读取8042 Output Port命令;
call WaitOutbufFull ;6.等待8042 Output buffer有数据;
mov dx, 60h
in al, dx ;7.读取8042 Output buffer
push ax ;保存读取的数据
call WaitInbufEmpty ;8.等待8042 Input buffer为空;
mov al, 0d1h
mov dx, 64h
out dx, al ;9.发送写 8042 Output Port命令
call WaitInbufEmpty ;10.等待8042 Input buffer为空
pop ax
or al, 02h ;11.将从8042 Output Port得到的字节的bit 1置1
mov dx, 60h
out dx, al ;写入Output Port
call WaitInbufEmpty ;12.等待8042 Input buffer为空
mov al, 0aeh
mov dx, 64h
out dx, al ;13.发送允许键盘操作命令
sti ;开中断
ret
WaitInbufEmpty:
mov dx, 64h
in al, dx ;读取Status Register
test al, 02h
jnz WaitInbufEmpty
ret
mov dx, 64h
in al, dx ;读取Status Register
test al, 02h
jnz WaitInbufEmpty
ret
WaitOutbufFull:
mov dx, 64h
in al, dx
test al, 01 ;读取Status Register
jz WaitOutbufFull
ret
mov dx, 64h
in al, dx
test al, 01 ;读取Status Register
jz WaitOutbufFull
ret
后来,由于感觉使用8042控制A20运行太慢了(确实,那么长的代码,中间还要若干次的wait),所以后来又出现了所谓的Fast A20,实际上,现在的大多数机器都是Fast A20,Fast A20使用92h端口控制A20,同时BIOS里提供了一个软中断来控制A20:
入口:ah=24h
al=0 关闭A20
1 打开A20
2 读取A20状态
int 15h
返回:如果BIOS支持此功能,CF=0,否则CF=1
CF=0时,AX返回当前A20状态,1=打开,0=关闭
像8042中的Output Port中的定义一样,92h端口的bit 1控制着A20,为1时打开,为0时关闭,从92h中读一个byte可以看a20的当前状态,所以对92h的操作如下:
读A20状态
mov dx, 92h
in al, dx
如果al的bit 1为1表示a20打开,否则为0
打开A20
mov dx, 92h
mov al, 02
out dx, al
关闭A20
mov dx, 92h
mov al, 0
out dx, al