任务概述:

  • 启动扇区的编写,读入10个柱面(每个柱面18个扇区,两个磁头所在的面也需要读入)
  • 开始编写操作系统程序,只需要简单的让CPU休眠即可
  • 从启动扇区处开始执行操作系统程序
  • 更改第二步的操作系统程序,往显存写东西,是屏幕黑屏,然后再让CPU休眠
  • 初步引入C语言,使用C语言编写操作系统

笔记

实际能运行的代码以github中的为准

任务1:启动扇区的编写

思路:
需要从软盘中读出一个个扇区(前10个柱面两个磁头的所有扇区,共2 10 18 = 360个扇区,三重循环就行)到指定的内存区域:

  • 0x08000 ~ 0x081ff,此部分为启动扇区(也就是0柱面,0磁头,1号扇区)。
  • 0x08200 ~ 0x08200 + 360 * 512 (转换为16进制为2 D000) = 0x35200,根据内存分布图,这部分内存可以用,那就不用担心了

具体怎么读呢,通过下面的几个BIOS中断可以实现读软盘的操作。(具体参数直接看附录里面的参考吧,懒得写了)
示例1:

  • 读取一个扇区(0磁头,0柱面的2号扇区) ```bash init:
    1. mov ax, 0x0820
    mov es, ax ; 不能直接给es赋值立即数 ; 下面这几个参数对应的寄存器不用背,查文档手册 mov ch, 0 ; 柱面号,0 mov dh, 0 ; 磁头号,0 mov cl, 2 ; 扇区号,2

; 开始读入扇区

readloop: mov si, 0 ; 错误计数,超过5次读取错误则直接退出

; 读入一个扇区,就和平常编程时一样,指定函数,给定参数,然后调用就行了 retry: mov ah, 0x02 ; 功能号,0x02 mov al, 1 ; 读入的扇区数(实际编写直接连续读入多个扇区,好像是可能有点问题) mov bx, 0 ; 这个缓冲区的地址我也不太明白,既然是缓冲区,难道不用指定大小吗 mov dl, 0x00 ; 驱动器号,0 int 0x13 ; 调用中断 jnc next ; 判断读取是否成功, 成功,读取下一个扇区。cf = 0,成功,否则失败 add si, 1 ; 错误数加1 cmp si, 5 jae error ; 错误超过5次跳转到错误处理,否则重置驱动器重新读 mov ax, 0x00 ; 0x00功能号,驱动器复位 mov dl, 0x00 ; 驱动器号, 0 int 0x13 ; 0x13中断

; ================= 至此,读取一个启动扇区就完成了,对中断参数不太熟悉,写一遍就记住了

next: ; 接着读取其他的扇区

error: ; 错误处理

  1. 示例2
  2. - 在上一个的基础上,实现读取一个柱面的18个扇区
  3. ```bash
  4. init:
  5. mov ax, 0x0820
  6. mov es, ax ; 不能直接给es赋值立即数
  7. ; 下面这几个参数对应的寄存器不用背,查文档手册
  8. mov ch, 0 ; 柱面号,0
  9. mov dh, 0 ; 磁头号,0
  10. mov cl, 2 ; 扇区号,2
  11. ; 开始读入扇区
  12. readloop:
  13. mov si, 0 ; 错误计数,超过5次读取错误则直接退出
  14. ; 读入一个扇区,就和平常编程时一样,指定函数,给定参数,然后调用就行了
  15. retry:
  16. mov ah, 0x02 ; 功能号,0x02
  17. mov al, 1 ; 读入的扇区数(实际编写直接连续读入多个扇区,好像是可能有点问题)
  18. mov bx, 0 ; 这个缓冲区的地址我也不太明白,既然是缓冲区,难道不用指定大小吗
  19. mov dl, 0x00 ; 驱动器号,0
  20. int 0x13 ; 调用中断
  21. jnc next ; 判断读取是否成功, 成功,读取下一个扇区。cf = 0,成功,否则失败
  22. add si, 1 ; 错误数加1
  23. cmp si, 5
  24. jae error ; 错误超过5次跳转到错误处理,否则重置驱动器重新读
  25. mov ax, 0x00 ; 0x00功能号,驱动器复位
  26. mov dl, 0x00 ; 驱动器号, 0
  27. int 0x13 ; 0x13中断
  28. ; ================= 至此,读取一个启动扇区就完成了,对中断参数不太熟悉,写一遍就记住了
  29. next:
  30. ; 接着读取其他的扇区
  31. ; 读取一个柱面的18个扇区
  32. mox ax, es
  33. add ax, 0x20
  34. mov es, ax ; 绝对地址后移512字节,实际上就是段地址
  35. ; 解释
  36. ; addr = es * 16 + bx
  37. ; addr + 512 = es * 16 + bx + 512 = (es + 32) * 16 + bx = (es + 0x20) * 16 + bx
  38. add cl, 1 ; 下一个扇区
  39. cmp cl, 18
  40. jbe readloop ; 没读完一个柱面的18个扇区则继续读
  41. error:
  42. ; 错误处理

示例3(实际上就是我们最终需要的代码):实现读取10个柱面两个磁头的所有扇区
实际上,这里为什么不是先读完10个柱面,在换另一个磁头继续读。这两个地方不太清楚这些扇区有没有顺序要求。

  1. init:
  2. mov ax, 0x0820
  3. mov es, ax ; 不能直接给es赋值立即数
  4. ; 下面这几个参数对应的寄存器不用背,查文档手册
  5. mov ch, 0 ; 柱面号,0
  6. mov dh, 0 ; 磁头号,0
  7. mov cl, 2 ; 扇区号,2
  8. ; 开始读入扇区
  9. readloop:
  10. mov si, 0 ; 错误计数,超过5次读取错误则直接退出
  11. ; 读入一个扇区,就和平常编程时一样,指定函数,给定参数,然后调用就行了
  12. retry:
  13. mov ah, 0x02 ; 功能号,0x02
  14. mov al, 1 ; 读入的扇区数(实际编写直接连续读入多个扇区,好像是可能有点问题)
  15. mov bx, 0 ; 这个缓冲区的地址我也不太明白,既然是缓冲区,难道不用指定大小吗
  16. mov dl, 0x00 ; 驱动器号,0
  17. int 0x13 ; 调用中断
  18. jnc next ; 判断读取是否成功, 成功,读取下一个扇区。cf = 0,成功,否则失败
  19. add si, 1 ; 错误数加1
  20. cmp si, 5
  21. jae error ; 错误超过5次跳转到错误处理,否则重置驱动器重新读
  22. mov ax, 0x00 ; 0x00功能号,驱动器复位
  23. mov dl, 0x00 ; 驱动器号, 0
  24. int 0x13 ; 0x13中断
  25. ; ================= 至此,读取一个启动扇区就完成了,对中断参数不太熟悉,写一遍就记住了
  26. next:
  27. ; 接着读取其他的扇区
  28. ; 读取一个柱面的18个扇区
  29. mox ax, es
  30. add ax, 0x20
  31. mov es, ax ; 绝对地址后移512字节,实际上就是段地址
  32. ; 解释
  33. ; addr = es * 16 + bx
  34. ; addr + 512 = es * 16 + bx + 512 = (es + 32) * 16 + bx = (es + 0x20) * 16 + bx
  35. add cl, 1 ; 下一个扇区
  36. cmp cl, 18
  37. jbe readloop ; 没读完一个柱面的18个扇区则继续读
  38. ; 换到下一个磁头, 柱面号重置为0,扇区号重置为1
  39. mov cl, 1
  40. add dh, 1
  41. cmp dh, 2
  42. jb readloop
  43. ;换到下一个柱面,柱面号加1,扇区号重置为每个柱面的开始扇区1,磁头号重置为0
  44. mov cl, 1
  45. mov dh, 0
  46. add ch, 1
  47. cmp ch, 10
  48. jb readloop
  49. error:
  50. ; 错误处理

最终的启动扇区程序:

  1. ; hxb-os
  2. ; TAB=4
  3. ; RESB指令会有个空间未初始化的告警,索性全换成times指令了
  4. ; FAT32软盘格式化代码,不用记,找相关文档看格式就行,这里是直接复制随书文件的,注释也是直接照抄的,后面明白了再说
  5. ORG 0x7c00
  6. JMP entry
  7. DB 0x90
  8. DB "hxbos " ; 启动扇区名称,8字节
  9. DW 512 ; 单个扇区的大小
  10. DB 1 ; 簇的大小,必须为1个扇区
  11. DW 1 ; FAT文件的起始位置(一般从第一个扇区的第一个字节开始)
  12. DB 2 ; FAT的个数,2
  13. DW 224 ; 根目录的大小,一般设置为224
  14. DW 2880 ; 磁盘的大小,必须为2880扇区
  15. DB 0xf0 ; 磁盘的种类
  16. DW 9 ; FAT的长度,必须是9扇区
  17. DW 18 ; 1个磁道几个扇区,必须为18
  18. DW 2 ; 磁头数必须为2
  19. DD 0 ; 不使用分区,必须为0
  20. DD 2880 ; 重写一次磁盘大小
  21. DB 0,0,0x29 ; 意义不明,固定
  22. DD 0xffffffff ; (可能是卷标号码)
  23. DB "HXB-OS " ; 磁盘的名称(11字节),不足加空格
  24. DB "FAT12 " ; 磁盘格式名称(8字节),不足加空格
  25. ;RESB 18 ; reserve byte,预留18字节
  26. times 18 db 0x00
  27. ; 程序主体,启动区的编写其实就是将之前的显示字符的核心程序换成加载扇区的核心程序
  28. entry:
  29. MOV AX,0 ; 初始化
  30. MOV SS,AX
  31. MOV SP,0x7c00
  32. MOV DS,AX
  33. mov ax, 0x0820
  34. mov es, ax ; 不能直接给es赋值立即数
  35. ; 下面这几个参数对应的寄存器不用背,查文档手册
  36. mov ch, 0 ; 柱面号,0
  37. mov dh, 0 ; 磁头号,0
  38. mov cl, 2 ; 扇区号,2
  39. ; 开始读入扇区
  40. readloop:
  41. mov si, 0 ; 错误计数,超过5次读取错误则直接退出
  42. ; 读入一个扇区,就和平常编程时一样,指定函数,给定参数,然后调用就行了
  43. retry:
  44. mov ah, 0x02 ; 功能号,0x02
  45. mov al, 1 ; 读入的扇区数(实际编写直接连续读入多个扇区,好像是可能有点问题)
  46. mov bx, 0 ; 这个缓冲区的地址我也不太明白,既然是缓冲区,难道不用指定大小吗
  47. mov dl, 0x00 ; 驱动器号,0
  48. int 0x13 ; 调用中断
  49. jnc next ; 判断读取是否成功, 成功,读取下一个扇区。cf = 0,成功,否则失败
  50. add si, 1 ; 错误数加1
  51. cmp si, 5
  52. jae error ; 错误超过5次跳转到错误处理,否则重置驱动器重新读
  53. mov ax, 0x00 ; 0x00功能号,驱动器复位
  54. mov dl, 0x00 ; 驱动器号, 0
  55. int 0x13 ; 0x13中断
  56. ; ================= 至此,读取一个启动扇区就完成了,对中断参数不太熟悉,写一遍就记住了
  57. next:
  58. ; 接着读取其他的扇区
  59. ; 读取一个柱面的18个扇区
  60. mov ax, es
  61. add ax, 0x20
  62. mov es, ax ; 绝对地址后移512字节,实际上就是段地址
  63. ; 解释
  64. ; addr = es * 16 + bx
  65. ; addr + 512 = es * 16 + bx + 512 = (es + 32) * 16 + bx = (es + 0x20) * 16 + bx
  66. add cl, 1 ; 下一个扇区
  67. cmp cl, 18
  68. jbe readloop ; 没读完一个柱面的18个扇区则继续读
  69. ; 换到下一个磁头, 扇区号重置为1
  70. mov cl, 1
  71. add dh, 1
  72. cmp dh, 2
  73. jb readloop
  74. ;换到下一个柱面,柱面号加1,扇区号重置为每个柱面的开始扇区1,磁头号重置为0
  75. mov cl, 1
  76. mov dh, 0
  77. add ch, 1
  78. cmp ch, 10
  79. jb readloop
  80. fin:
  81. HLT ; CPU休眠
  82. JMP fin ; 无线循环
  83. error:
  84. ; 错误处理,刚写的时候忘记写错误处理了,直接copy过来了,
  85. MOV SI,msg
  86. putloop:
  87. MOV AL,[SI]
  88. ADD SI,1 ;
  89. CMP AL,0
  90. JE fin
  91. MOV AH,0x0e ; bios中断调用显示字符
  92. MOV BX,15 ;
  93. INT 0x10 ;
  94. JMP putloop
  95. msg:
  96. DB 0x0a, 0x0a ;
  97. DB "load error"
  98. DB 0x0a ;
  99. DB 0
  100. ; 随书资源的代码有一份是直接写到0x7dff的,但是那样引导扇区就没了,因为格式化软盘的代码在第一扇区。
  101. times 0x1fe-($-$$) db 0x00 ; 第一扇区末尾
  102. DB 0x55, 0xaa

Makefile文件编写(比较简单,不详细说了):

  1. TOOLPATH = ..\..\tools\
  2. # TOOLPATH变量展开不知道为什么报个循环引用的错误,只能直接写在下面
  3. MAKE = ..\..\tools\make\make.exe -r
  4. MKIMAGE = ..\..\tools\mybximage\mkimage.exe
  5. DD = ..\..\tools\mydd\dd.exe
  6. NASM = ..\..\tools\nasm\nasm.exe
  7. # 虚拟机路径根据实际情况配置
  8. BOCHS = D:\Bochs-2.6.11\bochs.exe
  9. # windows自带命令
  10. COPY = copy
  11. DEL = del
  12. # 默认执行命令
  13. default:
  14. $(MAKE) wimg
  15. # 具体编译规则
  16. ipl.bin:ipl10.nas
  17. $(NASM) -o ipl.bin ipl10.nas
  18. hxbos.img:
  19. $(MKIMAGE) -of hxbos.img
  20. write_image:ipl.bin hxbos.img
  21. $(DD) -if ipl.bin -of hxbos.img -len 512
  22. run_bochs:bochsrc.bxrc
  23. $(BOCHS) -f bochsrc.bxrc -q
  24. clean:
  25. $(DEL) *.bin *.img *.lst
  26. boch_test:
  27. $(BOCHS)
  28. dd_test:
  29. $(DD)
  30. # 简化make命令用的
  31. ipl:
  32. $(MAKE) ipl.bin
  33. img:
  34. $(MAKE) hxbos.img
  35. wimg:
  36. $(MAKE) write_image
  37. run:
  38. $(MAKE) ipl
  39. $(MAKE) img
  40. $(MAKE) wimg
  41. $(MAKE) run_bochs

boch配置文件:

  1. # 内存分配32M
  2. megs:32
  3. # rom, 和操作系统中的rom是一个意思应该,存放指令代码的地方,CPU上电复位自检之后从这开始跑起来
  4. romimage:file=$BXSHARE/BIOS-bochs-latest
  5. # VGA rom, 应该是现存吧
  6. vgaromimage:file=$BXSHARE/VGABIOS-lgpl-latest
  7. # floppya,主软盘, floppya,floppyb...优先级依次降低
  8. floppya: image="hxbos.img", status=inserted
  9. # 从软盘启动
  10. boot:floppy
  11. # 日志输出文件位置
  12. log:bochsout.txt
  13. # 不启用鼠标
  14. mouse:enabled=0
  15. # 设置键盘
  16. keyboard: keymap=$BXSHARE/keymaps/x11-pc-de.map

目录结构:
image.png

运行:啥都没有,没事后边就有了。注意下,我们的程序是需要从引导扇区启动的,不能出现 no boot device..这类字样
image.png

任务2:操作系统程序的编写

  1. fin:
  2. HLT
  3. jmp fin

任务3:启动扇区中执行操作系统程序

任务4:改进操作系统程序,是屏幕全黑,然后CPU进入休眠

任务5:引入C语言,编写一个休眠程序,需要通过汇编语言提供的接口使得CPU休眠

附录:

1. 1M内存的内存分布图:

图片来源网络,如有侵权,联系删除
操作系统(三) - 图3

2. 磁盘bios中断调用

bios中断参考:https://blog.csdn.net/weixin_37656939/article/details/79684611

3.windows下将文件保存到镜像文件中

这部分我也不是很清楚,大概说一下我的理解
书中说的是用作者自己写的edimg.exe工具,这类工具从大的方向说就是将一个文件的数据复制到另一个软盘镜像文件中,但是作者给的这个工具也没源码也没使用参数的说明,自己用的话基本只能照抄。
不过既然知道了这个工具做了什么,那就可以自己写了。不过自己写的话需要先去了解fat软盘文件的格式,不然你怎么知道你的文件名和文件内容该复制到哪个地方。有点难度,这就不自己写了。(我是不会说是我没把握能写出来的)。
windows环境下下个winImage,我自己的实现是在windows下实现的,所以从头到尾除非必须用到,否则我是不会去用linux下的工具的。(我是不会承认是自己懒得去装虚拟机的)。当然,tools文件夹中也有这个工具,解压直接用即可。
下面是将hxb.img保存到hxbos.img文件中的示例(看过一遍就会用了):
注:为了方便学习,day03中已编写好Makefile文件,通过下面两条命令即可生成

  • 直接运行day03文件夹中的make run生成即可生成hxbos.img软盘映像文件
  • 运行make hxb即可生成hxb.img文件。
  1. 打开软件

image.png

  1. 【File】->【open】打开hxbos.img软盘文件。

image.png

  1. 插入hxb.img文件

【Image】->【Inject】,然后选择要插入的文件hxb.img,弹出弹框确认你是否要插入,确认就行。
插入完毕后就会看到软盘映像文件中有个hxb.img文件了。并且通过十六进制编辑器Hex打开,也会发现0x2600地址处是有文件名的,在0x4200处就是文件的内容了。
image.png
十六进制编辑器中查看:
image.png
image.png