loader.asm 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494
  1. ; |==================|
  2. ; | 这是loader程序 |
  3. ; |==================|
  4. ; Created by longjin, 2022/01/17
  5. ; 由于实模式下,物理地址为CS<<4+IP,而从boot的定义中直到,loader的CS为0x1000, 因此loader首地址为0x10000
  6. org 0x10000
  7. jmp Label_Start
  8. %include 'fat12.inc' ; 将fat12文件系统的信息包含进来
  9. Base_Of_Kernel_File equ 0x00
  10. Offset_Of_Kernel_File equ 0x100000 ; 设置内核文件的地址空间从1MB处开始。(大于实模式的寻址空间)
  11. Base_Tmp_Of_Kernel_Addr equ 0x00
  12. Offset_Tmp_Of_Kernel_File equ 0x7e00 ; 内核程序的临时转存空间
  13. Memory_Struct_Buffer_Addr equ 0x7e00 ; 内核被转移到最终的内存空间后,原来的临时空间就作为内存结构数据的存储空间
  14. [SECTION gdt]
  15. LABEL_GDT: dd 0,0
  16. LABEL_DESC_CODE32: dd 0x0000FFFF,0x00CF9A00
  17. LABEL_DESC_DATA32: dd 0x0000FFFF,0x00CF9200
  18. GdtLen equ $ - LABEL_GDT
  19. GdtPtr dw GdtLen - 1
  20. dd LABEL_GDT
  21. SelectorCode32 equ LABEL_DESC_CODE32 - LABEL_GDT
  22. SelectorData32 equ LABEL_DESC_DATA32 - LABEL_GDT
  23. [SECTION .s16] ;定义一个名为.s16的段
  24. [BITS 16] ; 通知nasm,将要运行在16位宽的处理器上
  25. Label_Start:
  26. mov ax, cs
  27. mov ds, ax ; 初始化数据段寄存器
  28. mov es, ax ; 初始化附加段寄存器
  29. mov ax, 0x00
  30. mov ss, ax ;初始化堆栈段寄存器
  31. mov sp, 0x7c00
  32. ;在屏幕上显示 start Loader
  33. mov ax, 0x1301
  34. mov bx, 0x000f
  35. mov dx, 0x0100 ;在第2行显示
  36. mov cx, 23 ;设置消息长度
  37. push ax
  38. mov ax, ds
  39. mov es, ax
  40. pop ax
  41. mov bp, Message_Start_Loader
  42. int 0x10
  43. ;jmp $
  44. ; 使用A20快速门来开启A20信号线
  45. push ax
  46. in al, 0x92 ; A20快速门使用I/O端口0x92来处理A20信号线
  47. or al, 0x02 ; 通过将0x92端口的第1位置1,开启A20地址线
  48. out 0x92, al
  49. pop ax
  50. cli ; 关闭外部中断
  51. db 0x66
  52. lgdt [GdtPtr] ; LGDT/LIDT - 加载全局/中断描述符表格寄存器
  53. ; 置位CR0寄存器的第0位,开启保护模式
  54. mov eax, cr0
  55. or eax, 1
  56. mov cr0, eax
  57. ; 为fs寄存器加载新的数据段的值
  58. mov ax, SelectorData32
  59. mov fs, ax
  60. ; fs寄存器加载完成后,立即从保护模式退出。 这样能使得fs寄存器在实模式下获得大于1MB的寻址能力。
  61. mov eax, cr0
  62. and al, 11111110b ; 将第0位置0
  63. mov cr0, eax
  64. sti ; 开启外部中断
  65. ; =========在文件系统中搜索 kernel.bin==========
  66. mov word [SectorNo], SectorNumOfRootDirStart ;保存根目录起始扇区号
  67. Label_Search_In_Root_Dir_Begin:
  68. cmp word [RootDirSizeForLoop], 0 ; 比较根目录扇区数量和0的关系。 cmp实际上是进行了一个减法运算
  69. jz Label_No_KernelBin ; 等于0,不存在kernel.bin
  70. dec word [RootDirSizeForLoop]
  71. mov ax, 0x00
  72. mov es, ax
  73. mov bx, 0x8000
  74. mov ax, [SectorNo] ;向函数传入扇区号
  75. mov cl, 1
  76. call Func_ReadOneSector
  77. mov si, Kernel_FileName ;向源变址寄存器传入Loader文件的名字
  78. mov di, 0x8000
  79. cld ;由于LODSB的加载方向与DF标志位有关,因此需要用CLD清零DF标志位
  80. mov dx, 0x10 ; 每个扇区的目录项的最大条数是(512/32=16,也就是0x10)
  81. Label_Search_For_LoaderBin:
  82. cmp dx, 0
  83. jz Label_Goto_Next_Sector_In_Root_Dir
  84. dec dx
  85. mov cx, 11 ; cx寄存器存储目录项的文件名长度, 11B,包括了文件名和扩展名,但是不包括 分隔符'.'
  86. Label_Cmp_FileName:
  87. cmp cx, 0
  88. jz Label_FileName_Found
  89. dec cx
  90. lodsb ; 把si对应的字节载入al寄存器中,然后,由于DF为0,si寄存器自增
  91. cmp al, byte [es:di] ; 间接取址[es+di]。 也就是进行比较当前文件的名字对应字节和loader文件名对应字节
  92. jz Label_Go_On ; 对应字节相同
  93. jmp Label_Different ; 字节不同,不是同一个文件
  94. Label_Go_On:
  95. inc di
  96. jmp Label_Cmp_FileName
  97. Label_Different:
  98. and di, 0xffe0 ;将di恢复到当前目录项的第0字节
  99. add di, 0x20 ;将di跳转到下一目录项的第0字节
  100. mov si, Kernel_FileName
  101. jmp Label_Search_For_LoaderBin ;继续搜索下一目录项
  102. Label_Goto_Next_Sector_In_Root_Dir:
  103. add word [SectorNo], 1
  104. jmp Label_Search_In_Root_Dir_Begin
  105. Label_No_KernelBin:
  106. ; 在屏幕上显示 [ERROR] No Kernel Found.
  107. mov ax, 0x1301
  108. mov bx, 0x000c ; 红色闪烁高亮黑底
  109. mov dx, 0x0200 ; 显示在第3行(前面已经显示过2行了)
  110. mov cx, 24 ; 字符串长度
  111. push ax
  112. mov ax, ds
  113. mov es, ax
  114. pop ax
  115. mov bp, Message_No_Loader
  116. int 0x10
  117. jmp $
  118. ; ========= 找到了 kernel.bin ===========
  119. ; 将内核加载到内存中
  120. Label_FileName_Found:
  121. mov ax, RootDirSectors
  122. ; 先取得目录项DIR_FstClus字段的值(起始簇号)
  123. and di, 0xffe0
  124. add di, 0x1a
  125. mov cx, word [es:di]
  126. push cx
  127. add cx, ax
  128. add cx, SectorBalance
  129. mov eax, Base_Tmp_Of_Kernel_Addr ; 内核放置的临时地址
  130. mov es, eax ;配置es和bx,指定kernel.bin在内存中的起始地址
  131. mov bx, Offset_Tmp_Of_Kernel_File
  132. mov ax, cx
  133. Label_Go_On_Loading_File:
  134. push ax
  135. push bx
  136. ; 显示字符.
  137. mov ah, 0x0e
  138. mov al, "."
  139. mov bl, 0x0f
  140. int 0x10
  141. pop bx
  142. pop ax
  143. ; 读取一个扇区
  144. mov cl, 1
  145. call Func_ReadOneSector
  146. pop ax
  147. ; ======逐字节将内核程序复制到临时空间,然后转存到内核空间===
  148. push cx
  149. push eax
  150. push fs
  151. push edi
  152. push ds
  153. push esi
  154. mov cx, 0x0200 ; 指定计数寄存器的值为512, 为后面循环搬运这个扇区的数据做准备
  155. mov ax, Base_Of_Kernel_File
  156. mov fs, ax ; 这样在物理机上是行不通的,因为这样移动的话,fs就失去了32位寻址能力
  157. mov edi, dword [OffsetOfKernelFileCount] ; 指定目的变址寄存器
  158. mov ax, Base_Tmp_Of_Kernel_Addr
  159. mov ds, ax
  160. mov esi, Offset_Tmp_Of_Kernel_File ; 指定来源变址寄存器
  161. Label_Move_Kernel:
  162. ; 真正进行数据的移动
  163. mov al, byte [ds:esi] ; 移动到临时区域
  164. mov byte [fs:edi], al ; 再移动到目标区域
  165. inc esi
  166. inc edi
  167. loop Label_Move_Kernel
  168. ; 当前扇区数据移动完毕
  169. mov eax, 0x1000
  170. mov ds, eax
  171. mov dword [OffsetOfKernelFileCount], edi ; 增加偏移量
  172. pop esi
  173. pop ds
  174. pop edi
  175. pop fs
  176. pop eax
  177. pop cx
  178. call Func_GetFATEntry
  179. cmp ax, 0x0fff
  180. jz Label_File_Loaded
  181. push ax
  182. mov dx, RootDirSectors
  183. add ax, dx
  184. add ax, SectorBalance
  185. ; 继续读取下一个簇
  186. jmp Label_Go_On_Loading_File
  187. Label_File_Loaded:
  188. ;在屏幕上显示 kernel loaded
  189. mov ax, 0x1301
  190. mov bx, 0x000f
  191. mov dx, 0x0200 ;在第3行显示
  192. mov cx, 20 ;设置消息长度
  193. push ax
  194. mov ax, ds
  195. mov es, ax
  196. pop ax
  197. mov bp, Message_Kernel_Loaded
  198. int 0x10
  199. ; ======直接操作显示内存=======
  200. ; 从内存的0x0B800开始,是一段用于显示字符的内存空间。
  201. ; 每个字符占用2bytes,低字节保存要显示的字符,高字节保存样式
  202. mov ax, 0xB800
  203. mov gs, ax
  204. mov ah, 0x0F ;黑底白字
  205. mov al, '.'
  206. mov [gs:((80 * 2 + 20) * 2)], ax ;在屏幕第0行,39列
  207. Label_Kill_Motor:
  208. ; =====关闭软驱的马达======
  209. ; 向IO端口0x03f2写入0,关闭所有软驱
  210. push dx
  211. mov dx, 0x03F2
  212. mov al, 0
  213. out dx, al
  214. pop dx
  215. ; =====获取物理地址空间====
  216. ; 显示 正在获取内存结构
  217. mov ax, 0x1301
  218. mov bx, 0x000F
  219. mov dx, 0x0300 ; 在第四行显示
  220. mov cx, 34
  221. push ax
  222. mov ax, ds
  223. mov es, ax
  224. pop ax
  225. mov bp, Message_Start_Get_Mem_Struct
  226. int 0x10
  227. mov ax, 0x00
  228. mov es, ax
  229. mov di, Memory_Struct_Buffer_Addr ; 设置内存结构信息存储的地址
  230. mov ebx, 0 ;第一次调用0x15的时候,ebx要置为0 ebx存储的是下一个待返回的ARDS (Address Range Descriptor Structure)
  231. Label_Get_Mem_Struct:
  232. ;==== 获取内存物理地址信息
  233. ; 使用0x15中断程序的功能号0xe820来获取内存信息
  234. ; 返回信息在[es:di]指向的内存中
  235. ; 一共要分5次才能把20个字节的信息获取完成
  236. ; 这些信息在内核初始化内存管理单元的时候,会去解析它们。
  237. mov eax, 0xe820
  238. mov ecx, 20 ; 指定ARDS结构的大小,是固定值20
  239. mov edx, 0x534d4150 ; 固定签名标记,是字符串“SMAP”的ASCII码
  240. int 0x15
  241. jc Label_Get_Mem_Fail ; 若调用出错,则CF=1
  242. add di, 20
  243. cmp ebx, 0
  244. jne Label_Get_Mem_Struct ; ebx不为0
  245. jmp Label_Get_Mem_OK ; 获取内存信息完成
  246. Label_Get_Mem_Fail:
  247. ; =====获取内存信息失败====
  248. ; 显示 正在获取内存结构
  249. mov ax, 0x1301
  250. mov bx, 0x000c
  251. mov dx, 0x0400 ; 在第5行显示
  252. mov cx, 33
  253. push ax
  254. mov ax, ds
  255. mov es, ax
  256. pop ax
  257. mov bp, Message_Get_Mem_Failed
  258. int 0x10
  259. jmp $
  260. Label_Get_Mem_OK:
  261. ; ==== 成功获取内存信息 ===
  262. mov ax, 0x1301
  263. mov bx, 0x000f
  264. mov dx, 0x0400 ; 在第5行显示
  265. mov cx, 39
  266. push ax
  267. mov ax, ds
  268. mov es, ax
  269. pop ax
  270. mov bp, Message_Get_Mem_Success
  271. int 0x10
  272. jmp Label_Get_SVGA_Info
  273. Label_Get_SVGA_Info:
  274. ; ==== 获取SVGA芯片的信息
  275. mov ax, 0x1301
  276. mov bx, 0x000f
  277. mov dx, 0x0500 ; 在第6行显示
  278. mov cx, 30
  279. push ax
  280. mov ax, ds
  281. mov es, ax
  282. pop ax
  283. mov bp, Message_Start_Get_SVGA_Info
  284. int 0x10
  285. jmp $
  286. ; 从软盘读取一个扇区
  287. ; AX=待读取的磁盘起始扇区号
  288. ; CL=读入的扇区数量
  289. ; ES:BX=>目标缓冲区起始地址
  290. Func_ReadOneSector:
  291. push bp
  292. mov bp, sp
  293. sub esp, 2
  294. mov byte [bp-2], cl
  295. push bx
  296. mov bl, [BPB_SecPerTrk]
  297. div bl ;用AX寄存器中的值除以BL,得到目标磁道号(商:AL)以及目标磁道内的起始扇区号(余数:AH)
  298. inc ah ; 由于磁道内的起始扇区号从1开始计数,因此将余数+1
  299. mov cl, ah
  300. mov dh, al
  301. shr al, 1 ;计算出柱面号
  302. mov ch, al
  303. and dh, 1;计算出磁头号
  304. pop bx
  305. mov dl, [BS_DrvNum]
  306. ;最终,dh存储了磁头号,dl存储驱动器号
  307. ; ch存储柱面号,cl存储起始扇区号
  308. Label_Go_On_Reading:
  309. ; 使用BIOS中断服务程序INT13h的主功能号AH=02h实现软盘读取操作
  310. mov ah, 2
  311. mov al, byte [bp-2]
  312. int 0x13
  313. jc Label_Go_On_Reading ;当CF标志位被复位时,说明数据读取完成,恢复调用现场
  314. add esp, 2
  315. pop bp
  316. ret
  317. ; 解析FAT表项,根据当前FAT表项索引出下一个FAT表项
  318. Func_GetFATEntry:
  319. ; AX=FAT表项号(输入、输出参数)
  320. ; 保存将要被修改的寄存器
  321. push es
  322. push bx
  323. push ax
  324. ; 扩展段寄存器
  325. mov ax, 00
  326. mov es, ax
  327. pop ax
  328. mov byte [Odd], 0 ;将奇数标志位置0
  329. ; 将FAT表项号转换为总的字节号
  330. mov bx, 3
  331. mul bx
  332. mov bx, 2
  333. div bx
  334. cmp dx, 0
  335. jz Label_Even ; 偶数项
  336. mov byte [Odd], 1
  337. Label_Even:
  338. xor dx, dx ;把dx置0
  339. ; 计算得到扇区号(商)和扇区内偏移(余数)
  340. mov bx, [BPB_BytesPerSec]
  341. div bx
  342. push dx
  343. ; 读取两个扇区到[es:bx]
  344. mov bx, 0x8000
  345. add ax, SectorNumOfFAT1Start
  346. mov cl, 2 ; 设置读取两个扇区,解决FAT表项跨扇区的问题
  347. call Func_ReadOneSector
  348. pop dx
  349. add bx, dx
  350. mov ax, [es:bx]
  351. cmp byte [Odd], 1
  352. jnz Label_Even_2 ;若是偶数项,则跳转
  353. shr ax, 4 ; 解决奇偶项错位问题
  354. Label_Even_2:
  355. and ax, 0x0fff ; 确保表项号在正确的范围内 0x0003~0x0fff
  356. pop bx
  357. pop es
  358. ret
  359. ;====临时变量=====
  360. RootDirSizeForLoop dw RootDirSectors
  361. SectorNo dw 0
  362. Odd db 0
  363. OffsetOfKernelFileCount dd Offset_Of_Kernel_File
  364. ; 要显示的消息文本
  365. Message_Start_Loader: db "[DragonOS] Start Loader"
  366. Message_No_Loader: db "[ERROR] No Kernel Found."
  367. Message_Kernel_Loaded: db "[INFO] Kernel loaded"
  368. Message_Start_Get_Mem_Struct: db "[INFO] Try to get memory struct..."
  369. Message_Get_Mem_Failed: db "[ERROR] Get memory struct failed."
  370. Message_Get_Mem_Success: db "[INFO] Successful to get memory struct."
  371. Message_Start_Get_SVGA_Info: db "[INFO] Try to get SVGA info..."
  372. Kernel_FileName: db "KERNEL BIN", 0