OS boot 的時候為什麼要 enable A20?

請問OS boot的時候 enable A20的意義是?

我知道enable A20後,real mode下,可以訪問到第21位地址。那麼如果我在跳轉到protect mode前,不訪問第21位,可以不打開A20么?跳轉後的代碼會因沒打開 A20,而被影響么?

=========================================

以下是 boot.S 的代碼,我把enable A20那一段注釋了,目前來看OS運行正常。(目前這個OS剛實現到分頁機制, mit 6.828 lab2)

#include &

# Start the CPU: switch to 32-bit protected mode, jump into C.
# The BIOS loads this code from the first sector of the hard disk into
# memory at physical address 0x7c00 and starts executing in real mode
# with %cs=0 %ip=7c00.

.set PROT_MODE_CSEG, 0x8 # kernel code segment selector
.set PROT_MODE_DSEG, 0x10 # kernel data segment selector
.set CR0_PE_ON, 0x1 # protected mode enable flag

.globl start
start:
.code16 # Assemble for 16-bit mode
cli # Disable interrupts
cld # String operations increment

# Set up the important data segment registers (DS, ES, SS).
xorw %ax,%ax # Segment number zero
movw %ax,%ds # -&> Data Segment
movw %ax,%es # -&> Extra Segment
movw %ax,%ss # -&> Stack Segment

# Enable A20:
# For backwards compatibility with the earliest PCs, physical
# address line 20 is tied low, so that addresses higher than
# 1MB wrap around to zero by default. This code undoes this.

# enable
#if 0
seta20.1:
inb $0x64,%al # Wait for not busy
testb $0x2,%al
jnz seta20.1

movb $0xd1,%al # 0xd1 -&> port 0x64
outb %al,$0x64

# write
seta20.2:
inb $0x64,%al # Wait for not busy
testb $0x2,%al
jnz seta20.2

movb $0xdf,%al # 0xdf -&> port 0x60
outb %al,$0x60
#endif

# Switch from real to protected mode, using a bootstrap GDT
# and segment translation that makes virtual addresses
# identical to their physical addresses, so that the
# effective memory map does not change during the switch.
lgdt gdtdesc
movl %cr0, %eax
orl $CR0_PE_ON, %eax
movl %eax, %cr0

# Jump to next instruction, but in 32-bit code segment.
# Switches processor into 32-bit mode.
ljmp $PROT_MODE_CSEG, $protcseg

.code32 # Assemble for 32-bit mode
protcseg:
# Set up the protected-mode data segment registers
movw $PROT_MODE_DSEG, %ax # Our data segment selector
movw %ax, %ds # -&> DS: Data Segment
movw %ax, %es # -&> ES: Extra Segment
movw %ax, %fs # -&> FS
movw %ax, %gs # -&> GS
movw %ax, %ss # -&> SS: Stack Segment

# Set up the stack pointer and call into C.
movl $start, %esp
call bootmain

# If bootmain returns (it shouldn"t), loop.
spin:
jmp spin

# Bootstrap GDT
.p2align 2 # force 4 byte alignment
gdt:
SEG_NULL # null seg
SEG(STA_X|STA_R, 0x0, 0xffffffff) # code seg
SEG(STA_W, 0x0, 0xffffffff) # data seg

gdtdesc:
.word 0x17 # sizeof(gdt) - 1
.long gdt # address gdt


不訪問當然可以不開——說的就是那些legacy software

但是你一個bootloader幹嘛不開呢?跟內存過不去?

--

先談A20的背景

還記得8088/8086有多少根地址線不?20根.

那16位寄存器怎麼表示20位的定址空間呢?Intel用了分段的方法——segment:offset.
實際的地址是segment
&<&< 4 + offset.

舉例說明,1000:FF03 表示的地址是 0x1000
&<&< 4 + 0xFF03 即 0x1FF03

那這種方法,填滿F表示的地址是多少呢?FFFF:FFFF = 0xFFFF &<&< 4 + 0xFFFF = 0x10FFEF.

你注意看 0x10FFEF 的二進位表示——它逆天地有一個第20位!在8088/8086上面,那個第20位被直接忽略掉了——因為就是沒有這根線嘛,所以最終得到的地址是除掉這個第20位的,即0xFFEF.

但是在80286中,Intel把地址線擴展成24根了,FFFF:FFFF真的就是0x10FFEF了,你讓那些legacy software怎麼活?!本來人家想讀0xFFEF的,怎麼成了0x10FFEF?不是人家不想好好工作,是你硬體設計的不讓人家好好工作嘛。

於是,「聰明的IBM工程師」 就想,你不是喜歡被truncate么,不是想把第20位拿掉么,那我默認就給你關掉這一位不就好啦,啥時候想用啥時候開開。

乃們看出來了沒?A20 是 80286 時代照顧8088軟體的產物。通常所說的32位保護模式是 80386 才出現的,所以,A20跟保護模式毛關係都沒有!開不開都一樣進,影響的只是第20位而已

--

不開的話,保護模式下第21位會不會總是被清0,或者21-32位總是為0

第20位總是0(從0開始數數好么),21-31位不變

但是Real Mode Addressing只有24位,所以其實是21-23位不變,高八位管不到了

--

測試A20是否默認開啟:

https://gist.github.com/sakamoto-poteko/0d50af2d9eb78f71c74f

測試結果:

Bochs 2.6.7 Win Prebuilt: 默認開 (但是bochs有個編譯選項可以選擇是否有A20這個東西,不清楚我用的預編譯版是不是選了)

VMWare Workstation 11.0.0 build-2305329: 默認關

QEMU 1.1.2 (Debian 1.1.2+dfsg-6a+deb7u6): 默認開

Hyper-V bundled with Win 8.1 Update, Gen 1: 默認開

本子沒法關UEFI,不試了

附代碼供各位沒法上gh的老爺看

; NASM
[bits 16]

org 0x7c00

mov ax, cs
mov ds, ax
mov es, ax

call check_a20
test ax, ax
mov ax, A20On
jnz Print ; Enabled
mov ax, A20Off

Print:
mov bp, ax
mov cx, 16
mov ax, 0x1301
mov bx, 0x000c
mov dl, 0
int 0x10

cli ; Shutdown
hlt

check_a20:
push ds
push es
push di
push si

cli

xor ax, ax ; ax = 0
mov es, ax

not ax ; ax = 0xFFFF
mov ds, ax

mov di, 0x0500
mov si, 0x0510

mov al, byte [es:di]
push ax

mov al, byte [ds:si]
push ax

mov byte [es:di], 0x00
mov byte [ds:si], 0xFF

cmp byte [es:di], 0xFF

pop ax
mov byte [ds:si], al

pop ax
mov byte [es:di], al

mov ax, 0
je check_a20__exit

mov ax, 1

check_a20__exit:
pop si
pop di
pop es
pop ds

ret

A20On:
db "A20 is On "
A20Off:
db "A20 is Off "

times 510-($-$$) db 0

db 0x55
db 0xaa


不開A20是不行的,下表是打開和不打開A20時訪問地址的情況:

代碼中的物理地址 實際的物理地址(開A20) 實際的物理地址(關A20)
0x00080000 0x00080000 0x00080000
0x00100000 0x00100000 0x00000000
0x00180000 0x00180000 0x00080000
0x00200000 0x00200000 0x00200000
0x00280000 0x00280000 0x00280000
0x00300000 0x00200000 0x00200000
0x00380000 0x00280000 0x00280000

所以,不打開A20的話,相當於A20那一位永遠都是0,相當於物理內存里有一半的空間你無法使用,並且如果你使用地址時,必須務必小心,否則會訪問到另一部分物理地址上,這對於代碼來說是難以接受的。

所以原則上,A20一定要開。

當然,如果不開A20是否一定就會有問題呢?答案是,不一定。因為A20實際上老的BIOS里的一種設置,新的BIOS基本上都是A20默認打開,所以代碼里即使不處理A20也不一定會有問題。

如果要測試,可以拿bochs或者vmware之類的進行測試。

有些回答把第20位和第20位以及更高的部分(高12位)弄混,注意了,Intel的手冊上明確寫是第20位,不是高12位

A20M# pin — On an IA-32 processor, the A20M# pin is typically provided for compatibility with the Intel 286 processor. Asserting this pin causes bit 20 of the physical address to be masked (forced to zero) for all external bus memory accesses. Processors supporting Intel Hyper-Threading Technology provide one A20M# pin, which affects the operation of both logical processors within the physical processor. The functionality of A20M# is used primarily by older operating systems and not

used by modern operating systems. On newer Intel 64 processors, A20M# may

be absent.

同時也提到了,在一些新的處理器上A20問題已經沒有了。

內容摘自:

Intel? 64 and IA-32 Architectures Software Developer"s Manual

Volume 3A: System Programming Guide, Part 1

8.7.13.4 External Signal Compatibility


推薦閱讀:

計算機行業領先的公司大多在美國,Linux為什麼卻是芬蘭人發明的?
為何 Linux 的系統 API 相比 Win32 到處是縮寫?有何優劣? 造成兩者差別的原因是什麼?
「系統程序員」的技能棧有哪些?
文件系統設計中的 Sectorsize有什麼用?
linux為什麼可以支持多個架構的CPU?

TAG:操作系統 | 彙編語言 | Linux內核 | 操作系統內核 |