bootsect.s 分析—— Linux-0.11 學習筆記(一)
bootsect.s
分析—— Linux-0.11
學習筆記(一)
為了節省篇幅,完整的程式碼就不貼了。感興趣的朋友可以去下載,下載地址是:
http://oldlinux.org/Linux.old/
本文,我打算詳解bootsect.s
。如有紕繆,還請各位看官斧正。關於如何講好程式碼,我暫時沒有找到什麼好的展示方法。姑且貼一段、註釋一段、講一段吧。為了不使程式碼片太長,我刪去了一些原來的註釋。
一些符號常量
SYSSIZE = 0x3000 ;system模組的長度
.globl begtext, begdata, begbss, endtext, enddata, endbss
.text
begtext:
.data
begdata:
.bss
begbss:
.text
SETUPLEN = 4 ! setup模組的長度,4個扇區
BOOTSEG = 0x07c0 ! original address of boot-sector
INITSEG = 0x9000 ! bootsect把自身搬運到0x90000
SETUPSEG = 0x9020 ! setup模組被載入到 0x90200
SYSSEG = 0x1000 ! system模組被載入到0x10000
ENDSEG = SYSSEG + SYSSIZE ! where to stop loading, 0x1000 + 0x3000 = 0x4000, 停止載入的段地址
ROOT_DEV = 0x306 !第2個硬碟的第1個分割槽
ROOT_DEV = 0x306
,這裡的0x306
表示第2個硬碟的第1個分割槽,當年Linus是在第2個硬碟的第1個分割槽上安裝了Linux-0.11作業系統。
老式Linux裝置號的命名規則
裝置號 = 主裝置號 * 256 + 次裝置號
或者說:
dev_no = (major << 8) + minor
這裡的主裝置號是事先定義好的(1-記憶體,2-磁碟,3-硬碟,4-ttyx,5-tty,6-並行口,7-非命名管道)。譬如對於硬碟,主裝置號為3,因此3*256+0=0x300即為系統中第一個硬碟的裝置號。更多的例子如下表:
裝置號 | 裝置檔案 | 對應的裝置 |
---|---|---|
0x300 | /dev/hd0 | 系統中第一個硬碟 |
0x301 | /dev/hd1 | 系統中第一個硬碟的第一分割槽 |
0x302 | /dev/hd2 | 系統中第一個硬碟的第二分割槽 |
0x303 | /dev/hd3 | 系統中第一個硬碟的第三分割槽 |
0x304 | /dev/hd4 | 系統中第一個硬碟的第四分割槽 |
0x305 | /dev/hd5 | 系統中第二個硬碟 |
0x306 | /dev/hd6 | 系統中第二個硬碟的第一分割槽 |
0x307 | /dev/hd7 | 系統中第二個硬碟的第二分割槽 |
0x308 | /dev/hd8 | 系統中第二個硬碟的第三分割槽 |
0x309 | /dev/hd9 | 系統中第二個硬碟的第四分割槽 |
bootsect 把自己搬運到 0x90000,並跳轉
entry _start
_start:
mov ax,#BOOTSEG
mov ds,ax !ds = 0x07c0
mov ax,#INITSEG
mov es,ax !ex = 0x9000
mov cx,#256 !搬運256次
sub si,si !si = 0
sub di,di !di = 0
!ds:si=0x07c0:0x0, es:di=0x9000:0x0
rep
movw !每次搬運2個位元組
jmpi go,INITSEG !跳轉到 0x9000:go
以上程式碼表示把ds:si
處(實體地址0x7c00)的內容搬運到es:di
(實體地址0x90000),一共搬運512位元組,即主引導扇區把自己移動到了0x90000處。
對於movw指令,可以參考我的博文。
http://blog.csdn.net/longintchar/article/details/50949923
我的疑問是,Linus為什麼沒有清除DF標誌呢?是不是設定DF=0會更嚴謹呢?
jmpi go,INITSEG
段間跳轉,INITSEG
是段地址,go
是偏移地址。這句話執行完,CPU就一下子跑到了0x9000:go
處執行了。(下圖中左邊的藍色箭頭,點選圖片可放大)
跳轉後繼續執行下面的指令,設定ds,es,ss和sp.
go: mov ax,cs
mov ds,ax
mov es,ax !ds=es=cs=0x9000
mov ss,ax
mov sp,#0xFF00
!es:sp = 0x9000:0xff00 ,棧的設定
載入 setup 模組到 0x90200
load_setup:
mov dx,#0x0000 ! 驅動器號(DL)0,磁頭號(DH)0
mov cx,#0x0002 ! 起始扇區號2, 磁軌號0
mov bx,#0x0200 ! 偏移地址0x200
mov ax,#0x0200+SETUPLEN ! 功能號AH=0x02,AL=要讀的扇區數目=SETUPLEN=4
int 0x13 ! read it
jnc ok_load_setup ! ok - continue
mov dx,#0x0000 !需要復位的驅動器號=DL=0
mov ax,#0x0000 !功能號AH=0
int 0x13 ! 復位磁碟
j load_setup
以上程式碼利用INT 13H, AH=02H
把setup模組從磁碟(2~5扇區)載入到0x90200後面。
注意:柱面號和磁頭號都從0開始,扇區號從1開始。
INT 13H AH=02H:讀扇區
此功能從磁碟上把一個或更多的扇區內容讀進記憶體。這是一個低階功能,在一個操作中讀取的全部扇區必須在同一條磁軌上。
引數 | 說明 |
---|---|
入口引數 | |
AH | =02H ,指明呼叫讀扇區功能。 |
AL | 要讀的扇區數目,不允許使用讀磁軌末端以外的數值,也不允許使該暫存器為0。 |
DL | 需要進行讀操作的驅動器號,0表示軟盤,80H表示硬碟。 |
DH | 所讀磁碟的磁頭號。 |
CH | 磁軌號的低8位數(磁軌號共10位)。 |
CL | 低5位放入所讀起始扇區號,位7-6表示磁軌號的高2位。 |
ES:BX | 讀出資料的緩衝區地址。 |
返回引數 | |
CF | =0,操作成功;=1,操作失敗。 |
AH | 錯誤返回碼。 |
AL | 實際讀到的扇區數。 |
INT 13H AH=00H:磁碟控制器復位
此功能用於復位磁碟(軟盤和硬碟)。當磁碟I/O功能呼叫出現錯誤時,需要呼叫此功能。
引數 | 說明 |
---|---|
入口引數 | |
AH | =00H,指明呼叫復位磁碟功能。 |
DL | 需要復位的驅動器號。軟盤:00H-7FH;硬碟:80H-FFH |
返回引數 | |
CF | =0,操作成功;=1,則操作失敗 |
AH | 錯誤返回碼。 |
獲得磁碟驅動器引數(主要是每磁軌的扇區數量)
ok_load_setup:
! Get disk drive parameters, specifically nr of sectors/track
mov dl,#0x00 !驅動器號為0,說明是軟盤
mov ax,#0x0800 ! AH=8 is get drive parameters
int 0x13
mov ch,#0x00 !這裡用不上軟盤的最大磁軌號,可以使CH=0
seg cs !把段超越字首設定為cs,隻影響下一條語句
mov sectors,cx
!儲存每磁軌最大扇區數。對於軟盤,最大磁軌號不會超過256,所以CH足以表示,CL[7:6]為0
!以上兩句可以寫為 mov cs:[sectors], cx
mov ax,#INITSEG
mov es,ax !因為上面ES的值被修改,所以令ES=0x9000
INT 13H AH=08H:讀取驅動器引數
引數 | 說明 |
---|---|
入口引數 | |
AH | =08H,讀取驅動器引數 |
DL | 驅動器號(如果是硬碟則[7]=1) |
返回引數 | |
CF | 0-操作成功;1-操作失敗 |
AH | 錯誤返回碼 |
BL | 驅動器型別 |
CH | 最大磁軌號的[7:0] |
CL[7:6] | 最大磁軌號的[9:8] |
CL[5:0] | 每磁軌最大扇區數 |
DH | 最大磁頭數 |
DL | 驅動器數量 |
ES:DI | 指向軟碟機磁碟參數列 |
列印 “Loading system …”
mov ah,#0x03 !讀游標的位置
xor bh,bh !bh=頁號
int 0x10
我們主要是用行號(DH中)和列號(DL中)。
INT 10H AH=03H:獲取游標位置和形狀
引數 | 說明 |
---|---|
入口引數 | |
AH | =03H,讀游標的位置 |
BH | 頁號 |
返回引數 | |
CH | 行掃描開始 |
CL | 行掃描結束 |
DH | 行號 |
DL | 列號 |
INT 10H AH=13H:在Teletype模式下顯示字串
引數 | 說明 |
---|---|
入口引數 | |
AH | =13H,在Teletype模式下顯示字串 |
BH | 頁碼 |
BL | 屬性(若 AL=00H 或 01H) |
CX | 要顯示的字串的長度 |
DH、DL | 座標(行、列) |
ES:BP | 指向要顯示的字串 |
AL | 顯示輸出方式 |
返回引數 | |
無 |
對於顯示輸出方式,解釋如下:
取值 | 說明 | 字串格式 |
---|---|---|
0 | 字串中只含顯示字元,顯示屬性在BL中;顯示後,游標位置不變 | char1,char2,……,charN |
1 | 字串中只含顯示字元,顯示屬性在BL中;顯示後,游標位置跟隨字串改變 | char1,char2,……,charN |
2 | 字串中含有顯示字元和顯示屬性;顯示後,游標位置不變 | char1,attri1,char2,attri2,……,charN,attriN |
3 | 字串中含有顯示字元和顯示屬性;顯示後,游標位置跟隨字串改變 | char1,attri1,char2,attri2,……,charN,attriN |
mov cx,#24 ! 24個字元
mov bx,#0x0007 ! page 0, attribute 7 (normal)
mov bp,#msg1
mov ax,#0x1301 ! write string, move cursor
int 0x10
msg1:
.byte 13,10
.ascii "Loading system ..."
.byte 13,10,13,10
13是回車,10是換行。它們的區別如下表。
回車和換行
中文名稱 | 英文名稱 | 字母簡寫 | ASCII碼 | 來源 |
---|---|---|---|---|
回車 | carriage return | CR | 0x0D=13D | “車”指的是紙車,它帶著紙向左移動。在開始打第一個字之前,要把紙車拉到最右邊,使彈簧收緊。隨著打字的進行,彈簧把紙車推向左邊。把紙車拉到最右邊,叫做“回車”。 |
換行 | line feed | LF | 0x0A=10D | 換行的概念是,打字機左邊有個”把手”,扳動一下把手,紙就會上移一行。 |
載入 system 到 0x10000
! we want to load the system (at 0x10000)
mov ax,#SYSSEG ! SYSSEG=0x1000
mov es,ax ! segment of 0x010000
call read_it
call kill_motor
3~5行,把system模組載入到0x10000。
第6行,關閉驅動器馬達。
過程read_it
這個過程的功能是把還未讀取的扇區載入到es:0x0000
處。注意:es必須是0x1000的整數倍,否則會陷入死迴圈。每讀64KB,都會使es的值增加0x1000,當es=0x4000的時候,停止讀取。
sread: .word 1+SETUPLEN !當前磁軌已經讀取的扇區數, 前面的1表示引導扇區bootsect.s
head: .word 0 ! current head,當前磁頭號
track: .word 0 ! current track,當前磁軌號
read_it:
mov ax,es
test ax,#0x0fff !使ax與0xfff按位與,測試es是否為0x1000的整數倍
die: jne die !結果不為0(說明es不是0x1000的整數倍)則陷入死迴圈
xor bx,bx ! bx(作為段內偏移地址)清零
rp_read:
mov ax,es
cmp ax,#ENDSEG ! 實際上求(ax-ENDSEG)
jb ok1_read ! 當CF=1(ax<ENDSEG, 有借位)時跳轉到ok1_read
ret ! 當ax>=ENDSEG時返回(我認為不會出現大於的情況)
ok1_read:
seg cs
mov ax,sectors ! 這兩句相當於 mov ax, cs:[sectors]; 獲得每磁軌扇區數
sub ax,sread ! ax = ax - sread, 得出本磁軌未讀扇區數
mov cx,ax
shl cx,#9 ! cx乘以512,求出位元組數
add cx,bx ! 以上3行相當於 cx = ax * 512 + bx
! 假設再讀ax個扇區,cx就是段內共讀入的位元組數
jnc ok2_read ! 若cx < 0x10000(CF=0,沒有進位)則跳轉到ok2_read
je ok2_read ! 若cx = 0(ZF=1),說明剛好讀入64KB,則跳轉到ok2_read
xor ax,ax ! ax = 0x0000
sub ax,bx ! 求bx對0x10000的補數,結果在ax中
shr ax,#9 ! 除以512,得到扇區數,AL作為引數,傳給read_track
ok2_read:
call read_track !呼叫read_track過程,用AL傳參,讀取AL個扇區到ES:BX
mov cx,ax !cx是該次操作已經讀取的扇區數
add ax,sread !ax是當前磁軌已經讀取的扇區數
seg cs
cmp ax,sectors
jne ok3_read !如果當前磁軌還有扇區未讀,跳轉到ok3_read
mov ax,#1 !說明當前磁軌的扇區都已讀完
sub ax,head !ax = 1 - 磁頭號
jne ok4_read !不為0則跳轉到 ok4_read,說明磁頭號為0
inc track !說明磁頭號為1,磁軌號增加1
ok4_read:
mov head,ax !更新磁頭號(如果是37行跳轉過來,則 head=1;否則 head=0)
xor ax,ax !ax=0, 因為更換了磁軌,所以當前磁軌已讀扇區數置0
ok3_read:
mov sread,ax !更新當前磁軌已經讀取的扇區數
shl cx,#9
add bx,cx !更新偏移地址
jnc rp_read !沒有進位,則跳轉到rp_read
mov ax,es !有進位,說明BX達到了64KB邊界
add ax,#0x1000
mov es,ax !es增加0x1000
xor bx,bx !bx = 0
jmp rp_read !繼續讀取
以上彙編程式碼看起來實在是費勁。為了便於理解,寫成C語言虛擬碼如下:
void read_it(es)//引數是es
{
if((es & 0xFFF) != 0) //es 必須是0x1000的倍數,否則進入死迴圈
while(1); //dead loop
bx = 0;
while(es < ENDSEG){
// 1. 看看要讀多少個扇區,用ax表示
// 2. sread:本磁軌已經讀取的扇區數
ax = SECTORS - sread;
if((ax * 512 + bx) > 0x10000){
ax = (0x10000 - bx) / 512;
}
read_track(ax); //呼叫讀扇區過程,al:要讀的扇區數,es:bx->緩衝區
cx = ax; //該次操作讀取的扇區數
ax += sread; //ax是本磁軌已讀取的扇區總數
if(ax==SECTORS){
//本磁軌的扇區全部讀完
if(head == 1){ //0和1磁頭都已經讀完,更新磁軌
++track;
head = 0; //從0磁頭開始
}
else{
head = 1; //切換到1磁頭
}
ax = 0; //本磁軌已讀扇區數置為0
}
sread = ax; //更新本磁軌已讀扇區數
bx += cx * 512; 更新偏移地址bx
if(bx == 0x10000)
{
//如果偏移地址到達0x10000,則更新es,並使bx=0
es += 0x1000;
bx = 0;
}
}
return;
}
過程read_track
讀取AL
個扇區到ES:BX
。此過程的入口引數是:
AL-要讀的扇區數目
ES:BX-緩衝區地址
read_track:
push ax
push bx
push cx
push dx
mov dx,track !當前磁軌號
mov cx,sread !已經讀取的扇區數
inc cx !CL是起始扇區號
mov ch,dl !CH是磁軌號----
mov dx,head !當前磁頭號
mov dh,dl !DH是磁頭號
mov dl,#0 !DL是驅動器號,0表示軟盤
and dx,#0x0100 !DH是磁頭號,不是0就是1
mov ah,#2 !功能號2,讀扇區
int 0x13
jc bad_rt !CF=1,表示出錯,復位磁碟
pop dx
pop cx
pop bx
pop ax
ret
bad_rt: mov ax,#0 !AH=0,磁碟復位功能
mov dx,#0 !DL=0,驅動器號
int 0x13
pop dx
pop cx
pop bx
pop ax
jmp read_track !重新讀取
過程kill_motor
kill_motor:
push dx
mov dx,#0x3f2 !軟盤控制器的埠-數字輸出暫存器埠,只寫
mov al,#0 !驅動器A,關閉FDC,禁止DMA和中斷請求,關閉馬達
outb !將al的值寫入埠dx
pop dx
ret
DOR(數字輸出暫存器)
DOR是一個8位暫存器,他控制驅動器馬達的開啟、驅動器選擇、啟動/復位FDC以及允許/禁止DMA及中斷請求。
位 | Name | Description |
---|---|---|
7 | MOT_EN3 | Driver D motor:1-start;0-stop |
6 | MOT_EN2 | Driver C motor:1-start;0-stop |
5 | MOT_EN1 | Driver B motor:1-start;0-stop |
4 | MOT_EN0 | Driver A motor:1-start;0-stop |
3 | DMA_INT | DMA and IRQs; 1 enable; 0-disable |
2 | RESET | 0= enter reset mode;1= normal operation |
1 and 0 | DRV_SEL1, DRV_SEL0 | “Select” drive number for next access |
確認根檔案系統裝置號
seg cs
mov ax,root_dev !ax = ROOT_DEV
cmp ax,#0
jne root_defined !如果 ROOT_DEV 不等於0則跳轉到 root_defined
seg cs
mov bx,sectors ! 取每磁軌扇區數
mov ax,#0x0208 ! /dev/ps0 - 1.2Mb
cmp bx,#15 ! 判斷每磁軌扇區數是否等於15
je root_defined ! 說明是1.2MB的軟盤
mov ax,#0x021c ! /dev/PS0 - 1.44Mb
cmp bx,#18 ! 判斷每磁軌扇區數是否等於18
je root_defined ! 說明是1.44MB的軟盤
undef_root:
jmp undef_root ! 死迴圈
root_defined:
seg cs
mov root_dev,ax ! 將檢查過的裝置號儲存到 root_dev 中
在Linux中軟碟機的主裝置號是2,次裝置號 = type * 4 + nr.
其中,nr等於0~3時分別對應軟碟機A、B、C、D;type是軟碟機的型別,比如2表示1.2MB,7表示1.44MB等。
因為是可引導的驅動器,所以肯定是A驅。對於1.2MB,裝置號 = 2 << 8 + 2 * 4 + 0 = 0x208;對於1.44MB,裝置號 = 2 << 8 + 7 * 4 + 0 = 0x21C.
.org 508
root_dev:
.word ROOT_DEV !這裡存放根檔案系統所在裝置號(init/main.c中會用)
ROOT_DEV
到底有何用,怎麼用,這裡先存疑,後面再探究。
跳轉到 setup 去執行
jmpi 0,SETUPSEG !到此本程式就結束了。
段間跳轉,跳轉到0x9020:0x0000(setup.s程式開始處)去執行。
程式碼分析到這裡,就差不多明白了。雖然是一個引導扇區,編譯後只有512位元組,可是涉及的知識點還真不少。真是太佩服Linus了,一個大學生就能寫出這樣的程式碼,實屬出眾。
參考資料
[1]《Linux核心完全剖析》(趙炯,2006)
[2] https://github.com/Wangzhike/HIT-Linux-0.11/blob/master/1-boot/OS-booting.md
[3] https://wiki.osdev.org/Floppy_Disk_Controller#DOR_bitflag_definitions
相關文章
- setup.s 分析—— Linux-0.11 學習筆記(二)Linux筆記
- main函式解析(一)——Linux-0.11 學習筆記(五)AI函式Linux筆記
- kernel_mktime() 詳解 —— Linux-0.11 學習筆記(四)Linux筆記
- main 函式解析(二)—— Linux-0.11 學習筆記(六)AI函式Linux筆記
- 學習筆記(一)筆記
- ThreeJs學習筆記——渲染(render)分析JS筆記
- kitten 學習教程(一) 學習筆記筆記
- Angular 學習筆記(一)Angular筆記
- React 學習筆記【一】React筆記
- vue學習筆記一Vue筆記
- Canvas學習筆記(一)Canvas筆記
- Jquery學習筆記(一)jQuery筆記
- goLang學習筆記(一)Golang筆記
- Android學習筆記一Android筆記
- css學習筆記(一)CSS筆記
- SCSS學習筆記(一)CSS筆記
- 深度學習 筆記一深度學習筆記
- ANFIS學習筆記(一)筆記
- Selenium 學習筆記 (一)筆記
- Kettle學習筆記(一)筆記
- Spring學習筆記(一)Spring筆記
- Matlab學習筆記(一)Matlab筆記
- opencv學習筆記(一)OpenCV筆記
- LaTeX學習筆記:一筆記
- GOLang 學習筆記(一)Golang筆記
- Python 學習筆記(一)Python筆記
- TS學習筆記(一)筆記
- unity學習筆記(一)Unity筆記
- oracle學習筆記《一》Oracle筆記
- Cesium學習筆記(一)筆記
- SpringMVC學習筆記(一)SpringMVC筆記
- kafka學習筆記(一)Kafka筆記
- 高等數學學習筆記(一)筆記
- Android Utils 之 Vector 學習筆記(一)—— VectorImpl 程式碼分析Android筆記
- 深度學習 DEEP LEARNING 學習筆記(一)深度學習筆記
- MySQL學習筆記之一MySql筆記
- 拉丁語學習筆記 一筆記
- Apache ShenYu 學習筆記一Apache筆記