1. 程式人生 > 實用技巧 >linux0.11boot之setup.s

linux0.11boot之setup.s

!
! setup.s (C) 1991 Linus Torvalds
!
! setup.s is responsible for getting the system data from the BIOS,
! and putting them into the appropriate places in system memory.
! both setup.s and system has been loaded by the bootblock.
!
! This code asks the bios for memory/disk/other parameters, and
! puts them 
in a "safe" place: 0x90000-0x901FF, ie where the ! boot-block used to be. It is then up to the protected mode ! system to read them from there before the area is overwritten ! for buffer-blocks. ! ! setup.s 負責從BIOS 中獲取系統資料,並將這些資料放到系統記憶體的適當地方。 ! 此時setup.s 和system 已經由bootsect 引導塊載入到記憶體中。 ! ! 這段程式碼詢問bios 有關記憶體/磁碟/其它引數,並將這些引數放到一個 ! “安全的”地方:0x90000-0x901FF,也即原來bootsect 程式碼塊曾經在 ! 的地方,然後在被緩衝塊覆蓋掉之前由保護模式的system 讀取。 ! ! NOTE! These had better be the same as
in bootsect.s! ! 以下這些引數最好和bootsect.s 中的相同! INITSEG = 0x9000 ! we move boot here - out of the way ! 原來bootsect 所處的段。 SYSSEG = 0x1000 ! system loaded at 0x10000 (65536). ! system 在0x10000(64k)處。 SETUPSEG = 0x9020 ! this is the current segment ! 本程式所在的段地址。 .globl begtext, begdata, begbss, endtext, enddata, endbss .text begtext:
.data begdata: .bss begbss: .text entry start start: ! ok, the read went well so we get current cursor position and save it for ! posterity. ! ok,整個讀磁碟過程都正常,現在將游標位置儲存以備今後使用。 mov ax,#INITSEG ! this is done in bootsect already, but... ! 將ds 置成#INITSEG(0x9000)。這已經在bootsect 程式中 ! 設定過,但是現在是setup 程式,Linus 覺得需要再重新 ! 設定一下。 mov ds,ax mov ah,#0x03 ! read cursor pos ! BIOS 中斷0x10 的讀游標功能號 ah = 0x03 ! 輸入:bh = 頁號 ! 返回:ch = 掃描開始線,cl = 掃描結束線, ! dh = 行號(0x00 是頂端),dl = 列號(0x00 是左邊)。 xor bh,bh int 0x10 ! save it in known place, con_init fetches mov [0],dx ! it from 0x90000. ! 上兩句是說將游標位置資訊存放在0x90000 處,控制檯 ! 初始化時會來取。 ! Get memory size (extended mem, kB) ! 下面3 句取擴充套件記憶體的大小值(KB)。 ! 是呼叫中斷0x15,功能號ah = 0x88 ! 返回:ax = 從0x100000(1M)處開始的擴充套件記憶體大小(KB)。 ! 若出錯則CF 置位,ax = 出錯碼。 mov ah,#0x88 int 0x15 mov [2],ax ! 將擴充套件記憶體數值存在0x90002 處(1 個字)。 ! Get video-card data: ! 下面這段用於取顯示卡當前顯示模式。 ! 呼叫BIOS 中斷0x10,功能號 ah = 0x0f ! 返回:ah = 字元列數,al = 顯示模式,bh = 當前顯示頁。 ! 0x90004(1 字)存放當前頁,0x90006 顯示模式,0x90007 字元列數。 mov ah,#0x0f int 0x10 mov [4],bx ! bh = display page mov [6],ax ! al = video mode, ah = window width ! check for EGA/VGA and some config parameters ! 檢查顯示方式(EGA/VGA)並取引數。 ! 呼叫BIOS 中斷0x10,附加功能選擇 -取方式資訊 ! 功能號:ah = 0x12,bl = 0x10 ! 返回:bh = 顯示狀態 ! (0x00 - 彩色模式,I/O 埠=0x3dX) ! (0x01 - 單色模式,I/O 埠=0x3bX) ! bl = 安裝的顯示記憶體 ! (0x00 - 64k, 0x01 - 128k, 0x02 - 192k, 0x03 = 256k) ! cx = 顯示卡特性引數(參見程式後的說明)。 mov ah,#0x12 mov bl,#0x10 int 0x10 mov [8],ax ! 0x90008 = ?? mov [10],bx ! 0x9000A = 安裝的顯示記憶體,0x9000B = 顯示狀態(彩色/單色) mov [12],cx ! 0x9000C = 顯示卡特性引數。 ! Get hd0 data ! 取第一個硬碟的資訊(複製硬碟引數表)。 ! 第1 個硬碟引數表的首地址竟然是中斷向量0x41 的向量值!而第2 個硬碟 ! 引數表緊接第1 個表的後面,中斷向量0x46 的向量值也指向這第2 個硬碟 ! 的引數表首址。表的長度是16 個位元組(0x10)。 ! 下面兩段程式分別複製BIOS 有關兩個硬碟的引數表,0x90080 處存放第1 個 ! 硬碟的表,0x90090 處存放第2 個硬碟的表。 mov ax,#0x0000 mov ds,ax lds si,[4*0x41] ! 取中斷向量0x41 的值,也即hd0 引數表的地址??ds:si mov ax,#INITSEG mov es,ax mov di,#0x0080 ! 傳輸的目的地址: 0x9000:0x0080 ?? es:di mov cx,#0x10 ! 共傳輸0x10 位元組。 rep movsb ! Get hd1 data mov ax,#0x0000 mov ds,ax lds si,[4*0x46] ! 取中斷向量0x46 的值,也即hd1 引數表的地址??ds:si mov ax,#INITSEG mov es,ax mov di,#0x0090 ! 傳輸的目的地址: 0x9000:0x0090 ?? es:di mov cx,#0x10 rep movsb ! Check that there IS a hd1 :-) ! 檢查系統是否存在第2 個硬碟,如果不存在則第2 個表清零。 ! 利用BIOS 中斷呼叫0x13 的取盤型別功能。 ! 功能號 ah = 0x15; ! 輸入:dl = 驅動器號(0x8X 是硬碟:0x80 指第1 個硬碟,0x81 第2 個硬碟) ! 輸出:ah = 型別碼;00 --沒有這個盤,CF 置位; 01 --是軟碟機,沒有change-line 支援; ! 02 --是軟碟機(或其它可移動裝置),有change-line 支援; 03 --是硬碟。 mov ax,#0x01500 mov dl,#0x81 int 0x13 jc no_disk1 cmp ah,#3 ! 是硬碟嗎?(型別 = 3 ?)。 je is_disk1 no_disk1: mov ax,#INITSEG ! 第2 個硬碟不存在,則對第2 個硬碟表清零。 mov es,ax mov di,#0x0090 mov cx,#0x10 mov ax,#0x00 rep stosb is_disk1: ! now we want to move to protected mode ... ! 從這裡開始我們要保護模式方面的工作了。 cli ! no interrupts allowed ! ! 此時不允許中斷。 ! first we move the system to it's rightful place ! 首先我們將system 模組移到正確的位置。 ! bootsect 載入程式是將system 模組讀入到從0x10000(64k)開始的位置。由於當時假設 ! system 模組最大長度不會超過0x80000(512k),也即其末端不會超過記憶體地址0x90000, ! 所以bootsect 會將自己移動到0x90000 開始的地方,並把setup 載入到它的後面。 ! 下面這段程式的用途是再把整個system 模組移動到0x00000 位置,即把從0x10000 到0x8ffff ! 的記憶體資料塊(512k),整塊地向記憶體低端移動了0x10000(64k)的位置。 mov ax,#0x0000 cld ! 'direction'=0, movs moves forward do_move: mov es,ax ! destination segment ! es:di??目的地址(初始為0x0000:0x0) add ax,#0x1000 cmp ax,#0x9000 ! 已經把從0x8000 段開始的64k 程式碼移動完? jz end_move mov ds,ax ! source segment ! ds:si??源地址(初始為0x1000:0x0) sub di,di sub si,si mov cx,#0x8000 ! 移動0x8000 字(64k 位元組)。 rep movsw jmp do_move ! then we load the segment descriptors ! 此後,我們載入段描述符。 ! 從這裡開始會遇到32 位保護模式的操作,因此需要Intel 32 位保護模式程式設計方面的知識了, ! 有關這方面的資訊請查閱列表後的簡單介紹或附錄中的詳細說明。這裡僅作概要說明。 ! ! lidt 指令用於載入中斷描述符表(idt)暫存器,它的運算元是6 個位元組,0-1 位元組是描述符表的 ! 長度值(位元組);2-5 位元組是描述符表的32 位線性基地址(首地址),其形式參見下面 ! 219-220 行和223-224 行的說明。中斷描述符表中的每一個表項(8 位元組)指出發生中斷時 ! 需要呼叫的程式碼的資訊,與中斷向量有些相似,但要包含更多的資訊。 ! ! lgdt 指令用於載入全域性描述符表(gdt)暫存器,其運算元格式與lidt 指令的相同。全域性描述符 ! 表中的每個描述符項(8 位元組)描述了保護模式下資料和程式碼段(塊)的資訊。其中包括段的 ! 最大長度限制(16 位)、段的線性基址(32 位)、段的特權級、段是否在記憶體、讀寫許可以及 ! 其它一些保護模式執行的標誌。參見後面205-216 行。 ! end_move: mov ax,#SETUPSEG ! right, forgot this at first. didn't work :-) mov ds,ax ! ds 指向本程式(setup)段。 lidt idt_48 ! load idt with 0,0 ! 載入中斷描述符表(idt)暫存器,idt_48 是6 位元組運算元的位置 ! (見218 行)。前2 位元組表示idt 表的限長,後4 位元組表示idt 表 ! 所處的基地址。 lgdt gdt_48 ! load gdt with whatever appropriate ! 載入全域性描述符表(gdt)暫存器,gdt_48 是6 位元組運算元的位置 ! (見222 行)。 ! that was painless, now we enable A20 ! 以上的操作很簡單,現在我們開啟A20 地址線。參見程式列表後有關A20 訊號線的說明。 call empty_8042 ! 等待輸入緩衝器空。 ! 只有當輸入緩衝器為空時才可以對其進行寫命令。 mov al,#0xD1 ! command write ! 0xD1 命令碼-表示要寫資料到 out #0x64,al ! 8042 的P2 埠。P2 埠的位1 用於A20 線的選通。 ! 資料要寫到0x60 口。 call empty_8042 ! 等待輸入緩衝器空,看命令是否被接受。 mov al,#0xDF ! A20 on ! 選通A20 地址線的引數。 out #0x60,al call empty_8042 ! 輸入緩衝器為空,則表示A20 線已經選通。 ! well, that went ok, I hope. Now we have to reprogram the interrupts :-( ! we put them right after the intel-reserved hardware interrupts, at ! int 0x20-0x2F. There they won't mess up anything. Sadly IBM really ! messed this up with the original PC, and they haven't been able to ! rectify it afterwards. Thus the bios puts interrupts at 0x08-0x0f, ! which is used for the internal hardware interrupts as well. We just ! have to reprogram the 8259's, and it isn't fun. !! 希望以上一切正常。現在我們必須重新對中斷進行程式設計?? !! 我們將它們放在正好處於intel 保留的硬體中斷後面,在int 0x20-0x2F。 !! 在那裡它們不會引起衝突。不幸的是IBM 在原PC 機中搞糟了,以後也沒有糾正過來。 !! PC 機的bios 將中斷放在了0x08-0x0f,這些中斷也被用於內部硬體中斷。 !! 所以我們就必須重新對8259 中斷控制器進行程式設計,這一點都沒勁。 mov al,#0x11 ! initialization sequence ! 0x11 表示初始化命令開始,是ICW1 命令字,表示邊 ! 沿觸發、多片8259 級連、最後要傳送ICW4 命令字。 out #0x20,al ! send it to 8259A-1 ! 傳送到8259A 主晶片。 .word 0x00eb,0x00eb ! jmp $+2, jmp $+2 ! $ 表示當前指令的地址, ! 兩條跳轉指令,跳到下一條指令,起延時作用。 out #0xA0,al ! and to 8259A-2 ! 再發送到8259A 從晶片。 .word 0x00eb,0x00eb mov al,#0x20 ! start of hardware int's (0x20) out #0x21,al ! 送主晶片ICW2 命令字,起始中斷號,要送奇地址。 .word 0x00eb,0x00eb mov al,#0x28 ! start of hardware int's 2 (0x28) out #0xA1,al ! 送從晶片ICW2 命令字,從晶片的起始中斷號。 .word 0x00eb,0x00eb mov al,#0x04 ! 8259-1 is master out #0x21,al ! 送主晶片ICW3 命令字,主晶片的IR2 連從晶片INT。 .word 0x00eb,0x00eb !參見程式碼列表後的說明。 mov al,#0x02 ! 8259-2 is slave out #0xA1,al ! 送從晶片ICW3 命令字,表示從晶片的INT 連到主芯 ! 片的IR2 引腳上。 .word 0x00eb,0x00eb mov al,#0x01 ! 8086 mode for both out #0x21,al ! 送主晶片ICW4 命令字。8086 模式;普通EOI 方式, ! 需傳送指令來複位。初始化結束,晶片就緒。 .word 0x00eb,0x00eb out #0xA1,al !送從晶片ICW4 命令字,內容同上。 .word 0x00eb,0x00eb mov al,#0xFF ! mask off all interrupts for now out #0x21,al ! 遮蔽主晶片所有中斷請求。 .word 0x00eb,0x00eb out #0xA1,al !遮蔽從晶片所有中斷請求。 ! well, that certainly wasn't fun :-(. Hopefully it works, and we don't ! need no steenking BIOS anyway (except for the initial loading :-). ! The BIOS-routine wants lots of unnecessary data, and it's less ! "interesting" anyway. This is how REAL programmers do it. ! ! Well, now's the time to actually move into protected mode. To make ! things as simple as possible, we do no register set-up or anything, ! we let the gnu-compiled 32-bit programs do that. We just jump to ! absolute address 0x00000, in 32-bit protected mode. !! 哼,上面這段當然沒勁??,希望這樣能工作,而且我們也不再需要乏味的BIOS 了(除了 !! 初始的載入?。BIOS 子程式要求很多不必要的資料,而且它一點都沒趣。那是“真正”的 !! 程式設計師所做的事。 ! 這裡設定進入32 位保護模式執行。首先載入機器狀態字(lmsw - Load Machine Status Word),也稱 ! 控制暫存器CR0,其位元位0 置1 將導致CPU 工作在保護模式。 mov ax,#0x0001 ! protected mode (PE) bit ! 保護模式位元位(PE)。 lmsw ax ! This is it! ! 就這樣載入機器狀態字! jmpi 0,8 ! jmp offset 0 of segment 8 (cs) ! 跳轉至cs 段8,偏移0 處。 ! 我們已經將system 模組移動到0x00000 開始的地方,所以這裡的偏移地址是0。這裡的段 ! 值的8 已經是保護模式下的段選擇符了,用於選擇描述符表和描述符表項以及所要求的特權級。 ! 段選擇符長度為16 位(2 位元組);位0-1 表示請求的特權級0-3,linux 作業系統只 ! 用到兩級:0 級(系統級)和3 級(使用者級);位2 用於選擇全域性描述符表(0)還是區域性描 ! 述符表(1);位3-15 是描述符表項的索引,指出選擇第幾項描述符。所以段選擇符 ! 8(0b0000,0000,0000,1000)表示請求特權級0、使用全域性描述符表中的第1 項,該項指出 ! 程式碼的基地址是0(參見209 行),因此這裡的跳轉指令就會去執行system 中的程式碼。 ! This routine checks that the keyboard command queue is empty ! No timeout is used - if this hangs there is something wrong with ! the machine, and we probably couldn't proceed anyway. ! 下面這個子程式檢查鍵盤命令佇列是否為空。這裡不使用超時方法 - 如果這裡宕機, ! 則說明PC 機有問題,我們就沒有辦法再處理下去了。 ! 只有當輸入緩衝器為空時(狀態暫存器位2 = 0)才可以對其進行寫命令。 empty_8042: .word 0x00eb,0x00eb ! 這是兩個跳轉指令的機器碼(跳轉到下一句),相當於延時空操作。 in al,#0x64 ! 8042 status port ! 讀AT 鍵盤控制器狀態暫存器。 test al,#2 ! is input buffer full? ! 測試位2,輸入緩衝器滿? jnz empty_8042 ! yes - loop ret gdt: ! 全域性描述符表開始處。描述符表由多個8 位元組長的描述符項組成。 ! 這裡給出了3 個描述符項。第1 項無用(206 行),但須存在。第2 項是系統程式碼段 ! 描述符(208-211 行),第3 項是系統資料段描述符(213-216 行)。每個描述符的具體 ! 含義參見列表後說明。 .word 0,0,0,0 ! dummy ! 第1 個描述符,不用。 ! 這裡在gdt 表中的偏移量為0x08,當載入程式碼段暫存器(段選擇符)時,使用的是這個偏移值。 .word 0x07FF ! 8Mb - limit=2047 (2048*4096=8Mb) .word 0x0000 ! base address=0 .word 0x9A00 ! code read/exec .word 0x00C0 ! granularity=4096, 386 ! 這裡在gdt 表中的偏移量是0x10,當載入資料段暫存器(如ds 等)時,使用的是這個偏移值。 .word 0x07FF ! 8Mb - limit=2047 (2048*4096=8Mb) .word 0x0000 ! base address=0 .word 0x9200 ! data read/write .word 0x00C0 ! granularity=4096, 386 idt_48: .word 0 ! idt limit=0 .word 0,0 ! idt base=0L gdt_48: .word 0x800 ! gdt limit=2048, 256 GDT entries ! 全域性表長度為2k 位元組,因為每8 位元組組成一個段描述符項 ! 所以表中共可有256 項。 .word 512+gdt,0x9 ! gdt base = 0X9xxxx ! 4 個位元組構成的記憶體線性地址:0x0009<<16 + 0x0200+gdt ! 也即0x90200 + gdt(即在本程式段中的偏移地址,205 行)。 .text endtext: .data enddata: .bss endbss: