《30天自制操作系统》笔记(02)——导入C语言

《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程序在计算机启动时将显示器置为全黑,如下图所示。

时间: 2024-10-12 23:56:34

《30天自制操作系统》笔记(02)——导入C语言的相关文章

30天自制操作系统笔记(第四天)

这一节讲的最出彩的地方是c语言的地址. 而要理清c语言地址,又必须追根溯源,看看汇编里内存地址的使用. MOV AL,0X15 MOV [1024],AL MOV BYTE[1024],0X15 这两种指令效果相同,都是在这个内存地址里存入一个数据,而学过汇编的我们知道,直接往内存某地址存入数据时,要说明填入的数据大小,或者说数据类型,不然机器不知道怎么填入该数据,到底是按照8位填入,还是十六位填入.因此,这个byte必不可少,而前面的指令,由于AL已经明确了是八位,因此不用说明. 好了,接下来

《30天自制操作系统》笔记(12)——多任务入门

<30天自制操作系统>笔记(12)——多任务入门 进度回顾 上一篇介绍了设置显示器高分辨率的方法.本篇讲一下操作系统实现多任务的方法. 什么是多任务 对程序员来说,也许这是废话,不过还是说清楚比较好. 多任务就是让电脑同时运行多个程序(如一边写代码一边听音乐一边下载电影). 电脑的CPU只有固定有限的那么一个或几个,不可能真的同时运行多个程序.所以就用近似的方式,让多个程序轮换着运行.当轮换速度够快(0.01秒),给人的感觉就是"同时"运行了. 多任务之不实用版 我们首先从

《30天自制操作系统》读书笔记(3) 引入C语言

这一次的学习相当曲折, 主要是因为粗心, Makefile里面的错误导致了文件生成出现各种奇奇怪怪的问题, 弄得心力交瘁, 因此制作过程还是尽量按着作者的路子来吧. 作者提供的源码的注释在中文系统下是乱码, 而且代码的分隔用了两个Tab, 在这里要处理一下: :%s/;.*//g 删除所有的注释; :%s/\t\t/\t 把两个Tab替换为一个Tab; 要让作者的nas文件和asm文件拥有相同的语法规则, 在_vimrc文件的最后一行添加 au BufNewFile,BufRead *.nas

《30天自制操作系统》读书笔记(2)hello, world

让系统跑起来 要写一个操作系统,我们首先要有一个储存系统的介质,原版书似乎是06年出版的,可惜那时候没有电脑,没想到作者用的还是软盘,现在的电脑谁有软驱?不得已我使用一张128M的SD卡来代替,而事实上你用的是U盘还是软盘对我们的操作系统没有影响,缺点是你的U盘刷入系统后容量只能是1440 MB,即当年流行的3.5英寸软盘的大小,当然不用担心,再格式化一次(用DiskGeniu),就可以恢复. 我做事情的话,总是怕自己的努力的结果白费了,害怕辛辛苦苦看完这本书但是发现做出来的东西现在根本没法用,

多定时器处理1(30天自制操作系统--读书笔记)

自认为写过很多MCU程序,但总是回头想想,我所了解的MCU编程思想大体有两种,其中具体的想法我得再找时间写下来. 总想总结出一个可扩展的,易移植的写法,但能力还没到这个层次.但<30天自制操作系统>这本书确实给我了一个思路,就像我已经写过的两篇读书笔记. 将两个独立的内容--FIFO和内存动态管理做到高度模块化,尤其是其中数据结构模型的设计更是我学习的好例子. 今天要学习的设计内容是多定时器处理.原书对这部分的处理讲的很详细,由浅入深,看得我由衷佩服作者,也可能是因为我水平低,稍稍看出点门道来

内存管理(30天自制操作系统--读书笔记)

今天继续读书笔记,“挑战内存管理”(30天自制操作系统). 为什么对这块内容敢兴趣呢,因为曾经遇到这么一个问题.在STM32程序中想使用队列,可不是上篇讲的FIFO,而是使用了较大的内存空间,又想做队列的顺序存取管理. 在这个队列里用到了malloc,动态申请内存,一开始是直接申请不到内存,后来在启动脚本里更改了设置堆的地址值,可以申请成功,但发现申请几次后,也申请不到内存. 果然MCU级别的程序,内存这块处理起来就没有windows程序那么随心所欲了.讲了这么多,开始正题吧. 1.相关数据结构

单字节的FIFO缓存(30天自制操作系统--读书笔记)

从今天起,写一些读书笔记.最近几个月都在看<30天自制操作系统这本书>,书虽说看的是电子书,但可以花钱买的正版书,既然花费了金钱,就总得有些收获. 任何人都不能总是固步自封,想要进步就得学习别人的知识,对于程序员而言,最简单的方法即是学习别人的代码. 今天的标题是“单字节的FIFO缓存”,其实就是做一个FIFO,看名字就知道了.也就4个函数和1个相关结构体,这样的小代码在嵌入式系统中很常用,也会很好用. 1.相关数据结构体 struct FIFO8 { unsigned char *buf;

《30天自制操作系统》读书笔记(4) 绘图

暑假果然是滋生懒散的温床. (╯‵□′)╯︵┻━┻ 好久不动都忘记之前做到哪里了, 上次好像做到了C语言的引入, 这一节所做的东西都相当轻松, 将会绘制出操作系统的基本界面. 绘图的原理 按照书中所说, 将值写入到显存中就能在屏幕上显示相应的像素, 在asmhead.nas 中有这一段: 1 CYLS EQU 0x0ff0 ; 设定启动区 2 LEDS EQU 0x0ff1 3 VMODE EQU 0x0ff2 ; 关于颜色数目的信息,颜色的位数 4 SCRNX EQU 0x0ff4 ; 分辨率

《30天自制操作系统》笔记(08)——叠加窗口刷新

<30天自制操作系统>笔记(08)--叠加窗口刷新 进度回顾 上一篇中介绍了内存管理的思路和算法,我们已经可以动态申请和释放内存了.这不就是堆(Heap)么.在此基础上,本篇要做一段程序,一并解决窗口和鼠标的叠加处理问题. 问题 在之前的<<30天自制操作系统>笔记(05)--启用鼠标键盘>篇,已经能够移动鼠标了.但是遗留了如下图所示的一个小问题. 我们希望的情形是这样的: 实际上,当前版本的OS还没有窗口图层的东西.本篇要做一段程序,一并解决窗口和鼠标的叠加处理问题.