2010年3月1日 星期一

Fast A20 和92H,及8042到底是什麼關係



關於A20 Gate
本來想直接寫一篇關於保護模式的文章,因為有一位讀者不斷地問我這個問題,隨著問題的深入,在評論上回答這個問題實在是太困難了,動起筆來,發現涉及的事情太多,免不了又是長篇大論惹人煩,而且要寫很長時間,不知道我能不能把它寫完,所以乾脆把一些問題分離出來寫,或許還可以堅持寫出來。
在許多PC的CMOS設置裏,都有一項叫做“A20 Enable“的設置,不知道大家是否就此設置困惑過,這個A20是什麼呢?
說A20則不得不說PC機的記憶體,我儘量用簡短的語言說明白PC機記憶體的一些獨有的術語,在我剛接觸PC機的時候(1985年前後),如果你的機器有一塊10M或者20M的硬碟,那已經是一台不錯的機器,如果你的機器有256K的記憶體,那絕對是高配置,那時候還沒有3.5"的軟碟,都是使用5"的容量僅360K的軟碟,所以那個時候,設計IBM PC的IBM公司非常自信地認為,1M記憶體是一個根本達不到的天文數字,我想由於這種思想的作怪(我猜的),IBM非常愚蠢地把PC機1M存儲空間的最上面的384K用作了ROM和系統設備,這種設計給現在的PC機的記憶體結構埋下了麻煩的伏筆,如果當初IBM把PC機的1M存儲空間的最下面的384K用作ROM和系統設備,可能現在就會少好多麻煩,這個後面說。
寫到這裏的時候,我想起了許多事,大家是否知道我國的第一台電子電腦是什麼型號?是什麼樣子?是1958年生產的103機,後來又有了全電晶體的電子電腦,很榮幸的是,在我工作過的單位裏,曾經有過這麼一台全電晶體的電子電腦,就是那種使用穿孔紙帶輸入電腦程式,占地好幾百平方米,一旦開動需要幾十人進行維護的傢伙,我到這個單位的時候,這台機器早已不再運轉的,80年代的時候,我有幸參加了這台機器的拆解工作,給我印象最深的是,我終於看到了當時在教科書裏說的“磁鼓”(估計現在的教科書裏也沒有了),一種看上去像篩子一樣的東西,那就是當年的記憶體,用於在紙帶上打孔進行程式輸入的穿孔機我用過,樣子很像老式的英文打字機。還有那種8英寸的軟碟,估計現在也很少有人見過了。
好了言歸正傳,大家都知道,8088/8086只有20位位址線,按理它的定址空間是2^20,應該是1024KB,但PC機的定址結構是segment:offset,segment和offset都是16位的寄存器,最大值是0ffffh,換算成物理位址的計算方法是把segment左移4位,再加上offset,所以segment:offset所能表達的定址空間最大應為0ffff0h + 0ffffh = 10ffefh(前面的0ffffh是segment=0ffffh並向左移動4位的結果,後面的0ffffh是可能的最大offset),這個計算出的10ffefh是多大呢?大約是1088KB,就是說,segment:offset的位址表達能力,超過了20位元位址線的物理定址能力,你說這是不是有點麻煩。在早先,由於所有的機器都沒有那麼大的記憶體,加上位址線只有20位,所以當你用segment:offset的方式企圖定址100000h這個位址時,由於沒有實際的第21位位址線,你實際定址的記憶體是00000h的位置,如果你企圖定址100001h這個位址時,你實際得到的內容是位址00001h上的內容,所以這個事對實際使用幾乎沒有任何影響,但是後來就不行了,出現了80286,地址線達到了24位,使segment:offset定址100000h--10ffefh這將近64K的記憶體成為可能,為了保持向下相容,於是出現了A20 Gate,這是後話,我們後面再細說。
我們可能經常聽到一些只有在PC機上才有的一些關於記憶體的專有名詞,包括:常規記憶體(Conventional Memory)、上位記憶體區(Upper Memory Area)、高端記憶體區(High Memory Area)和擴展記憶體(Extended Memory),我儘量把這幾個東東說明白,這需要上面這張著名的圖。

這張圖很清楚地說明了問題,大家都知道,DOS下的“常規記憶體”只有640K,這640K就是從0--A0000H這段位址空間;所謂“上位記憶體區”,指的就是20位位址線所能定址到的1M位址空間的上面384K空間,就是從A0001H--100000H這段位址空間,也就是我們說的用於ROM和系統設備的位址區域,這384K空間和常規記憶體的640K空間加起來就是20位位址線所能定址的完整空間1024KB;由於80286和80386的出現使PC機的位址線從20位變成24位又變成32位,定址能力極大地增加,1M以上的記憶體定址空間,我們統稱為“擴展記憶體”;這裏面絕大部分記憶體區域只能在保護模式下才能定址到,但有一部分既可以在保護模式下,也可以在實模式下定址,這就是我們前面提到過的位址100000h--10ffefh之間的這塊記憶體,為了表明其特殊性,我們把這塊有趣的記憶體區叫做“高端記憶體”。
前面我們提過由於IBM的愚蠢設計給PC機的記憶體結構埋下了麻煩的伏筆,現在我們來說說這個麻煩。我們都見過PC機上的記憶體條,但是由於上位記憶體區的存在,這個記憶體條上的位址居然不能連續,就是說,這個記憶體條上要有0--A0000H的位址空間,還要有100000h--最大記憶體容量的位址空間,中間的384K位址空間必須留出來給ROM用,在現如今一個晶片就好幾兆的情況下,你說這個記憶體條應該怎麼做,當然我相信一定是可以做出來的,但肯定很麻煩,如果當初IBM把這個“上位記憶體區”放在位址低端,就是0--6000h這一部分,豈不是這個麻煩就沒有了?!
但是,實際的記憶體條上位址都是連續的,並沒有人把這段位址空間留出來給ROM使用,原因很簡單,採用技術手段把這段位址空間空出來,比浪費這384K記憶體的成本還要高,所以在這個位址區域就出現了很奇怪的現象,ROM和RAM的地址重疊。 實際上,往往ROM並不能完全覆蓋整個384K區域,這樣就會有一些位址沒有被ROM佔用,那麼這部分位址上的RAM仍然是可以使用的。實際上,和ROM重疊的這384K RAM一般也不會被浪費,說到這裏,不得不說所謂的ROM Shadowing了,RAM和ROM的性能是有很大差異的,RAM的存取速度要遠遠大於ROM,而且RAM可以32位存取,ROM通常只能16位,所以目前的PC機對這塊RAM和ROM重疊的區域的處理採用一種ROM Shadowing的技術方式,當機器加電後,先讓ROM有效,RAM無效,然後讀出ROM內容,再讓ROM無效,RAM有效,把讀出的ROM內容放到相同位址的RAM中,並把相應位置的RAM設定為唯讀,這樣就把ROM搬到了RAM中,位址完全一樣,只是性能比使用ROM要高些,這塊RAM就好像ROM的Shadow一樣。
回到我們的主題A20 Gate,出現80286以後,為了保持和8086的相容,PC機在設計上在第21條位址線(也就是A20)上做了一個開關,當這個開關打開時,這條位址線和其他位址線一樣可以使用,當這個開關關閉時,第21條位址線(A20)恒為0,這個開關就叫做A20 Gate,很顯然,在實模式下要訪問高端記憶體區,這個開關必須打開,在保護模式下,由於使用32位位址線,如果A20恒等於0,那麼系統只能訪問奇數兆的記憶體,即只能訪問0--1M、2-3M、4-5M......,這顯然是不行的,所以在保護模式下,這個開關也必須打開。
下面我們來看一下PC機是怎麼實現A20 Gate的。
早期的PC機,控制鍵盤有一個單獨的單片機8042,現如今這個晶片已經給集成到了其他大片子中,但其功能和使用方法還是一樣,當PC機剛剛出現A20 Gate的時候,估計實在找不到控制它的地方了,同時為這點小事也不值得增加晶片,於是工程師使用這個8042鍵盤控制器來控制A20 Gate,但A20 Gate真的和鍵盤一點關係也沒有,我們先從軟體的角度簡單介紹一下8042這個晶片。
8042有4個寄存器
· 1個8-bit長的Input buffer;Write-Only;
· 1個8-bit長的Output buffer; Read-Only;
· 1個8-bit長的Status Register;Read-Only;
· 1個8-bit長的Control Register;Read/Write。
有兩個埠位址:60h和64h。
· 讀60h埠,讀output buffer
· 寫60h埠,寫input buffer
· 讀64h埠,讀Status Register
對Control Register的操作相對要複雜一些,首先要向64h埠寫一個命令(20h為讀命令,60h為寫命令),然後根據命令從60h埠讀出Control Register的資料或者向60h埠寫入Control Register的資料(64h埠還可以接受許多其他的命令)。
先來看看Status Register的定義,我們後面要用bit 0和bit 1:
bit meaning ----------------------------------------------------------------------- 0 output register (60h) 中有數據

1 input register (60h/64h) 有數據

2 系統標誌(上電重定後被置為0)

3 data in input register is command (1) or data (0)

4 1=keyboard enabled, 0=keyboard disabled (via switch)

5 1=transmit timeout (data transmit not complete)

6 1=receive timeout (data transmit not complete)

7 1=even parity rec'd, 0=odd parity rec'd (should be odd)
除了這些資源外,8042還有3個內部埠:Input Port、Outport Port和Test Port,這三個埠的操作都是通過向64h發送命令,然後在60h進行讀寫的方式完成,其中本文要操作的A20 Gate被定義在Output Port的bit 1上,所以我們有必要對Outport Port的操作及埠定義做一個說明。
· 讀Output Port向64h發送0d0h命令,然後從60h讀取Output Port的內容
· 寫Output Port向64h發送0d1h命令,然後向60h寫入Output Port的資料
另外我們還應該介紹兩個命令:
· 禁止鍵盤操作命令向64h發送0adh
· 打開鍵盤操作命令向64h發送0aeh
有了這些命令和知識,我們可以考慮操作A20 Gate了,有關8042晶片更詳細的資料,請參考該晶片的Data Sheet。
如何打開和關閉A20 Gate。
理論上講,我們只要操作8042晶片的輸出埠(64h)的bit 1,就可以控制A20 Gate,但實際上,當你準備向8042的輸入緩衝區裏寫資料時,可能裏面還有其他資料沒有處理,所以,我們要首先禁止鍵盤操作,同時等待資料緩衝區中沒有資料以後,才能真正地去操作8042打開或者關閉A20 Gate。打開A20 Gate的具體步驟大致如下:
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

WaitInbufEmpty:

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
後來,由於感覺使用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, 92hin al, dx如果al的bit 1為1表示a20打開,否則為0
· 打開A20mov dx, 92hmov al, 02out dx, al
· 關閉A20mov dx, 92hmov al, 0out dx, al
特別要注意的是,大家從這篇文章的文字中可能也能感覺到,A20 Gate的設計本身就讓人感覺很彆扭,不是那麼流暢,所以和A20有關的事情就難免也會有相同的感覺,很奇怪的是,上面介紹的三種方法並不是在每台機器上都適用,所以如果你要做一個商業軟體其中要操作A20,那一定要三種方式聯合使用才比較穩妥,否則會有意想不到的結果,LINUX公開的啟動代碼中就是這麼做的
在DOS下有時我們會在config.sys中寫上一句:dos=high,這句就會把駐留的DOS放到高端記憶體區域去,怎麼放的呢?關鍵點就是打開A20,然後把DOS從常規記憶體搬到100000h起始的區域去,並把在常規記憶體中佔用的記憶體釋放掉,當然說起來容易,實際做的時候還有很多細節要處理。
說到DOS=HIGH,就不得不提醒大家另一件事,如果在config.sys中有dos=high這一句,那麼恐怕92h的方法和BIOS的方法都會不完全靈驗(至於操作8042的方法靈不靈我沒有試),這是DOS做了手腳,因為DOS被放到了高端記憶體中,為了保證DOS能正常運行,它不允許你把A20給關掉,遇到這種情況不要驚慌,不是我寫得不對,確實是DOS太狡猾了。
還有最後一點要特別注意,92h的bit 0是給機器發重定信號的(8042 Output Port的bit 0也是),所以在向92h寫資料時,千萬不要讓bit 0為1,否則機器會重新啟動,如果你的應用程式需要重新啟動機器,這也是方法之一,比jmp 0ffffh:0來的還要乾脆。
在其他我們介紹保護模式的文章中,我們會用到上面提到的打開A20的方法,屆時可能就不會做更多的解釋了。
另外,涉及操作A20的資料其實很少,有些資料我手裏也很缺乏,比如92h除bit 0和bit 1以外的定義是什麼我至今也不知道,包括本文中的一些內容也是摸索所得,並沒有資料予以佐證,所以萬一有不對的地方,不要見怪,並懇請指出。
--> 閱讀更多...