Linux_關於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),我儘量把這幾個東東說明白,這需要下面這張著名的圖。

關於A20 Gate - whowin - DOS編程技術

    這張圖很清楚地說明了問題,大家都知道,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, 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

    特別要注意的是,大家從這篇文章的文字中可能也能感覺到,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以外的定義是什麼我至今也不知道,包括本文中的一些內容也是摸索所得,並沒有資料予以佐證,所以萬一有不對的地方,不要見怪,並懇請指出。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章