《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