文章出處

《30天自制操作系統》筆記(02)——導入C語言

進度回顧

上一篇,記錄了計算機開機時加載IPL程序(initial program loader,一個nas匯編程序)的情況,包括IPL代碼(helloos.nas)、編譯生成helloos.img文件、用虛擬機QEMU加載helloos.img、制作U盤啟動盤和用物理機加載helloos.img。

計算機啟動時會自動加載和執行IPL程序,但IPL程序只能占用512字節。若直接用IPL寫OS,空間不夠用。所以IPL程序一般用于將真正的OS程序加載到內存某處(記作A),然后跳轉到A。這樣計算機就可以執行OS的程序了。

在上一篇中的IPL程序只是個hello world式的試驗品,本篇通過修改上一篇的IPL,讓它真正實現加載OS程序的功能。同時,將IPL程序代碼和OS代碼放到不同的源代碼文件中;用C語言來編寫以后的OS代碼;用Makefile來編譯源代碼。

有了本篇的基礎,就算是正式開始編寫OS源代碼了

OS開發設計方案

關于軟盤的預備知識

一張軟盤有80個柱面、2個磁頭、18個扇區(Cylinder:0~79;Header:0~2;Sector:1~18),1個扇區有512個字節,所以軟盤的容量是80*2*18*512=1440KB。

向一個軟盤保存文件時,文件名會從0x2600開始往后存,文件的內容會從0x4200開始往后存。

我們的OS開發設計方案如下

1. 把IPL程序作為一個獨立的源文件(ipl10.nas)開發,編譯后生成二進制文件(ipl10.bin)。

2. 把OS程序作為若干獨立的源文件開發,編譯后生成二進制文件(haribote.sys)。haribote.sys就是我們的OS程序。

3. 用二進制的方式把ipl10.bin寫入haribote.img(磁盤映像文件,看作一個軟盤即可)的第一個扇區(這樣,計算機啟動時就會自動加載ipl10.bin程序)。

4. 把haribote.sys作為一個文件復制到haribote.img。根據上文的預備知識可知,這個文件的內容會從軟盤的0x4200位置開始往后存。

實現一個開發結構完整的OS

完備的IPL程序

下面的代碼是完備的IPL程序,它讀了10個柱面上的代碼到內存,所以文件名從helloos.nas改成了ipl10.nas。

  1 ; haribote-ipl
  2 ; TAB=4
  3 
  4 CYLS    EQU        10                ; どこまで読み込むか
  5 
  6         ORG        0x7c00            ; 指明程序的裝載地址
  7 
  8 ; 以下這段是標準FAT32格式軟盤專用的代碼
  9 
 10         JMP        entry
 11         DB        0x90
 12         DB        "HARIBOTE"        ; freeparam 啟動區的名稱可以是任意的字符串(8字節)
 13         DW        512                ; 每個扇區(sector)的大小(必須為512字節)
 14         DB        1                ; 簇(cluster)的大小(必須為1個扇區)
 15         DW        1                ; FAT的起始位置(一般從第一個扇區開始)
 16         DB        2                ; FAT的個數(必須為2)
 17         DW        224                ; 根目錄的大小(一般設成224項)
 18         DW        2880            ; 該磁盤的大小(必須是2880扇區)
 19         DB        0xf0            ; 磁盤的種類(必須是0xf0)
 20         DW        9                ; FAT的長度(必須是9扇區)
 21         DW        18                ; 1個磁道(track)有幾個扇區(必須是18)
 22         DW        2                ; 磁頭數(必須是2)
 23         DD        0                ; 不使用分區,必須是0
 24         DD        2880            ; 重寫一次磁盤大小
 25         DB        0,0,0x29        ; 意義不明,固定
 26         DD        0xffffffff        ; (可能是)卷標號碼
 27         DB        "HARIBOTEOS "    ; freeparam 磁盤的名稱(11字節)
 28         DB        "FAT12   "        ; 磁盤格式名稱(8字節)
 29         RESB    18                ; 先空出18字節
 30 
 31 ; 程序核心
 32 
 33 entry:
 34         MOV        AX,0            ; 初始化寄存器
 35         MOV        SS,AX
 36         MOV        SP,0x7c00
 37         MOV        DS,AX
 38 
 39 ; 讀磁盤
 40 
 41         MOV        AX,0x0820
 42         MOV        ES,AX
 43         MOV        CH,0            ; 柱面0
 44         MOV        DH,0            ; 磁頭0
 45         MOV        CL,2            ; 扇區2
 46 readloop:
 47         MOV        SI,0            ; 記錄失敗次數的寄存器
 48 retry:
 49         MOV        AH,0x02            ; AH=0x02 : 讀入磁盤
 50         MOV        AL,1            ; 1個扇區
 51         MOV        BX,0
 52         MOV        DL,0x00            ; A驅動器
 53         INT        0x13            ; 調用磁盤BIOS
 54         JNC        next            ; 沒出錯時跳轉到next
 55         ADD        SI,1            ; SI加1
 56         CMP        SI,5            ; 比較SI與5
 57         JAE        error            ; SI >= 5時,跳轉到 error
 58         MOV        AH,0x00
 59         MOV        DL,0x00            ; A驅動器
 60         INT        0x13            ; 重置驅動器
 61         JMP        retry
 62 next:
 63         MOV        AX,ES            ; 把內存地址后移0x200(0x200 = 512)
 64         ADD        AX,0x0020        ; ADD    AX, 512 / 16
 65         MOV        ES,AX            ; 因為沒有ADD ES,0x020 指令,所以這里稍微繞個彎
 66         ADD        CL,1            ; CL加1
 67         CMP        CL,18            ; 比較CL與18
 68         JBE        readloop        ; 如果CL <= 18,則跳轉到readloop
 69         MOV        CL,1
 70         ADD        DH,1
 71         CMP        DH,2
 72         JB        readloop        ; 如果DH < 2,則跳轉到readloop
 73         MOV        DH,0
 74         ADD        CH,1
 75         CMP        CH,CYLS
 76         JB        readloop        ; 如果CH < CYLS,則跳轉到readloop
 77 
 78 ; 讀完所有數據后,調到0x8200位置,即haribote.sys中的指令
 79 
 80         MOV        [0x0ff0],CH        ; 將CYLS的值寫到內存地址0x0ff0中。
 81         JMP        0xc200
 82 
 83 error:
 84         MOV        SI,msg
 85 putloop:
 86         MOV        AL,[SI]
 87         ADD        SI,1            ; 給SI加1
 88         CMP        AL,0
 89         JE        fin
 90         MOV        AH,0x0e            ; 顯示一個文字
 91         MOV        BX,15            ; 指定字符顏色
 92         INT        0x10            ; 調用顯卡BIOS
 93         JMP        putloop
 94 fin:
 95         HLT                        ; 讓CPU停止;等待指令
 96         JMP        fin                ; 無限循環
 97 msg:
 98         DB        0x0a, 0x0a        ; 換行2次
 99         DB        "load error"    ; freeparam
100         DB        0x0a            ; 換行
101         DB        0
102 
103         RESB    0x7dfe-$        ; 填寫0x00,直到0x001fe
104 
105         DB        0x55, 0xaa
ipl10.nas

 

簡單地說,這個ipl10.nas讀了軟盤(U盤)最開始的10個柱面,即C0-H0-S1到C9-H1-S18。那么從軟盤(U盤)讀到的這些內容放到哪里了呢?答:放到了內存的0x8000到0x34FFF這一段空間,如下表所示。

序號

軟盤(U盤)位置

內存位置

備注

1

C0-H0-S1

0x8000~0x81FF

實際上沒有讀這一扇區,這一扇區存的是IPL程序

2

C0-H0-S2

0x8200~0x83FF

從軟盤(U盤)的512字節到內存的512字節的一一對應。

3

C0-H0-S3

0x8400~0x85FF

同上

……

……

……

……

360(10*2*18)

C9-H1-S18

0x34E00~0x34FFF

同上

從剛剛的軟盤預備知識中可知,haribote.sys程序會被加載到內存的(0x8000+0x4200=0xc200)處。所以IPL程序中會有"JMP        0xc200"這一行代碼。這行代碼的意思是:把10個柱面讀到內存后,haribote.sys就準備好了,IPL可以功成身退。下一步就從haribote.sys的第一句指令開始運行我們的OS。

 

拆分出OS源代碼文件

我們的目的是用C語言寫OS,所以當前給出如下幾個OS源代碼文件。

源代碼文件

功能

asmhead.nas

承接IPL程序,調用bootpack.c中的主函數

bootpack.c

OS程序主函數

naskfunc.nas

用匯編語言寫一些供C語言調用的函數

下面分別列出這三個源代碼文件的內容。

源代碼asmhead.nas中用日語注釋的地方是原作者在后續章節中解釋的,現在我也不知道是什么意思。我只知道asmhead.nas起了一個承上啟下的作用,以后就可以越來越多得用C來干活了。

  1 ; haribote-os boot asm
  2 ; TAB=4
  3 
  4 BOTPAK    EQU        0x00280000        ; bootpackのロード先
  5 DSKCAC    EQU        0x00100000        ; ディスクキャッシュの場所
  6 DSKCAC0    EQU        0x00008000        ; ディスクキャッシュの場所(リアルモード)
  7 
  8 ; 有關BOOT_INFO
  9 CYLS    EQU        0x0ff0            ; 設定啟動區
 10 LEDS    EQU        0x0ff1
 11 VMODE    EQU        0x0ff2            ; 關于顏色數目的信息。顏色的位數。
 12 SCRNX    EQU        0x0ff4            ; 分辨率的X(screen x)
 13 SCRNY    EQU        0x0ff6            ; 分辨率的Y(screen y)
 14 VRAM    EQU        0x0ff8            ; 圖像緩沖區的開始地址
 15 
 16         ORG        0xc200            ; 這個程序將要被裝載到內存的什么地方呢?
 17 
 18 ; 畫面モードを設定
 19 
 20         MOV        AL,0x13            ; VGA顯卡,320x200x8bit彩色
 21         MOV        AH,0x00
 22         INT        0x10
 23         MOV        BYTE [VMODE],8    ; 記錄畫面模式
 24         MOV        WORD [SCRNX],320
 25         MOV        WORD [SCRNY],200
 26         MOV        DWORD [VRAM],0x000a0000
 27 
 28 ; 用BIOS取得鍵盤上各種LED指示燈的狀態
 29 
 30         MOV        AH,0x02
 31         INT        0x16             ; keyboard BIOS
 32         MOV        [LEDS],AL
 33 
 34 ; PICが一切の割り込みを受け付けないようにする
 35 ;    AT互換機の仕様では、PICの初期化をするなら、
 36 ;    こいつをCLI前にやっておかないと、たまにハングアップする
 37 ;    PICの初期化はあとでやる
 38 
 39         MOV        AL,0xff
 40         OUT        0x21,AL
 41         NOP                        ; OUT命令を連続させるとうまくいかない機種があるらしいので
 42         OUT        0xa1,AL
 43 
 44         CLI                        ; さらにCPUレベルでも割り込み禁止
 45 
 46 ; CPUから1MB以上のメモリにアクセスできるように、A20GATEを設定
 47 
 48         CALL    waitkbdout
 49         MOV        AL,0xd1
 50         OUT        0x64,AL
 51         CALL    waitkbdout
 52         MOV        AL,0xdf            ; enable A20
 53         OUT        0x60,AL
 54         CALL    waitkbdout
 55 
 56 ; プロテクトモード移行
 57 
 58 [INSTRSET "i486p"]                ; 486の命令まで使いたいという記述
 59 
 60         LGDT    [GDTR0]            ; 暫定GDTを設定
 61         MOV        EAX,CR0
 62         AND        EAX,0x7fffffff    ; bit31を0にする(ページング禁止のため)
 63         OR        EAX,0x00000001    ; bit0を1にする(プロテクトモード移行のため)
 64         MOV        CR0,EAX
 65         JMP        pipelineflush
 66 pipelineflush:
 67         MOV        AX,1*8            ;  読み書き可能セグメント32bit
 68         MOV        DS,AX
 69         MOV        ES,AX
 70         MOV        FS,AX
 71         MOV        GS,AX
 72         MOV        SS,AX
 73 
 74 ; bootpackの転送
 75 
 76         MOV        ESI,bootpack    ; 転送元
 77         MOV        EDI,BOTPAK        ; 転送先
 78         MOV        ECX,512*1024/4
 79         CALL    memcpy
 80 
 81 ; ついでにディスクデータも本來の位置へ転送
 82 
 83 ; まずはブートセクタから
 84 
 85         MOV        ESI,0x7c00        ; 転送元
 86         MOV        EDI,DSKCAC        ; 転送先
 87         MOV        ECX,512/4
 88         CALL    memcpy
 89 
 90 ; 殘り全部
 91 
 92         MOV        ESI,DSKCAC0+512    ; 転送元
 93         MOV        EDI,DSKCAC+512    ; 転送先
 94         MOV        ECX,0
 95         MOV        CL,BYTE [CYLS]
 96         IMUL    ECX,512*18*2/4    ; シリンダ數からバイト數/4に変換
 97         SUB        ECX,512/4        ; IPLの分だけ差し引く
 98         CALL    memcpy
 99 
100 ; asmheadでしなければいけないことは全部し終わったので、
101 ;    あとはbootpackに任せる
102 
103 ; bootpackの起動
104 
105         MOV        EBX,BOTPAK
106         MOV        ECX,[EBX+16]
107         ADD        ECX,3            ; ECX += 3;
108         SHR        ECX,2            ; ECX /= 4;
109         JZ        skip            ; 転送するべきものがない
110         MOV        ESI,[EBX+20]    ; 転送元
111         ADD        ESI,EBX
112         MOV        EDI,[EBX+12]    ; 転送先
113         CALL    memcpy
114 skip:
115         MOV        ESP,[EBX+12]    ; スタック初期値
116         JMP        DWORD 2*8:0x0000001b
117 
118 waitkbdout:
119         IN         AL,0x64
120         AND         AL,0x02
121         JNZ        waitkbdout        ; ANDの結果が0でなければwaitkbdoutへ
122         RET
123 
124 memcpy:
125         MOV        EAX,[ESI]
126         ADD        ESI,4
127         MOV        [EDI],EAX
128         ADD        EDI,4
129         SUB        ECX,1
130         JNZ        memcpy            ; 引き算した結果が0でなければmemcpyへ
131         RET
132 ; memcpyはアドレスサイズプリフィクスを入れ忘れなければ、ストリング命令でも書ける
133 
134         ALIGNB    16
135 GDT0:
136         RESB    8                ; ヌルセレクタ
137         DW        0xffff,0x0000,0x9200,0x00cf    ; 読み書き可能セグメント32bit
138         DW        0xffff,0x0000,0x9a28,0x0047    ; 実行可能セグメント32bit(bootpack用)
139 
140         DW        0
141 GDTR0:
142         DW        8*3-1
143         DD        GDT0
144 
145         ALIGNB    16
146 bootpack:
asmhead.nas

 

目前的主函數什么都沒有做。

 1 /* 告訴C編譯器,有一個函數在別的文件里。 */
 2 
 3 void io_hlt(void);
 4 
 5 /* 是函數聲明卻不用{}。而用;,這表示的意思是:函數是在別的文件中,你自己找一下吧! */
 6 
 7 void HariMain(void)
 8 {
 9 
10 fin:
11     io_hlt(); /* 執行naskfunc.nas里的_io_hlt */
12     goto fin;
13 
14 }
bootpack.c

 

這個naskfunc.nas可以說是一個封裝硬件供C語言調用的函數庫。

 1 ; naskfunc
 2 ; TAB=4
 3 
 4 [FORMAT "WCOFF"]                ; 制作目標文件的模式
 5 [BITS 32]                        ; 制作32位模式用的機器語言
 6 
 7 
 8 ; 制作目標文件的信息
 9 
10 [FILE "naskfunc.nas"]            ; 源文件名信息
11 
12         GLOBAL    _io_hlt            ; 程序中包含的函數名
13 
14 
15 ; 以下是實際的函數
16 
17 [SECTION .text]        ; 目標文件中寫了這些后再寫程序
18 
19 _io_hlt:    ; void io_hlt(void);
20         HLT
21         RET
naskfunc.nas

 

從Makefile畫出編譯和部署流程

上述的ipl10.nas、asmhead.nas、bootpack.c、naskfunc.nas是如何組合成為一個OS程序的呢?下面的Makefile文件描述了編譯流程。

 1 TOOLPATH = ../z_tools/
 2 INCPATH  = ../z_tools/haribote/
 3 
 4 MAKE     = $(TOOLPATH)make.exe -r
 5 NASK     = $(TOOLPATH)nask.exe
 6 CC1      = $(TOOLPATH)cc1.exe -I$(INCPATH) -Os -Wall -quiet
 7 GAS2NASK = $(TOOLPATH)gas2nask.exe -a
 8 OBJ2BIM  = $(TOOLPATH)obj2bim.exe
 9 BIM2HRB  = $(TOOLPATH)bim2hrb.exe
10 RULEFILE = $(TOOLPATH)haribote/haribote.rul
11 EDIMG    = $(TOOLPATH)edimg.exe
12 IMGTOL   = $(TOOLPATH)imgtol.com
13 COPY     = copy
14 DEL      = del
15 
16 # デフォルト動作
17 
18 default :
19     $(MAKE) img
20 
21 # ファイル生成規則
22 
23 ipl10.bin : ipl10.nas Makefile
24     $(NASK) ipl10.nas ipl10.bin ipl10.lst
25 
26 asmhead.bin : asmhead.nas Makefile
27     $(NASK) asmhead.nas asmhead.bin asmhead.lst
28 
29 bootpack.gas : bootpack.c Makefile
30     $(CC1) -o bootpack.gas bootpack.c
31 
32 bootpack.nas : bootpack.gas Makefile
33     $(GAS2NASK) bootpack.gas bootpack.nas
34 
35 bootpack.obj : bootpack.nas Makefile
36     $(NASK) bootpack.nas bootpack.obj bootpack.lst
37 
38 naskfunc.obj : naskfunc.nas Makefile
39     $(NASK) naskfunc.nas naskfunc.obj naskfunc.lst
40 
41 bootpack.bim : bootpack.obj naskfunc.obj Makefile
42     $(OBJ2BIM) @$(RULEFILE) out:bootpack.bim stack:3136k map:bootpack.map \
43         bootpack.obj naskfunc.obj
44 # 3MB+64KB=3136KB
45 
46 bootpack.hrb : bootpack.bim Makefile
47     $(BIM2HRB) bootpack.bim bootpack.hrb 0
48 
49 haribote.sys : asmhead.bin bootpack.hrb Makefile
50     copy /B asmhead.bin+bootpack.hrb haribote.sys
51 
52 haribote.img : ipl10.bin haribote.sys Makefile
53     $(EDIMG)   imgin:../z_tools/fdimg0at.tek \
54         wbinimg src:ipl10.bin len:512 from:0 to:0 \
55         copy from:haribote.sys to:@: \
56         imgout:haribote.img
57 
58 # コマンド
59 
60 img :
61     $(MAKE) haribote.img
62 
63 run :
64     $(MAKE) img
65     $(COPY) haribote.img ..\z_tools\qemu\fdimage0.bin
66     $(MAKE) -C ../z_tools/qemu
67 
68 install :
69     $(MAKE) img
70     $(IMGTOL) w a: haribote.img
71 
72 clean :
73     -$(DEL) *.bin
74     -$(DEL) *.lst
75     -$(DEL) *.gas
76     -$(DEL) *.obj
77     -$(DEL) bootpack.nas
78     -$(DEL) bootpack.map
79     -$(DEL) bootpack.bim
80     -$(DEL) bootpack.hrb
81     -$(DEL) haribote.sys
82 
83 src_only :
84     $(MAKE) clean
85     -$(DEL) haribote.img
Makefile in haribote00j

 

通過Makefile,我們可以畫出如下所示的編譯和部署流程。

總結

今后開發OS時,就可以直接在bootpack.c中寫代碼;當遇到C語言無法完成的情況時,就在naskfunc.nas里用匯編語言寫函數,然后用bootpack.c調用這些函數。

現在的asmhead.nas程序在計算機啟動時將顯示器置為全黑,如下圖所示。

 點此查看下一篇《30天自制操作系統》筆記(03)——使用Vmware


文章列表


不含病毒。www.avast.com
arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()