1. 程式人生 > >linux啟動時軟盤引數表

linux啟動時軟盤引數表

From:http://docs.huihoo.com/gnu_linux/own_os/index.htm

1.5 Loading Processing

[Home]  [Top]  [Previous]  [Next]

 1.5.1 Overview

我們在After Power-On一節已經介紹——當主機被啟動之後,會自動將軟盤或硬碟的第一個扇區的內容(boot block for floppy, master boot for hard disk)Load到記憶體0000:7c00H的位置,然後執行它。但OS的核心絕對不僅僅只有一個扇區大小。這就意味著boot block需要進一步的從磁碟(軟盤或硬碟)上讀取OS的剩餘部分。

事實上,從機器被Power-On或Reset之後,到OS的Kernel被正式的載入到執行,中間可以經歷這些過程:

  1. 由BIOS INT 19中斷讀入Master boot;
  2. 由Master boot讀入並執行Boot Block;
  3. 由Master boot讀入並執行Second Booter(Monitor or Setup Program);
  4. 最後由Second booter讀入並執行OS Kernel。

比如Linux的啟動過程為:

Master boot(LILO)Boot Block(bootsect.S)Setup(Setup.S)Linux Kernel

Loading Processing是Booting階段的一個重要任務。為了很好的完成這一任務,我們必須清楚的瞭解磁碟(硬碟和軟盤)操作的詳細細節,對Loading過程和各個部分記憶體佈局進行詳細的規劃。

 1.5.2 Memory Layout

在Real mode下,磁碟操作可以通過兩種方法進行,BIOS 13H中斷,或者通過讀寫埠。而在Protected Mode下,BIOS中斷不可用,只能通過讀寫埠的方式。而BIOS中斷簡單易用而且功能強大,同時我們在Booting階段除了Loading Processing之外,所作的許多工作都需要依賴BIOS中斷,所以我們Loading Processing應該發生在Real Mode下,通過BIOS中斷呼叫來完成。

但Real Mode下的一種重要限制是1MB記憶體的侷限,事實上僅有不到640 KB的可用RAM,我們必須把啟動階段所需要的各個程式,以及在啟動階段獲取到的各種硬體引數,以及OS Kernel放入到這不到640 KB的RAM中。這樣一來,我們如何在整個Booting階段,合理有效的安排記憶體佈局就顯得非常重要。

主機在Power-on或Reset後,經過POST,然後讀取磁碟的第一扇區之後,常規記憶體的佈局如上圖所示。

我們在進入保護模式之前BIOS Interrupt Ventors Table, BIOS Data Area和Extend BIOS Data Area都可能會有用,即使進入Protected Mode之後,如果你想讓你的OS支援VM8086模式,也就是說如果你想讓你的OS上可以執行Real Mode軟體,這兩個區域也會用得著,所以不要去對這些內容進行修改。令人不解的是,為什麼BIOS廠商將BIOS Data Area和Extend BIOS Data Area分別放在常規RAM的頂端和底部。在進行記憶體佈局規劃的時候,面臨著令人非常困擾的選擇。這也是為什麼Linux對640KB常規記憶體進行規劃的時候,將最底部的64KB保留,而最頂部的64KB中只有低地址的16KB被使用,其它的部分都被保留的原因。

由於First Boot Block只有512 bytes的大小,所以它所能做的事情非常有限,如果我們想在Real Mode下作更多的事情,我們必須有一個Second booter,它一方面負責獲取各種物理裝置引數,這些引數將來要被OS Kernel使用;另一方面,如果OS Kernel執行在Protected Mode下的話,The Second Booter需要為進入Protected Mode做各種準備,並進入Protected Mode。

既然The Second Booter沒有侷促的大小限制——最起碼幾十K是沒有問題的,如果用匯編的話,幾十K已經足夠了——那麼我們就沒有必要花費心思讓The First Boot Block做太多的工作,我們只需要讓The Second Booter來做就可以了。The First Boot Block需要做的只是檢測啟動裝置型別,並將The Second Booter裝入RAM。

當載入OS Kernel時,你需要仔細決定將其載入到哪個位置。OS Kernel可能會比較大,這時候,你可以將Kernel的Image壓縮,然後將其讀入到常規記憶體中;讀入之後將其解壓,將解壓後的Kernel放置到擴充套件記憶體中(1M以上的位置)。

How to place big kernel into extend memory?

1.Linux大核心方式(bzImage)所用的方法,使用int 15h,每次拷貝64k資料

2.用BIOS所用的方法,臨時切換到保護模式, 設定某一個段描述為0~4G,再切換回真實模式,引用這個段,就可以自由訪問4G空間了

1.5.3 Floppy System

相對於其他大多數子系統,軟盤BIOS服務的相關資料要詳盡的多。從第一臺PC機開始,軟盤系統的基本控制器介面就基本保持未變。當然,AT還加入了一些新的BIOS服務,最新的系統還支援2.88M的軟盤。

軟盤的訪問要通過適配卡上的硬體和內置於系統主BIOS內的BIOS軟體。該組合提供了簡單方便的格式化,讀和寫軟盤操作,從AT開始起,BIOS所支援的軟盤驅動器的數目從四個減少到兩個以內。訪問兩個以上的軟碟機需要專門的介面卡硬體和驅動程式,比如MS-DOS的Driver.sys。

上面兩圖表現的是軟盤的邏輯結構,每個軟盤分為柱面(Cylinder),磁軌(Track),扇區(Sector),最小的組成部分是扇區,通常每個扇區有512 Bytes的資料。多個扇區組成一個磁軌。例如,一個1.44 MB的軟盤每磁軌有18個扇區,其它軟盤型別每個磁軌有9到36個扇區,這取決於在每英寸介質上可以可靠儲存的位數。兩個對稱的磁軌,軟盤的一面各一個,定義為一個柱面。軟盤有40或80個柱面,這取決於介質型別。

事實上,一個扇區的座標位置由柱面,磁軌,和扇區三個因素的座標共同決定:X-柱面(0-39或0-79),Y-磁軌(0-1),Z-扇區(0-9或0-18或0-36)。如下圖所示:

下表總結了由512 bytes扇區組成的各種軟盤的引數。此表也列出了所使用的傳輸率,單位是bit/second。它是資料在Driver和Controler之間傳輸的速率。傳輸速率取決於軟盤的容量、Driver型別以及軟盤轉動的速率,單位是RPM(Running Per Minute)轉每分。軟盤轉得越快,每線性英寸的介質上被選中的資料就越多,傳輸速率總是固定的,軟體不能對其進行定義。

驅動器及軟盤型別 每面磁軌數 每磁軌扇區數 RPM 傳輸速率
360K,5.25英寸 40 9 300 250Kbps
1.2M,5.25英寸 40 9 360 300Kbps
1.2M,5.25英寸 80 15 360 500Kbps
720K,3.5英寸 80 9 360 250Kbps
1.44M,3.5英寸 80 9 360 250Kbps
1.44M,3.5英寸 80 18 360 500Kbps
2.88M,3.5英寸 80 9 360 250Kbps
2.88M,3.5英寸 80 18 360 500Kbps
2.88M,3.5英寸 80 36 360 1Mbps

表1-5-1 軟盤種類表

軟盤引數表

在BIOS的中斷向量表中斷1Eh的地址0:78h處(參見A.1 BIOS Interrupt Overview),儲存著指向存放於系統BIOS ROM中的軟盤引數表的遠指標(遠指標指需要通過segment:offset的方式來制定的指標,它所指向的地址可以跨越不同的64K段;而近指標則只需要通過offset來制定同一段內的另外一個地址)。裡面包含了有關驅動器(Driver)的資訊。BIOS使用這個表來為軟盤控制器程式設計和指定軟碟機的控制定時——當某個程式呼叫13h中斷來使用軟盤服務時,BIOS將會使用軟盤引數表中提供的資料來對軟盤控制器進行程式設計。

軟盤引數表是一個重要的概念——如果系統上只有一個驅動器型別,正像早期的PC和XT系統一樣。引數表在早期的PS/2上工作得也很好,在這裡要從兩種型別的驅動器中選擇一個,比如3.5英寸,1.44M。軟盤引數表用於完整的定義驅動器引數。

下表為軟盤引數表的概要內容:

偏移量 描述
0 軟盤控制器(埠3F5h)
1 軟盤控制器(埠3F5h)
2 驅動器休眠馬達關閉延時
3

每扇區位元組數:

0=128 bytes;1=256 bytes;2=512 bytes;3=1024 bytes;

4=2048 bytes;5=4096 bytes;7=16384 bytes;8-FFh=Reserverd

4

每磁軌的扇區數:

9=9 sectors;360K和720K

0Fh=15 sectors;1.2M

12h=18 sectors;1.44M

24h=36 sectors;2.88M

5

扇區之間的間隙長度:

1Bh for 1.2M,1.44M and 2.88M floppy

2Ah for 360K and 720K floppy

6 資料長度
7 格式化間隙長度
8 格式化填充位元組
9 磁頭定位時間
A 等待馬達開啟時間

從軟盤讀取資料,不能夠跨越磁軌,即每次讀取,儘管可以同時讀取多個扇區,而這些扇區卻必須都屬於同一個磁軌。BIOS 13h服務例程將對此進行驗證,如果發現使用者所要讀取扇區跨越了不同的磁軌,則返回呼叫失敗。而驗證所依據的資料則是軟盤引數表。假如,軟盤引數表中所記錄的“每磁軌的扇區數”(偏移量為4的位置)為9;如果現在一個INT 13h, ax = 2的呼叫指定:磁頭為0,磁軌為1,磁軌內起始扇區為2,要讀取的扇區數目為9;由於磁軌內起始扇區+要讀取的扇區數-1 = 2+9 -1 = 10 〉9,則呼叫將會失敗。(之所以減去1是因為磁軌內起始扇區是從1開始計數的,而不是0)。

但這並不意味著,如果通過了BIOS軟盤引數表的驗證,而讀取就一定能夠成功。例如,軟盤引數表中所記錄的“每磁軌的扇區數”為18,而實際的“每磁軌的扇區數”為9,如果現在一個INT 13h, ax = 2的呼叫指定:磁頭為0,磁軌為1,磁軌內起始扇區為2,要讀取的扇區數目為9;由於磁軌內起始扇區+要讀取的扇區數-1 = 2+9 -1 = 10 < 18,通過軟盤引數表的驗證沒有任何問題,但當軟盤驅動器真正去讀取磁頭0,磁軌1,第10個扇區的時候,而這個扇區並不存在,呼叫依然會失敗。

所以,為了保證一個需要讀取軟盤的程式不出錯誤,我們必須保證:

“要讀取的扇區數” + “磁軌內起始扇區” - 1 <= 軟盤引數表中“每磁軌的扇區數”的數值

同時保證:

“要讀取的扇區數” + “磁軌內起始扇區” - 1 <= 實際的“每磁軌的扇區數”

你如果想保證你的程式永遠不在讀取軟盤的時候出此類錯誤,最穩妥的方法是每次只讀取一個扇區,但這樣效率很低,真正作軟體的人都願意有效率更高,而又穩妥地方法——所以我們要在提高效率的目標下,尋找一種方法來確保效率。

我們還是看一看上面所列的兩個條件,從中我們可以首先得知——我們必須知道實際的“每磁軌的扇區數”,否則,我們肯定會出錯。那麼,在我們知道實際的“每磁軌的扇區數”的前提下,軟盤引數表中“每磁軌的扇區數”的數值不能小於實際的“每磁軌的扇區數”,否則,仍然會出錯。那麼大於呢?由於BIOS對軟盤引數表中“每磁軌的扇區數”的數值的驗證放在前面,如果大於的話,這一級驗證即使通過了,還有實際的“每磁軌的扇區數”這一關需要通過。所以大於是不會有任何問題的。等於就更不用說了。

由此得知,要想讓我們的程式不出此類錯誤,我們必須保證兩點:

1、保證 軟盤引數表中“每磁軌的扇區數”的數值 大於等於 實際的“每磁軌的扇區數”。

2、知道實際的“每磁軌的扇區數”;

對於第1點,或許大家會問,難道BIOS廠商不能夠來保證這一點嗎?答案是,能夠。但不幸的是,在現實生活中,由於歷史的原因,確確實實有一些BIOS廠商將軟盤引數表中“每磁軌的扇區數”的數值設定的比較小。這樣,我們就必須靠自己來保證這一點。

如何保證軟盤引數表中“每磁軌的扇區數”大於等於實際的“每磁軌的扇區數”? 

我們在前面已經說過,在BIOS的中斷向量表中斷1Eh的地址0:78h處,放置的僅僅是指向軟盤引數表的的指標,而軟盤引數表被放在BIOS ROM中,我們無法修改。所以,我們可以在RAM中複製一份軟盤引數表,進行修改。然後將BIOS的中斷向量表中斷1Eh的地址0:78h處的遠指標修改為指向RAM中新複製的軟盤引數表。

由於到目前為止,最大可能的“每磁軌扇區數”為36(參見表1-5-1),所以我們只需要將新的軟盤引數表中的“每磁軌扇區數”修改為36,即可以保證軟盤引數表中“每磁軌的扇區數”的數值大於等於實際的“每磁軌的扇區數”。

下面是Linux 2.4的相關程式碼。

# 將0:78h位置所儲存的磁碟引數表指標所指向的位置的12格位元組拷貝到0x4000-12的 # 位置處,然後將磁碟新引數表的sectors/track值修改為36(2.88M軟盤的引數), # 最後修改0:78h處的指標指向0x4000-12 (es:di) #  movw   %cx, %fs            # %fs = 0  movw   $0x78, %bx       # %fs:%bx is parameter table address  pushw  %ds  ldsw     %fs:(%bx), %si    # %ds:%si is source  movb   $6, %cl                # copy 12 bytes  pushw  %di                     # %di = 0x4000-12.  rep                                  # don't worry about cld  movsw                            # already done above  popw    %di  popw    %ds  movb    $36, 0x4(%di)     # patch sector count  movw    %di, %fs:(%bx)  movw    %es, %fs:2(%bx)

如何獲取實際的“每磁軌的扇區數”?

除了去猜測之外,沒有什麼好的方法能夠獲取“實際的每磁軌扇區數”。所幸的是,實際存在的軟盤只有有限的幾個種類,而不同的“實際的每磁軌扇區數”就更加有限。參見表1-5-1,我們可以得知,“實際的每磁軌扇區數”有9,15,18,36四種。於是,我們就可以對一張軟盤使用這四個數值從大到小依次猜測。

猜測的方法,是讀取磁頭0,磁軌0,當前磁軌最大扇區。首先猜測“當前磁軌最大扇區”為36,如果實際的“每磁軌扇區數”小於36,則讀取失敗。然後猜測“當前磁軌最大扇區”為18,如果失敗了,再猜測15……依次類推,直到猜測為9為止,這是最小可能的“每磁軌扇區數”。

下面是Linux 2.4的相關程式碼。

# Get disk drive parameters, specifically number of sectors/track.

# It seems that there is no BIOS call to get the number of sectors.

# Guess 36 sectors if sector 36 can be read, 18 sectors if sector 18

# can be read, 15 if sector 15 can be read.  Otherwise guess 9.

# Note that %cx = 0 from rep movsw above.

        movw   $disksizes, %si   # table of sizes to try

probe_loop:  lodsb                                # Load byte at address DS:SI to AL (DN)  cbtw                                  # extend to word (ax = (disksizes))  movw   %ax, sectors        # sectors = max sectors per track (DN)  cmpw   $disksizes+4, %si # there is 4 parameters

        jae       got_sectors            # If all else fails, try 9

    # 讀取第一個軟碟機的Head 0, Track 0, Max sector

        xchgw   %cx, %ax             # %cx = track and sector  xorw     %dx, %dx             # drive 0, head 0  movw   $0x0200, %bx      # address = 512, in INITSEG (%es = %cs)  movw   $0x0201, %ax      # service 2, 1 sector  int        $0x13  jc          probe_loop          # try next value

got_sectors: