一、/charpter5/i中代码树状图如下:
| -- Makefile
| -- a.img
| -- bochsrc
| -- boot
| | -- boot.asm
| | -- include
| | | -- fat12hdr.inc
| | | -- load.inc
| | | -- pm.inc
| | -- loader.asm
| -- include
| | -- const.h
| | -- global.h
| | -- protect.h
| | -- proto.h
| | -- string.h
| | -- type.h
| -- kernel
| | -- global.c
| | -- i8259.c
| | -- kernel.asm
| | -- protect.c
| | -- start.c
| -- lib
| | -- klib.c
| | -- kliba.asm
| | -- string.asm
二、代码整体架构
此代码分析涉及到FAT12文件系统,ELF文件格式,X86/WIN32函数调用规范,int 10h,int 13h,int 15h。请参考以下博客:
FAT12文件系统:http://blog.csdn.net/jltxgcy/article/details/8665475
ELF文件格式:http://blog.csdn.net/jltxgcy/article/details/8687737
X86/WIN32函数调用规范:http://blog.csdn.net/jltxgcy/article/details/8668666
int 10h,int 13h,int 15h:http://blog.csdn.net/jltxgcy/article/details/8687881
系统内存分配如图:
程序整体流程如下:
boot从软盘存入7c00h,开始执行把loader加载到内存90100h处,跳转到此处,loader把kernel加载到内存80000h处,跳入保护模式,在保护模式下,重新放置kernel的位置到30000h处,并跳转到3040D处开始执行代码。然后用C语言和汇编共同扩展kernel。首先把gdt和堆栈移到了kernel中,添加中断和异常处理。
核心思路,把用于C语言引用的.c和汇编中的global的,还有常量,变量(用extern)都放在.h文件夹中,这样在C语言调用只需#include就能用,汇编里面用C语言,只需要extern就可以。
汇编语言传递参数有两种方式,一、push 参数 因为call还要传入eip,所以在子函数中如果要取到,需要[esp+8] ,调用者返回后要恢复堆栈,传递了几个参数就要让esp+4*参数的个数。二、mov cl , 1 此种方法如果想要暂时保持数据还要sub esp-2。 push在其他处的用法均为下面的语句会改变当前寄存器的值,并且这些寄存器的值后面会用到。movebp,
esp 因为后面的push 语言要改变esp,那为什么不push esp呢?因为我们还要用到ebp来取得传递过来的参数。
这里面C语言的参数都是二进制的。
三、代码详细分析
boot/boot.asm
根目录读取一个扇区到内存09000h:0100位置,遍历此扇区的16个根目录,看是否有LOADER BIN,如果没找到,再读取一个扇区到内存09000h:0100位置,循环刚才的动作,直到14个扇区全部查找完毕;如果找到了,那么取该目录的开始簇号,①根据开始簇号取得他在数据区的扇区号然后读入内存09000h:0100处;然后根据开始簇号取得它在FAT1中的扇区号,然后读入内存08F00:0000处,一般读两个扇区;根据偏移计算FAT项的值,如果为FFF则结束,如果为008,转到①继续执行。
;%define _BOOT_DEBUG_ ; 做 Boot Sector 时一定将此行注释掉!将此行打开后用 nasm Boot.asm -o Boot.com 做成一个.COM文件易于调试 %ifdef _BOOT_DEBUG_ org 0100h ; 调试状态, 做成 .COM 文件, 可调试 %else org 07c00h ; Boot 状态, Bios 将把 Boot Sector 加载到 0:7C00 处并开始执行 %endif ;================================================================================================ %ifdef _BOOT_DEBUG_ BaseOfStack equ 0100h ; 调试状态下堆栈基地址(栈底, 从这个位置向低地址生长) %else BaseOfStack equ 07c00h ; Boot状态下堆栈基地址(栈底, 从这个位置向低地址生长) %endif %include "load.inc" ;================================================================================================ jmp short LABEL_START ; Start to boot. nop ; 这个 nop 不可少 ; 下面是 FAT12 磁盘的头, 之所以包含它是因为下面用到了磁盘的一些信息 %include "fat12hdr.inc" LABEL_START: mov ax, cs ;cs=0000h mov ds, ax mov es, ax mov ss, ax mov sp, BaseOfStack;上面的地方没有代码,可以自由存数据 ; 清屏 mov ax, 0600h ; AH = 6, AL = 0h mov bx, 0700h ; 黑底白字(BL = 07h) mov cx, 0 ; 左上角: (0, 0) mov dx, 0184fh ; 右下角: (80, 50) int 10h ; int 10h mov dh, 0 ; "Booting " call DispStr ;int 10号暂时不考虑,功能显示字符串,因为有很多种方式 xor ah, ah ; ┓ xor dl, dl ; ┣ 软驱复位 int 13h ; ┛ ; 下面在 A 盘的根目录寻找 LOADER.BIN mov word [wSectorNo], SectorNoOfRootDirectory ;19 LABEL_SEARCH_IN_ROOT_DIR_BEGIN: cmp word [wRootDirSizeForLoop], 0 ; ┓ 14 jz LABEL_NO_LOADERBIN ; ┣ 判断根目录区是不是已经读完 dec word [wRootDirSizeForLoop] ; ┛ 如果读完表示没有找到 LOADER.BIN mov ax, BaseOfLoader ;09000h mov es, ax ; es <- BaseOfLoader mov bx, OffsetOfLoader ; 0100h bx <- OffsetOfLoader 于是, es:bx = BaseOfLoader:OffsetOfLoader mov ax, [wSectorNo] ; ax <- Root Directory 中的某 Sector 号 mov cl, 1 call ReadSector mov si, LoaderFileName ; ds:si -> "LOADER BIN" mov di, OffsetOfLoader ; es:di -> BaseOfLoader:0100,,正好指向根目录项的文件名属性 cld mov dx, 10h ;因为一个扇区最多有512/32=16个根目录 LABEL_SEARCH_FOR_LOADERBIN: cmp dx, 0 ; 循环次数控制, jz LABEL_GOTO_NEXT_SECTOR_IN_ROOT_DIR ;如果已经读完这个扇区的所有根目录,就跳到下一个扇区。 dec dx ; 就跳到这个扇区的下一个根目录 mov cx, 11 ;因为根目录的DIR_Name有11个字节 LABEL_CMP_FILENAME: cmp cx, 0 jz LABEL_FILENAME_FOUND ; 如果比较了 11 个字符都相等, 表示找到 dec cx lodsb ; ds:si -> al cmp al, byte [es:di] jz LABEL_GO_ON jmp LABEL_DIFFERENT ; 只要发现不一样的字符就表明本 DirectoryEntry 不是我们要找的 LOADER.BIN LABEL_GO_ON: inc di jmp LABEL_CMP_FILENAME ; 继续循环 LABEL_DIFFERENT: and di, 0FFE0h ; di &= E0 为了让它指向本条目开头 add di, 20h ; di += 20h 下一个目录条目 20h=32个字节 mov si, LoaderFileName jmp LABEL_SEARCH_FOR_LOADERBIN; LABEL_GOTO_NEXT_SECTOR_IN_ROOT_DIR: add word [wSectorNo], 1 jmp LABEL_SEARCH_IN_ROOT_DIR_BEGIN LABEL_NO_LOADERBIN: mov dh, 2 ; "No LOADER." call DispStr ; 显示字符串 %ifdef _BOOT_DEBUG_ mov ax, 4c00h ; ┓ int 21h ; ┛没有找到 LOADER.BIN, 回到 DOS %else jmp $ ; 没有找到 LOADER.BIN, 死循环在这里 %endif LABEL_FILENAME_FOUND: ; 找到 LOADER.BIN 后便来到这里继续 mov ax, RootDirSectors and di, 0FFE0h ; di -> 当前条目的开始 add di, 01Ah ; 取得此条目对应的开始簇号的偏移 mov cx, word [es:di] ;2 push cx ; 保存此 Sector 在 FAT 中的序号 add cx, ax ;2+14 add cx, DeltaSectorNo ; 19+14+簇号-2 mov ax, BaseOfLoader mov es, ax ; mov bx, OffsetOfLoader ; mov ax, cx ; ax=34 LABEL_GOON_LOADING_FILE: push ax ; `. push bx ; | mov ah, 0Eh ; | 每读一个扇区就在 "Booting " 后面 mov al, '.' ; | 打一个点, 形成这样的效果: mov bl, 0Fh ; | Booting ...... int 10h ; | pop bx ; | pop ax ; / mov cl, 1 ;扇区号为34,读一个扇区,到09000h:0100h call ReadSector pop ax ; 取出此 Sector 在 FAT 中的序号 call GetFATEntry ;或者FAT项的值 cmp ax, 0FFFh jz LABEL_FILE_LOADED push ax ; 保存 Sector 在 FAT 中的序号 mov dx, RootDirSectors add ax, dx ;ax+14 add ax, DeltaSectorNo ;ax+14+17 add bx, [BPB_BytsPerSec];0100h+200h,又读取一个扇区 jmp LABEL_GOON_LOADING_FILE LABEL_FILE_LOADED: mov dh, 1 ; "Ready." call DispStr ; 显示字符串 ; ***************************************************************************************************** jmp BaseOfLoader:OffsetOfLoader ; 这一句正式跳转到已加载到内 ; 存中的 LOADER.BIN 的开始处, ; 开始执行 LOADER.BIN 的代码。 ; Boot Sector 的使命到此结束 ; ***************************************************************************************************** ;============================================================================ ;变量 ;---------------------------------------------------------------------------- wRootDirSizeForLoop: dw RootDirSectors ; 因为要用到这个存储单元,不是单一的用14这个数字,Root Directory 占用的扇区数, 在循环中会递减至零. wSectorNo: dw 0 ; 要读取的扇区号 bOdd: db 0 ; 奇数还是偶数 ;============================================================================ ;字符串 ;---------------------------------------------------------------------------- LoaderFileName: db "LOADER BIN", 0 ; LOADER.BIN 之文件名 ; 为简化代码, 下面每个字符串的长度均为 MessageLength MessageLength equ 9 BootMessage: db "Booting "; 9字节, 不够则用空格补齐. 序号 0 Message1: db "Ready. "; 9字节, 不够则用空格补齐. 序号 1 Message2: db "No LOADER"; 9字节, 不够则用空格补齐. 序号 2 ;============================================================================ ;---------------------------------------------------------------------------- ; 函数名: DispStr ;---------------------------------------------------------------------------- ; 作用: ; 显示一个字符串, 函数开始时 dh 中应该是字符串序号(0-based) DispStr: mov ax, MessageLength mul dh add ax, BootMessage mov bp, ax ; ┓ mov ax, ds ; ┣ ES:BP = 串地址 mov es, ax ; ┛ mov cx, MessageLength ; CX = 串长度 mov ax, 01301h ; AH = 13, AL = 01h mov bx, 0007h ; 页号为0(BH = 0) 黑底白字(BL = 07h) mov dl, 0 ;起始行列 int 10h ; int 10h ret ;---------------------------------------------------------------------------- ; 函数名: ReadSector ;---------------------------------------------------------------------------- ; 作用: ; 从第 ax 个 Sector 开始, 将 cl 个 Sector 读入 es:bx 中 ReadSector: ; ----------------------------------------------------------------------- ; 怎样由扇区号求扇区在磁盘中的位置 (扇区号 -> 柱面号, 起始扇区, 磁头号) ; ----------------------------------------------------------------------- ; 设扇区号为 x ; ┌ 柱面号 = y >> 1 ; x ┌ 商 y ┤ ; -------------- => ┤ └ 磁头号 = y & 1 ; 每磁道扇区数 │ ; └ 余 z => 起始扇区号 = z + 1 push bp ;因为要用到bp mov bp, sp ; sub esp, 2 ; 辟出两个字节的堆栈区域保存要读的扇区数: byte [bp-2] mov byte [bp-2], cl ;大家奇怪为什么不直接push呢,因为如果直接push,那么则不能直接取出来数据 push bx ; 保存 bx,因为要做除数 mov bl, [BPB_SecPerTrk] ; bl: 除数为18 div bl ; y 在 al 中, z 在 ah 中 inc ah ; z ++ mov cl, ah ; cl <- 起始扇区号 mov dh, al ; dh <- y shr al, 1 ; y >> 1 (其实是 y/BPB_NumHeads, 这里BPB_NumHeads=2) mov ch, al ; ch <- 柱面号 and dh, 1 ; dh & 1 = 磁头号 pop bx ; 恢复 bx ; 至此, "柱面号, 起始扇区, 磁头号" 全部得到 ^^^^^^^^^^^^^^^^^^^^^^^^ mov dl, [BS_DrvNum] ; 驱动器号 (0 表示 A 盘) .GoOnReading: mov ah, 2 ; 读 mov al, byte [bp-2] ; 读 al 个扇区 int 13h jc .GoOnReading ; 如果读取错误 CF 会被置为 1, 这时就不停地读, 直到正确为止 add esp, 2 ;由于前面辟出了两个字节,保存要读的扇区数 pop bp ret ;---------------------------------------------------------------------------- ; 函数名: GetFATEntry ;---------------------------------------------------------------------------- ; 作用: ; 找到序号为 ax 的 Sector 在 FAT 中的条目, 结果放在 ax 中 ; 需要注意的是, 中间需要读 FAT 的扇区到 es:bx 处, 所以函数一开始保存了 es 和 bx GetFATEntry: push es push bx push ax mov ax, BaseOfLoader; `. sub ax, 0100h ; | 在 BaseOfLoader 后面留出 4K 空间用于存放 FAT;9000-0100=8F00,90000和8F000之前差4K mov es, ax ; / pop ax ;ax=2 mov byte [bOdd], 0 mov bx, 3 mul bx ; ax=6; mov bx, 2 div bx ;ax=3(商);dx=0(余数),本质是ax*1.5,为了求偏移 cmp dx, 0 jz LABEL_EVEN mov byte [bOdd], 1 ;如果为1,说明是奇数 LABEL_EVEN:;偶数 ; 现在 ax 中是 FATEntry 在 FAT 中的偏移量,下面来 ; 计算 FATEntry 在哪个扇区中(FAT占用不止一个扇区) xor dx, dx mov bx, [BPB_BytsPerSec] :512 div bx ; dx:ax / BPB_BytsPerSec ; ax <- 商 (FATEntry 所在的扇区相对于 FAT 的扇区号) 结果是0号扇区 ; dx <- 余数 (FATEntry 在扇区内的偏移) 结果是3号偏移 push dx mov bx, 0 ; bx <- 0 于是, es:bx = (BaseOfLoader - 100):00 add ax, SectorNoOfFAT1 ; 引导扇区占一个扇区,此句之后的 ax 就是 FATEntry 所在的扇区号 mov cl, 2 call ReadSector ; 读取 FATEntry 所在的扇区, 一次读两个, 避免在边界 ; 发生错误, 因为一个 FATEntry 可能跨越两个扇区 pop dx add bx, dx ;偏移为3 mov ax, [es:bx] ; 如前面的图所说,ax=8FFFh cmp byte [bOdd], 1 jnz LABEL_EVEN_2 shr ax, 4 ;如果偏移为4,上步得到ax=008Fh,右移后ax=0008h,与后还为0008h LABEL_EVEN_2: and ax, 0FFFh ;不等于跳到此处,ax=0FFFh LABEL_GET_FAT_ENRY_OK: pop bx pop es ret ;---------------------------------------------------------------------------- times 510-($-$$) db 0 ; 填充剩下的空间,使生成的二进制代码恰好为512字节 dw 0xaa55 ; 结束标志
boot/include/fat12hdr.inc
; FAT12 磁盘的头 ; ---------------------------------------------------------------------- ;一个磁道有18个扇区,一个扇区有512字节 ; 下面是 FAT12 磁盘的头 BS_OEMName DB 'ForrestY' ; OEM String, 必须 8 个字节 BPB_BytsPerSec DW 512 ; 每扇区字节数 ;重要 BPB_SecPerClus DB 1 ; 每簇多少扇区 BPB_RsvdSecCnt DW 1 ; Boot 记录占用多少扇区 ;所以FAT的第一个扇区为1 BPB_NumFATs DB 2 ; 共有多少 FAT 表 BPB_RootEntCnt DW 224 ; 根目录文件数最大值 ;计算扇区个数时候用到 BPB_TotSec16 DW 2880 ; 逻辑扇区总数 BPB_Media DB 0xF0 ; 媒体描述符 BPB_FATSz16 DW 9 ; 每FAT扇区数 BPB_SecPerTrk DW 18 ; 每磁道扇区数 ;重要 BPB_NumHeads DW 2 ; 磁头数(面数) ;由扇区求柱面,磁头,扇区时候用到 BPB_HiddSec DD 0 ; 隐藏扇区数 BPB_TotSec32 DD 0 ; 如果 wTotalSectorCount 是 0 由这个值记录扇区数 BS_DrvNum DB 0 ; 中断 13 的驱动器号 ;后来赋值给dl BS_Reserved1 DB 0 ; 未使用 BS_BootSig DB 29h ; 扩展引导标记 (29h) BS_VolID DD 0 ; 卷序列号 BS_VolLab DB 'OrangeS0.02'; 卷标, 必须 11 个字节 BS_FileSysType DB 'FAT12 ' ; 文件系统类型, 必须 8个字节 ;------------------------------------------------------------------------ ; ------------------------------------------------------------------------- ; 基于 FAT12 头的一些常量定义,如果头信息改变,下面的常量可能也要做相应改变 ; ------------------------------------------------------------------------- FATSz equ 9 ; BPB_FATSz16 ;equ为伪指令,编译时候就变成对应的代码,本身不占用空间 RootDirSectors equ 14 ; 根目录占用14个扇区 根据BPB_RootEntCnt计算出来的 SectorNoOfRootDirectory equ 19 ; 根目录的第一个扇区号,每个根目录项占32个字节 SectorNoOfFAT1 equ 1 ; FAT1 的第一个扇区号 = BPB_RsvdSecCnt DeltaSectorNo equ 17 ; DeltaSectorNo = BPB_RsvdSecCnt + (BPB_NumFATs * FATSz) - 2 ; 文件的开始Sector号 = DirEntry中的开始Sector号 + 根目录占用Sector数目 + DeltaSectorNo
boot/include/load.inc
BaseOfLoader equ 09000h ; LOADER.BIN 被加载到的位置 ---- 段地址 OffsetOfLoader equ 0100h ; LOADER.BIN 被加载到的位置 ---- 偏移地址 BaseOfLoaderPhyAddr equ BaseOfLoader * 10h ; LOADER.BIN 被加载到的位置 ---- 物理地址 (= BaseOfLoader * 10h) BaseOfKernelFile equ 08000h ; KERNEL.BIN 被加载到的位置 ---- 段地址 OffsetOfKernelFile equ 0h ; KERNEL.BIN 被加载到的位置 ---- 偏移地址 BaseOfKernelFilePhyAddr equ BaseOfKernelFile * 10h KernelEntryPointPhyAddr equ 030400h ; 注意:1、必须与 MAKEFILE 中参数 -Ttext 的值相等!! ; 2、这是个地址而非仅仅是个偏移,如果 -Ttext 的值为 0x400400,则它的值也应该是 0x400400。 PageDirBase equ 200000h ; 页目录开始地址: 2M PageTblBase equ 201000h ; 页表开始地址: 2M + 4K
/boot/include/pm.inc
参考http://blog.csdn.net/jltxgcy/article/details/8656101
/boot/loader.asm
org 0100h jmp LABEL_START ; Start ; 下面是 FAT12 磁盘的头, 之所以包含它是因为下面用到了磁盘的一些信息 %include "fat12hdr.inc" %include "load.inc" %include "pm.inc" ; GDT ------------------------------------------------------------------------------------------------------------------------------------------------------------ ; 段基址 段界限 , 属性 LABEL_GDT: Descriptor 0, 0, 0 ; 空描述符 LABEL_DESC_FLAT_C: Descriptor 0, 0fffffh, DA_CR | DA_32 | DA_LIMIT_4K ; 0 ~ 4G LABEL_DESC_FLAT_RW: Descriptor 0, 0fffffh, DA_DRW | DA_32 | DA_LIMIT_4K ; 0 ~ 4G LABEL_DESC_VIDEO: Descriptor 0B8000h, 0ffffh, DA_DRW | DA_DPL3 ; 显存首地址 ; GDT ------------------------------------------------------------------------------------------------------------------------------------------------------------ GdtLen equ $ - LABEL_GDT GdtPtr dw GdtLen - 1 ; 段界限 dd BaseOfLoaderPhyAddr + LABEL_GDT ; 基地址 ; GDT 选择子 ---------------------------------------------------------------------------------- SelectorFlatC equ LABEL_DESC_FLAT_C - LABEL_GDT SelectorFlatRW equ LABEL_DESC_FLAT_RW - LABEL_GDT SelectorVideo equ LABEL_DESC_VIDEO - LABEL_GDT + SA_RPL3 ; GDT 选择子 ---------------------------------------------------------------------------------- BaseOfStack equ 0100h LABEL_START: ; <--- 从这里开始 ************* mov ax, cs ;cs:09000h mov ds, ax mov es, ax mov ss, ax mov sp, BaseOfStack mov dh, 0 ; "Loading " call DispStrRealMode ; 显示字符串 ; 得到内存数 mov ebx, 0 ; ebx = 后续值, 开始时需为 0 mov di, _MemChkBuf ; es:di 指向一个地址范围描述符结构(Address Range Descriptor Structure) .MemChkLoop: mov eax, 0E820h ; eax = 0000E820h mov ecx, 20 ; ecx = 地址范围描述符结构的大小 mov edx, 0534D4150h ; edx = 'SMAP' int 15h ; int 15h jc .MemChkFail add di, 20 inc dword [_dwMCRNumber] ; dwMCRNumber = ARDS 的个数 cmp ebx, 0 jne .MemChkLoop jmp .MemChkOK .MemChkFail: mov dword [_dwMCRNumber], 0 .MemChkOK: ; 下面在 A 盘的根目录寻找 KERNEL.BIN mov word [wSectorNo], SectorNoOfRootDirectory xor ah, ah ; ┓ xor dl, dl ; ┣ 软驱复位 int 13h ; ┛ LABEL_SEARCH_IN_ROOT_DIR_BEGIN: cmp word [wRootDirSizeForLoop], 0 ; ┓ jz LABEL_NO_KERNELBIN ; ┣ 判断根目录区是不是已经读完, 如果读完表示没有找到 KERNEL.BIN dec word [wRootDirSizeForLoop] ; ┛ mov ax, BaseOfKernelFile mov es, ax ; es <- BaseOfKernelFile mov bx, OffsetOfKernelFile ; bx <- OffsetOfKernelFile 于是, es:bx = BaseOfKernelFile:OffsetOfKernelFile = BaseOfKernelFile * 10h + OffsetOfKernelFile mov ax, [wSectorNo] ; ax <- Root Directory 中的某 Sector 号 mov cl, 1 call ReadSector mov si, KernelFileName ; ds:si -> "KERNEL BIN" mov di, OffsetOfKernelFile ; es:di -> BaseOfKernelFile:???? = BaseOfKernelFile*10h+???? cld mov dx, 10h LABEL_SEARCH_FOR_KERNELBIN: cmp dx, 0 ; ┓ jz LABEL_GOTO_NEXT_SECTOR_IN_ROOT_DIR ; ┣ 循环次数控制, 如果已经读完了一个 Sector, 就跳到下一个 Sector dec dx ; ┛ mov cx, 11 LABEL_CMP_FILENAME: cmp cx, 0 ; ┓ jz LABEL_FILENAME_FOUND ; ┣ 循环次数控制, 如果比较了 11 个字符都相等, 表示找到 dec cx ; ┛ lodsb ; ds:si -> al cmp al, byte [es:di] ; if al == es:di jz LABEL_GO_ON jmp LABEL_DIFFERENT LABEL_GO_ON: inc di jmp LABEL_CMP_FILENAME ; 继续循环 LABEL_DIFFERENT: and di, 0FFE0h ; else┓ 这时di的值不知道是什么, di &= e0 为了让它是 20h 的倍数 add di, 20h ; ┃ mov si, KernelFileName ; ┣ di += 20h 下一个目录条目 jmp LABEL_SEARCH_FOR_KERNELBIN; ┛ LABEL_GOTO_NEXT_SECTOR_IN_ROOT_DIR: add word [wSectorNo], 1 jmp LABEL_SEARCH_IN_ROOT_DIR_BEGIN LABEL_NO_KERNELBIN: mov dh, 2 ; "No KERNEL." call DispStrRealMode ; 显示字符串 jmp $ ; 没有找到 KERNEL.BIN, 死循环在这里 LABEL_FILENAME_FOUND: ; 找到 KERNEL.BIN 后便来到这里继续 mov ax, RootDirSectors and di, 0FFF0h ; di -> 当前条目的开始 push eax mov eax, [es : di + 01Ch] ; ┓ mov dword [dwKernelSize], eax ; ┛保存 KERNEL.BIN 文件大小 pop eax add di, 01Ah ; di -> 首 Sector mov cx, word [es:di] push cx ; 保存此 Sector 在 FAT 中的序号 add cx, ax add cx, DeltaSectorNo ; 这时 cl 里面是 LOADER.BIN 的起始扇区号 (从 0 开始数的序号) mov ax, BaseOfKernelFile mov es, ax ; es <- BaseOfKernelFile mov bx, OffsetOfKernelFile ; bx <- OffsetOfKernelFile 于是, es:bx = BaseOfKernelFile:OffsetOfKernelFile = BaseOfKernelFile * 10h + OffsetOfKernelFile mov ax, cx ; ax <- Sector 号 LABEL_GOON_LOADING_FILE: push ax ; ┓ push bx ; ┃ mov ah, 0Eh ; ┃ 每读一个扇区就在 "Loading " 后面打一个点, 形成这样的效果: mov al, '.' ; ┃ mov bl, 0Fh ; ┃ Loading ...... int 10h ; ┃ pop bx ; ┃ pop ax ; ┛ mov cl, 1;传递参数 call ReadSector pop ax ; 取出此 Sector 在 FAT 中的序号 call GetFATEntry cmp ax, 0FFFh jz LABEL_FILE_LOADED push ax ; 保存 Sector 在 FAT 中的序号 mov dx, RootDirSectors add ax, dx add ax, DeltaSectorNo add bx, [BPB_BytsPerSec] jmp LABEL_GOON_LOADING_FILE LABEL_FILE_LOADED: call KillMotor ; 关闭软驱马达 mov dh, 1 ; "Ready." call DispStrRealMode ; 显示字符串 ; 下面准备跳入保护模式 ------------------------------------------- ; 加载 GDTR lgdt [GdtPtr] ; 关中断 cli ; 打开地址线A20 in al, 92h or al, 00000010b out 92h, al ; 准备切换到保护模式 mov eax, cr0 or eax, 1 mov cr0, eax ; 真正进入保护模式 jmp dword SelectorFlatC:(BaseOfLoaderPhyAddr+LABEL_PM_START) ;============================================================================ ;变量 ;---------------------------------------------------------------------------- wRootDirSizeForLoop: dw RootDirSectors ; Root Directory 占用的扇区数 wSectorNo: dw 0 ; 要读取的扇区号 bOdd: db 0 ; 奇数还是偶数 dwKernelSize: dd 0 ; KERNEL.BIN 文件大小 ;============================================================================ ;字符串 ;---------------------------------------------------------------------------- KernelFileName db "KERNEL BIN", 0 ; KERNEL.BIN 之文件名 ; 为简化代码, 下面每个字符串的长度均为 MessageLength MessageLength equ 9 LoadMessage: db "Loading " Message1: db "Ready. " Message2: db "No KERNEL" ;============================================================================ ;---------------------------------------------------------------------------- ; 函数名: DispStrRealMode ;---------------------------------------------------------------------------- ; 运行环境: ; 实模式(保护模式下显示字符串由函数 DispStr 完成) ; 作用: ; 显示一个字符串, 函数开始时 dh 中应该是字符串序号(0-based) DispStrRealMode: mov ax, MessageLength mul dh add ax, LoadMessage mov bp, ax ; ┓ mov ax, ds ; ┣ ES:BP = 串地址 mov es, ax ; ┛ mov cx, MessageLength ; CX = 串长度 mov ax, 01301h ; AH = 13, AL = 01h mov bx, 0007h ; 页号为0(BH = 0) 黑底白字(BL = 07h) mov dl, 0 add dh, 3 ; 从第 3 行往下显示 int 10h ; int 10h ret ;---------------------------------------------------------------------------- ; 函数名: ReadSector ;---------------------------------------------------------------------------- ; 作用: ; 从序号(Directory Entry 中的 Sector 号)为 ax 的的 Sector 开始, 将 cl 个 Sector 读入 es:bx 中 ReadSector: ; ----------------------------------------------------------------------- ; 怎样由扇区号求扇区在磁盘中的位置 (扇区号 -> 柱面号, 起始扇区, 磁头号) ; ----------------------------------------------------------------------- ; 设扇区号为 x ; ┌ 柱面号 = y >> 1 ; x ┌ 商 y ┤ ; -------------- => ┤ └ 磁头号 = y & 1 ; 每磁道扇区数 │ ; └ 余 z => 起始扇区号 = z + 1 push bp mov bp, sp sub esp, 2 ; 辟出两个字节的堆栈区域保存要读的扇区数: byte [bp-2] mov byte [bp-2], cl ;暂时存放参数,如果是push进来的就不存在此问题 push bx ; 保存 bx mov bl, [BPB_SecPerTrk] ; bl: 除数 div bl ; y 在 al 中, z 在 ah 中 inc ah ; z ++ mov cl, ah ; cl <- 起始扇区号 mov dh, al ; dh <- y shr al, 1 ; y >> 1 (其实是 y/BPB_NumHeads, 这里BPB_NumHeads=2) mov ch, al ; ch <- 柱面号 and dh, 1 ; dh & 1 = 磁头号 pop bx ; 恢复 bx ; 至此, "柱面号, 起始扇区, 磁头号" 全部得到 ^^^^^^^^^^^^^^^^^^^^^^^^ mov dl, [BS_DrvNum] ; 驱动器号 (0 表示 A 盘) .GoOnReading: mov ah, 2 ; 读 mov al, byte [bp-2] ; 读 al 个扇区 int 13h jc .GoOnReading ; 如果读取错误 CF 会被置为 1, 这时就不停地读, 直到正确为止 add esp, 2 pop bp ret ;---------------------------------------------------------------------------- ; 函数名: GetFATEntry ;---------------------------------------------------------------------------- ; 作用: ; 找到序号为 ax 的 Sector 在 FAT 中的条目, 结果放在 ax 中 ; 需要注意的是, 中间需要读 FAT 的扇区到 es:bx 处, 所以函数一开始保存了 es 和 bx GetFATEntry: push es push bx push ax mov ax, BaseOfKernelFile ; ┓ sub ax, 0100h ; ┣ 在 BaseOfKernelFile 后面留出 4K 空间用于存放 FAT mov es, ax ; ┛ pop ax mov byte [bOdd], 0 mov bx, 3 mul bx ; dx:ax = ax * 3 mov bx, 2 div bx ; dx:ax / 2 ==> ax <- 商, dx <- 余数 cmp dx, 0 jz LABEL_EVEN mov byte [bOdd], 1 LABEL_EVEN:;偶数 xor dx, dx ; 现在 ax 中是 FATEntry 在 FAT 中的偏移量. 下面来计算 FATEntry 在哪个扇区中(FAT占用不止一个扇区) mov bx, [BPB_BytsPerSec] div bx ; dx:ax / BPB_BytsPerSec ==> ax <- 商 (FATEntry 所在的扇区相对于 FAT 来说的扇区号) ; dx <- 余数 (FATEntry 在扇区内的偏移)。 push dx mov bx, 0 ; bx <- 0 于是, es:bx = (BaseOfKernelFile - 100):00 = (BaseOfKernelFile - 100) * 10h add ax, SectorNoOfFAT1 ; 此句执行之后的 ax 就是 FATEntry 所在的扇区号 mov cl, 2 call ReadSector ; 读取 FATEntry 所在的扇区, 一次读两个, 避免在边界发生错误, 因为一个 FATEntry 可能跨越两个扇区 pop dx add bx, dx mov ax, [es:bx] cmp byte [bOdd], 1 jnz LABEL_EVEN_2 shr ax, 4 LABEL_EVEN_2: and ax, 0FFFh LABEL_GET_FAT_ENRY_OK: pop bx pop es ret ;---------------------------------------------------------------------------- ;---------------------------------------------------------------------------- ; 函数名: KillMotor ;---------------------------------------------------------------------------- ; 作用: ; 关闭软驱马达 KillMotor: push dx mov dx, 03F2h mov al, 0 out dx, al pop dx ret ;---------------------------------------------------------------------------- ; 从此以后的代码在保护模式下执行 ---------------------------------------------------- ; 32 位代码段. 由实模式跳入 --------------------------------------------------------- [SECTION .s32] ALIGN 32 [BITS 32] LABEL_PM_START: mov ax, SelectorVideo mov gs, ax mov ax, SelectorFlatRW mov ds, ax mov es, ax mov fs, ax mov ss, ax mov esp, TopOfStack push szMemChkTitle call DispStr add esp, 4 call DispMemInfo call SetupPaging ;mov ah, 0Fh ; 0000: 黑底 1111: 白字 ;mov al, 'P' ;mov [gs:((80 * 0 + 39) * 2)], ax ; 屏幕第 0 行, 第 39 列。 call InitKernel ;jmp $ ;*************************************************************** jmp SelectorFlatC:KernelEntryPointPhyAddr ; 正式进入内核 * ;*************************************************************** ; 内存看上去是这样的: ; ┃ ┃ ; ┃ . ┃ ; ┃ . ┃ ; ┃ . ┃ ; ┣━━━━━━━━━━━━━━━━━━┫ ; ┃■■■■■■■■■■■■■■■■■■┃ ; ┃■■■■■■Page Tables■■■■■■┃ ; ┃■■■■■(大小由LOADER决定)■■■■┃ ; 00101000h ┃■■■■■■■■■■■■■■■■■■┃ PageTblBase ; ┣━━━━━━━━━━━━━━━━━━┫ ; ┃■■■■■■■■■■■■■■■■■■┃ ; 00100000h ┃■■■■Page Directory Table■■■■┃ PageDirBase <- 1M ; ┣━━━━━━━━━━━━━━━━━━┫ ; ┃□□□□□□□□□□□□□□□□□□┃ ; F0000h ┃□□□□□□□System ROM□□□□□□┃ ; ┣━━━━━━━━━━━━━━━━━━┫ ; ┃□□□□□□□□□□□□□□□□□□┃ ; E0000h ┃□□□□Expansion of system ROM □□┃ ; ┣━━━━━━━━━━━━━━━━━━┫ ; ┃□□□□□□□□□□□□□□□□□□┃ ; C0000h ┃□□□Reserved for ROM expansion□□┃ ; ┣━━━━━━━━━━━━━━━━━━┫ ; ┃□□□□□□□□□□□□□□□□□□┃ B8000h ← gs ; A0000h ┃□□□Display adapter reserved□□□┃ ; ┣━━━━━━━━━━━━━━━━━━┫ ; ┃□□□□□□□□□□□□□□□□□□┃ ; 9FC00h ┃□□extended BIOS data area (EBDA)□┃ ; ┣━━━━━━━━━━━━━━━━━━┫ ; ┃■■■■■■■■■■■■■■■■■■┃ ; 90000h ┃■■■■■■■LOADER.BIN■■■■■■┃ somewhere in LOADER ← esp ; ┣━━━━━━━━━━━━━━━━━━┫ ; ┃■■■■■■■■■■■■■■■■■■┃ ; 80000h ┃■■■■■■■KERNEL.BIN■■■■■■┃ ; ┣━━━━━━━━━━━━━━━━━━┫ ; ┃■■■■■■■■■■■■■■■■■■┃ ; 30000h ┃■■■■■■■■KERNEL■■■■■■■┃ 30400h ← KERNEL 入口 (KernelEntryPointPhyAddr) ; ┣━━━━━━━━━━━━━━━━━━┫ ; ┃ ┃ ; 7E00h ┃ F R E E ┃ ; ┣━━━━━━━━━━━━━━━━━━┫ ; ┃■■■■■■■■■■■■■■■■■■┃ ; 7C00h ┃■■■■■■BOOT SECTOR■■■■■■┃ ; ┣━━━━━━━━━━━━━━━━━━┫ ; ┃ ┃ ; 500h ┃ F R E E ┃ ; ┣━━━━━━━━━━━━━━━━━━┫ ; ┃□□□□□□□□□□□□□□□□□□┃ ; 400h ┃□□□□ROM BIOS parameter area □□┃ ; ┣━━━━━━━━━━━━━━━━━━┫ ; ┃◇◇◇◇◇◇◇◇◇◇◇◇◇◇◇◇◇◇┃ ; 0h ┃◇◇◇◇◇◇Int Vectors◇◇◇◇◇◇┃ ; ┗━━━━━━━━━━━━━━━━━━┛ ← cs, ds, es, fs, ss ; ; ; ┏━━━┓ ┏━━━┓ ; ┃■■■┃ 我们使用 ┃□□□┃ 不能使用的内存 ; ┗━━━┛ ┗━━━┛ ; ┏━━━┓ ┏━━━┓ ; ┃ ┃ 未使用空间 ┃◇◇◇┃ 可以覆盖的内存 ; ┗━━━┛ ┗━━━┛ ; ; 注:KERNEL 的位置实际上是很灵活的,可以通过同时改变 LOAD.INC 中的 KernelEntryPointPhyAddr 和 MAKEFILE 中参数 -Ttext 的值来改变。 ; 比如,如果把 KernelEntryPointPhyAddr 和 -Ttext 的值都改为 0x400400,则 KERNEL 就会被加载到内存 0x400000(4M) 处,入口在 0x400400。 ; ; ------------------------------------------------------------------------ ; 显示 AL 中的数字 ; ------------------------------------------------------------------------ DispAL:;主要就是一个2进制到16进制转换的过程 push ecx push edx push edi mov edi, [dwDispPos] mov ah, 0Fh ; 0000b: 黑底 1111b: 白字 mov dl, al ;al 赋给dl,先把al的值保存起来 shr al, 4 ;al右移4位,原来al中的高四位成为低四位 mov ecx, 2 .begin: and al, 01111b ;处理al的高4位 cmp al, 9 ;前者大于后者跳转 ,即al大于9跳转,al小于9不跳转。 ja .1 add al, '0' ;al小于9还时用数字表示 jmp .2 ;显示出来 .1: sub al, 0Ah ;al大于9就要转换为16进制,用字母的方式表示 add al, 'A' .2: mov [gs:edi], ax ;al中要显示的数值已经转化为16进制了,就显示出来 add edi, 2 mov al, dl ;这时al中低四位就是存放的原来al中的低四位 loop .begin ;跳转到begin继续执行,这时就是跳转上去处理原来al中的低四位 ;add edi, 2 mov [dwDispPos], edi ;每次都要修改,以便下一个演示,可以接着显示 pop edi pop edx pop ecx ret ; DispAL 结束------------------------------------------------------------- ; ------------------------------------------------------------------------ ; 显示一个整形数 ; ------------------------------------------------------------------------ DispInt: mov eax, [esp + 4] ;ss:(esp+4)注意这里esp+4是因为调入之前的call指令,因为call指令会自动把一些参数入栈,esp要减4,esp+4正好指向了第一次push进去的 shr eax, 24 call DispAL mov eax, [esp + 4] shr eax, 16 call DispAL mov eax, [esp + 4] shr eax, 8 call DispAL mov eax, [esp + 4] call DispAL mov ah, 07h ; 0000b: 黑底 0111b: 灰字 mov al, 'h' push edi mov edi, [dwDispPos] mov [gs:edi], ax ;显示h add edi, 4;本来ax应该是edi+2,但是为了再空一格,所以又加了2 ,一共加4,从而得到新的位置 mov [dwDispPos], edi ;每次都要修改,以便下一个演示,可以接着显示 pop edi ret ; DispInt 结束------------------------------------------------------------ ; ------------------------------------------------------------------------ ; 显示一个字符串 ; ------------------------------------------------------------------------ DispStr: push ebp ;因为还压入了eip mov ebp, esp push ebx push esi push edi mov esi, [ebp + 8] ; ss:ebp+8 pszInfo mov edi, [dwDispPos] mov ah, 0Fh .1: lodsb test al, al jz .2 ;结束了么? cmp al, 0Ah ; 是回车吗? jnz .3 push eax mov eax, edi mov bl, 160 div bl and eax, 0FFh inc eax mov bl, 160 mul bl mov edi, eax pop eax ;如果是回车,那么光标另起一行 jmp .1 ;跳到1 .3: mov [gs:edi], ax add edi, 2 jmp .1 .2: mov [dwDispPos], edi; 每次都要修改,以便下一个演示,可以接着显示 pop edi pop esi pop ebx pop ebp ret ; DispStr 结束------------------------------------------------------------ ; ------------------------------------------------------------------------ ; 换行 ; ------------------------------------------------------------------------ DispReturn: push szReturn call DispStr ;printf("\n"); add esp, 4 ret ; DispReturn 结束--------------------------------------------------------- ; ------------------------------------------------------------------------ ; 内存拷贝,仿 memcpy ; ------------------------------------------------------------------------ ; void* MemCpy(void* es:pDest, void* ds:pSrc, int iSize); ; ------------------------------------------------------------------------ MemCpy: push ebp mov ebp, esp push esi push edi push ecx mov edi, [ebp + 8] ; Destination mov esi, [ebp + 12] ; Source mov ecx, [ebp + 16] ; Counter .1: cmp ecx, 0 ; 判断计数器 jz .2 ; 计数器为零时跳出 mov al, [ds:esi] ; ┓ inc esi ; ┃ ; ┣ 逐字节移动 mov byte [es:edi], al ; ┃ inc edi ; ┛ dec ecx ; 计数器减一 jmp .1 ; 循环 .2: mov eax, [ebp + 8] ; 返回值 pop ecx pop edi pop esi mov esp, ebp pop ebp ret ; 函数结束,返回 ; MemCpy 结束------------------------------------------------------------- ; 显示内存信息 -------------------------------------------------------------- DispMemInfo: push esi push edi push ecx mov esi, MemChkBuf mov ecx, [dwMCRNumber] ;for(int i=0;i<[MCRNumber];i++) // 每次得到一个ARDS(Address Range Descriptor Structure)结构 .loop: ;{ mov edx, 5 ; for(int j=0;j<5;j++) // 每次得到一个ARDS中的成员,共5个成员 mov edi, ARDStruct ; { // 依次显示:BaseAddrLow,BaseAddrHigh,LengthLow,LengthHigh,Type .1: ; push dword [esi] ; BaseAddrLow,BaseAddrHigh,LengthLow,LengthHigh,Type 每个对应4个字节,每次压入4个字节 call DispInt ; DispInt(MemChkBuf[j*4]); // 显示一个成员 pop eax ; stosd ; ARDStruct[j*4] = MemChkBuf[j*4]; add esi, 4 ; dec edx ; cmp edx, 0 ; jnz .1 ; } call DispReturn ; printf("\n"); cmp dword [dwType], 1 ; if(Type == AddressRangeMemory) // AddressRangeMemory : 1, AddressRangeReserved : 2 jne .2 ; { mov eax, [dwBaseAddrLow] ; add eax, [dwLengthLow] ; cmp eax, [dwMemSize] ; if(BaseAddrLow + LengthLow > MemSize) jb .2 ; mov [dwMemSize], eax ; MemSize = BaseAddrLow + LengthLow; .2: ; } loop .loop ;} ; call DispReturn ;printf("\n"); push szRAMSize ; call DispStr ;printf("RAM size:"); add esp, 4 ; ; push dword [dwMemSize] ; call DispInt ;DispInt(MemSize); add esp, 4 ; pop ecx pop edi pop esi ret ; --------------------------------------------------------------------------- ; 启动分页机制 -------------------------------------------------------------- SetupPaging: ; 根据内存大小计算应初始化多少PDE以及多少页表 xor edx, edx mov eax, [dwMemSize] mov ebx, 400000h ; 400000h = 4M = 4096 * 1024, 一个页表对应的内存大小 div ebx mov ecx, eax ; 此时 ecx 为页表的个数,也即 PDE 应该的个数 test edx, edx jz .no_remainder inc ecx ; 如果余数不为 0 就需增加一个页表 .no_remainder: push ecx ; 暂存页表个数 ; 为简化处理, 所有线性地址对应相等的物理地址. 并且不考虑内存空洞. ; 首先初始化页目录 mov ax, SelectorFlatRW mov es, ax mov edi, PageDirBase ; 此段首地址为 PageDirBase xor eax, eax mov eax, PageTblBase | PG_P | PG_USU | PG_RWW .1: stosd add eax, 4096 ; 为了简化, 所有页表在内存中是连续的. loop .1 ; 再初始化所有页表 pop eax ; 页表个数 mov ebx, 1024 ; 每个页表 1024 个 PTE mul ebx mov ecx, eax ; PTE个数 = 页表个数 * 1024 mov edi, PageTblBase ; 此段首地址为 PageTblBase xor eax, eax mov eax, PG_P | PG_USU | PG_RWW .2: stosd add eax, 4096 ; 每一页指向 4K 的空间 loop .2 mov eax, PageDirBase mov cr3, eax mov eax, cr0 or eax, 80000000h mov cr0, eax jmp short .3 .3: nop ret ; 分页机制启动完毕 ---------------------------------------------------------- ; InitKernel --------------------------------------------------------------------------------- ; 将 KERNEL.BIN 的内容经过整理对齐后放到新的位置 ; -------------------------------------------------------------------------------------------- InitKernel: ; 遍历每一个 Program Header,根据 Program Header 中的信息来确定把什么放进内存,放到什么位置,以及放多少。 xor esi, esi mov cx, word [BaseOfKernelFilePhyAddr + 2Ch]; 一共有几个Program Header放入cx movzx ecx, cx ; mov esi, [BaseOfKernelFilePhyAddr + 1Ch] ; Program Header相对ELF文件的偏移地址 add esi, BaseOfKernelFilePhyAddr ; 0x00080000+0x34,Program Header在内存中的偏移 .Begin: mov eax, [esi + 0] ;如果为0,那么直接换另一个Program Header cmp eax, 0 ; PT_NULL jz .NoAction push dword [esi + 010h] ; 把文件大小压入栈,作为第三个参数 mov eax, [esi + 04h] ; eax=0x00 add eax, BaseOfKernelFilePhyAddr ; add eax,00080000h push eax ; 源地址为00080000h压入栈,作为第二个参数 push dword [esi + 08h] ;把段的第一个字节在内存中的地址30000h压入栈,作为第一个参数 call MemCpy ; add esp, 12 ; .NoAction: add esi, 020h ; esi指向下一个Program Header Entry程序头目录 dec ecx jnz .Begin ret ; InitKernel ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ; SECTION .data1 之开始 --------------------------------------------------------------------------------------------- [SECTION .data1] ALIGN 32 LABEL_DATA: ; 实模式下使用这些符号 ; 字符串 _szMemChkTitle: db "BaseAddrL BaseAddrH LengthLow LengthHigh Type", 0Ah, 0 _szRAMSize: db "RAM size:", 0 _szReturn: db 0Ah, 0 ;0的意义是结束 ;; 变量 _dwMCRNumber: dd 0 ; Memory Check Result _dwDispPos: dd (80 * 6 + 0) * 2 ; 屏幕第 6 行, 第 0 列。 _dwMemSize: dd 0 _ARDStruct: ; Address Range Descriptor Structure _dwBaseAddrLow: dd 0 _dwBaseAddrHigh: dd 0 _dwLengthLow: dd 0 _dwLengthHigh: dd 0 _dwType: dd 0 _MemChkBuf: times 256 db 0 ; ;; 保护模式下使用这些符号 szMemChkTitle equ BaseOfLoaderPhyAddr + _szMemChkTitle szRAMSize equ BaseOfLoaderPhyAddr + _szRAMSize szReturn equ BaseOfLoaderPhyAddr + _szReturn dwDispPos equ BaseOfLoaderPhyAddr + _dwDispPos dwMemSize equ BaseOfLoaderPhyAddr + _dwMemSize dwMCRNumber equ BaseOfLoaderPhyAddr + _dwMCRNumber ARDStruct equ BaseOfLoaderPhyAddr + _ARDStruct dwBaseAddrLow equ BaseOfLoaderPhyAddr + _dwBaseAddrLow dwBaseAddrHigh equ BaseOfLoaderPhyAddr + _dwBaseAddrHigh dwLengthLow equ BaseOfLoaderPhyAddr + _dwLengthLow dwLengthHigh equ BaseOfLoaderPhyAddr + _dwLengthHigh dwType equ BaseOfLoaderPhyAddr + _dwType MemChkBuf equ BaseOfLoaderPhyAddr + _MemChkBuf ; 堆栈就在数据段的末尾 StackSpace: times 1000h db 0 TopOfStack equ BaseOfLoaderPhyAddr + $ ; 栈顶 ; SECTION .data1 之结束
/include/const.h
#ifndef _ORANGES_CONST_H_ #define _ORANGES_CONST_H_ /* EXTERN is defined as extern except in global.c */ #define EXTERN extern /* 函数类型 */ #define PUBLIC /* PUBLIC is the opposite of PRIVATE */ #define PRIVATE static /* PRIVATE x limits the scope of x */ /* GDT 和 IDT 中描述符的个数 */ #define GDT_SIZE 128 #define IDT_SIZE 256 /* 权限 */ #define PRIVILEGE_KRNL 0 #define PRIVILEGE_TASK 1 #define PRIVILEGE_USER 3 /* 8259A interrupt controller ports. */ #define INT_M_CTL 0x20 控制8259A的端口 #define INT_M_CTLMASK 0x21 #define INT_S_CTL 0xA0 #define INT_S_CTLMASK 0xA1 #endif /* _ORANGES_CONST_H_ */
/include/global.h
#ifdef GLOBAL_VARIABLES_HERE #undef EXTERN #define EXTERN #endif EXTERN int disp_pos; //此处没有extern,是引用global.h中的。这个变量记录了显存gs:edi中的edi,为了连续显示打下基础 EXTERN u8 gdt_ptr[6]; /* 0~15:Limit 16~47:Base */ EXTERN DESCRIPTOR gdt[GDT_SIZE]; EXTERN u8 idt_ptr[6]; /* 0~15:Limit 16~47:Base */ EXTERN GATE idt[IDT_SIZE];
/include/protect.h
#ifndef _ORANGES_PROTECT_H_ #define _ORANGES_PROTECT_H_ /* 存储段描述符/系统段描述符 */ typedef struct s_descriptor /* 共 8 个字节 */ { u16 limit_low; /* Limit */ u16 base_low; /* Base */ u8 base_mid; /* Base */ u8 attr1; /* P(1) DPL(2) DT(1) TYPE(4) */ u8 limit_high_attr2; /* G(1) D(1) 0(1) AVL(1) LimitHigh(4) */ u8 base_high; /* Base */ }DESCRIPTOR; /* 门描述符 */ typedef struct s_gate //按字节排放顺序写的 { u16 offset_low; /* Offset Low */ u16 selector; /* Selector */ u8 dcount; /* 该字段只在调用门描述符中有效。如果在利用 调用门调用子程序时引起特权级的转换和堆栈 的改变,需要将外层堆栈中的参数复制到内层 堆栈。该双字计数字段就是用于说明这种情况 发生时,要复制的双字参数的数量。*/ u8 attr; /* P(1) DPL(2) DT(1) TYPE(4) */ u16 offset_high; /* Offset High */ }GATE; /* GDT */ /* 描述符索引 */ #define INDEX_DUMMY 0 // ┓ #define INDEX_FLAT_C 1 // ┣ LOADER 里面已经确定了的. #define INDEX_FLAT_RW 2 // ┃ #define INDEX_VIDEO 3 // ┛ /* 选择子 */ #define SELECTOR_DUMMY 0 // ┓ #define SELECTOR_FLAT_C 0x08 // ┣ LOADER 里面已经确定了的. #define SELECTOR_FLAT_RW 0x10 // ┃ #define SELECTOR_VIDEO (0x18+3) // ┛<-- RPL=3 #define SELECTOR_KERNEL_CS SELECTOR_FLAT_C #define SELECTOR_KERNEL_DS SELECTOR_FLAT_RW /* 描述符类型值说明 */ #define DA_32 0x4000 /* 32 位段 */ #define DA_LIMIT_4K 0x8000 /* 段界限粒度为 4K 字节 */ #define DA_DPL0 0x00 /* DPL = 0 */ #define DA_DPL1 0x20 /* DPL = 1 */ #define DA_DPL2 0x40 /* DPL = 2 */ #define DA_DPL3 0x60 /* DPL = 3 */ /* 存储段描述符类型值说明 */ #define DA_DR 0x90 /* 存在的只读数据段类型值 */ #define DA_DRW 0x92 /* 存在的可读写数据段属性值 */ #define DA_DRWA 0x93 /* 存在的已访问可读写数据段类型值 */ #define DA_C 0x98 /* 存在的只执行代码段属性值 */ #define DA_CR 0x9A /* 存在的可执行可读代码段属性值 */ #define DA_CCO 0x9C /* 存在的只执行一致代码段属性值 */ #define DA_CCOR 0x9E /* 存在的可执行可读一致代码段属性值 */ /* 系统段描述符类型值说明 */ #define DA_LDT 0x82 /* 局部描述符表段类型值 */ #define DA_TaskGate 0x85 /* 任务门类型值 */ #define DA_386TSS 0x89 /* 可用 386 任务状态段类型值 */ #define DA_386CGate 0x8C /* 386 调用门类型值 */ #define DA_386IGate 0x8E /* 386 中断门类型值 */ #define DA_386TGate 0x8F /* 386 陷阱门类型值 */ /* 中断向量 */ #define INT_VECTOR_DIVIDE 0x0 #define INT_VECTOR_DEBUG 0x1 #define INT_VECTOR_NMI 0x2 #define INT_VECTOR_BREAKPOINT 0x3 #define INT_VECTOR_OVERFLOW 0x4 #define INT_VECTOR_BOUNDS 0x5 #define INT_VECTOR_INVAL_OP 0x6 #define INT_VECTOR_COPROC_NOT 0x7 #define INT_VECTOR_DOUBLE_FAULT 0x8 #define INT_VECTOR_COPROC_SEG 0x9 #define INT_VECTOR_INVAL_TSS 0xA #define INT_VECTOR_SEG_NOT 0xB #define INT_VECTOR_STACK_FAULT 0xC #define INT_VECTOR_PROTECTION 0xD #define INT_VECTOR_PAGE_FAULT 0xE #define INT_VECTOR_COPROC_ERR 0x10 /* 中断向量 */ #define INT_VECTOR_IRQ0 0x20 #define INT_VECTOR_IRQ8 0x28 #endif /* _ORANGES_PROTECT_H_ */
/include/proto.h
PUBLIC void out_byte(u16 port, u8 value); //把用于C语言引用的.c和汇编中的global的,还有常量,变量(用extern)都放在.h文件夹中 PUBLIC u8 in_byte(u16 port); PUBLIC void disp_str(char * info); PUBLIC void disp_color_str(char * info, int color); PUBLIC void init_prot(); PUBLIC void init_8259A();
/include/string.h
PUBLIC void* memcpy(void* p_dst, void* p_src, int size); ////把用于C语言引用的.c和汇编中的global的,还有常量,变量(用extern)都放在.h文件夹中
/include/type.h
#ifndef _ORANGES_TYPE_H_ #define _ORANGES_TYPE_H_ typedef unsigned int u32;32位的数据 typedef unsigned short u16;16位的数据 typedef unsigned char u8;一个字节的数据 typedef void (*int_handler) ();//定义了一个函数指针,具体细节不追究 #endif /* _ORANGES_TYPE_H_ */
/kernel/global.c
#define GLOBAL_VARIABLES_HERE #include "type.h" #include "const.h" #include "protect.h" #include "proto.h" #include "global.h" //此处定义了gdt ldt那些变量
/kernel/i8259.c
#include "type.h" #include "const.h" #include "protect.h" #include "proto.h" /*======================================================================* init_8259A *======================================================================*/ PUBLIC void init_8259A() //初始化8259A,并打开键盘中断 { /* Master 8259, ICW1. */ out_byte(INT_M_CTL, 0x11); /* Slave 8259, ICW1. */ out_byte(INT_S_CTL, 0x11); /* Master 8259, ICW2. 设置 '主8259' 的中断入口地址为 0x20. */ out_byte(INT_M_CTLMASK, INT_VECTOR_IRQ0); /* Slave 8259, ICW2. 设置 '从8259' 的中断入口地址为 0x28 */ out_byte(INT_S_CTLMASK, INT_VECTOR_IRQ8); /* Master 8259, ICW3. IR2 对应 '从8259'. */ out_byte(INT_M_CTLMASK, 0x4); /* Slave 8259, ICW3. 对应 '主8259' 的 IR2. */ out_byte(INT_S_CTLMASK, 0x2); /* Master 8259, ICW4. */ out_byte(INT_M_CTLMASK, 0x1); /* Slave 8259, ICW4. */ out_byte(INT_S_CTLMASK, 0x1); /* Master 8259, OCW1. */ out_byte(INT_M_CTLMASK, 0xFD);//打开键盘中断 /* Slave 8259, OCW1. */ out_byte(INT_S_CTLMASK, 0xFF); } /*======================================================================* spurious_irq *======================================================================*/ PUBLIC void spurious_irq(int irq)//键盘中断处理函数 { disp_str("spurious_irq: "); disp_int(irq); disp_str("\n"); }
/kernel/kernel.asm
; ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
; kernel.asm
; ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
; Forrest Yu, 2005
; ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++SELECTOR_KERNEL_CS equ 8
; 导入函数
extern cstart
extern exception_handler;硬件中断处理函数
extern spurious_irq;异常处理函数;