diff --git a/.gitignore b/.gitignore index c317f9c..b672fde 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1 @@ -*.bin -*.img -obj/ -.vscode \ No newline at end of file +obj diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json new file mode 100644 index 0000000..0927960 --- /dev/null +++ b/.vscode/c_cpp_properties.json @@ -0,0 +1,13 @@ +{ + "configurations": [ + { + "name": "Linux", + "includePath": [ + "${workspaceFolder}/inc" + ], + "defines": [], + "cStandard": "gnu99" + } + ], + "version": 4 +} \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..633def3 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,22 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "kernel-debug", + "type": "cppdbg", + "request": "launch", + "miDebuggerServerAddress": "127.0.0.1:1234", + "program": "${workspaceFolder}/obj/kern/kernel.dbg", + "args": [], + "stopAtEntry": false, + "cwd": "${workspaceFolder}", + "environment": [], + "externalConsole": false, + "logging": { + "engineLogging": false + }, + "MIMode": "gdb", + "miDebuggerPath": "/usr/bin/gdb", + } + ] +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..2cd9ff5 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,10 @@ +{ + "editor.detectIndentation": false, + "editor.tabSize": 8, + "editor.insertSpaces": false, + "files.associations": { + "*.h": "c", + "*.c": "c", + "*.asm": "nasm" + } +} \ No newline at end of file diff --git a/2020301918-李懋良-试点班第3个报告.docx b/2020301918-李懋良-试点班第3个报告.docx deleted file mode 100644 index 1a6ce9f..0000000 Binary files a/2020301918-李懋良-试点班第3个报告.docx and /dev/null differ diff --git a/Makefile b/Makefile index ad9a863..66e7731 100644 --- a/Makefile +++ b/Makefile @@ -1,11 +1,5 @@ # # make的主文件 -# 这个可是个大文件,可能乍一看人吓傻了,没事,对于makefile,可以先不去理会。 -# 只需要make,make run/monitor/gdb/clean即可。 -# 如果要研究细节别光读代码,建议用make -n先去理解每条命令在干啥,这是xv6里写的非常漂亮的地方。 -# 我们miniOS也仅仅做了部分的移植,它的功能比你想的强大。 -# 当你的源代码/头文件/kernel的编译命令发生变动时都会及时重编译。 -# 需要添加新的文件只需要在kern/Makefrag中修改KERN_SRCFILES即可 # # 文件夹 @@ -29,7 +23,6 @@ OBJDUMP := objdump # 查询可重定位文件符号表 NM := nm -# 预定义,-Dwdnmd相当于在C程序中`#ifdef wdnmd`为真 DEFS := # gcc的相关命令参数 @@ -44,6 +37,8 @@ CFLAGS += -I $(INCDIR) -MD CFLAGS += -fno-stack-protector # -std=gnu99 规定编译的语言规范为gnu99 CFLAGS += -std=gnu99 +# -fno-pie 不创建动态链接库 +CFLAGS += -fno-pie # -static 编译静态程序 # -m32 编译32位程序 CFLAGS += -static -m32 @@ -55,6 +50,11 @@ CFLAGS += -Wall -Wno-format -Wno-unused -Werror # ld链接器的相关命令参数 # -m elf_i386 链接的格式为i386 LDFLAGS := -m elf_i386 +# -nostdlib 不链接gcc的标准库,用库只能用命令行的 +LDFLAGS += -nostdlib + +# 获取gcc的库文件(除法取模会用到) +GCC_LIB := $(shell $(CC) $(CFLAGS) -print-libgcc-file-name) # 记录每个OBJDIR里存放的每个子文件夹 # 对于这个系统来说,最后的值为./obj/kern和./obj/boot @@ -64,31 +64,28 @@ OBJDIRS := # all的依赖会在kern/Makefrag中填充 all: -# xv6黑科技,获取编译命令,如果命令较新则会重新编译所有文件 +# xv6黑科技,获取编译命令,如果命令较新则重新编译所有文件 .PRECIOUS: $(OBJDIR)/.vars.% $(OBJDIR)/.vars.%: FORCE @echo "$($*)" | cmp -s $@ || echo "$($*)" > $@ .PHONY: FORCE -# 导入两个文件,两个文件分别编写,方便管理,也让主makefile更加清晰 +# 导入两个文件,两个文件分别编写,方便管理,也让主makefile更加舒服 include boot/Makefrag include kern/Makefrag -# FAT12镜像文件 +# FAT32镜像文件 IMAGE = $(OBJDIR)/kern/a.img -# 最后将boot.bin, loader.bin, kernel.bin组装成a.img $(IMAGE): $(OBJDIR)/boot/boot.bin $(OBJDIR)/boot/loader.bin $(OBJDIR)/kern/kernel.bin - @dd if=/dev/zero of=$@ bs=512 count=2880 - @mkfs.vfat $@ - @dd if=$(OBJDIR)/boot/boot.bin of=$@ bs=512 count=1 conv=notrunc + @stat $@ 1>/dev/null 2>/dev/null || dd if=/dev/zero of=$@ bs=512 count=102400 + @mkfs.vfat -F 32 -s8 $@ + @dd if=$< of=$@ bs=1 count=423 seek=90 conv=notrunc @sudo mount -o loop $@ /mnt @sudo cp $(OBJDIR)/boot/loader.bin /mnt -v @sudo cp $(OBJDIR)/kern/kernel.bin /mnt -v @sudo umount /mnt -all: $(IMAGE) - clean: @rm -rf $(OBJDIR) @@ -111,32 +108,20 @@ gdb-no-graphic: $(IMAGE) -s -S \ # 调试的内核代码elf -KERNBIN := $(OBJDIR)/kern/kernel.bin +KERNDBG := $(OBJDIR)/kern/kernel.dbg -monitor: $(KERNBIN) - @gdb -tui \ +monitor: $(IMAGE) + @gdb \ -ex 'set confirm off' \ -ex 'target remote localhost:1234' \ - -ex 'file $(KERNBIN)' + -ex 'file $(KERNDBG)' -debug: $(KERNBIN) - @qemu-system-i386 \ - -boot order=a \ - -drive file=$(IMAGE),format=raw \ - -s -S & - @gdb -tui \ - -ex 'set confirm off' \ - -ex 'target remote localhost:1234' \ - -ex 'file $(KERNBIN)' -# 测试%s -tests: - @make DEFS=-DTESTS --no-print-directory - -# xv6黑科技,获取头文件依赖,如果头文件更新则会重新编译所有文件 +# 黑科技时间,获取每个.c对应的头文件依赖 +# 挺难整明白的,不建议一开始整明白,反正从xv6上抄的,不明觉厉 $(OBJDIR)/.deps: $(foreach dir, $(OBJDIRS), $(wildcard $(OBJDIR)/$(dir)/*.d)) @mkdir -p $(@D) @perl mergedep.pl $@ $^ -include $(OBJDIR)/.deps -.PHONY: all clean run gdb gdb-no-graphic monitor debug +.PHONY: all clean run gdb gdb-no-graphic monitor diff --git a/boot/Makefrag b/boot/Makefrag index 8e12f00..1f8fb4e 100644 --- a/boot/Makefrag +++ b/boot/Makefrag @@ -8,24 +8,21 @@ OBJDIRS += boot LOADER_OBJS := $(OBJDIR)/boot/loader.o $(OBJDIR)/boot/loadkernel.o LOADER_LINKER := boot/linker.ld -# 根据源文件pattern判断编译的方式 -$(OBJDIR)/boot/%.o: boot/%.c +$(OBJDIR)/boot/%.o: boot/%.c $(OBJDIR)/.vars.CFLAGS @echo + cc $< @mkdir -p $(@D) - @$(CC) $(CFLAGS) -fno-pie -c -o $@ $< + @$(CC) $(CFLAGS) -c -o $@ $< $(OBJDIR)/boot/%.o: boot/%.asm @echo + as obj $< @mkdir -p $(@D) @$(AS) -f elf -o $@ $< -# 对于boot,它是特殊的,需要特殊编译 $(OBJDIR)/boot/boot.bin: boot/boot.asm @echo + as bin $< @mkdir -p $(@D) @$(AS) -o $@ $< -# loader通过一个特定的脚本进行链接,反正可以先不管,将核心放到内核代码上来 -$(OBJDIR)/boot/loader.bin: $(LOADER_OBJS) $(LOADER_LINKER) + +$(OBJDIR)/boot/loader.bin: $(LOADER_OBJS) $(LOADER_LINKER) $(OBJDIR)/.vars.LDFLAGS @echo + ld $@ - @mkdir -p $(@D) @$(LD) $(LDFLAGS) -s -T $(LOADER_LINKER) --oformat binary -o $@ $(LOADER_OBJS) diff --git a/boot/boot.asm b/boot/boot.asm index f88b1df..1dac0f4 100644 --- a/boot/boot.asm +++ b/boot/boot.asm @@ -1,336 +1,215 @@ - org 07c00h ; Boot 状态, Bios 将把 Boot Sector 加载到 0:7C00h 处并开始执行 +;============================================================================================================================== +BaseOfStack equ 0x07c00 ; Boot状态下堆栈基地址 +STACK_ADDR equ BaseOfStack-28 ; 堆栈栈顶 -; 下面这部分不仅是boot的内容,也是Fat12文件系统的引导部分 -; boot需要文件系统的加持(可以没有,但是如果这么干的话boot可扩展性为0,磁盘内容位置稍微变动就不能用了)才能正确的将loader的内容导入到内存里执行 -;================================================================================================ - ; Fat12规定前3字节是跳转代码,后面再是文件系统信息 - ; 如果是短跳转(2字节,指令机器码以0xEB开头),就需要后面加一个nop填充到3字节 - ; 如果是长跳转(3字节,指令机器码以0xE9开头),就不需要添加nop了 - jmp Main ; Start to boot. - ; nop ; 这个 nop 不可少 +BaseOfBoot equ 1000h ; added by mingxuan 2020-9-12 +OffsetOfBoot equ 7c00h ; load Boot sector to BaseOfBoot:OffsetOfBoot +OffsetOfActiPartStartSec equ 7e00h ; 活动分区的起始扇区号相对于BaseOfBoot的偏移量 ;added by mingxuan 2020-9-12 + ; 该变量来自分区表,保存在该内存地址,用于在os_boot和loader中查找FAT32文件 +BOOT_FAT32_INFO equ 0x5A ; 位于boot中的FAT32配置信息的长度 - ; 下面是 FAT12 磁盘的头 - ; 正常情况下,boot是要对磁盘头的数据进行解析的 - ; 但是出于简单考虑,直接将磁盘头硬编码进来,如果要分析可就太麻烦了,汇编本来就看的头大,还搞那么多未知元 - ; 里面很多信息实际上在boot里用不上,请各位别对里面的参数纠结太多,用到了再查也不迟 - BS_OEMName DB 'ForrestY' ; OEM String, 必须 8 个字节 - BPB_BytsPerSec DW 512 ; 每扇区字节数 - BPB_SecPerClus DB 1 ; 每簇多少扇区 - BPB_RsvdSecCnt DW 1 ; Boot 记录占用多少扇区 - 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 80h ; 中断 13 的驱动器号 - BS_Reserved1 DB 0 ; 未使用 - BS_BootSig DB 29h ; 扩展引导标记 (29h) - BS_VolID DD 0 ; 卷序列号 - BS_VolLab DB 'OrangeS0.02' ; 卷标, 必须 11 个字节 - BS_FileSysType DB 'FAT12 ' ; 文件系统类型, 必须 8个字节 +OSLOADER_SEG equ 0x1000 ; 段地址 +OSLOADER_OFF equ 0x5000 ; 段偏移 +OSLOADER_ADDR equ 0x15000 ; loader地址 +BUF_OFF equ 0x0000 ; 加载FAT表的临时存储 +BUF_ADDR equ 0x10000 ; 加载FAT表的临时存储 - ; 文件系统信息存放完毕后后面的内容就可以自由调整了,撒花! +DIR_PER_SECTOR equ 0x10 ; 每个扇区所容纳的目录 BYTE -; 原先Orange的代码是按照 主函数->常量->变量->子函数的顺序给出,逻辑很乱 -; 这次尝试用C语言风格按照 常量->变量->子函数->主函数的顺序给出,更加符合正常的编程逻辑 -;============================================================================ -;常量 -;================================================================================================ -; boot的内存模型实际上比较简单(区间左闭右开) -; (0x500~0x7c00) 栈 -; (0x7c00~0x7e00) 引导扇区 -; (0x90000~0x90400) 缓冲区,GetNextCluster函数会用到它 -; (0x90400~?) 加载区,loader代码会加载到这里 -BaseOfStack equ 07c00h ; Boot状态下堆栈基地址(栈底, 从这个位置向低地址生长) -BaseOfLoader equ 09000h ; LOADER.BIN 被加载到的位置 ---- 段地址 -OffsetOfLoader equ 0400h ; LOADER.BIN 被加载到的位置 ---- 偏移地址 +; 扩展磁盘服务所使用的地址包 +DAP_SECTOR equ 8 ; 起始扇区号 +DAP_BUFFER_SEG equ 10 ; 缓冲区地址 +DAP_BUFFER_OFF equ 12 ; 缓冲区地址 +DAP_READ_SECTORS equ 14 ; 要处理的扇区数 +DAP_PACKET_SIZE equ 16 ; 包的大小为24字节 +CURRENT_CLUSTER equ 20 ; 当前正在处理的簇号 DWORD +FAT_START_SECTOR equ 24 ; FAT表的起始扇区号 ;added by mingxuan 2020-9-17 +DATA_START_SECTOR equ 28 ; 数据区起始扇区号 ;added by mingxuan 2020-9-17 -; 这部分请看手册 -RootDirSectors equ 14 -SectorNoOfRootDirectory equ 19 -SectorNoOfFAT1 equ 1 -DeltaSectorNo equ 31 -;================================================================================================ +; 目录项结构 +OFF_START_CLUSTER_HIGH equ 20 ; 起始簇号高位 WORD +OFF_START_CLUSTER_LOW equ 26 ; 起始簇号低位 WORD -;============================================================================ -;变量 -;---------------------------------------------------------------------------- -LeftRootDirSectors dw RootDirSectors ; 还未搜索的根目录扇区数 -RootDirSectorNow dw SectorNoOfRootDirectory ; 目前正在搜索的根目录扇区 -BufferPacket times 010h db 0 ; ReadSector函数会用到的,用于向int 13h中断的一个缓冲区 +; 相关常量 +DIR_NAME_FREE equ 0x00 ; 该项是空闲的 +DIR_ENTRY_SIZE equ 32 ; 每个目录项的尺寸 +; 簇属性 +CLUSTER_MASK equ 0FFFFFFFH ; 簇号掩码 +CLUSTER_LAST equ 0FFFFFF8H ;0xFFFFFFF8-0xFFFFFFFF表示文件的最后一个簇 -;============================================================================ -;字符串 -;---------------------------------------------------------------------------- -LoaderFileName db "LOADER BIN", 0 ; LOADER.BIN 的文件名(为什么中间有空格请RTFM) -; 为简化代码, 下面每个字符串的长度均为 MessageLength -MessageLength equ 9 -BootMessage: db "Booting " ; 9字节, 不够则用空格补齐. 序号 0 -Message1 db "Ready. " ; 9字节, 不够则用空格补齐. 序号 1 -Message2 db "Read Fail" ; 9字节, 不够则用空格补齐. 序号 2 -Message3 db "No Loader" ; 9字节, 不够则用空格补齐. 序号 3 -;============================================================================ -; 汇编并不像高级语言一样规范,寄存器忘保存,调用子函数后发现值变了可太痛苦了 -; 所以为了减少这份痛苦,这里的所有函数都保证函数除了返回值寄存器其余的主要寄存器都有保护现场 -; 保证调用之后不用担心寄存器值变了 +; added by mingxuan 2020-9-12 +BPB_BytesPerSec equ (OffsetOfBoot + 0xb) ;每扇区字节数 +BPB_SecPerClu equ (OffsetOfBoot + 0xd) ;每簇扇区数 +BPB_RsvdSecCnt equ (OffsetOfBoot + 0xe) ;保留扇区数 +BPB_NumFATs equ (OffsetOfBoot + 0x10) ;FAT表数 +BPB_RootEntCnt equ (OffsetOfBoot + 0x11) ;FAT32不使用 +BPB_TotSec16 equ (OffsetOfBoot + 0x13) ;扇区总数 +BPB_Media equ (OffsetOfBoot + 0x15) ;介质描述符 +BPB_FATSz16 equ (OffsetOfBoot + 0x16) ;每个FAT表的大小扇区数(FAT32不使用) +BPB_SecPerTrk equ (OffsetOfBoot + 0x18) ;每磁道扇区数 +BPB_NumHeads equ (OffsetOfBoot + 0x1a) ;磁头数 +BPB_HiddSec equ (OffsetOfBoot + 0x1c) ;分区已使用扇区数 +BPB_TotSec32 equ (OffsetOfBoot + 0x20) ;文件系统大小扇区数 +BPB_FATSz32 equ (OffsetOfBoot + 0x24) ;每个FAT表大小扇区数 +BPB_ExtFlags equ (OffsetOfBoot + 0x28) ;标记 +BPB_FSVer equ (OffsetOfBoot + 0x2a) ;版本号 +BPB_RootClus equ (OffsetOfBoot + 0x2c) ;根目录簇号 +BPB_FSInfo equ (OffsetOfBoot + 0x30) ;FSINFO扇区号 +BS_BackBootSec equ (OffsetOfBoot + 0x32) ;备份引导扇区位置 +BPB_Reserved equ (OffsetOfBoot + 0x34) ;未使用 +BS_DrvNum equ (OffsetOfBoot + 0x40) ;设备号 +BS_Reserved1 equ (OffsetOfBoot + 0x41) ;未使用 +BS_BootSig equ (OffsetOfBoot + 0x42) ;扩展引导标志 +BS_VolID equ (OffsetOfBoot + 0x43) ;卷序列号 +BS_VolLab equ (OffsetOfBoot + 0x47) ;卷标 +BS_FilSysType equ (OffsetOfBoot + 0x52) ;文件系统类型 +;============================================================================================================================== + org (07c00h + BOOT_FAT32_INFO) ;FAT322规范规定第90~512个字节(共423个字节)是引导程序 + ;modified by mingxuan 2020-9-16 +START: + cld + mov ax, cs + mov ds, ax + mov ss, ax + mov ax, OSLOADER_SEG + mov es, ax ;deleted by mingxuan 2020-9-13 -;---------------------------------------------------------------------------- -; 函数名: DispStr -;---------------------------------------------------------------------------- -; 作用: -; 显示一个字符串, 函数开始时 dh 中应该是字符串序号(从0开始) -DispStr: - push bp - mov bp, sp - pusha - push es + mov bp, BaseOfStack + mov sp, STACK_ADDR - mov ax, MessageLength - mul dh - add ax, BootMessage - mov bp, ax - mov ax, ds - mov es, ax ; ES:BP = 串地址 - mov cx, MessageLength ; CX = 串长度 - mov ax, 01301h ; AH = 13, AL = 01h - mov bx, 0007h ; 页号为0(BH = 0) 黑底白字(BL = 07h) - mov dl, 0 - int 10h + movzx eax, word [BPB_RsvdSecCnt] + mov [bp - FAT_START_SECTOR], eax - pop es - popa - pop bp - ret + ; 计算数据区起始扇区号 ; added by mingxuan 2020-9-17 + movzx cx, byte [BPB_NumFATs] +_CALC_DATA_START: + add eax, [BPB_FATSz32] + loop _CALC_DATA_START -;---------------------------------------------------------------------------- -; 函数名: DispDot -;---------------------------------------------------------------------------- -; 作用: -; 打印一个点 -DispDot: - push bp - mov bp, sp - pusha + mov [bp - DATA_START_SECTOR], eax - mov ah, 0Eh ; `. 每读一个扇区就在 "Booting " 后面 - mov al, '.' ; | 打一个点, 形成这样的效果: - mov bl, 0Fh ; | Booting ...... - int 10h ; / + mov word [bp - DAP_PACKET_SIZE], 10h + mov word [bp - DAP_BUFFER_SEG], OSLOADER_SEG - popa - pop bp - ret - -;---------------------------------------------------------------------------- -; 函数名: ReadSector -;---------------------------------------------------------------------------- -; 作用: -; 将磁盘的数据读入到内存中 -; ax: 从哪个扇区开始 -; cx: 读入多少个扇区 -; (es:bx): 读入的缓冲区的起始地址 -; -; 这里使用的是bios的扩展读功能,写成C语言的结构体是这样的 -; struct buffer_packet { -; short buffer_packet_size; /* struct's size(可以为 0x10 或 0x18)*/ -; short sectors; /* 读多少个 sectors */ -; char *buffer; /* buffer address */ -; long long start_sectors; /* 从哪个 sector 开始读 */ -; long long *l_buffer; /* 64 位的 buffer address 这个我们不管!!!*/ -; }; -; 需要注意的是,buffer参数的高16位填充的是段地址(es),低16位填充的是偏移量(bx) -; -; int 13h扩展读功能要求 -; ah = 0x42 -; dl = 驱动号 -; (ds:si)是一个指向buffer_packet的指针 -; 中断结束后有一个状态返回,具体请STFW & RTFM + jmp _SEARCH_LOADER + +LoaderName db "LOADER BIN" ; 第二阶段启动程序 FDOSLDR.BIN ReadSector: - push bp - mov bp, sp - pusha + pusha + + mov ah, 42h ;ah是功能号,扩展读对应0x42 + lea si, [bp - DAP_PACKET_SIZE] ;使用扩展int13时DAP结构体首地址传给si + mov dl, [BS_DrvNum] ;modified by mingxuan 2020-9-17 + int 13h + jc $ - mov si, BufferPacket - mov word [si + 0], 010h ; buffer_packet_size - mov word [si + 2], cx ; sectors - mov word [si + 4], bx ; buffer-offset - mov word [si + 6], es ; buffer-segment - mov word [si + 8], ax ; start_sectors + popa + ret - mov dl, [BS_DrvNum] ; 驱动号 - mov ah, 42h ; 扩展读 - int 13h - jc .ReadFail ; 读取失败,简单考虑就默认bios坏了 - - popa - pop bp - ret -.ReadFail: - mov dh, 2 - call DispStr - jmp $ ; 如果cf位置1,就意味着读入错误,这个时候建议直接开摆 +InlineReadDataSector: + sub eax, 2 + movzx edx, byte [BPB_SecPerClu] + mul edx + add eax, [bp - DATA_START_SECTOR] + mov [bp - DAP_SECTOR], eax -;---------------------------------------------------------------------------- -; 函数名: GetNextCluster -;---------------------------------------------------------------------------- -; 作用: -; ax存放的是当前的簇(cluster)号,根据当前的簇号在fat表里查找,找到下一个簇的簇号,并将返回值存放在ax -GetNextCluster: - push bp - mov bp, sp - pusha + movzx edx, byte [BPB_SecPerClu] + mov [bp - DAP_READ_SECTORS], dx - mov bx, 3 ; 一个FAT项长度为1.5字节 - mul bx - mov bx, 2 ; ax = floor(clus_number * 1.5) - div bx ; 这个时候ax里面放着的是FAT项基地址相对于FAT表开头的字节偏移量 - ; 如果clus_number为奇数,则dx为1,否则为0 - push dx ; 临时保存奇数标识信息 - mov dx, 0 ; 下面除法要用到 - mov bx, [BPB_BytsPerSec] - div bx ; dx:ax / BPB_BytsPerSec - ; ax <- 商 (基地址在FAT表的第几个扇区) - ; dx <- 余数 (基地址在扇区内的偏移) - mov bx, 0 ; bx <- 0 于是, es:bx = BaseOfLoader:0 - add ax, SectorNoOfFAT1 ; 此句之后的 ax 就是FAT项所在的扇区号 - mov cx, 2 ; 读取FAT项所在的扇区, 一次读两个, 避免在边界 - call ReadSector ; 发生错误, 因为一个FAT项可能跨越两个扇区 + mov [bp - DAP_BUFFER_OFF], bx + call ReadSector + ret - mov bx, dx ; 将偏移量搬回bx - mov ax, [es:bx] - pop bx ; 取回奇数标识信息 - cmp bx, 0 ; 如果是第奇数个FAT项还得右移四位 - jz EvenCluster ; 可能是微软(FAT是微软创建的)第一个亲儿子的原因,有它的历史局限性 - shr ax, 4 ; 当时的磁盘很脆弱,经常容易写坏,所以需要两张FAT表备份,而且人们能够制作的存储设备的容量很小 -EvenCluster: - and ax, 0FFFh ; 读完需要与一下,因为高位是未定义的,防止ax值有误 - mov word [bp - 2], ax ; 这里用了一个技巧,这样在popa的时候ax也顺便更新了 +; 根据簇号计算扇区号 +; Comments, added by mingxuan 2020-9-10 +;==================================================================== +; 检查是否还有下一个簇(读取FAT表的相关信息) +; N = 数据簇号 +; FAT_BYTES(在FAT表中的偏移) = N*4 (FAT32) +; FAT_SECTOR = FAT_BYTES / BPB_BytesPerSec +; FAT_OFFSET = FAT_BYTES % BPB_BytesPerSec +;==================================================================== +NextCluster: ;added by yangxiaofeng 2021-12-1 + pushad + + mov eax, [bp - CURRENT_CLUSTER] + mov ebx, 4 + mul ebx + movzx ebx, word [BPB_BytesPerSec] + div ebx + add eax, [bp - FAT_START_SECTOR] + mov [bp - DAP_SECTOR], eax - popa - pop bp - ret + mov word [bp - DAP_READ_SECTORS], 1 + + mov word [bp - DAP_BUFFER_OFF], BUF_OFF + + call ReadSector + mov bx, dx + mov eax, [es:bx + BUF_OFF] + mov [bp - DATA_START_SECTOR - 6], eax + + popad + ret -;---------------------------------------------------------------------------- -; 函数名: StringCmp -;---------------------------------------------------------------------------- -; 作用: -; 比较 ds:si 和 es:di 处的字符串(比较长度为11,仅为loader.bin所用) -; 如果两个字符串相等ax返回1,否则ax返回0 -StringCmp: - push bp - mov bp, sp - pusha +_SEARCH_LOADER: + mov eax, [BPB_RootClus] - mov cx, 11 ; 比较长度为11 - cld ; 清位保险一下 -.STARTCMP: - lodsb ; ds:si -> al - cmp al, byte [es:di] - jnz .DIFFERENT - inc di - dec cx - cmp cx, 0 - jz .SAME - jmp .STARTCMP -.DIFFERENT: - mov word [bp - 2], 0 ; 这里用了一个技巧,这样在popa的时候ax也顺便更新了 - jmp .ENDCMP -.SAME: - mov word [bp - 2], 1 ; 下一步就是ENDCMP了,就懒得jump了 -.ENDCMP: - popa - pop bp - ret -;---------------------------------------------------------------------------- -; 这里就是真正的boot的处理函数了,boot实际上只做了一件事,将loader从磁盘里搬到内存指定位置 -; 如何将loader搬到内存中就需要文件系统里面的信息的帮助 -; 通过扫描根目录区中所有可能的目录项找到loader.bin对应的目录项 -; 然后根据目录项信息读入loader.bin的文件内容 -Main: - mov ax, cs ; cs <- 0 - mov ds, ax ; ds <- 0 - mov ss, ax ; ss <- 0 - mov ax, BaseOfLoader - mov es, ax ; es <- BaseOfLoader - mov sp, BaseOfStack ; 这几个段寄存器在Main里都不会变了 +_NEXT_ROOT_CLUSTER: ;这个时候eax拿着的是当前的簇号 + mov [bp - CURRENT_CLUSTER], eax + mov bx, BUF_OFF - ; 清屏 - 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 + call InlineReadDataSector - mov dh, 0 ; "Booting " - call DispStr ; 显示字符串 + mov di, BUF_OFF + ; 这里dl拿着的是BPB_SecPerClu +_NEXT_ROOT_SECTOR: + mov bl, DIR_PER_SECTOR - mov ah, 0 ; ┓ - mov dl, [BS_DrvNum] ; ┣ 硬盘复位 - int 13h ; ┛ - -; 下面在 A 盘的根目录寻找 LOADER.BIN -FindLoaderInRootDir: - mov ax, [RootDirSectorNow]; ax <- 现在正在搜索的扇区号 - mov bx, OffsetOfLoader ; es:bx = BaseOfLoader:OffsetOfLoader - mov cx, 1 - call ReadSector +_NEXT_ROOT_ENTRY: + mov si, LoaderName + mov cx, 11 + repe cmpsb + and di, DIR_ENTRY_SIZE + jcxz _FOUND_LOADER + add di, DIR_ENTRY_SIZE - mov si, LoaderFileName ; ds:si -> "LOADER BIN" - mov di, OffsetOfLoader ; es:di -> BaseOfLoader:400h = BaseOfLoader*10h+400h - mov dx, 10h ; 32(目录项大小) * 16(dx) = 512(BPB_BytsPerSec) + dec bl + jnz _NEXT_ROOT_ENTRY -CompareFilename: - call StringCmp - cmp ax, 1 - jz LoaderFound ; ax == 1 -> 比对成了 - dec dx - cmp dx, 0 - jz GotoNextRootDirSector ; 该扇区的所有目录项都探索完了,去探索下一个扇区 - add di, 20h ; 32 -> 目录项大小 - jmp CompareFilename + dec dl + jz _CHECK_NEXT_ROOT_CLUSTER -GotoNextRootDirSector: - inc word [RootDirSectorNow] ; 改变正在搜索的扇区号 - dec word [LeftRootDirSectors] ; ┓ - cmp word [LeftRootDirSectors], 0 ; ┣ 判断根目录区是不是已经读完 - jz NoLoader ; ┛ 如果读完表示没有找到 LOADER.BIN,就直接开摆 - jmp FindLoaderInRootDir + jmp _NEXT_ROOT_SECTOR -NoLoader: - mov dh, 3 - call DispStr - jmp $ +_CHECK_NEXT_ROOT_CLUSTER: ; 检查是否还有下一个簇 + call NextCluster ;added by yangxiaofeng 2021-12-1 + cmp eax, CLUSTER_LAST ;CX >= 0FFFFFF8H,则意味着没有更多的簇了 + jb _NEXT_ROOT_CLUSTER + jmp $ -LoaderFound: ; 找到 LOADER.BIN 后便来到这里继续 - add di, 01Ah ; 0x1a = 28 这个 28 在目录项里偏移量对应的数据是起始簇号(RTFM) - mov dx, word [es:di] ; 起始簇号占2字节,读入到dx里 - mov bx, OffsetOfLoader ; es:bx = BaseOfLoader:OffsetOfLoader +_FOUND_LOADER: + movzx eax, word [es:di + OFF_START_CLUSTER_HIGH]; 起始簇号高32位 + shl eax, 16 + mov ax, [es:di + OFF_START_CLUSTER_LOW] ; 起始簇号低32位 + mov bx, OSLOADER_OFF +;这个时候eax拿着的是当前的簇号,ebx存放加载的地址 +_LOAD_LOADER: + mov dword [bp - CURRENT_CLUSTER], eax -LoadLoader: - call DispDot - mov ax, dx ; ax <- 数据区簇号 - add ax, DeltaSectorNo ; 数据区的簇号需要加上一个偏移量才能得到真正的扇区号 - mov cx, 1 ; 一个簇就仅有一个扇区 - call ReadSector - mov ax, dx ; ax <- 数据区簇号(在之前ax = 数据区簇号+偏移量) - call GetNextCluster ; 根据数据区簇号获取文件下一个簇的簇号 - mov dx, ax ; dx <- 下一个簇的簇号 - cmp dx, 0FFFh ; 判断是否读完了(根据文档理论上dx只要在0xFF8~0xFFF都行,但是这里直接偷懒只判断0xFFF) - jz LoadFinished - add bx, [BPB_BytsPerSec] ; 别忘了更新bx,否则你会发现文件发生复写的情况(指来回更新BaseOfLoader:OffsetOfLoader ~ BaseOfLoader:OffsetOfLoader+0x200) - jmp LoadLoader -LoadFinished: - mov dh, 1 ; "Ready." - call DispStr ; 显示字符串 - ; 这一句正式跳转到已加载到内 - ; 存中的 LOADER.BIN 的开始处, - ; 开始执行 LOADER.BIN 的代码。 - ; Boot Sector 的使命到此结束 - jmp BaseOfLoader:OffsetOfLoader + call InlineReadDataSector -times 510-($-$$) db 0 ; 填充剩下的空间,使生成的二进制代码恰好为512字节 -dw 0xaa55 ; 结束标志 + movzx eax, byte [BPB_SecPerClu] + mov cx, [BPB_BytesPerSec] + mul cx + add ebx, eax + + call NextCluster + cmp eax, CLUSTER_LAST ;CX >= 0FFFFFF8H,则意味着没有更多的簇了 + jb _LOAD_LOADER + +_RUN_LOADER: + jmp OSLOADER_SEG : OSLOADER_OFF + +times 510 - BOOT_FAT32_INFO - ($-$$) db 0 ; 填充剩下的空间,使生成的二进制代码恰好为512字节 +dw 0xaa55 ; 结束标志 \ No newline at end of file diff --git a/boot/inc/fat12.inc b/boot/inc/fat12.inc deleted file mode 100644 index 86e854a..0000000 --- a/boot/inc/fat12.inc +++ /dev/null @@ -1,301 +0,0 @@ - ; 下面是 FAT12 磁盘的头 - ; 正常情况下,boot是要对磁盘头的数据进行解析的 - ; 但是出于简单考虑,直接将磁盘头硬编码进来,如果要分析可就太麻烦了,汇编本来就看的头大,还搞那么多未知元 - ; 里面很多信息实际上在boot里用不上,请各位别对里面的参数纠结太多,用到了再查也不迟 - BS_OEMName DB 'ForrestY' ; OEM String, 必须 8 个字节 - BPB_BytsPerSec DW 512 ; 每扇区字节数 - BPB_SecPerClus DB 1 ; 每簇多少扇区 - BPB_RsvdSecCnt DW 1 ; Boot 记录占用多少扇区 - 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 80h ; 中断 13 的驱动器号 - BS_Reserved1 DB 0 ; 未使用 - BS_BootSig DB 29h ; 扩展引导标记 (29h) - BS_VolID DD 0 ; 卷序列号 - BS_VolLab DB 'OrangeS0.02' ; 卷标, 必须 11 个字节 - BS_FileSysType DB 'FAT12 ' ; 文件系统类型, 必须 8个字节 - - ; 文件系统信息存放完毕后后面的内容就可以自由调整了,撒花! - -; 原先Orange的代码是按照 主函数->常量->变量->子函数的顺序给出,逻辑很乱 -; 这次尝试用C语言风格按照 常量->变量->子函数->主函数的顺序给出,更加符合正常的编程逻辑 -;============================================================================ -;常量 -;================================================================================================ - -; 这部分请看手册 -RootDirSectors equ 14 -SectorNoOfRootDirectory equ 19 -SectorNoOfFAT1 equ 1 -DeltaSectorNo equ 31 -;================================================================================================ - -;============================================================================ -;变量 -;---------------------------------------------------------------------------- -LeftRootDirSectors dw RootDirSectors ; 还未搜索的根目录扇区数 -RootDirSectorNow dw SectorNoOfRootDirectory ; 目前正在搜索的根目录扇区 - -;============================================================================ -;字符串 -;---------------------------------------------------------------------------- -KernelFileName db "KERNEL BIN", 0 ; KERNEL.BIN 的文件名(为什么中间有空格请RTFM) -; 为简化代码, 下面每个字符串的长度均为 MessageLength -MessageLength equ 9 -BootMessage: db "Booting " ; 9字节, 不够则用空格补齐. 序号 0 -Message1 db "Ready. " ; 9字节, 不够则用空格补齐. 序号 1 -Message2 db "No KERNEL" ; 9字节, 不够则用空格补齐. 序号 2 -;============================================================================ -; 汇编并不像高级语言一样规范,寄存器忘保存,调用子函数后发现值变了可太痛苦了 -; 所以为了减少这份痛苦,这里的所有函数都保证函数除了返回值寄存器其余的主要寄存器都有保护现场 -; 保证调用之后不用担心寄存器值变了 -;---------------------------------------------------------------------------- -; 函数名: Panic -;---------------------------------------------------------------------------- -; 作用: -; 当遇到一些程序不可解决恢复的事,就建议直接开摆!输出“No KERNEL”后就地死循环开摆! -Panic: - mov dh, 2 - call DispStr - jmp $ - -;---------------------------------------------------------------------------- -; 函数名: DispStr -;---------------------------------------------------------------------------- -; 作用: -; 显示一个字符串, 函数开始时 dh 中应该是字符串序号(从0开始) -DispStr: - pusha - push es - - mov ax, MessageLength - mul dh - add ax, BootMessage - mov bp, ax - mov ax, ds - mov es, ax ; ES:BP = 串地址 - mov cx, MessageLength ; CX = 串长度 - mov ax, 01301h ; AH = 13, AL = 01h - mov bx, 0007h ; 页号为0(BH = 0) 黑底白字(BL = 07h) - mov dl, 0 - int 10h ; RTFM - - pop es - popa - ret - -;---------------------------------------------------------------------------- -; 函数名: DispDot -;---------------------------------------------------------------------------- -; 作用: -; 打印一个点 -DispDot: - pusha - - mov ah, 0Eh ; `. 每读一个扇区就在 "Booting " 后面 - mov al, '.' ; | 打一个点, 形成这样的效果: - mov bl, 0Fh ; | Booting ...... - int 10h ; / - - popa - ret - -;---------------------------------------------------------------------------- -; 函数名: ReadSector -;---------------------------------------------------------------------------- -; 作用: -; 将磁盘的数据读入到内存中 -; ax: 从哪个扇区开始 -; cx: 读入多少个扇区 -; (es:bx): 读入的缓冲区的起始地址 -; -; 这里使用的是bios的扩展读功能,写成C语言的结构体是这样的 -; struct buffer_packet { -; short buffer_packet_size; /* struct's size(可以为 0x10 或 0x18)*/ -; short sectors; /* 读多少个 sectors */ -; char *buffer; /* buffer address */ -; long long start_sectors; /* 从哪个 sector 开始读 */ -; long long *l_buffer; /* 64 位的 buffer address 这个我们不管!!!*/ -; } buffer; -; 需要注意的是,buffer参数的高4位填充的是段地址(es),低4位填充的是偏移量(bx) -; -; int 13h扩展读功能要求 -; ah = 0x42 -; dl = 驱动号 -; (ds:si)是一个指向buffer的指针 -; 中断结束后有一个状态返回,具体请STFW & RTFM -ReadSector: - pusha - - push dword 0 - push word 0 - push word ax ; start_sectors - push word es - push word bx ; buffer - push word cx ; sectors - push word 10h ; buffer_packet_size - - mov si, sp ; si <- 指向buffer的地址 - mov dl, [BS_DrvNum] ; 驱动号 - mov ah, 42h ; 扩展读 - int 13h - jc Panic ; 如果cf位置1,就意味着读入错误,这个时候建议直接开摆 - - add sp, 10h - - popa - ret - -;---------------------------------------------------------------------------- -; 函数名: GetNextCluster -;---------------------------------------------------------------------------- -; 作用: -; ax存放的是当前的簇(cluster)号,根据当前的簇号在fat表里查找,找到下一个簇的簇号,并将返回值存放在ax -GetNextCluster: - pusha - mov bp, sp - - mov bx, 3 ; 一个FAT项长度为1.5字节 - mul bx - mov bx, 2 ; ax = floor(clus_number * 1.5) - div bx ; 这个时候ax里面放着的是FAT项基地址相对于FAT表开头的字节偏移量 - ; 如果clus_number为奇数,则dx为1,否则为0 - push dx ; 临时保存奇数标识信息 - mov dx, 0 ; 下面除法要用到 - mov bx, [BPB_BytsPerSec] - div bx ; dx:ax / BPB_BytsPerSec - ; ax <- 商 (基地址在FAT表的第几个扇区) - ; dx <- 余数 (基地址在扇区内的偏移) - mov bx, 0 ; bx <- 0 于是, es:bx = BaseOfKernelFile:0 - add ax, SectorNoOfFAT1 ; 此句之后的 ax 就是FAT项所在的扇区号 - mov cx, 2 ; 读取FAT项所在的扇区, 一次读两个, 避免在边界 - call ReadSector ; 发生错误, 因为一个FAT项可能跨越两个扇区 - - mov bx, dx ; 将偏移量搬回bx - mov ax, [es:bx] - pop bx ; 取回奇数标识信息 - cmp bx, 0 ; 如果是第奇数个FAT项还得右移四位,所以这也是为什么FAT12这么辣鸡的原因, - jz .EvenCluster ; 诶……徒增那么多判断,看看隔壁FAT16,FAT32,exFAT,不会发生FAT项跨扇区的情况,也不需要判断第奇偶个 - shr ax, 4 ; 可能是微软(FAT是微软创建的)第一个亲儿子的原因,有它的历史局限性 -.EvenCluster: - and ax, 0FFFh ; 读完需要与一下,因为高位是未定义的 - mov word [bp + 14], ax ; 这里用了一个技巧,这样在popa的时候ax也顺便更新了 - - popa - ret - -;---------------------------------------------------------------------------- -; 函数名: StringCmp -;---------------------------------------------------------------------------- -; 作用: -; 比较 ds:si 和 es:di 处的字符串(比较长度为11,仅为kernel.bin所用) -; 如果两个字符串相等ax返回1,否则ax返回0 -StringCmp: - pusha - mov bp, sp - - mov cx, 11 ; 比较长度为11 - cld ; 清位保险一下 -.STARTCMP: - lodsb ; ds:si -> al - cmp al, byte [es:di] - jnz .DIFFERENT - inc di - dec cx - cmp cx, 0 - jz .SAME - jmp .STARTCMP -.DIFFERENT: - mov word [bp + 14], 0 ; 这里用了一个技巧,这样在popa的时候ax也顺便更新了 - jmp .ENDCMP -.SAME: - mov word [bp + 14], 1 ; 下一步就是ENDCMP了,就懒得jump了 -.ENDCMP: - popa - ret -;---------------------------------------------------------------------------- -; 函数名: LoadKernelFile -;---------------------------------------------------------------------------- -; 作用: -; 将kernel的elf文件加载到BaseOfKernelFile:OffsetOfKernelFile -LoadKernelFile: - pusha - push es - - mov ax, BaseOfKernelFile - mov es, ax ; es <- BaseOfKernelFile - - ; 清屏 - 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 ; 显示字符串 - - mov ah, 0 ; ┓ - mov dl, [BS_DrvNum] ; ┣ 硬盘复位 - int 13h ; ┛ - -; 下面在 A 盘的根目录寻找 KERNEL.BIN -.FindKernelInRootDir: - mov ax, [RootDirSectorNow] ; ax <- 现在正在搜索的扇区号 - mov bx, OffsetOfKernelFile ; es:bx = BaseOfKernelFile:OffsetOfKernelFile - mov cx, 1 - call ReadSector - - mov si, KernelFileName ; ds:si -> "KERNEL BIN" - mov di, OffsetOfKernelFile ; es:di -> BaseOfKernelFile:400h = BaseOfKernelFile*10h+400h - mov dx, 10h ; 32(目录项大小) * 16(dx) = 512(BPB_BytsPerSec) - -.CompareFilename: - call StringCmp - cmp ax, 1 - jz .KernelFound ; ax == 1 -> 比对成了 - dec dx - cmp dx, 0 - jz .GotoNextRootDirSector ; 该扇区的所有目录项都探索完了,去探索下一个扇区 - add di, 20h ; 32 -> 目录项大小 - jmp .CompareFilename - -.GotoNextRootDirSector: - inc word [RootDirSectorNow] ; 改变正在搜索的扇区号 - dec word [LeftRootDirSectors] ; ┓ - cmp word [LeftRootDirSectors], 0 ; ┣ 判断根目录区是不是已经读完 - jz Panic ; ┛ 如果读完表示没有找到 KERNEL.BIN,就直接开摆 - jmp .FindKernelInRootDir - -.KernelFound: ; 找到 KERNEL.BIN 后便来到这里继续 - add di, 01Ah ; 0x1a = 28 这个 28 在目录项里偏移量对应的数据是起始簇号(RTFM) - mov dx, word [es:di] ; 起始簇号占2字节,读入到dx里 - mov bx, OffsetOfKernelFile ; es:bx = BaseOfKernelFile:OffsetOfKernelFile - -.LoadKernel: - call DispDot - mov ax, dx ; ax <- 数据区簇号 - add ax, DeltaSectorNo ; 数据区的簇号需要加上一个偏移量才能得到真正的扇区号 - mov cx, 1 ; 一个簇就仅有一个扇区 - call ReadSector - mov ax, dx ; ax <- 数据区簇号(在之前ax = 数据区簇号+偏移量) - call GetNextCluster ; 根据数据区簇号获取文件下一个簇的簇号 - mov dx, ax ; dx <- 下一个簇的簇号 - cmp dx, 0FFFh ; 判断是否读完了(根据文档理论上dx只要在0xFF8~0xFFF都行,但是这里直接偷懒只判断0xFFF) - jz .LoadFinished - add bx, [BPB_BytsPerSec] ; 别忘了更新bx,否则你会发现文件发生复写的情况(指来回更新BaseOfKernelFile:OffsetOfKernelFile ~ BaseOfKernelFile:OffsetOfKernelFile+0x200) - jmp .LoadKernel -.LoadFinished: - mov dh, 1 ; "Ready." - call DispStr ; 显示字符串 - - pop es - popa - ret \ No newline at end of file diff --git a/boot/inc/load.inc b/boot/inc/load.inc deleted file mode 100644 index 243cb86..0000000 --- a/boot/inc/load.inc +++ /dev/null @@ -1,23 +0,0 @@ - -; ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ -; load.inc -; ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ -; Forrest Yu, 2005 -; ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - -BaseOfLoader equ 09000h ; LOADER.BIN 被加载到的位置 ---- 段地址 -OffsetOfLoader equ 0400h ; LOADER.BIN 被加载到的位置 ---- 偏移地址 -LoaderSegPhyAddr equ 90000h ; LOADER.BIN 被加载到的位置 ---- 物理地址 (= Base * 10h) - -BaseOfKernelFile equ 08000h ; KERNEL.BIN 被加载到的位置 ---- 段地址 -OffsetOfKernelFile equ 0400h ; KERNEL.BIN 被加载到的位置 ---- 偏移地址 -KernelSegPhyAddr equ 80000h ; KERNEL.BIN 被加载到的位置 ---- 偏移地址 (= Base * 10h) - -; 注意:1、必须与 MAKEFILE 中参数 -Ttext 的值相等!! -; 2、这是个地址而非仅仅是个偏移,如果 -Ttext 的值为 0x400400,则它的值也应该是 0x400400。 -KernelEntryPointPhyAddr equ 030400h - -BaseOfStack equ OffsetOfLoader - -PageDirBase equ 200000h ; 页目录开始地址: 2M -PageTblBase equ 201000h ; 页表开始地址: 2M + 4K \ No newline at end of file diff --git a/boot/linker.ld b/boot/linker.ld index f09c2e2..a444d74 100644 --- a/boot/linker.ld +++ b/boot/linker.ld @@ -1,8 +1,8 @@ OUTPUT_FORMAT("elf32-i386", "elf32-i386", "elf32-i386") OUTPUT_ARCH(i386) ENTRY(_start) -BASE_ADDRESS = 0x400; -LOADER_SEG_PHYADDR = 0x90000; +BASE_ADDRESS = 0x5000; +LOADER_SEG_PHYADDR = 0x10000; SECTIONS { diff --git a/boot/loader.asm b/boot/loader.asm index f7230b9..ec87b07 100644 --- a/boot/loader.asm +++ b/boot/loader.asm @@ -5,17 +5,23 @@ ; Forrest Yu, 2005 ; ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +BaseOfLoader equ 01000h ; LOADER.BIN 被加载到的位置 ---- 段地址 +OffsetOfLoader equ 05000h ; LOADER.BIN 被加载到的位置 ---- 偏移地址 +LoaderSegPhyAddr equ 10000h ; LOADER.BIN 被加载到的位置 ---- 物理地址 (= Base * 10h) + +BaseOfStack equ OffsetOfLoader + +PageDirBase equ 100000h ; 页目录开始地址: 1M +PageTblBase equ 101000h ; 页表开始地址: 1M + 4K + [SECTION .text.s16] ALIGN 16 [BITS 16] -%include "./boot/inc/load.inc" - global _start _start: jmp Main ; Start -%include "./boot/inc/fat12.inc" %include "./boot/inc/pm.inc" ALIGN 4 @@ -45,9 +51,7 @@ Main: ; <--- 从这里开始 ************* mov cx, 02000h mov ah, 01h - int 10h ; 隐藏光标 - - call LoadKernelFile + int 10h ; 下面准备跳入保护模式 ------------------------------------------- diff --git a/boot/loadkernel.c b/boot/loadkernel.c index 710528c..e2b912a 100644 --- a/boot/loadkernel.c +++ b/boot/loadkernel.c @@ -1,63 +1,7 @@ -/* - * 类型 - */ -typedef int i32; -typedef unsigned int u32; -typedef short i16; -typedef unsigned short u16; -typedef char i8; -typedef unsigned char u8; - -// 通常描述一个对象的大小,会根据机器的型号变化类型 -typedef u32 size_t; -// elf文件格式会用 -typedef u32 Elf32_Addr; -typedef u16 Elf32_Half; -typedef u32 Elf32_Off; -typedef i32 Elf32_Sword; -typedef u32 Elf32_Word; - -/* - * elf相关 - */ -#define KERNEL_ELF 0x80400 - -#define EI_NIDENT 16 -typedef struct elf32_hdr { - unsigned char e_ident[EI_NIDENT]; - Elf32_Half e_type; - Elf32_Half e_machine; - Elf32_Word e_version; - Elf32_Addr e_entry; /* Entry point */ - Elf32_Off e_phoff; - Elf32_Off e_shoff; - Elf32_Word e_flags; - Elf32_Half e_ehsize; - Elf32_Half e_phentsize; - Elf32_Half e_phnum; - Elf32_Half e_shentsize; - Elf32_Half e_shnum; - Elf32_Half e_shstrndx; -} Elf32_Ehdr; - -#define PT_NULL 0 -#define PT_LOAD 1 -#define PT_DYNAMIC 2 -#define PT_INTERP 3 -#define PT_NOTE 4 -#define PT_SHLIB 5 -#define PT_PHDR 6 - -typedef struct elf32_phdr { - Elf32_Word p_type; - Elf32_Off p_offset; - Elf32_Addr p_vaddr; - Elf32_Addr p_paddr; - Elf32_Word p_filesz; - Elf32_Word p_memsz; - Elf32_Word p_flags; - Elf32_Word p_align; -} Elf32_Phdr; +#include "type.h" +#include "x86.h" +#include "elf.h" +#include "fat32.h" /* * 显示相关 @@ -76,7 +20,7 @@ typedef struct elf32_phdr { * 第0个字符在0行0列,第1个字符在0行1列,第80个字符在1行0列,以此类推 * 用的是内联汇编,等价于mov word [gs:disp_pos * 2], content */ -inline static void +inline static void write_to_terminal(u16 disp_pos, u16 content) { asm( @@ -87,7 +31,7 @@ write_to_terminal(u16 disp_pos, u16 content) /* * 清屏 */ -static void +void clear_screen() { for (int i = 0; i < TERMINAL_ROW; i++) @@ -96,7 +40,7 @@ clear_screen() DEFAULT_COLOR | ' '); } -static void * +void * memset(void *v, int c, size_t n) { char *p; @@ -110,26 +54,116 @@ memset(void *v, int c, size_t n) return v; } -static void * -memcpy(void *dst, const void *src, size_t n) +int +strncmp(const char *p, const char *q, size_t n) { - const char *s; - char *d; + while (n > 0 && *p && *p == *q) + n--, p++, q++; + if (n == 0) + return 0; + else + return (int) ((unsigned char) *p - (unsigned char) *q); +} - s = src; - d = dst; +#define SECTSIZE 512 +#define BUF_ADDR 0x30000 +#define ELF_ADDR 0x40000 - if (s < d && s + n > d) { - s += n; - d += n; - while (n-- > 0) - *--d = *--s; - } else { - while (n-- > 0) - *d++ = *s++; +void +waitdisk(void) +{ + // wait for disk reaady + while ((inb(0x1F7) & 0xC0) != 0x40) + /* do nothing */; +} + +void +readsect(void *dst, u32 offset) +{ + // wait for disk to be ready + waitdisk(); + + outb(0x1F2, 1); // count = 1 + outb(0x1F3, offset); + outb(0x1F4, offset >> 8); + outb(0x1F5, offset >> 16); + outb(0x1F6, (offset >> 24) | 0xE0); + outb(0x1F7, 0x20); // cmd 0x20 - read sectors + + // wait for disk to be ready + waitdisk(); + + // read a sector + insl(0x1F0, dst, SECTSIZE/4); +} + +u32 fat_start_sec; +u32 data_start_sec; +u32 elf_clus; +u32 elf_off; +u32 fat_now_sec; +struct BPB bpb; + +u32 +get_next_clus(u32 current_clus) +{ + u32 sec = current_clus * 4 / SECTSIZE; + u32 off = current_clus * 4 % SECTSIZE; + if (fat_now_sec != fat_start_sec + sec) { + readsect((void *)BUF_ADDR, fat_start_sec + sec); + fat_now_sec = fat_start_sec + sec; + } + return *(u32 *)(BUF_ADDR + off); +} + +/* + * 读入簇号对应的数据区的所有数据 + */ +void * +read_data_sec(void *dst, u32 current_clus) +{ + current_clus -= 2; + current_clus *= bpb.BPB_SecPerClus; + current_clus += data_start_sec; + + for (int i = 0 ; i < bpb.BPB_SecPerClus ; i++, dst += SECTSIZE) + readsect(dst, current_clus + i); + return dst; +} + +/* + * 根据输入的参数读取对应的一段,由于kernel.bin是正经ld链接的,所以文件偏移和 + * 虚拟地址的增长速度相同,所以采取激进的读取策略,如果文件偏移不是4096对齐或 + * 不能从当前的elf_clus推出到需要读取的簇号则直接默认为读取失败, + */ +void +readseg(u32 va, u32 count, u32 offset) +{ + u32 end_va; + + end_va = va + count; + if ((offset & (8 * SECTSIZE - 1)) != 0) + goto bad; + + if (offset < elf_off) + goto bad; + + while (va < end_va) { + if (elf_off == offset) { + read_data_sec((void *)va, elf_clus); + va += 8 * SECTSIZE; + offset += 8 * SECTSIZE; + } + elf_off += 8 * SECTSIZE; + elf_clus = get_next_clus(elf_clus); } - return dst; + return; +bad: + for (char *s = "----fail to kernel elf----", *st = s; *s; s++) + write_to_terminal(TERMINAL_POS(1, s - st), DEFAULT_COLOR | *s); + while (1) + ;// do nothing } /* @@ -140,38 +174,55 @@ load_kernel() { clear_screen(); for (char *s = "----start loading kernel elf----", *st = s; *s; s++) - write_to_terminal(s - st, DEFAULT_COLOR | *s); + write_to_terminal(TERMINAL_POS(0, s - st), DEFAULT_COLOR | *s); + + readsect((void *)&bpb, 0); - Elf32_Ehdr *kernel_ehdr = (Elf32_Ehdr *)KERNEL_ELF; - u32* elf_magic = (u32*)KERNEL_ELF; - if (*elf_magic != 0x464c457f) { - // make sure it's a elf file - for (char *s = "not a elf file", *st = s; *s; s++) - write_to_terminal(80 + s - st, DEFAULT_COLOR | *s); - while (1); + if (bpb.BPB_SecPerClus != 8 || bpb.BPB_BytsPerSec != SECTSIZE) + goto bad; + + fat_start_sec = bpb.BPB_RsvdSecCnt; + data_start_sec = fat_start_sec + bpb.BPB_FATSz32 * bpb.BPB_NumFATs; + + u32 root_clus = bpb.BPB_RootClus; + + while (root_clus < 0x0FFFFFF8) { + void *read_end = read_data_sec((void *)BUF_ADDR, root_clus); + for (struct Directory_Entry *p = (void *)BUF_ADDR + ; (void *)p < read_end ; p++) { + if (strncmp(p->DIR_Name, "KERNEL BIN", 11) == 0) { + elf_clus = (u32)p->DIR_FstClusHI << 16 | + p->DIR_FstClusLO; + break; + } + } + if (elf_clus != 0) + break; + root_clus = get_next_clus(root_clus); } - if (kernel_ehdr->e_machine != 0x03) { - for (char *s = "not a x86 executable", *st = s; *s; s++) - write_to_terminal(80 + s - st, DEFAULT_COLOR | *s); - while (1); - } + if (elf_clus == 0) + goto bad; - Elf32_Phdr *kernel_phdr = (void *)kernel_ehdr + kernel_ehdr->e_phoff; - for (u32 i = 0; i < kernel_ehdr->e_phnum; i++, kernel_phdr++) - { - if (kernel_phdr->p_type != PT_LOAD) + read_data_sec((void *)ELF_ADDR, elf_clus); + + struct Elf *eh = (void *)ELF_ADDR; + struct Proghdr *ph = (void *)eh + eh->e_phoff; + for (int i = 0 ; i < eh->e_phnum ; i++, ph++) { + if (ph->p_type != PT_LOAD) continue; - // 将elf的文件数据复制到指定位置 - memcpy( - (void *)kernel_phdr->p_vaddr, - (void *)kernel_ehdr + kernel_phdr->p_offset, - kernel_phdr->p_filesz); - // 将后面的字节清零(p_memsz >= p_filesz) + readseg(ph->p_va, ph->p_filesz, ph->p_offset); memset( - (void *)kernel_phdr->p_vaddr + kernel_phdr->p_filesz, - 0, - kernel_phdr->p_memsz - kernel_phdr->p_filesz); + (void *)ph->p_va + ph->p_filesz, + 0, + ph->p_memsz - ph->p_filesz); } - ((void (*)(void))(kernel_ehdr->e_entry))(); + for (char *s = "----finish loading kernel elf----", *st = s; *s; s++) + write_to_terminal(TERMINAL_POS(1, s - st), DEFAULT_COLOR | *s); + ((void (*)(void))(eh->e_entry))(); +bad: + for (char *s = "----fail to load kernel elf----", *st = s; *s; s++) + write_to_terminal(TERMINAL_POS(1, s - st), DEFAULT_COLOR | *s); + while (1) + ;// do nothing } \ No newline at end of file diff --git a/inc/assert.h b/inc/assert.h new file mode 100644 index 0000000..e3a3ab0 --- /dev/null +++ b/inc/assert.h @@ -0,0 +1,18 @@ +#ifndef MINIOS_ASSERT_H +#define MINIOS_ASSERT_H + +#include "stdio.h" + +void _warn(const char*, int, const char*, ...); +void _panic(const char*, int, const char*, ...) __attribute__((noreturn)); + +#define warn(...) _warn(__FILE__, __LINE__, __VA_ARGS__) +#define panic(...) _panic(__FILE__, __LINE__, __VA_ARGS__) + +#define assert(x) \ + do { if (!(x)) panic("assertion failed: %s", #x); } while (0) + +// 静态assert,如果不符合条件就会直接在编译期报错 +#define static_assert(x) switch (x) case 0: case (x):; + +#endif /* MINIOS_ASSERT_H */ \ No newline at end of file diff --git a/inc/cmatrix.h b/inc/cmatrix.h deleted file mode 100644 index 14b559b..0000000 --- a/inc/cmatrix.h +++ /dev/null @@ -1,14 +0,0 @@ -#ifndef MINIOS_CMATRIX_H -#define MINIOS_CMATRIX_H - -#include "type.h" -struct color_char { - u8 foreground;//输出字符的前景色(0~15) - u8 background;//输出字符的背景色(0~15) - u8 print_char;//输出字符的ASCII码(0~127) - u16 print_pos;//输出字符在终端中的位置 -}; - -void cmatrix_start(); - -#endif /* MINIOS_CMATRIX_H */ \ No newline at end of file diff --git a/inc/elf.h b/inc/elf.h new file mode 100644 index 0000000..6371596 --- /dev/null +++ b/inc/elf.h @@ -0,0 +1,67 @@ +#ifndef MINIOS_ELF_H +#define MINIOS_ELF_H + +#include "type.h" + +#define ELF_MAGIC 0x464C457FU /* "\x7FELF" in little endian */ + +struct Elf { + u32 e_magic; // must equal ELF_MAGIC + u8 e_elf[12]; + u16 e_type; + u16 e_machine; + u32 e_version; + u32 e_entry; + u32 e_phoff; + u32 e_shoff; + u32 e_flags; + u16 e_ehsize; + u16 e_phentsize; + u16 e_phnum; + u16 e_shentsize; + u16 e_shnum; + u16 e_shstrndx; +}; + +struct Proghdr { + u32 p_type; + u32 p_offset; + u32 p_va; + u32 p_pa; + u32 p_filesz; + u32 p_memsz; + u32 p_flags; + u32 p_align; +}; + +struct Secthdr { + u32 sh_name; + u32 sh_type; + u32 sh_flags; + u32 sh_addr; + u32 sh_offset; + u32 sh_size; + u32 sh_link; + u32 sh_info; + u32 sh_addralign; + u32 sh_entsize; +}; + +// Values for Proghdr::p_type +#define PT_LOAD 1 + +// Flag bits for Proghdr::p_flags +#define ELF_PROG_FLAG_EXEC 1 +#define ELF_PROG_FLAG_WRITE 2 +#define ELF_PROG_FLAG_READ 4 + +// Values for Secthdr::sh_type +#define ELF_SHT_NULL 0 +#define ELF_SHT_PROGBITS 1 +#define ELF_SHT_SYMTAB 2 +#define ELF_SHT_STRTAB 3 + +// Values for Secthdr::sh_name +#define ELF_SHN_UNDEF 0 + +#endif /* MINIOS_ELF_H */ \ No newline at end of file diff --git a/inc/fat32.h b/inc/fat32.h new file mode 100644 index 0000000..4d6665e --- /dev/null +++ b/inc/fat32.h @@ -0,0 +1,55 @@ +#ifndef MINIOS_FAT32_H +#define MINIOS_FAT32_H + +#include "type.h" + +struct BPB { + u8 BS_jmpBoot[3]; + u8 BS_OEMName[8]; + u16 BPB_BytsPerSec; + u8 BPB_SecPerClus; + u16 BPB_RsvdSecCnt; + u8 BPB_NumFATs; + u16 BPB_RootEntCnt; + u16 BPB_TotSec16; + u8 BPB_Media; + u16 BPB_FATSz16; + u16 BPB_SecPerTrk; + u16 BPB_NumHeads; + u32 BPB_HiddSec; + u32 BPB_TotSec32; + u32 BPB_FATSz32; + u16 BPB_ExtFlags; + u16 BPB_FSVer; + u32 BPB_RootClus; + u16 BPB_FSInfo; + u16 BPB_BkBootSec; + u8 BPB_Reserved[12]; + u8 BS_DrvNum; + u8 BS_Reserved1; + u8 BS_BootSig; + u32 BS_VolID; + u8 BS_VolLabp[11]; + u8 BS_FilSysType[8]; + u8 zero[420]; + u16 Signature_word; +} __attribute__((packed)); + +struct Directory_Entry { + char DIR_Name[11]; + u8 DIR_Attr; + u8 DIR_NTRes; + u8 DIR_CrtTimeTenth; + u16 DIR_CrtTime; + u16 DIR_CrtDate; + u16 DIR_LstAccDate; + u16 DIR_FstClusHI; + u16 DIR_WrtTime; + u16 DIR_WrtDate; + u16 DIR_FstClusLO; + u32 DIR_FileSize; +} __attribute__((packed)); + + + +#endif /* MINIOS_FAT32_H */ \ No newline at end of file diff --git a/inc/keyboard.h b/inc/keyboard.h new file mode 100644 index 0000000..75b6a67 --- /dev/null +++ b/inc/keyboard.h @@ -0,0 +1,8 @@ +#ifndef MINIOS_KEYBOARD_H +#define MINIOS_KEYBOARD_H + +#include "type.h" + +void add_keyboard_buf(u8 ch); + +#endif /* MINIOS_KEYBOARD_H */ \ No newline at end of file diff --git a/inc/keymap.h b/inc/keymap.h new file mode 100644 index 0000000..03e72a5 --- /dev/null +++ b/inc/keymap.h @@ -0,0 +1,323 @@ +#ifndef MINIOS_KEYMAP_H +#define MINIOS_KEYMAP_H + +#include "type.h" + +#define NR_SCAN_CODES 128 + +#define FLAG_BREAK 0x0080 /* Break Code */ +#define FLAG_EXT 0x0100 /* Normal function keys */ +#define FLAG_SHIFT_L 0x0200 /* Shift key */ +#define FLAG_SHIFT_R 0x0400 /* Shift key */ +#define FLAG_CTRL_L 0x0800 /* Control key */ +#define FLAG_CTRL_R 0x1000 /* Control key */ +#define FLAG_ALT_L 0x2000 /* Alternate key */ +#define FLAG_ALT_R 0x4000 /* Alternate key */ +#define FLAG_PAD 0x8000 /* keys in num pad */ + +#define MASK_RAW 0x01FF /* raw key value = code_passed_to_tty & MASK_RAW + * the value can be found either in the keymap + * column 0 or in the list below + */ + +/* Special keys */ +#define ESC (0x01 + FLAG_EXT) /* Esc */ +#define TAB (0x02 + FLAG_EXT) /* Tab */ +#define ENTER (0x03 + FLAG_EXT) /* Enter */ +#define BACKSPACE (0x04 + FLAG_EXT) /* BackSpace */ + +#define GUI_L (0x05 + FLAG_EXT) /* L GUI */ +#define GUI_R (0x06 + FLAG_EXT) /* R GUI */ +#define APPS (0x07 + FLAG_EXT) /* APPS */ + +/* Shift, Ctrl, Alt */ +#define SHIFT_L (0x08 + FLAG_EXT) /* L Shift */ +#define SHIFT_R (0x09 + FLAG_EXT) /* R Shift */ +#define CTRL_L (0x0A + FLAG_EXT) /* L Ctrl */ +#define CTRL_R (0x0B + FLAG_EXT) /* R Ctrl */ +#define ALT_L (0x0C + FLAG_EXT) /* L Alt */ +#define ALT_R (0x0D + FLAG_EXT) /* R Alt */ + +/* Lock keys */ +#define CAPS_LOCK (0x0E + FLAG_EXT) /* Caps Lock */ +#define NUM_LOCK (0x0F + FLAG_EXT) /* Number Lock */ +#define SCROLL_LOCK (0x10 + FLAG_EXT) /* Scroll Lock */ + +/* Function keys */ +#define F1 (0x11 + FLAG_EXT) /* F1 */ +#define F2 (0x12 + FLAG_EXT) /* F2 */ +#define F3 (0x13 + FLAG_EXT) /* F3 */ +#define F4 (0x14 + FLAG_EXT) /* F4 */ +#define F5 (0x15 + FLAG_EXT) /* F5 */ +#define F6 (0x16 + FLAG_EXT) /* F6 */ +#define F7 (0x17 + FLAG_EXT) /* F7 */ +#define F8 (0x18 + FLAG_EXT) /* F8 */ +#define F9 (0x19 + FLAG_EXT) /* F9 */ +#define F10 (0x1A + FLAG_EXT) /* F10 */ +#define F11 (0x1B + FLAG_EXT) /* F11 */ +#define F12 (0x1C + FLAG_EXT) /* F12 */ + +/* Control Pad */ +#define PRINTSCREEN (0x1D + FLAG_EXT) /* Print Screen */ +#define PAUSEBREAK (0x1E + FLAG_EXT) /* Pause/Break */ +#define INSERT (0x1F + FLAG_EXT) /* Insert */ +#define DELETE (0x20 + FLAG_EXT) /* Delete */ +#define HOME (0x21 + FLAG_EXT) /* Home */ +#define END (0x22 + FLAG_EXT) /* End */ +#define PAGEUP (0x23 + FLAG_EXT) /* Page Up */ +#define PAGEDOWN (0x24 + FLAG_EXT) /* Page Down */ +#define UP (0x25 + FLAG_EXT) /* Up */ +#define DOWN (0x26 + FLAG_EXT) /* Down */ +#define LEFT (0x27 + FLAG_EXT) /* Left */ +#define RIGHT (0x28 + FLAG_EXT) /* Right */ + +/* ACPI keys */ +#define POWER (0x29 + FLAG_EXT) /* Power */ +#define SLEEP (0x2A + FLAG_EXT) /* Sleep */ +#define WAKE (0x2B + FLAG_EXT) /* Wake Up */ + +/* Num Pad */ +#define PAD_SLASH (0x2C + FLAG_EXT) /* / */ +#define PAD_STAR (0x2D + FLAG_EXT) /* * */ +#define PAD_MINUS (0x2E + FLAG_EXT) /* - */ +#define PAD_PLUS (0x2F + FLAG_EXT) /* + */ +#define PAD_ENTER (0x30 + FLAG_EXT) /* Enter */ +#define PAD_DOT (0x31 + FLAG_EXT) /* . */ +#define PAD_0 (0x32 + FLAG_EXT) /* 0 */ +#define PAD_1 (0x33 + FLAG_EXT) /* 1 */ +#define PAD_2 (0x34 + FLAG_EXT) /* 2 */ +#define PAD_3 (0x35 + FLAG_EXT) /* 3 */ +#define PAD_4 (0x36 + FLAG_EXT) /* 4 */ +#define PAD_5 (0x37 + FLAG_EXT) /* 5 */ +#define PAD_6 (0x38 + FLAG_EXT) /* 6 */ +#define PAD_7 (0x39 + FLAG_EXT) /* 7 */ +#define PAD_8 (0x3A + FLAG_EXT) /* 8 */ +#define PAD_9 (0x3B + FLAG_EXT) /* 9 */ +#define PAD_UP PAD_8 /* Up */ +#define PAD_DOWN PAD_2 /* Down */ +#define PAD_LEFT PAD_4 /* Left */ +#define PAD_RIGHT PAD_6 /* Right */ +#define PAD_HOME PAD_7 /* Home */ +#define PAD_END PAD_1 /* End */ +#define PAD_PAGEUP PAD_9 /* Page Up */ +#define PAD_PAGEDOWN PAD_3 /* Page Down */ +#define PAD_INS PAD_0 /* Ins */ +#define PAD_MID PAD_5 /* Middle key */ +#define PAD_DEL PAD_DOT /* Del */ + +/* Keymap for US MF-2 keyboard. */ + +const static u32 keymap[NR_SCAN_CODES] = { + +/* scan-code !Shift */ +/* =============================================*/ +/* 0x00 - none */ 0, +/* 0x01 - ESC */ ESC, +/* 0x02 - '1' */ '1', +/* 0x03 - '2' */ '2', +/* 0x04 - '3' */ '3', +/* 0x05 - '4' */ '4', +/* 0x06 - '5' */ '5', +/* 0x07 - '6' */ '6', +/* 0x08 - '7' */ '7', +/* 0x09 - '8' */ '8', +/* 0x0A - '9' */ '9', +/* 0x0B - '0' */ '0', +/* 0x0C - '-' */ '-', +/* 0x0D - '=' */ '=', +/* 0x0E - BS */ BACKSPACE, +/* 0x0F - TAB */ TAB, +/* 0x10 - 'q' */ 'q', +/* 0x11 - 'w' */ 'w', +/* 0x12 - 'e' */ 'e', +/* 0x13 - 'r' */ 'r', +/* 0x14 - 't' */ 't', +/* 0x15 - 'y' */ 'y', +/* 0x16 - 'u' */ 'u', +/* 0x17 - 'i' */ 'i', +/* 0x18 - 'o' */ 'o', +/* 0x19 - 'p' */ 'p', +/* 0x1A - '[' */ '[', +/* 0x1B - ']' */ ']', +/* 0x1C - CR/LF */ ENTER, +/* 0x1D - l. Ctrl */ CTRL_L, +/* 0x1E - 'a' */ 'a', +/* 0x1F - 's' */ 's', +/* 0x20 - 'd' */ 'd', +/* 0x21 - 'f' */ 'f', +/* 0x22 - 'g' */ 'g', +/* 0x23 - 'h' */ 'h', +/* 0x24 - 'j' */ 'j', +/* 0x25 - 'k' */ 'k', +/* 0x26 - 'l' */ 'l', +/* 0x27 - ';' */ ';', +/* 0x28 - '\'' */ '\'', +/* 0x29 - '`' */ '`', +/* 0x2A - l. SHIFT */ SHIFT_L, +/* 0x2B - '\' */ '\\', +/* 0x2C - 'z' */ 'z', +/* 0x2D - 'x' */ 'x', +/* 0x2E - 'c' */ 'c', +/* 0x2F - 'v' */ 'v', +/* 0x30 - 'b' */ 'b', +/* 0x31 - 'n' */ 'n', +/* 0x32 - 'm' */ 'm', +/* 0x33 - ',' */ ',', +/* 0x34 - '.' */ '.', +/* 0x35 - '/' */ '/', +/* 0x36 - r. SHIFT */ SHIFT_R, +/* 0x37 - '*' */ '*', +/* 0x38 - ALT */ ALT_L, +/* 0x39 - ' ' */ ' ', +/* 0x3A - CapsLock */ CAPS_LOCK, +/* 0x3B - F1 */ F1, +/* 0x3C - F2 */ F2, +/* 0x3D - F3 */ F3, +/* 0x3E - F4 */ F4, +/* 0x3F - F5 */ F5, +/* 0x40 - F6 */ F6, +/* 0x41 - F7 */ F7, +/* 0x42 - F8 */ F8, +/* 0x43 - F9 */ F9, +/* 0x44 - F10 */ F10, +/* 0x45 - NumLock */ NUM_LOCK, +/* 0x46 - ScrLock */ SCROLL_LOCK, +/* 0x47 - Home */ PAD_HOME, +/* 0x48 - CurUp */ PAD_UP, +/* 0x49 - PgUp */ PAD_PAGEUP, +/* 0x4A - '-' */ PAD_MINUS, +/* 0x4B - Left */ PAD_LEFT, +/* 0x4C - MID */ PAD_MID, +/* 0x4D - Right */ PAD_RIGHT, +/* 0x4E - '+' */ PAD_PLUS, +/* 0x4F - End */ PAD_END, +/* 0x50 - Down */ PAD_DOWN, +/* 0x51 - PgDown */ PAD_PAGEDOWN, +/* 0x52 - Insert */ PAD_INS, +/* 0x53 - Delete */ PAD_DOT, +/* 0x54 - Enter */ 0, +/* 0x55 - ??? */ 0, +/* 0x56 - ??? */ 0, +/* 0x57 - F11 */ F11, +/* 0x58 - F12 */ F12, +/* 0x59 - ??? */ 0, +/* 0x5A - ??? */ 0, +/* 0x5B - ??? */ 0, +/* 0x5C - ??? */ 0, +/* 0x5D - ??? */ 0, +/* 0x5E - ??? */ 0, +/* 0x5F - ??? */ 0, +/* 0x60 - ??? */ 0, +/* 0x61 - ??? */ 0, +/* 0x62 - ??? */ 0, +/* 0x63 - ??? */ 0, +/* 0x64 - ??? */ 0, +/* 0x65 - ??? */ 0, +/* 0x66 - ??? */ 0, +/* 0x67 - ??? */ 0, +/* 0x68 - ??? */ 0, +/* 0x69 - ??? */ 0, +/* 0x6A - ??? */ 0, +/* 0x6B - ??? */ 0, +/* 0x6C - ??? */ 0, +/* 0x6D - ??? */ 0, +/* 0x6E - ??? */ 0, +/* 0x6F - ??? */ 0, +/* 0x70 - ??? */ 0, +/* 0x71 - ??? */ 0, +/* 0x72 - ??? */ 0, +/* 0x73 - ??? */ 0, +/* 0x74 - ??? */ 0, +/* 0x75 - ??? */ 0, +/* 0x76 - ??? */ 0, +/* 0x77 - ??? */ 0, +/* 0x78 - ??? */ 0, +/* 0x78 - ??? */ 0, +/* 0x7A - ??? */ 0, +/* 0x7B - ??? */ 0, +/* 0x7C - ??? */ 0, +/* 0x7D - ??? */ 0, +/* 0x7E - ??? */ 0, +/* 0x7F - ??? */ 0, +}; +/*==================================================================== + Appendix: Scan code set 1 + *==================================================================== +KEY MAKE BREAK| KEY MAKE BREAK | KEY MAKE BREAK +---------------------|------------------------|----------------------- +A 1E 9E | 9 0A 8A | [ 1A 9A +B 30 B0 | ` 29 89 | INSERT E0,52 E0,D2 +C 2E AE | - 0C 8C | HOME E0,47 E0,C7 +D 20 A0 | = 0D 8D | PG UP E0,49 E0,C9 +E 12 92 | \ 2B AB | DELETE E0,53 E0,D3 +F 21 A1 | BKSP 0E 8E | END E0,4F E0,CF +G 22 A2 | SPACE 39 B9 | PG DN E0,51 E0,D1 +H 23 A3 | TAB 0F 8F | U ARROW E0,48 E0,C8 +I 17 97 | CAPS 3A BA | L ARROW E0,4B E0,CB +J 24 A4 | L SHFT 2A AA | D ARROW E0,50 E0,D0 +K 25 A5 | L CTRL 1D 9D | R ARROW E0,4D E0,CD +L 26 A6 | L GUI E0,5B E0,DB | NUM 45 C5 +M 32 B2 | L ALT 38 B8 | KP / E0,35 E0,B5 +N 31 B1 | R SHFT 36 B6 | KP * 37 B7 +O 18 98 | R CTRL E0,1D E0,9D | KP - 4A CA +P 19 99 | R GUI E0,5C E0,DC | KP + 4E CE +Q 10 19 | R ALT E0,38 E0,B8 | KP EN E0,1C E0,9C +R 13 93 | APPS E0,5D E0,DD | KP . 53 D3 +S 1F 9F | ENTER 1C 9C | KP 0 52 D2 +T 14 94 | ESC 01 81 | KP 1 4F CF +U 16 96 | F1 3B BB | KP 2 50 D0 +V 2F AF | F2 3C BC | KP 3 51 D1 +W 11 91 | F3 3D BD | KP 4 4B CB +X 2D AD | F4 3E BE | KP 5 4C CC +Y 15 95 | F5 3F BF | KP 6 4D CD +Z 2C AC | F6 40 C0 | KP 7 47 C7 +0 0B 8B | F7 41 C1 | KP 8 48 C8 +1 02 82 | F8 42 C2 | KP 9 49 C9 +2 03 83 | F9 43 C3 | ] 1B 9B +3 04 84 | F10 44 C4 | ; 27 A7 +4 05 85 | F11 57 D7 | ' 28 A8 +5 06 86 | F12 58 D8 | , 33 B3 + | | +6 07 87 | PRTSCRN E0,2A E0,B7 | . 34 B4 + | E0,37 E0,AA | + | | +7 08 88 | SCROLL 46 C6 | / 35 B5 + | | +8 09 89 | PAUSE E1,1D | + | 45,E1, -NONE-| + | 9D,C5 | +---------------------------------------------------------------------- +----------------- +ACPI Scan Codes: +------------------------------------------- +Key Make Code Break Code +------------------------------------------- +Power E0, 5E E0, DE +Sleep E0, 5F E0, DF +Wake E0, 63 E0, E3 +------------------------------- +Windows Multimedia Scan Codes: +------------------------------------------- +Key Make Code Break Code +------------------------------------------- +Next Track E0, 19 E0, 99 +Previous Track E0, 10 E0, 90 +Stop E0, 24 E0, A4 +Play/Pause E0, 22 E0, A2 +Mute E0, 20 E0, A0 +Volume Up E0, 30 E0, B0 +Volume Down E0, 2E E0, AE +Media Select E0, 6D E0, ED +E-Mail E0, 6C E0, EC +Calculator E0, 21 E0, A1 +My Computer E0, 6B E0, EB +WWW Search E0, 65 E0, E5 +WWW Home E0, 32 E0, B2 +WWW Back E0, 6A E0, EA +WWW Forward E0, 69 E0, E9 +WWW Stop E0, 68 E0, E8 +WWW Refresh E0, 67 E0, E7 +WWW Favorites E0, 66 E0, E6 +*=====================================================================================*/ + +#endif /* MINIOS_KEYMAP_H */ diff --git a/inc/process.h b/inc/process.h new file mode 100644 index 0000000..c8526e3 --- /dev/null +++ b/inc/process.h @@ -0,0 +1,41 @@ +#ifndef MINIOS_PROCESS_H +#define MINIOS_PROCESS_H + +#include "type.h" + +/* pcb中存储用户态进程的寄存器信息 */ +typedef struct s_stackframe { /* proc_ptr points here ↑ Low */ + u32 gs; /* ┓ │ */ + u32 fs; /* ┃ │ */ + u32 es; /* ┃ │ */ + u32 ds; /* ┃ │ */ + u32 edi; /* ┃ │ */ + u32 esi; /* ┣ pushed by save() │ */ + u32 ebp; /* ┃ │ */ + u32 kernel_esp; /* <- 'popad' will ignore it │ */ + u32 ebx; /* ┃ ↑栈从高地址往低地址增长 */ + u32 edx; /* ┃ │ */ + u32 ecx; /* ┃ │ */ + u32 eax; /* ┛ │ */ + u32 retaddr; /* return address for assembly code save() │ */ + u32 eip; /* ┓ │ */ + u32 cs; /* ┃ │ */ + u32 eflags; /* ┣ these are pushed by CPU during interrupt │ */ + u32 esp; /* ┃ │ */ + u32 ss; /* ┛ ┷High */ +}STACK_FRAME; + +/* pcb */ +typedef struct s_proc { + STACK_FRAME regs; /* process' registers saved in stack frame */ + u32 pid; /* process id passed in from MM */ + char p_name[16]; /* name of the process */ +}PROCESS; + +#define PCB_SIZE 3 +/* 指向当前进程pcb的指针 */ +extern PROCESS *p_proc_ready; +/* pcb表 */ +extern PROCESS proc_table[PCB_SIZE]; + +#endif \ No newline at end of file diff --git a/inc/protect.h b/inc/protect.h index df1c734..73830fe 100644 --- a/inc/protect.h +++ b/inc/protect.h @@ -13,7 +13,164 @@ typedef struct s_descriptor { /* 共 8 个字节 */ u8 base_high; /* Base */ }DESCRIPTOR; -/* GDT 和 IDT 中描述符的个数 */ +/* 门描述符 */ +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; + +/* 描述符选择子 */ +#define SELECTOR_DUMMY 0x00 // ┓ +#define SELECTOR_FLAT_C 0x08 // ┣ LOADER 里面已经确定了的. +#define SELECTOR_FLAT_RW 0x10 // ┃ +#define SELECTOR_VIDEO (0x18+3) // ┛<-- RPL=3 +#define SELECTOR_TSS 0x20 // TSS. 从外层跳到内存时 SS 和 ESP 的值从里面获得. +#define SELECTOR_LDT 0x28 +/* 描述符类型值说明 */ +#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 陷阱门类型值 */ + +/* 选择子类型值说明 */ +/* 其中, SA_ : Selector Attribute */ +#define SA_RPL_MASK 0xFFFC +#define SA_RPL0 0 +#define SA_RPL1 1 +#define SA_RPL2 2 +#define SA_RPL3 3 + +#define SA_TI_MASK 0xFFFB +#define SA_TIG 0 +#define SA_TIL 4 + +/* 异常中断向量 */ +#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 + +/* 权限 */ +#define PRIVILEGE_KRNL 0 +#define PRIVILEGE_TASK 1 +#define PRIVILEGE_USER 3 + +#define RPL_KRNL SA_RPL0 +#define RPL_TASK SA_RPL1 +#define RPL_USER SA_RPL3 + +/* 初始化描述符函数 */ +static void +init_segment(DESCRIPTOR *p_desc, u32 base, u32 limit, u16 attribute) +{ + p_desc->limit_low = limit & 0x0FFFF; // 段界限1 + p_desc->base_low = base & 0x0FFFF; // 段基址1 + p_desc->base_mid = (base >> 16) & 0x0FF; // 段基址2 + p_desc->attr1 = attribute & 0xFF; // 属性1 + p_desc->limit_high_attr2 = ((limit >> 16) & 0x0F) | // 段界限2 + ((attribute >> 8) & 0xF0); // 属性2 + p_desc->base_high = (base >> 24) & 0x0FF; // 段基址3 +} + +static void +init_gate(GATE *p_gate, u8 desc_type, void *handler, u8 privilage) +{ + p_gate->offset_low = (u32)handler & 0xffff; //中断处理地址的低16位 + p_gate->selector = SELECTOR_FLAT_C; //内核代码段选择子 + p_gate->dcount = 0; //不需要复制参数 + p_gate->attr = desc_type | (privilage << 5); //属性 + p_gate->offset_high = ((u32)handler >> 16) & 0xffff;//中断处理地址的高16位 +} + +/* GDT 、 LDT 和 IDT 中描述符的个数 */ #define GDT_SIZE 128 +#define LDT_SIZE GDT_SIZE +#define IDT_SIZE 256 + +extern DESCRIPTOR gdt[GDT_SIZE]; +extern DESCRIPTOR ldt[LDT_SIZE]; +extern GATE idt[IDT_SIZE]; + +/* TSS(Taskstate) */ +typedef struct s_tss { + u32 backlink; + u32 esp0; /* 当发生中断的时候esp就会变成esp0 */ + u32 ss0; /* 当发生中断的时候ss就会变成ss0,由于ss0存储的是内核态权限段,于是顺利进入内核态 */ + u32 esp1; + u32 ss1; + u32 esp2; + u32 ss2; + u32 cr3; + u32 eip; + u32 flags; + u32 eax; + u32 ecx; + u32 edx; + u32 ebx; + u32 esp; + u32 ebp; + u32 esi; + u32 edi; + u32 es; + u32 cs; + u32 ss; + u32 ds; + u32 fs; + u32 gs; + u32 ldt; + u16 trap; + u16 iobase; /* I/O位图基址大于或等于TSS段界限,就表示没有I/O许可位图 */ + /*u8 iomap[2];*/ +}TSS; + +/* 全局的tss数据结构 */ +extern TSS tss; + +/* 描述符表 */ +struct Pseudodesc { + u16 pd_lim; // Limit + u32 pd_base; // Base address +} __attribute__ ((packed)); + +extern struct Pseudodesc gdt_ptr, idt_ptr; #endif /* MINIOS_PROTECT_H */ diff --git a/inc/stdarg.h b/inc/stdarg.h new file mode 100644 index 0000000..f705cf3 --- /dev/null +++ b/inc/stdarg.h @@ -0,0 +1,12 @@ +#ifndef MINIOS_STDARG_H +#define MINIOS_STDARG_H + +typedef __builtin_va_list va_list; + +#define va_start(ap, last) __builtin_va_start(ap, last) + +#define va_arg(ap, type) __builtin_va_arg(ap, type) + +#define va_end(ap) __builtin_va_end(ap) + +#endif /* MINIOS_STDARG_H */ \ No newline at end of file diff --git a/inc/stdio.h b/inc/stdio.h new file mode 100644 index 0000000..56416a6 --- /dev/null +++ b/inc/stdio.h @@ -0,0 +1,24 @@ +#ifndef MINIOS_STDIO_H +#define MINIOS_STDIO_H + +#include "type.h" +#include "stdarg.h" + +#ifndef NULL +#define NULL ((void *) 0) +#endif /* NULL */ + +// lib/printfmt.c +void printfmt(void (*putch)(int, void*), void *putdat, const char *fmt, ...); +void vprintfmt(void (*putch)(int, void*), void *putdat, const char *fmt, va_list); +int snprintf(char *str, int size, const char *fmt, ...); +int vsnprintf(char *str, int size, const char *fmt, va_list); + +// lib/terminal.c +int kprintf(const char *fmt, ...); +int vkprintf(const char *fmt, va_list); + +// kern/keyboard.c +u8 getch(void); + +#endif /* MINIOS_STDIO_H */ \ No newline at end of file diff --git a/inc/string.h b/inc/string.h index 3e71713..6e3c91d 100644 --- a/inc/string.h +++ b/inc/string.h @@ -3,7 +3,15 @@ #include "type.h" -void * memset(void *v, int c, size_t n); -void * memcpy(void *dst, const void *src, size_t n); +int strlen(const char *s); +int strnlen(const char *s, size_t size); +char * strcpy(char *dst, const char *src); +char * strncpy(char *dst, const char *src, size_t size); +char * strcat(char *dst, const char *src); +int strcmp(const char *s1, const char *s2); +int strncmp(const char *s1, const char *s2, size_t size); + +void * memset(void *v, int c, size_t n); +void * memcpy(void *dst, const void *src, size_t n); #endif /* MINIOS_STRING_H */ \ No newline at end of file diff --git a/inc/terminal.h b/inc/terminal.h index 57a9abd..512032b 100644 --- a/inc/terminal.h +++ b/inc/terminal.h @@ -6,10 +6,13 @@ /* * 终端大小参数 */ +#define TERMINAL_ADDR 0xB8000 #define TERMINAL_COLUMN 80 #define TERMINAL_ROW 25 +#define TERMINAL_SIZE ((TERMINAL_ROW) * (TERMINAL_COLUMN)) -#define TERMINAL_POS(row, column) ((u16)(row) * TERMINAL_COLUMN + (column)) +#define TERMINAL_POS(row, column) ((u16)((row) - 1) * TERMINAL_COLUMN \ + + (column) - 1) /* * 颜色码 */ @@ -37,7 +40,4 @@ */ #define DEFAULT_COLOR FOREGROUND(WHITE) | BACKGROUND(BLACK) -void clear_screen(); -void kprintf(u16 disp_pos, const char* format, ...); - #endif /* MINIOS_TERMINAL_H */ \ No newline at end of file diff --git a/inc/time.h b/inc/time.h new file mode 100644 index 0000000..b29a1a9 --- /dev/null +++ b/inc/time.h @@ -0,0 +1,9 @@ +#ifndef MINIOS_TIME_H +#define MINIOS_TIME_H + +#include "type.h" + +void timecounter_inc(); +size_t clock(); + +#endif \ No newline at end of file diff --git a/inc/trap.h b/inc/trap.h new file mode 100644 index 0000000..b6c1d69 --- /dev/null +++ b/inc/trap.h @@ -0,0 +1,79 @@ +#ifndef MINIOS_TRAP_H +#define MINIOS_TRAP_H + +#define INT_M_CTL 0x20 /* I/O port for interrupt controller */ +#define INT_M_CTLMASK 0x21 /* setting bits in this port disables ints */ +#define INT_S_CTL 0xA0 /* I/O port for second interrupt controller */ +#define INT_S_CTLMASK 0xA1 /* setting bits in this port disables ints */ + +/* Hardware interrupts */ +#define NR_IRQ 16 /* Number of IRQs */ +#define CLOCK_IRQ 0 +#define KEYBOARD_IRQ 1 +#define CASCADE_IRQ 2 /* cascade enable for 2nd AT controller */ +#define ETHER_IRQ 3 /* default ethernet interrupt vector */ +#define SECONDARY_IRQ 3 /* RS232 interrupt vector for port 2 */ +#define RS232_IRQ 4 /* RS232 interrupt vector for port 1 */ +#define XT_WINI_IRQ 5 /* xt winchester */ +#define FLOPPY_IRQ 6 /* floppy disk */ +#define PRINTER_IRQ 7 +#define AT_WINI_IRQ 14 /* at winchester */ + +/* 一个用于标记是否有重入内核的标志 */ +extern int k_reenter; + +/* 执行用户进程的入口(汇编接口) */ +void restart(); + +/* 导入中断处理函数(汇编接口) */ +// 异常 +void divide_error(); +void single_step_exception(); +void nmi(); +void breakpoint_exception(); +void overflow(); +void bounds_check(); +void inval_opcode(); +void copr_not_available(); +void double_fault(); +void copr_seg_overrun(); +void inval_tss(); +void segment_not_present(); +void stack_exception(); +void general_protection(); +void page_fault(); +void copr_error(); +// 外设(时钟、键盘等) +void hwint00(); +void hwint01(); +void hwint02(); +void hwint03(); +void hwint04(); +void hwint05(); +void hwint06(); +void hwint07(); +void hwint08(); +void hwint09(); +void hwint10(); +void hwint11(); +void hwint12(); +void hwint13(); +void hwint14(); +void hwint15(); + +/* 中断开关 */ +void enable_irq(int irq); +void disable_irq(int irq); + +/* 异常中断实际处理函数(C接口) */ +void exception_handler(int vec_no, int err_code, int eip, + int cs, int eflags); + +/* 外设中断实际处理函数(C接口) */ +void default_interrupt_handler(int irq); +void clock_interrupt_handler(int irq); + +/* 外设中断实际处理函数表 */ +extern void (*irq_table[])(int); + +#endif /* MINIOS_TRAP_H */ \ No newline at end of file diff --git a/inc/type.h b/inc/type.h index dd5b060..0a95dc3 100644 --- a/inc/type.h +++ b/inc/type.h @@ -1,7 +1,12 @@ #ifndef MINIOS_TYPE_H #define MINIOS_TYPE_H -#define NULL 0 +#ifndef NULL +#define NULL ((void*) 0) +#endif + +typedef _Bool bool; +enum { false, true }; typedef int i32; typedef unsigned int u32; @@ -10,6 +15,9 @@ typedef unsigned short u16; typedef char i8; typedef unsigned char u8; +typedef i32 intptr_t; +typedef u32 uintptr_t; + // 通常描述一个对象的大小,会根据机器的型号变化类型 typedef u32 size_t; // signed size_t 通常描述系统调用返回值,会根据机器的型号变化类型 diff --git a/inc/x86.h b/inc/x86.h new file mode 100644 index 0000000..e52f001 --- /dev/null +++ b/inc/x86.h @@ -0,0 +1,114 @@ +#ifndef MINIOS_X86_H +#define MINIOS_X86_H + +#include "type.h" + +static inline u8 +inb(int port) +{ + u8 data; + asm volatile("inb %w1,%0" : "=a" (data) : "d" (port)); + return data; +} + +static inline void +insb(int port, void *addr, int cnt) +{ + asm volatile("cld\n\trepne\n\tinsb" + : "=D" (addr), "=c" (cnt) + : "d" (port), "0" (addr), "1" (cnt) + : "memory", "cc"); +} + +static inline u16 +inw(int port) +{ + u16 data; + asm volatile("inw %w1,%0" : "=a" (data) : "d" (port)); + return data; +} + +static inline void +insw(int port, void *addr, int cnt) +{ + asm volatile("cld\n\trepne\n\tinsw" + : "=D" (addr), "=c" (cnt) + : "d" (port), "0" (addr), "1" (cnt) + : "memory", "cc"); +} + +static inline u32 +inl(int port) +{ + u32 data; + asm volatile("inl %w1,%0" : "=a" (data) : "d" (port)); + return data; +} + +static inline void +insl(int port, void *addr, int cnt) +{ + asm volatile("cld\n\trepne\n\tinsl" + : "=D" (addr), "=c" (cnt) + : "d" (port), "0" (addr), "1" (cnt) + : "memory", "cc"); +} + +static inline void +outb(int port, u8 data) +{ + asm volatile("outb %0,%w1" : : "a" (data), "d" (port)); +} + +static inline void +outsb(int port, const void *addr, int cnt) +{ + asm volatile("cld\n\trepne\n\toutsb" + : "=S" (addr), "=c" (cnt) + : "d" (port), "0" (addr), "1" (cnt) + : "cc"); +} + +static inline void +outw(int port, u16 data) +{ + asm volatile("outw %0,%w1" : : "a" (data), "d" (port)); +} + +static inline void +outsw(int port, const void *addr, int cnt) +{ + asm volatile("cld\n\trepne\n\toutsw" + : "=S" (addr), "=c" (cnt) + : "d" (port), "0" (addr), "1" (cnt) + : "cc"); +} + +static inline void +outsl(int port, const void *addr, int cnt) +{ + asm volatile("cld\n\trepne\n\toutsl" + : "=S" (addr), "=c" (cnt) + : "d" (port), "0" (addr), "1" (cnt) + : "cc"); +} + +static inline void +outl(int port, u32 data) +{ + asm volatile("outl %0,%w1" : : "a" (data), "d" (port)); +} + +static inline void +disable_int() +{ + asm volatile("cli"); +} + +static inline void +enable_int() +{ + asm volatile("sti"); +} + +#endif \ No newline at end of file diff --git a/kern/Makefrag b/kern/Makefrag index b2f9f01..bd042a8 100644 --- a/kern/Makefrag +++ b/kern/Makefrag @@ -5,22 +5,23 @@ OBJDIRS += kern -KERN_ENTRY := 0x30400 - -# kernel的所有源码文件,如果要添加什么新文件就只需要在这里添加即可,其余makefile都不需要动 -KERN_SRCFEILS:= kern/kernel.asm \ +KERN_ENTRY_ADDR := 0x200000 +KERN_SRCFILES:= kern/astart.asm \ + kern/atrap.asm \ + kern/keyboard.c \ + kern/main.c \ kern/start.c \ - kern/cmatrix.c \ - kern/kprintf.asm\ + kern/time.c \ + kern/trap.c \ + lib/libch4Core.a\ lib/terminal.c \ + lib/printfmt.c \ lib/string.c -# 根据KERN_SRCFEILS获取所有需要的可重定位文件 -KERN_OBJFILES := $(patsubst %.c, $(OBJDIR)/%.o, $(KERN_SRCFEILS)) +KERN_OBJFILES := $(patsubst %.c, $(OBJDIR)/%.o, $(KERN_SRCFILES)) KERN_OBJFILES := $(patsubst %.asm, $(OBJDIR)/%.o, $(KERN_OBJFILES)) KERN_OBJFILES := $(patsubst $(OBJDIR)/lib/%, $(OBJDIR)/kern/%, $(KERN_OBJFILES)) -# 根据源文件pattern判断编译的方式 $(OBJDIR)/kern/%.o: kern/%.c $(OBJDIR)/.vars.CFLAGS @echo + cc $< @mkdir -p $(@D) @@ -36,7 +37,14 @@ $(OBJDIR)/kern/%.o: kern/%.asm $(OBJDIR)/.vars.CFLAGS @mkdir -p $(@D) @$(AS) -f elf -o $@ $< -# 将所有可重定位文件链接成kernel.bin -$(OBJDIR)/kern/kernel.bin: $(KERN_OBJFILES) +$(OBJDIR)/kern/%.a: lib/%.a $(OBJDIR)/.vars.CFLAGS + @echo + cp lib $< + @mkdir -p $(@D) + @cp $< $(@D) + +$(OBJDIR)/kern/kernel.bin: $(KERN_OBJFILES) $(OBJDIR)/.vars.LDFLAGS @echo + ld $@ - @$(LD) $(LDFLAGS) -Ttext $(KERN_ENTRY) -o $@ $^ + @$(LD) $(LDFLAGS) -s -Ttext $(KERN_ENTRY_ADDR) -o $@ $(KERN_OBJFILES) $(GCC_LIB) + @$(LD) $(LDFLAGS) -Ttext $(KERN_ENTRY_ADDR) -o $(OBJDIR)/kern/kernel.dbg $(KERN_OBJFILES) $(GCC_LIB) + +all: $(OBJDIR)/kern/a.img diff --git a/kern/astart.asm b/kern/astart.asm new file mode 100644 index 0000000..4e8170a --- /dev/null +++ b/kern/astart.asm @@ -0,0 +1,77 @@ +; 此时内存看上去是这样的(更详细的内存情况在 LOADER.ASM 中有说明): +; ┃ ┃ +; ┃ ... ┃ +; ┣━━━━━━━━━━━━━━━━━━┫ +; ┃■■■■■■Page Tables■■■■■■┃ +; ┃■■■■■(大小由LOADER决定)■■■■┃ PageTblBase +; 00101000h ┣━━━━━━━━━━━━━━━━━━┫ +; ┃■■■■Page Directory Table■■■■┃ PageDirBase = 1M +; 00100000h ┣━━━━━━━━━━━━━━━━━━┫ +; ┃□□□□ Hardware Reserved □□□□┃ B8000h ← gs +; 9FC00h ┣━━━━━━━━━━━━━━━━━━┫ +; ┃■■■■■■■LOADER.BIN■■■■■■┃ somewhere in LOADER ← esp +; 90000h ┣━━━━━━━━━━━━━━━━━━┫ +; ┃■■■■■■■KERNEL.BIN■■■■■■┃ +; 80000h ┣━━━━━━━━━━━━━━━━━━┫ +; ┃■■■■■■■■KERNEL■■■■■■■┃ 30400h ← KERNEL 入口 (KernelEntryPointPhyAddr) +; 30000h ┣━━━━━━━━━━━━━━━━━━┫ +; ┋ ... ┋ +; ┋ ┋ +; 0h ┗━━━━━━━━━━━━━━━━━━┛ ← cs, ds, es, fs, ss +; +; +; GDT 以及相应的描述符是这样的: +; +; Descriptors Selectors +; ┏━━━━━━━━━━━━━━━━━━┓ +; ┃ Dummy Descriptor ┃ +; ┣━━━━━━━━━━━━━━━━━━┫ +; ┃ DESC_FLAT_C (0~4G) ┃ 8h = cs +; ┣━━━━━━━━━━━━━━━━━━┫ +; ┃ DESC_FLAT_RW (0~4G) ┃ 10h = ds, es, fs, ss +; ┣━━━━━━━━━━━━━━━━━━┫ +; ┃ DESC_VIDEO ┃ 1Bh = gs +; ┗━━━━━━━━━━━━━━━━━━┛ +; +; 注意! 在使用 C 代码的时候一定要保证 ds, es, ss 这几个段寄存器的值是一样的 +; 因为编译器有可能编译出使用它们的代码, 而编译器默认它们是一样的. 比如串拷贝操作会用到 ds 和 es. +; +; + +SELECTOR_KERNEL_CS equ 008h +SELECTOR_KERNEL_DS equ 010h +SELECTOR_KERNEL_GS equ 018h + 3 +SELECTOR_TSS equ 020h +SELECTOR_LDT equ 028h + +; 导入函数 +extern cstart +extern kernel_main + +; 导入全局变量 +extern gdt_ptr +extern idt_ptr + +[SECTION .bss] +StackSpace resb 4096 +global StackTop +StackTop: ; 栈顶 + +[section .text] ; 代码在此 + +global _start ; 导出 _start + +_start: + ; 把 esp 从 LOADER 挪到 KERNEL + mov esp, StackTop ; 堆栈在 bss 段中 + + call cstart ; 在此函数中改变了gdt_ptr,让它指向新的GDT + + lgdt [gdt_ptr] ; 装载GDT + mov ax, SELECTOR_LDT; 装载LDT + lldt ax + mov ax, SELECTOR_TSS; 装载TSS + ltr ax + lidt [idt_ptr] + + jmp SELECTOR_KERNEL_CS:kernel_main \ No newline at end of file diff --git a/kern/atrap.asm b/kern/atrap.asm new file mode 100644 index 0000000..5ea61eb --- /dev/null +++ b/kern/atrap.asm @@ -0,0 +1,270 @@ +[section .text] + +; 导入 +extern StackTop ; kern/astart.asm +extern tss ; inc/protect.h +extern k_reenter ; inc/trap.h +extern exception_handler ; inc/trap.h +extern irq_table ; inc/trap.h +extern p_proc_ready ; inc/process.h + +; 具体看inc/process.h pcb维护的寄存器表 +P_STACKBASE equ 0 +GSREG equ P_STACKBASE +FSREG equ GSREG + 4 +ESREG equ FSREG + 4 +DSREG equ ESREG + 4 +EDIREG equ DSREG + 4 +ESIREG equ EDIREG + 4 +EBPREG equ ESIREG + 4 +KERNELESPREG equ EBPREG + 4 +EBXREG equ KERNELESPREG + 4 +EDXREG equ EBXREG + 4 +ECXREG equ EDXREG + 4 +EAXREG equ ECXREG + 4 +RETADR equ EAXREG + 4 +EIPREG equ RETADR + 4 +CSREG equ EIPREG + 4 +EFLAGSREG equ CSREG + 4 +ESPREG equ EFLAGSREG + 4 +SSREG equ ESPREG + 4 +P_STACKTOP equ SSREG + 4 + +save: + pushad ; `. + push ds ; | + push es ; | 保存原寄存器值 + push fs ; | + push gs ; / + mov dx, ss + mov ds, dx + mov es, dx + + mov eax, esp ;eax = 进程表起始地址 + + inc dword [k_reenter] ;k_reenter++; + cmp dword [k_reenter], 0 ;if(k_reenter ==0) + jne .1 ;{ + mov esp, StackTop ; mov esp, StackTop <--切换到内核栈 + push restart ; push restart + jmp [eax + RETADR - P_STACKBASE]; return; +.1: ;} else { 已经在内核栈,不需要再切换 + push restart_reenter ; push restart_reenter + jmp [eax + RETADR - P_STACKBASE]; return; + ;} +; 具体看inc/protect.h TSS维护的寄存器表 +TSS3_S_SP0 equ 4 + + +restart: + mov esp, [p_proc_ready] + lea eax, [esp + P_STACKTOP] + mov dword [tss + TSS3_S_SP0], eax +restart_reenter: + cli + dec dword [k_reenter] + pop gs + pop fs + pop es + pop ds + popad + add esp, 4 + iretd + +EOI equ 0x20 +INT_M_CTL equ 0x20 ; I/O port for interrupt controller +INT_M_CTLMASK equ 0x21 ; setting bits in this port disables ints +INT_S_CTL equ 0xA0 ; I/O port for second interrupt controller +INT_S_CTLMASK equ 0xA1 ; setting bits in this port disables ints + +; 中断和异常 -- 硬件中断 +; --------------------------------- +%macro hwint_master 1 + call save + in al, INT_M_CTLMASK ; `. + or al, (1 << %1) ; | 屏蔽当前中断 + out INT_M_CTLMASK, al ; / + mov al, EOI ; `. 置EOI位 + out INT_M_CTL, al ; / + sti ; CPU在响应中断的过程中会自动关中断,这句之后就允许响应新的中断 + push %1 ; `. + call [irq_table + 4 * %1] ; | 中断处理程序 + pop ecx ; / + cli + in al, INT_M_CTLMASK ; `. + and al, ~(1 << %1) ; | 恢复接受当前中断 + out INT_M_CTLMASK, al ; / + ret +%endmacro + +ALIGN 16 +hwint00: ; Interrupt routine for irq 0 (the clock). + hwint_master 0 + +ALIGN 16 +hwint01: ; Interrupt routine for irq 1 (keyboard) + hwint_master 1 + +ALIGN 16 +hwint02: ; Interrupt routine for irq 2 (cascade!) + hwint_master 2 + +ALIGN 16 +hwint03: ; Interrupt routine for irq 3 (second serial) + hwint_master 3 + +ALIGN 16 +hwint04: ; Interrupt routine for irq 4 (first serial) + hwint_master 4 + +ALIGN 16 +hwint05: ; Interrupt routine for irq 5 (XT winchester) + hwint_master 5 + +ALIGN 16 +hwint06: ; Interrupt routine for irq 6 (floppy) + hwint_master 6 + +ALIGN 16 +hwint07: ; Interrupt routine for irq 7 (printer) + hwint_master 7 + +; --------------------------------- +%macro hwint_slave 1 + hlt ; 后面的8个外设中断暂时不需要,先hlt休眠核 +%endmacro +; --------------------------------- + +ALIGN 16 +hwint08: ; Interrupt routine for irq 8 (realtime clock). + hwint_slave 8 + +ALIGN 16 +hwint09: ; Interrupt routine for irq 9 (irq 2 redirected) + hwint_slave 9 + +ALIGN 16 +hwint10: ; Interrupt routine for irq 10 + hwint_slave 10 + +ALIGN 16 +hwint11: ; Interrupt routine for irq 11 + hwint_slave 11 + +ALIGN 16 +hwint12: ; Interrupt routine for irq 12 + hwint_slave 12 + +ALIGN 16 +hwint13: ; Interrupt routine for irq 13 (FPU exception) + hwint_slave 13 + +ALIGN 16 +hwint14: ; Interrupt routine for irq 14 (AT winchester) + hwint_slave 14 + +ALIGN 16 +hwint15: ; Interrupt routine for irq 15 + hwint_slave 15 + +; 中断和异常 -- 异常 +divide_error: + push 0xFFFFFFFF ; no err code + push 0 ; vector_no = 0 + jmp exception +single_step_exception: + push 0xFFFFFFFF ; no err code + push 1 ; vector_no = 1 + jmp exception +nmi: + push 0xFFFFFFFF ; no err code + push 2 ; vector_no = 2 + jmp exception +breakpoint_exception: + push 0xFFFFFFFF ; no err code + push 3 ; vector_no = 3 + jmp exception +overflow: + push 0xFFFFFFFF ; no err code + push 4 ; vector_no = 4 + jmp exception +bounds_check: + push 0xFFFFFFFF ; no err code + push 5 ; vector_no = 5 + jmp exception +inval_opcode: + push 0xFFFFFFFF ; no err code + push 6 ; vector_no = 6 + jmp exception +copr_not_available: + push 0xFFFFFFFF ; no err code + push 7 ; vector_no = 7 + jmp exception +double_fault: + push 8 ; vector_no = 8 + jmp exception +copr_seg_overrun: + push 0xFFFFFFFF ; no err code + push 9 ; vector_no = 9 + jmp exception +inval_tss: + push 10 ; vector_no = A + jmp exception +segment_not_present: + push 11 ; vector_no = B + jmp exception +stack_exception: + push 12 ; vector_no = C + jmp exception +general_protection: + push 13 ; vector_no = D + jmp exception +page_fault: + push 14 ; vector_no = E + jmp exception +copr_error: + push 0xFFFFFFFF ; no err code + push 16 ; vector_no = 10h + jmp exception + +exception: + call exception_handler + add esp, 4*2 ; 让栈顶指向 EIP,堆栈中从顶向下依次是:EIP、CS、EFLAGS + hlt + +; 一堆符号导出,没别的 +global restart +; 异常处理 +global divide_error +global single_step_exception +global nmi +global breakpoint_exception +global overflow +global bounds_check +global inval_opcode +global copr_not_available +global double_fault +global copr_seg_overrun +global inval_tss +global segment_not_present +global stack_exception +global general_protection +global page_fault +global copr_error +; 外设中断 +global hwint00 +global hwint01 +global hwint02 +global hwint03 +global hwint04 +global hwint05 +global hwint06 +global hwint07 +global hwint08 +global hwint09 +global hwint10 +global hwint11 +global hwint12 +global hwint13 +global hwint14 +global hwint15 \ No newline at end of file diff --git a/kern/cmatrix.c b/kern/cmatrix.c deleted file mode 100644 index 90b6205..0000000 --- a/kern/cmatrix.c +++ /dev/null @@ -1,203 +0,0 @@ -#include "type.h" -#include "cmatrix.h" -#include "terminal.h" - -static size_t random_seed = 114; -static size_t -rand() { - return ( ((random_seed = random_seed * 214013L - + 2531011L) >> 16) & 0x7fff ); -} - -struct cmatrix_node { - u8 row; - u8 character; - struct cmatrix_node *pre; - struct cmatrix_node *nxt; -}; - -struct cmatrix_info { - u8 column; - u8 max_length; - u8 now_length; - struct cmatrix_node *head; - struct cmatrix_node *tail; -}; - -typedef struct cmatrix_node cm_node; -typedef struct cmatrix_info cm_info; - -#define NODE_POOL_SIZE ((TERMINAL_ROW) * (TERMINAL_COLUMN)) -static cm_node node_pool[NODE_POOL_SIZE]; - -static cm_node* -alloc_node() -{ - for (int i = 0; i < NODE_POOL_SIZE; i++) - if (node_pool[i].character == 0) - return &node_pool[i]; - return NULL; -} - -static void -free_node(cm_node *node_ptr) -{ - node_ptr -> row = 0; - node_ptr -> character = 0; - if (node_ptr -> pre != NULL) { - node_ptr -> pre -> nxt = NULL; - node_ptr -> pre = NULL; - } - if (node_ptr -> nxt != NULL) { - node_ptr -> nxt -> pre = NULL; - node_ptr -> nxt = NULL; - } -} - -#define INFO_POOL_SIZE (TERMINAL_COLUMN) -static cm_info info_pool[INFO_POOL_SIZE]; - -static const u8 char_pool[] = { - 'a','b','c','d','e','f','g','h','i','j','k','l','m', - 'n','o','p','q','r','s','t','u','v','w','x','y','z', - 'A','B','C','D','E','F','G','H','I','J','K','L','M', - 'N','O','P','Q','R','S','T','U','V','W','X','Y','Z', - '!','@','#','$','^','&','*','(',')','{','}','[',']', - '|','?','/',':',';','`','~','>','<',',','.','%','-', - '_','+','=','0','1','2','3','4','5','6','7','8','9', -}; - -static void -init_info(cm_info *info_ptr) -{ - info_ptr -> max_length = rand() % (TERMINAL_ROW - 1) + 1; - info_ptr -> head = info_ptr -> tail = NULL; -} - -static void -expand_info(cm_info *info_ptr) -{ - u8 character = char_pool[rand() % ARRAY_SIZE(char_pool)]; - - if (info_ptr -> head == NULL) { - cm_node node = { - .row = 0, - .character = character, - .pre = NULL, - .nxt = NULL, - }; - cm_node *node_ptr = alloc_node(); - *node_ptr = node; - // 修改info - info_ptr -> head = node_ptr; - info_ptr -> tail = node_ptr; - info_ptr -> now_length++; - // 打印字符 -#ifdef TESTS - struct color_char black_green = { - .background = BLACK, - .foreground = GREEN, - .print_char = info_ptr -> head -> character, - .print_pos = TERMINAL_POS(info_ptr -> head -> row, - info_ptr -> column) - }; - kprintf(0, "%s", black_green); -#else - kprintf(TERMINAL_POS(info_ptr -> head -> row, - info_ptr -> column), - "%b%c", GREEN, info_ptr -> head -> character); -#endif - - } else if (info_ptr -> head -> row < TERMINAL_ROW - 1) { - cm_node node = { - .row = info_ptr -> head -> row + 1, - .character = character, - .pre = NULL, - .nxt = info_ptr -> head, - }; - cm_node *node_ptr = alloc_node(); - *node_ptr = node; - // 打印字符 -#ifdef TESTS - struct color_char black_green = { - .background = BLACK, - .foreground = GREEN, - .print_char = info_ptr -> head -> character, - .print_pos = TERMINAL_POS(info_ptr -> head -> row, - info_ptr -> column) - }; - kprintf(0, "%s", black_green); -#else - kprintf(TERMINAL_POS(info_ptr -> head -> row, - info_ptr -> column), - "%f%c", GREEN, info_ptr -> head -> character); -#endif - // 修改info - info_ptr -> head -> pre = node_ptr; - info_ptr -> head = node_ptr; - info_ptr -> now_length++; - // 打印字符 -#ifdef TESTS - struct color_char green_white = { - .background = GREEN, - .foreground = WHITE, - .print_char = info_ptr -> head -> character, - .print_pos = TERMINAL_POS(info_ptr -> head -> row, - info_ptr -> column) - }; - kprintf(0, "%s", green_white); -#else - kprintf(TERMINAL_POS(info_ptr -> head -> row, - info_ptr -> column), - "%b%c", GREEN, info_ptr -> head -> character); -#endif - } else { - // 打印字符 -#ifdef TESTS - struct color_char black_green = { - .background = BLACK, - .foreground = GREEN, - .print_char = info_ptr -> head -> character, - .print_pos = TERMINAL_POS(info_ptr -> head -> row, - info_ptr -> column) - }; - kprintf(0, "%s", black_green); -#else - kprintf(TERMINAL_POS(info_ptr -> head -> row, - info_ptr -> column), - "%f%c", GREEN, info_ptr -> head -> character); -#endif - } - - if (info_ptr -> now_length > info_ptr -> max_length - || info_ptr -> head -> row == TERMINAL_ROW - 1) { - cm_node *node_ptr = info_ptr -> tail; - // 打印字符 - kprintf(TERMINAL_POS(info_ptr -> tail -> row, info_ptr -> column), - "%c", ' '); - // 修改info - info_ptr -> tail = info_ptr -> tail -> pre; - info_ptr -> now_length--; - // 释放内存 - free_node(node_ptr); - } -} - -void -cmatrix_start() -{ - clear_screen(); - for (int i = 0; i < TERMINAL_COLUMN; i++) - info_pool[i].column = i; - while(1) { - for (int i = 0; i < TERMINAL_COLUMN; i++) { - cm_info *info_ptr = &info_pool[i]; - if (info_ptr -> now_length == 0) - init_info(info_ptr); - expand_info(info_ptr); - } - for (int i = 0; i < 1e7; i++) { - // do nothing - } - } -} \ No newline at end of file diff --git a/kern/game.h b/kern/game.h new file mode 100644 index 0000000..d476e47 --- /dev/null +++ b/kern/game.h @@ -0,0 +1,10 @@ +// +// Created by ASUS on 2022/10/3. +// + +#ifndef CH3_GAME_H +#define CH3_GAME_H + +void startGame(); + +#endif //CH3_GAME_H diff --git a/kern/kernel.asm b/kern/kernel.asm deleted file mode 100644 index 29f235a..0000000 --- a/kern/kernel.asm +++ /dev/null @@ -1,86 +0,0 @@ -SELECTOR_KERNEL_CS equ 8 - -; 导入函数 -extern cstart - -; 导入全局变量 -extern gdt_ptr - -[SECTION .bss] -StackSpace resb 4096 -StackTop: ; 栈顶 - -[section .text] ; 代码在此 - -global _start ; 导出 _start - -_start: - ; 此时内存看上去是这样的(更详细的内存情况在 LOADER.ASM 中有说明): - ; ┃ ┃ - ; ┃ ... ┃ - ; ┣━━━━━━━━━━━━━━━━━━┫ - ; ┃■■■■■■Page Tables■■■■■■┃ - ; ┃■■■■■(大小由LOADER决定)■■■■┃ PageTblBase - ; 00101000h ┣━━━━━━━━━━━━━━━━━━┫ - ; ┃■■■■Page Directory Table■■■■┃ PageDirBase = 1M - ; 00100000h ┣━━━━━━━━━━━━━━━━━━┫ - ; ┃□□□□ Hardware Reserved □□□□┃ B8000h ← gs - ; 9FC00h ┣━━━━━━━━━━━━━━━━━━┫ - ; ┃■■■■■■■LOADER.BIN■■■■■■┃ somewhere in LOADER ← esp - ; 90000h ┣━━━━━━━━━━━━━━━━━━┫ - ; ┃■■■■■■■KERNEL.BIN■■■■■■┃ - ; 80000h ┣━━━━━━━━━━━━━━━━━━┫ - ; ┃■■■■■■■■KERNEL■■■■■■■┃ 30400h ← KERNEL 入口 (KernelEntryPointPhyAddr) - ; 30000h ┣━━━━━━━━━━━━━━━━━━┫ - ; ┋ ... ┋ - ; ┋ ┋ - ; 0h ┗━━━━━━━━━━━━━━━━━━┛ ← cs, ds, es, fs, ss - ; - ; - ; GDT 以及相应的描述符是这样的: - ; - ; Descriptors Selectors - ; ┏━━━━━━━━━━━━━━━━━━┓ - ; ┃ Dummy Descriptor ┃ - ; ┣━━━━━━━━━━━━━━━━━━┫ - ; ┃ DESC_FLAT_C (0~4G) ┃ 8h = cs - ; ┣━━━━━━━━━━━━━━━━━━┫ - ; ┃ DESC_FLAT_RW (0~4G) ┃ 10h = ds, es, fs, ss - ; ┣━━━━━━━━━━━━━━━━━━┫ - ; ┃ DESC_VIDEO ┃ 1Bh = gs - ; ┗━━━━━━━━━━━━━━━━━━┛ - ; - ; 注意! 在使用 C 代码的时候一定要保证 ds, es, ss 这几个段寄存器的值是一样的 - ; 因为编译器有可能编译出使用它们的代码, 而编译器默认它们是一样的. 比如串拷贝操作会用到 ds 和 es. - ; - ; - - ; 把 esp 从 LOADER 挪到 KERNEL - mov esp, StackTop ; 堆栈在 bss 段中 - ; task1, make sure we arrive here - pusha ; just feel free to use gpr - mov esi, 0 - mov ebx, STR_kernel -.loop: - mov al, [ebx, esi * 1] - or ax, 0f00h - mov word [gs:esi * 2 + 80 * 1 * 2], ax ; gs <-> DESC_VIDEO, copy from write_terminal - add esi, 1 - cmp esi, 6 - jne .loop - popa - - sgdt [gdt_ptr] ; cstart() 中将会用到 gdt_ptr - call cstart ; 在此函数中改变了gdt_ptr,让它指向新的GDT - lgdt [gdt_ptr] ; 使用新的GDT - - - jmp SELECTOR_KERNEL_CS:csinit -csinit: ; “这个跳转指令强制使用刚刚初始化的结构”——<> P90. - - push 0 - popfd ; Pop top of stack into EFLAGS - - hlt - -STR_kernel: db 'kernel' \ No newline at end of file diff --git a/kern/keyboard.c b/kern/keyboard.c new file mode 100644 index 0000000..29a4792 --- /dev/null +++ b/kern/keyboard.c @@ -0,0 +1,36 @@ +#include "keymap.h" +#include "stdio.h" +#include "type.h" + +#define KB_INBUF_SIZE 4 + +typedef struct kb_inbuf { + u8* p_head; + u8* p_tail; + int count; + u8 buf[KB_INBUF_SIZE]; +} KB_INPUT; + +static KB_INPUT kb_input = { + .p_head = kb_input.buf, + .p_tail = kb_input.buf, + .count = 0, +}; + +/* + * 将ch这个字符放进内核的字符缓冲区 + */ +void +add_keyboard_buf(u8 ch) +{ +} + +/* + * 如果内核的字符缓冲区为空,则返回-1 + * 否则返回缓冲区队头的字符并弹出队头 + */ +u8 +getch(void) +{ + return -1; +} \ No newline at end of file diff --git a/kern/kprintf.asm b/kern/kprintf.asm deleted file mode 100644 index 831cb9d..0000000 --- a/kern/kprintf.asm +++ /dev/null @@ -1,80 +0,0 @@ -[SECTION .text] - -[BITS 32] - -DEFAULT_COLOR equ 0f00h - -global kprintf -;=============================================== -; void kprintf(u16 disp_pos, const char *format, ...) -; 参数说明: -; disp_pos: 开始打印的位置,0为0行0列,1为0行1列,80位1行0列 -; format: 需要格式化输出的字符串,默认输出的字符颜色为黑底白字 -; %c: 输出下一个参数的字符信息(保证参数范围在0~127),输出完打印的位置往下移动一位 -; %b: 更改之后输出的字符的背景色(保证参数范围在0~15) -; %f: 更改之后输出的字符的前景色(保证参数范围在0~15) -; %s(提高内容): 参考inc/terminal.h,传进来的是一个结构体,结构体参数足够明确不复赘述, -; 输出是独立的,输出完打印的位置不会往下移动一位,不会影响接下来%c的输出的颜色 -; 其余字符:按照字符输出(保证字符里不会有%,\n等奇奇怪怪的字符,都是常见字符,%后面必会跟上述三个参数之一),输出完打印的位置往下移动一位 -kprintf: -; jmp $ - push ebp - mov ebp, esp - push edi - push esi - push ebx ; %ebp, %ebx, %edi, %esi, %esp are callee saved regs - mov esi, 0 ; string offset - mov edi, [ebp + 8] ; disp_pos, stack always word aligned - mov ebx, [ebp + 12] ; format base - mov edx, 4 ; offset from ebp of the first va_list - mov ecx, DEFAULT_COLOR ; color - xor eax, eax ; clear eax -.loop: - movzx eax, byte [ebx + esi] ; fetch a char into eax - inc esi ; - cmp al, 0 - je .exit ; if reaches \0, then break - - cmp al, '%' - jne .normal_print - movzx eax, byte [ebx + esi] - inc esi ; handle special format - cmp al, 'c' - je .fmt_c - cmp al, 'b' - je .fmt_b - cmp al, 'f' - je .fmt_f - dec esi ; fall back to normal print - movzx eax, byte [ebx + esi] - jmp .normal_print -.fmt_c: - mov eax, [ebp + edx * 4] ; fetch va into eax, here its a byte of character - inc edx ; align to 4bytes, maybe - jmp .normal_print -.fmt_b: - mov eax, [ebp + edx * 4] - inc edx - and cx, 00F00H ; clear background color(and keep foreground) - shl ax, 12 - or cx, ax - jmp .loop ; not a printable option -.fmt_f: - mov eax, [ebp + edx * 4] - inc edx - and cx, 0F000H ; clear foreground color(and keep backgroung) - shl ax, 8 - or cx, ax - jmp .loop ; not a printable option -.normal_print: - or ax, cx ; combine character and color - mov word[gs:edi * 2], ax; load to dest video mem, edi as the cursor - inc edi ; cursor ++ - jmp .loop ; it is a while loop -.exit: - pop ebx - pop edi - pop esi - leave - ret - jmp $ \ No newline at end of file diff --git a/kern/main.c b/kern/main.c new file mode 100644 index 0000000..919620f --- /dev/null +++ b/kern/main.c @@ -0,0 +1,101 @@ +#include "assert.h" +#include "stdio.h" +#include "string.h" +#include "process.h" +#include "protect.h" +#include "type.h" +#include "trap.h" +#include "x86.h" + +/* + * 三个测试函数,用户进程的执行流 + */ +void TestA() +{ + int i = 0; + while(1){ + kprintf("A%d.",i++); + for (int j = 0 ; j < 5e7 ; j++) + ;//do nothing + } +} + +void TestB() +{ + int i = 0; + while(1){ + kprintf("B%d.",i++); + for (int j = 0 ; j < 5e7 ; j++) + ;//do nothing + } +} + +void TestC() +{ + int i = 0; + while(1){ + kprintf("C%d.",i++); + for (int j = 0 ; j < 5e7 ; j++) + ;//do nothing + } +} + +// 每个栈空间有4kb大 +#define STACK_PREPROCESS 0x1000 +#define STACK_TOTSIZE STACK_PREPROCESS * PCB_SIZE +// 用户栈(直接在内核开一个临时数组充当) +char process_stack[STACK_TOTSIZE]; +// 指向当前进程pcb的指针 +PROCESS *p_proc_ready; +// pcb表 +PROCESS proc_table[PCB_SIZE]; +void (*entry[]) = { + TestA, + TestB, + TestC, +}; +char pcb_name[][16] = { + "TestA", + "TestB", + "TestC", +}; + +/* + * 内核的main函数 + * 用于初始化用户进程,然后将执行流交给用户进程 + */ +void kernel_main() +{ + kprintf("---start kernel main---\n"); + + PROCESS *p_proc = proc_table; + char *p_stack = process_stack; + + for (int i = 0 ; i < PCB_SIZE ; i++, p_proc++) { + strcpy(p_proc->p_name, pcb_name[i]); + p_proc->regs.cs = (SELECTOR_FLAT_C & SA_RPL_MASK & SA_TI_MASK) + | SA_TIL | RPL_USER; + p_proc->regs.ds = (SELECTOR_FLAT_RW & SA_RPL_MASK & SA_TI_MASK) + | SA_TIL | RPL_USER; + p_proc->regs.es = (SELECTOR_FLAT_RW & SA_RPL_MASK & SA_TI_MASK) + | SA_TIL | RPL_USER; + p_proc->regs.fs = (SELECTOR_FLAT_RW & SA_RPL_MASK & SA_TI_MASK) + | SA_TIL | RPL_USER; + p_proc->regs.ss = (SELECTOR_FLAT_RW & SA_RPL_MASK & SA_TI_MASK) + | SA_TIL | RPL_USER; + p_proc->regs.gs = (SELECTOR_VIDEO & SA_RPL_MASK & SA_TI_MASK) + | RPL_USER; + + p_proc->regs.eip = (u32)entry[i]; + p_stack += STACK_PREPROCESS; + p_proc->regs.esp = (u32)p_stack; + p_proc->regs.eflags = 0x1202; /* IF=1, IOPL=1 */ + } + + p_proc_ready = proc_table; + + enable_irq(CLOCK_IRQ); + + restart(); + assert(0); +} \ No newline at end of file diff --git a/kern/start.c b/kern/start.c index 82dc94a..6327e00 100644 --- a/kern/start.c +++ b/kern/start.c @@ -1,57 +1,218 @@ #include "type.h" #include "protect.h" -#include "string.h" -#include "cmatrix.h" -#include "terminal.h" +#include "stdio.h" +#include "assert.h" +#include "trap.h" +#include "x86.h" -u8 gdt_ptr[6]; /* 0~15:Limit 16~47:Base */ -DESCRIPTOR gdt[GDT_SIZE]; +struct Pseudodesc gdt_ptr, idt_ptr; +DESCRIPTOR gdt[GDT_SIZE]; +DESCRIPTOR ldt[LDT_SIZE]; +GATE idt[IDT_SIZE]; + +TSS tss; + +/* + * 当发生不可挽回的错误时就打印错误信息并使CPU核休眠 + */ +void +_panic(const char *file, int line, const char *fmt,...) +{ + va_list ap; + + // 确保CPU核不受外界中断的影响 + asm volatile("cli"); + asm volatile("cld"); + + va_start(ap, fmt); + kprintf("\x1b[0m\x1b[91mkernel panic at %s:%d: ", file, line); + vkprintf(fmt, ap); + kprintf("\n\x1b[0m"); + va_end(ap); + // 休眠CPU核,直接罢工 + while(1) + asm volatile("hlt"); +} + +/* + * 很像panic,但是不会休眠CPU核,就是正常打印信息 + */ +void +_warn(const char *file, int line, const char *fmt,...) +{ + va_list ap; + + va_start(ap, fmt); + kprintf("\x1b[0m\x1b[93mkernel warning at %s:%d: ", file, line); + vkprintf(fmt, ap); + kprintf("\n\x1b[0m"); + va_end(ap); +} + +/* + * 再创建一遍gdt表,与loader中的gdt表不同的是 + * 新增了tss和ldt的两个的全局描述符 + */ +static void +init_gdt() +{ + init_segment(&gdt[0], 0, 0, 0); + // 代码段(cs) + init_segment(&gdt[1], 0, 0xfffff, DA_CR | DA_32 | DA_LIMIT_4K); + // 数据段(ds,es,fs,ss) + init_segment(&gdt[2], 0, 0xfffff, DA_DRW | DA_32 | DA_LIMIT_4K); + // 显存段(gs) + init_segment(&gdt[3], 0xb8000, 0xffff, DA_DRW | DA_DPL3); + tss.ss0 = SELECTOR_FLAT_RW; + tss.iobase = sizeof(tss); /* 没有I/O许可位图 */ + // tss段 + init_segment(&gdt[4], (u32)&tss, sizeof(tss) - 1, DA_386TSS); + // ldt段 + init_segment(&gdt[5], (u32)ldt, sizeof(ldt) - 1, DA_LDT); + + gdt_ptr.pd_base = (u32)gdt; + gdt_ptr.pd_lim = sizeof(gdt) - 1; +} +/* + * 创建ldt表,ldt表用于用户态,用户进程的段寄存器不能存放内核权限的gdt + * ,而是使用用户权限的ldt + */ +static void +init_ldt() +{ + init_segment(&ldt[0], 0, 0, 0); + // 代码段(cs) + init_segment(&ldt[1], 0, 0xfffff, + DA_CR | DA_32 | DA_LIMIT_4K | DA_DPL3); + // 数据段(ds,es,fs,ss) + init_segment(&ldt[2], 0, 0xfffff, + DA_DRW | DA_32 | DA_LIMIT_4K | DA_DPL3); + // 用户态的gs继承自gdt的显存段 +} + +/* + * 创建idt表,看起来挺吓人的,实际上全是按照手册抄的。 + */ +static void +init_idt() +{ + // 异常处理 + init_gate(idt + INT_VECTOR_DIVIDE, DA_386IGate, + divide_error, PRIVILEGE_KRNL); + init_gate(idt + INT_VECTOR_DEBUG, DA_386IGate, + single_step_exception, PRIVILEGE_KRNL); + init_gate(idt + INT_VECTOR_NMI, DA_386IGate, + nmi, PRIVILEGE_KRNL); + init_gate(idt + INT_VECTOR_BREAKPOINT, DA_386IGate, + breakpoint_exception, PRIVILEGE_USER); + init_gate(idt + INT_VECTOR_OVERFLOW, DA_386IGate, + overflow, PRIVILEGE_USER); + init_gate(idt + INT_VECTOR_BOUNDS, DA_386IGate, + bounds_check, PRIVILEGE_KRNL); + init_gate(idt + INT_VECTOR_INVAL_OP, DA_386IGate, + inval_opcode, PRIVILEGE_KRNL); + init_gate(idt + INT_VECTOR_COPROC_NOT, DA_386IGate, + copr_not_available, PRIVILEGE_KRNL); + init_gate(idt + INT_VECTOR_DOUBLE_FAULT,DA_386IGate, + double_fault, PRIVILEGE_KRNL); + init_gate(idt + INT_VECTOR_COPROC_SEG, DA_386IGate, + copr_seg_overrun, PRIVILEGE_KRNL); + init_gate(idt + INT_VECTOR_INVAL_TSS, DA_386IGate, + inval_tss, PRIVILEGE_KRNL); + init_gate(idt + INT_VECTOR_SEG_NOT, DA_386IGate, + segment_not_present, PRIVILEGE_KRNL); + init_gate(idt + INT_VECTOR_STACK_FAULT, DA_386IGate, + stack_exception, PRIVILEGE_KRNL); + init_gate(idt + INT_VECTOR_PROTECTION, DA_386IGate, + general_protection, PRIVILEGE_KRNL); + init_gate(idt + INT_VECTOR_PAGE_FAULT, DA_386IGate, + page_fault, PRIVILEGE_KRNL); + init_gate(idt + INT_VECTOR_COPROC_ERR, DA_386IGate, + copr_error, PRIVILEGE_KRNL); + // 外设中断 + init_gate(idt + INT_VECTOR_IRQ0 + 0, DA_386IGate, + hwint00, PRIVILEGE_KRNL); + init_gate(idt + INT_VECTOR_IRQ0 + 1, DA_386IGate, + hwint01, PRIVILEGE_KRNL); + init_gate(idt + INT_VECTOR_IRQ0 + 2, DA_386IGate, + hwint02, PRIVILEGE_KRNL); + init_gate(idt + INT_VECTOR_IRQ0 + 3, DA_386IGate, + hwint03, PRIVILEGE_KRNL); + init_gate(idt + INT_VECTOR_IRQ0 + 4, DA_386IGate, + hwint04, PRIVILEGE_KRNL); + init_gate(idt + INT_VECTOR_IRQ0 + 5, DA_386IGate, + hwint05, PRIVILEGE_KRNL); + init_gate(idt + INT_VECTOR_IRQ0 + 6, DA_386IGate, + hwint06, PRIVILEGE_KRNL); + init_gate(idt + INT_VECTOR_IRQ0 + 7, DA_386IGate, + hwint07, PRIVILEGE_KRNL); + init_gate(idt + INT_VECTOR_IRQ8 + 0, DA_386IGate, + hwint08, PRIVILEGE_KRNL); + init_gate(idt + INT_VECTOR_IRQ8 + 1, DA_386IGate, + hwint09, PRIVILEGE_KRNL); + init_gate(idt + INT_VECTOR_IRQ8 + 2, DA_386IGate, + hwint10, PRIVILEGE_KRNL); + init_gate(idt + INT_VECTOR_IRQ8 + 3, DA_386IGate, + hwint11, PRIVILEGE_KRNL); + init_gate(idt + INT_VECTOR_IRQ8 + 4, DA_386IGate, + hwint12, PRIVILEGE_KRNL); + init_gate(idt + INT_VECTOR_IRQ8 + 5, DA_386IGate, + hwint13, PRIVILEGE_KRNL); + init_gate(idt + INT_VECTOR_IRQ8 + 6, DA_386IGate, + hwint14, PRIVILEGE_KRNL); + init_gate(idt + INT_VECTOR_IRQ8 + 7, DA_386IGate, + hwint15, PRIVILEGE_KRNL); + + idt_ptr.pd_base = (u32)idt; + idt_ptr.pd_lim = sizeof(idt) - 1; +} + +/* + * 初始化8259A,设置外设中断,看起来挺吓人的,实际上全是按照手册抄的。 + */ +static void +init_8259A() +{ + outb(INT_M_CTL, 0x11); // Master 8259, ICW1. + outb(INT_S_CTL, 0x11); // Slave 8259, ICW1. + + // Master 8259, ICW2. 设置 '主8259' 的中断入口地址为 0x20. + outb(INT_M_CTLMASK, INT_VECTOR_IRQ0); + // Slave 8259, ICW2. 设置 '从8259' 的中断入口地址为 0x28 + outb(INT_S_CTLMASK, INT_VECTOR_IRQ8); + // Master 8259, ICW3. IR2 对应 '从8259'. + outb(INT_M_CTLMASK, 0x4); + // Slave 8259, ICW3. 对应 '主8259' 的 IR2. + outb(INT_S_CTLMASK, 0x2); + + outb(INT_M_CTLMASK, 0x1); // Master 8259, ICW4. + outb(INT_S_CTLMASK, 0x1); // Slave 8259, ICW4. + + outb(INT_M_CTLMASK, 0xFF); // Master 8259, OCW1. + outb(INT_S_CTLMASK, 0xFF); // Slave 8259, OCW1. +} + +/* + * 内核初始化函数 + */ void cstart() { - /* 将 LOADER 中的 GDT 复制到新的 GDT 中 */ - memcpy(&gdt, /* New GDT */ - (void *)(*((u32 *)(&gdt_ptr[2]))), /* Base of Old GDT */ - *((u16 *)(&gdt_ptr[0])) + 1 /* Limit of Old GDT */ - ); - /* gdt_ptr[6] 共 6 个字节:0~15:Limit 16~47:Base。用作 sgdt/lgdt 的参数。*/ - u16 *p_gdt_limit = (u16 *)(&gdt_ptr[0]); - u32 *p_gdt_base = (u32 *)(&gdt_ptr[2]); - *p_gdt_limit = GDT_SIZE * sizeof(DESCRIPTOR) - 1; - *p_gdt_base = (u32)&gdt; - // 在终端的第1行依次打出N(黑底白字),W(黑底蓝字),P(白底蓝字),U(白底黑字) - kprintf(TERMINAL_POS(1, 0), - "N%fW%bP%fU%f%b ", LIGHT_BLUE, WHITE, BLACK, WHITE, BLACK); - // 在终端的第2行依次输出白底绿紫相间的字符 - for (char *s = "even old new york once Amsterdam", - *st = s ; *s ; s += 4) { - kprintf(TERMINAL_POS(2, s - st), - "%b%f%c%f%c%f%c%f%c", - WHITE, GREEN, *(s + 0), - FUCHUSIA, *(s + 1), GREEN, - *(s + 2), FUCHUSIA, *(s + 3)); - } - cmatrix_start(); -#ifdef TESTS - //在终端第3,4行分别输出两句话,而且两行的格式相同 - //第3行为绿底黄字,第4行为黄底绿字 - for (char *s1 = "never gonna give you up ", *st1 = s1, - *s2 = "never gonna let you down" ; *s1 ; s1 += 2, s2 += 2) { - struct color_char c1 = { - .background = YELLOW, - .foreground = GREEN, - .print_char = *(s2 + 0), - .print_pos = TERMINAL_POS(4, s1 - st1 + 0), - }; - struct color_char c2 = { - .background = YELLOW, - .foreground = GREEN, - .print_char = *(s2 + 1), - .print_pos = TERMINAL_POS(4, s1 - st1 + 1), - }; - kprintf(TERMINAL_POS(3, s1 - st1), - "%b%f%c%s%c%s", - GREEN, YELLOW, *(s1 + 0), c1, *(s1 + 1), c2); - } -#endif + // 清屏并将光标置为开头 + // ANSI转义序列,由\x1b(ascii码27,为Esc)开头的序列 + // 能够控制光标控制终端(linux上也在用这个控制终端) + // 目前支持的功能在inc/terminal.c中的kprintf函数有写 + // \x1b[2J 清屏 + // \x1b[H 将光标放置到左上角 + kprintf("\x1b[2J\x1b[H"); + + kprintf("---loading gdt "); + init_gdt(); + kprintf("ldt "); + init_ldt(); + kprintf("8259A "); + init_8259A(); + kprintf("idt "); + init_idt(); + kprintf("---\n"); } diff --git a/kern/time.c b/kern/time.c new file mode 100644 index 0000000..e080889 --- /dev/null +++ b/kern/time.c @@ -0,0 +1,22 @@ +#include "type.h" +#include "time.h" + +static size_t timecounter; + +/* + * 时间戳加一 + */ +void +timecounter_inc() +{ + timecounter++; +} + +/* + * 获取内核当前的时间戳 + */ +size_t +clock() +{ + return timecounter; +} \ No newline at end of file diff --git a/kern/trap.c b/kern/trap.c new file mode 100644 index 0000000..7d268fb --- /dev/null +++ b/kern/trap.c @@ -0,0 +1,110 @@ +#include "assert.h" +#include "process.h" +#include "stdio.h" +#include "time.h" +#include "trap.h" +#include "x86.h" + +/* + * 当前内核需要处理中断的数量 + */ +int k_reenter; + +void (*irq_table[16])(int) = { + clock_interrupt_handler, + default_interrupt_handler, + default_interrupt_handler, + default_interrupt_handler, + default_interrupt_handler, + default_interrupt_handler, + default_interrupt_handler, + default_interrupt_handler, + default_interrupt_handler, + default_interrupt_handler, + default_interrupt_handler, + default_interrupt_handler, + default_interrupt_handler, + default_interrupt_handler, + default_interrupt_handler, + default_interrupt_handler, +}; + +/* + * 开启对应外设中断(将掩码对应位置为0) + */ +void +enable_irq(int irq) +{ + u8 mask = 1 << (irq % 8); + if (irq < 8) + outb(INT_M_CTLMASK, inb(INT_M_CTLMASK) & ~mask); + else + outb(INT_S_CTLMASK, inb(INT_S_CTLMASK) & ~mask); +} + +/* + * 关闭对应外设中断(将掩码对应位置为1) + */ +void +disable_irq(int irq) +{ + u8 mask = 1 << (irq % 8); + if (irq < 8) + outb(INT_M_CTLMASK, inb(INT_M_CTLMASK) | mask); + else + outb(INT_S_CTLMASK, inb(INT_S_CTLMASK) | mask); +} +/* + * 中断默认处理函数 + * 理论上是不支持的,所以会给个warning + */ +void +default_interrupt_handler(int irq) +{ + warn("unsupport interrupt! irq = %d", irq); +} + +/* + * 异常默认处理函数 + * 由于没有任何处理异常的手段,所以会给个panic + */ +void +exception_handler(int vec_no, int err_code, int eip, int cs, int eflags) +{ + char err_description[][64] = { "#DE Divide Error", + "#DB RESERVED", + "— NMI Interrupt", + "#BP Breakpoint", + "#OF Overflow", + "#BR BOUND Range Exceeded", + "#UD Invalid Opcode (Undefined Opcode)", + "#NM Device Not Available (No Math Coprocessor)", + "#DF Double Fault", + " Coprocessor Segment Overrun (reserved)", + "#TS Invalid TSS", + "#NP Segment Not Present", + "#SS Stack-Segment Fault", + "#GP General Protection", + "#PF Page Fault", + "— (Intel reserved. Do not use.)", + "#MF x87 FPU Floating-Point Error (Math Fault)", + "#AC Alignment Check", + "#MC Machine Check", + "#XF SIMD Floating-Point Exception" + }; + panic("\x1b[H\x1b[2JException! --> %s\nEFLAGS: %x CS: %x EIP: %x\nError code: %x", + err_description[vec_no], eflags, cs, eip, err_code); +} +/* + * 时钟中断处理函数 + */ +void +clock_interrupt_handler(int irq) +{ + kprintf("#"); + timecounter_inc(); + p_proc_ready++; + if (p_proc_ready >= proc_table + PCB_SIZE) { + p_proc_ready = proc_table; + } +} \ No newline at end of file diff --git a/lab3-finish.png b/lab3-finish.png deleted file mode 100644 index db0287d..0000000 Binary files a/lab3-finish.png and /dev/null differ diff --git a/lib/libch4Core.a b/lib/libch4Core.a new file mode 100644 index 0000000..1b7e0d1 Binary files /dev/null and b/lib/libch4Core.a differ diff --git a/lib/printfmt.c b/lib/printfmt.c new file mode 100644 index 0000000..45c708d --- /dev/null +++ b/lib/printfmt.c @@ -0,0 +1,267 @@ +#include "type.h" +#include "stdio.h" +#include "string.h" +#include "stdarg.h" + +/* + * 从xv6中抄过来的printf,感觉非常好用。 + * 支持各种常规的格式化输出,比如%d,%c,%f,%s,%u,%p + * 这会极大的节省我们重复造输出相关轮子的时间 + */ + +/* + * Print a number (base <= 16) in reverse order, + * using specified putch function and associated pointer putdat. + */ +static void +printnum(void (*putch)(int, void*), void *putdat, + unsigned long long num, unsigned base, int width, int padc) +{ + // first recursively print all preceding (more significant) digits + if (num >= base) { + printnum(putch, putdat, num / base, base, width - 1, padc); + } else { + // print any needed pad characters before first digit + while (--width > 0) + putch(padc, putdat); + } + + // then print this (the least significant) digit + putch("0123456789abcdef"[num % base], putdat); +} + +// Get an unsigned int of various possible sizes from a varargs list, +// depending on the lflag parameter. +static unsigned long long +getuint(va_list *ap, int lflag) +{ + if (lflag >= 2) + return va_arg(*ap, unsigned long long); + else if (lflag) + return va_arg(*ap, unsigned long); + else + return va_arg(*ap, unsigned int); +} + +// Same as getuint but signed - can't use getuint +// because of sign extension +static long long +getint(va_list *ap, int lflag) +{ + if (lflag >= 2) + return va_arg(*ap, long long); + else if (lflag) + return va_arg(*ap, long); + else + return va_arg(*ap, int); +} + +// Main function to format and print a string. +void printfmt(void (*putch)(int, void*), void *putdat, const char *fmt, ...); + +void +vprintfmt(void (*putch)(int, void*), void *putdat, const char *fmt, va_list ap) +{ + register const char *p; + register int ch, err; + unsigned long long num; + int base, lflag, width, precision, altflag; + char padc; + + while (1) { + while ((ch = *(unsigned char *) fmt++) != '%') { + if (ch == '\0') + return; + putch(ch, putdat); + } + + // Process a %-escape sequence + padc = ' '; + width = -1; + precision = -1; + lflag = 0; + altflag = 0; + reswitch: + switch (ch = *(unsigned char *) fmt++) { + + // flag to pad on the right + case '-': + padc = '-'; + goto reswitch; + + // flag to pad with 0's instead of spaces + case '0': + padc = '0'; + goto reswitch; + + // width field + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + for (precision = 0; ; ++fmt) { + precision = precision * 10 + ch - '0'; + ch = *fmt; + if (ch < '0' || ch > '9') + break; + } + goto process_precision; + + case '*': + precision = va_arg(ap, int); + goto process_precision; + + case '.': + if (width < 0) + width = 0; + goto reswitch; + + case '#': + altflag = 1; + goto reswitch; + + process_precision: + if (width < 0) + width = precision, precision = -1; + goto reswitch; + + // long flag (doubled for long long) + case 'l': + lflag++; + goto reswitch; + + // character + case 'c': + putch(va_arg(ap, int), putdat); + break; + + // string + case 's': + if ((p = va_arg(ap, char *)) == NULL) + p = "(null)"; + if (width > 0 && padc != '-') + for (width -= strnlen(p, precision); width > 0; width--) + putch(padc, putdat); + for (; (ch = *p++) != '\0' && (precision < 0 || --precision >= 0); width--) + if (altflag && (ch < ' ' || ch > '~')) + putch('?', putdat); + else + putch(ch, putdat); + for (; width > 0; width--) + putch(' ', putdat); + break; + + // (signed) decimal + case 'd': + num = getint(&ap, lflag); + if ((long long) num < 0) { + putch('-', putdat); + num = -(long long) num; + } + base = 10; + goto number; + + // unsigned decimal + case 'u': + num = getuint(&ap, lflag); + base = 10; + goto number; + + // (unsigned) octal + case 'o': + // Replace this with your code. + putch('X', putdat); + putch('X', putdat); + putch('X', putdat); + break; + + // pointer + case 'p': + putch('0', putdat); + putch('x', putdat); + num = (unsigned long long) + (uintptr_t) va_arg(ap, void *); + base = 16; + goto number; + + // (unsigned) hexadecimal + case 'x': + num = getuint(&ap, lflag); + base = 16; + number: + printnum(putch, putdat, num, base, width, padc); + break; + + // escaped '%' character + case '%': + putch(ch, putdat); + break; + + // unrecognized escape sequence - just print it literally + default: + putch('%', putdat); + for (fmt--; fmt[-1] != '%'; fmt--) + /* do nothing */; + break; + } + } +} + +void +printfmt(void (*putch)(int, void*), void *putdat, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + vprintfmt(putch, putdat, fmt, ap); + va_end(ap); +} + +struct sprintbuf { + char *buf; + char *ebuf; + int cnt; +}; + +static void +sprintputch(int ch, struct sprintbuf *b) +{ + b->cnt++; + if (b->buf < b->ebuf) + *b->buf++ = ch; +} + +int +vsnprintf(char *buf, int n, const char *fmt, va_list ap) +{ + struct sprintbuf b = {buf, buf+n-1, 0}; + + if (buf == NULL || n < 1) + return -1; + + // print the string to the buffer + vprintfmt((void*)sprintputch, &b, fmt, ap); + + // null terminate the buffer + *b.buf = '\0'; + + return b.cnt; +} + +int +snprintf(char *buf, int n, const char *fmt, ...) +{ + va_list ap; + int rc; + + va_start(ap, fmt); + rc = vsnprintf(buf, n, fmt, ap); + va_end(ap); + + return rc; +} \ No newline at end of file diff --git a/lib/string.c b/lib/string.c index d8a8233..d1e5417 100644 --- a/lib/string.c +++ b/lib/string.c @@ -1,5 +1,78 @@ #include "string.h" +int +strlen(const char *s) +{ + int n; + + for (n = 0; *s != '\0'; s++) + n++; + return n; +} + +int +strnlen(const char *s, size_t size) +{ + int n; + + for (n = 0; size > 0 && *s != '\0'; s++, size--) + n++; + return n; +} + +char * +strcpy(char *dst, const char *src) +{ + char *ret; + + ret = dst; + while ((*dst++ = *src++) != '\0') + /* do nothing */; + return ret; +} + +char * +strcat(char *dst, const char *src) +{ + int len = strlen(dst); + strcpy(dst + len, src); + return dst; +} + +char * +strncpy(char *dst, const char *src, size_t size) { + size_t i; + char *ret; + + ret = dst; + for (i = 0; i < size; i++) { + *dst++ = *src; + // If strlen(src) < size, null-pad 'dst' out to 'size' chars + if (*src != '\0') + src++; + } + return ret; +} + +int +strcmp(const char *p, const char *q) +{ + while (*p && *p == *q) + p++, q++; + return (int) ((unsigned char) *p - (unsigned char) *q); +} + +int +strncmp(const char *p, const char *q, size_t n) +{ + while (n > 0 && *p && *p == *q) + n--, p++, q++; + if (n == 0) + return 0; + else + return (int) ((unsigned char) *p - (unsigned char) *q); +} + void * memset(void *v, int c, size_t n) { diff --git a/lib/terminal.c b/lib/terminal.c index 3228258..007e8b8 100644 --- a/lib/terminal.c +++ b/lib/terminal.c @@ -1,5 +1,10 @@ +#include "assert.h" +#include "stdio.h" +#include "string.h" +#include "stdarg.h" #include "terminal.h" -#include "cmatrix.h" +#include "type.h" +#include "x86.h" inline static void write_to_terminal(u16 disp_pos, u16 content) @@ -8,11 +13,411 @@ write_to_terminal(u16 disp_pos, u16 content) "mov %1, %%gs:(%0)" ::"r"(disp_pos * 2), "r"(content) : "memory"); } -void -clear_screen() + +struct kprintfbuf { + // 通用 + u16 color; + i16 cursor_row; + i16 cursor_col; + int cnt; + // 控制字符 + int CSI; + i16 param1; + i16 param2; +}; + +#define CSI_ESC 0 +#define CSI_BRACKET 1 +#define CSI_PARAM1 2 +#define CSI_PARAM2 3 + +static struct kprintfbuf TTY = { + .color = DEFAULT_COLOR, + .cursor_row = 1, + .cursor_col = 1, +}; + +inline static u16 +cursor_pos(struct kprintfbuf *b) +{ + return TERMINAL_POS(b->cursor_row, b->cursor_col); +} + +static void +cursor_move(i16 move_row, i16 move_col, struct kprintfbuf *b) +{ + b->cursor_row += move_row; + if (b->cursor_row < 1) + b->cursor_row = 1; + if (b->cursor_row > TERMINAL_ROW) + b->cursor_row = TERMINAL_ROW; + + b->cursor_col += move_col; + if (b->cursor_col < 1) + b->cursor_col = 1; + if (b->cursor_col > TERMINAL_COLUMN) + b->cursor_col = TERMINAL_COLUMN; +} + +static void +cursor_move_pre(struct kprintfbuf *b) +{ + if (b->cursor_col == 1) { + if (b->cursor_row > 1) { + b->cursor_row--; + b->cursor_col = TERMINAL_COLUMN; + } + } else { + b->cursor_col--; + } +} + +static void +cursor_move_nxt(struct kprintfbuf *b) +{ + if (b->cursor_col == TERMINAL_COLUMN) { + if (b->cursor_row < TERMINAL_ROW) { + b->cursor_row++; + b->cursor_col = 1; + } + } else { + b->cursor_col++; + } +} + +#define CLEAR_CURSOR2END 0 +#define CLEAR_CURSOR2BEGIN 1 +#define CLEAR_ENTIRE 2 + +static void +clear_screen(struct kprintfbuf *b) { u16 content = DEFAULT_COLOR | ' '; - for (int i = 0; i < TERMINAL_ROW; i++) - for (int j = 0; j < TERMINAL_COLUMN; j++) - write_to_terminal(TERMINAL_POS(i, j), content); + if (b->param1 == CLEAR_CURSOR2END) { + u16 disp_pos = cursor_pos(b); + while (disp_pos < TERMINAL_SIZE) + write_to_terminal(disp_pos++, content); + } else if (b->param1 == CLEAR_CURSOR2BEGIN) { + u16 disp_pos = cursor_pos(b); + while (disp_pos > 0) + write_to_terminal(disp_pos--, content); + write_to_terminal(disp_pos, content); + } else if (b->param1 == CLEAR_ENTIRE) { + u16 disp_pos = 0; + while (disp_pos < TERMINAL_SIZE) + write_to_terminal(disp_pos++, content); + } +} + +static void +clear_line(struct kprintfbuf *b) +{ + u16 content = DEFAULT_COLOR | ' '; + if (b->param1 == CLEAR_CURSOR2END) { + u16 disp_pos = cursor_pos(b); + while (disp_pos % TERMINAL_COLUMN != 0) + write_to_terminal(disp_pos++, content); + } else if (b->param1 == CLEAR_CURSOR2BEGIN) { + u16 disp_pos = cursor_pos(b); + while (disp_pos % TERMINAL_COLUMN != 0) + write_to_terminal(disp_pos--, content); + write_to_terminal(disp_pos, content); + } else if (b->param1 == CLEAR_ENTIRE) { + u16 disp_pos = TERMINAL_POS(b->cursor_row, 1); + do { + write_to_terminal(disp_pos++, content); + } while (disp_pos % TERMINAL_COLUMN != 0); + } +} + +static void +scroll(i16 scroll_up_num) +{ + scroll_up_num = MIN(scroll_up_num, +TERMINAL_ROW); + scroll_up_num = MAX(scroll_up_num, -TERMINAL_ROW); + + if (scroll_up_num > 0) { + void *dst = TERMINAL_POS(1, 1) * 2; + dst = dst + TERMINAL_ADDR; + void *src = (void*)(TERMINAL_POS(1 + scroll_up_num, 1) * 2); + src = src + TERMINAL_ADDR; + size_t clear_size = scroll_up_num * TERMINAL_COLUMN * 2; + size_t copy_size = TERMINAL_SIZE * 2 - clear_size; + + memcpy(dst, src, copy_size); + + void *v = dst + copy_size; + memset(v, 0, clear_size); + } else if (scroll_up_num < 0) { + i16 scroll_down_num = -scroll_up_num; + void *dst = (void*)(TERMINAL_POS(1 + scroll_down_num, 1) * 2); + dst = dst + TERMINAL_ADDR; + void *src = TERMINAL_POS(1, 1) * 2; + src = src + TERMINAL_ADDR; + size_t clear_size = scroll_down_num * TERMINAL_COLUMN * 2; + size_t copy_size = TERMINAL_SIZE * 2 - clear_size; + + memcpy(dst, src, copy_size); + + void *v = src; + memset(v, 0, clear_size); + } +} + +inline static void +param12vga_color(struct kprintfbuf *b) +{ + u8 tmp = b->param1 & 1; + b->param1 &= 0b0110; + b->param1 |= b->param1 >> 2; + b->param1 &= 0b0011; + b->param1 |= tmp << 2; +} + +static void +set_color(struct kprintfbuf *b) +{ + if (b->param1 == 0) { + b->color = DEFAULT_COLOR; + } else if (b->param1 == 1) { + b->color |= 0x8800; + } else if (b->param1 == 2) { + b->color &= 0x7700; + } else if (30 <= b->param1 && b->param1 <= 37) { + b->param1 -= 30; + param12vga_color(b); + b->color = (b->color & 0xf8ff) | FOREGROUND(b->param1); + } else if (40 <= b->param1 && b->param1 <= 47) { + b->param1 -= 40; + param12vga_color(b); + b->color = (b->color & 0x8fff) | BACKGROUND(b->param1); + } else if (90 <= b->param1 && b->param1 <= 97) { + b->param1 -= 90; + param12vga_color(b); + b->param1 |= 0x8; + b->color = (b->color & 0xf0ff) | FOREGROUND(b->param1); + } else if (100 <= b->param1 && b->param1 <= 107) { + b->param1 -= 100; + param12vga_color(b); + b->param1 |= 0x8; + b->color = (b->color & 0x0fff) | BACKGROUND(b->param1); + } else { + warn("unsupport CSI: %dm", b->param1); + } +} + +static void +CSI_handler(u8 terminator, struct kprintfbuf *b) +{ + b->CSI = CSI_ESC; + + switch (terminator) { + case 'A': + if (b->param1 == 0) + b->param1 = 1; + cursor_move(-b->param1, 0, b); + break; + case 'B': + if (b->param1 == 0) + b->param1 = 1; + cursor_move(+b->param1, 0, b); + break; + case 'C': + if (b->param1 == 0) + b->param1 = 1; + cursor_move(0, +b->param1, b); + break; + case 'D': + if (b->param1 == 0) + b->param1 = 1; + cursor_move(0, -b->param1, b); + break; + case 'E': + if (b->param1 == 0) + b->param1 = 1; + cursor_move(+b->param1, 1 - b->cursor_col, b); + break; + case 'F': + if (b->param1 == 0) + b->param1 = 1; + cursor_move(-b->param1, 1 - b->cursor_col, b); + break; + case 'H': + if (b->param1 == 0) + b->param1 = 1; + if (b->param2 == 0) + b->param2 = 1; + cursor_move(b->param1 - b->cursor_row, + b->param2 - b->cursor_col, b); + break; + case 'J': + clear_screen(b); + break; + case 'K': + clear_line(b); + break; + case 'S': + if (b->param1 == 0) + b->param1 = 1; + scroll(+b->param1); + break; + case 'T': + if (b->param1 == 0) + b->param1 = 1; + scroll(-b->param1); + break; + case 'm': + set_color(b); + break; + default: + warn("unsupport CSI: %c", terminator); + break; + } +} + +static void +kprintfputch(int ch, struct kprintfbuf *b) +{ + ch = ch & 0xff; + b->cnt++; + + reswitch: + if (b->CSI == CSI_ESC) { + switch (ch) { + case '\b': + cursor_move_pre(b); + break; + case '\t': + if (cursor_pos(b) == TERMINAL_SIZE - 1) + break; + while (b->cursor_col % 4 != 1) + cursor_move_nxt(b); + break; + case '\n': + cursor_move(+1, 1 - b->cursor_col, b); + break; + case '\x1b': + b->CSI++; + break; + default: + { + u16 disp_pos = TERMINAL_POS(b->cursor_row, b->cursor_col); + u16 content = b->color | ch; + write_to_terminal(disp_pos, content); + cursor_move_nxt(b); + break; + } + } + } else if (b->CSI == CSI_BRACKET) { + switch (ch) { + case '[': + b->CSI++; + b->param1 = b->param2 = 0; + break; + default: + b->CSI = CSI_ESC; + break; + } + } else { + switch (ch) { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + if (b->CSI == CSI_PARAM1) + b->param1 = b->param1 * 10 + ch - '0'; + else if (b->CSI == CSI_PARAM2) + b->param2 = b->param2 * 10 + ch - '0'; + else + ;//do nothing + break; + case ';': + b->CSI++; + break; + default: + if (!(0x20 <= ch && ch <= 0x7e)) + b->CSI = CSI_ESC; + if (0x40 <= ch && ch <= 0x7e) + CSI_handler(ch, b); + break; + } + } +} + +int +vkprintf(const char *fmt, va_list ap) +{ + TTY.cnt = 0; + + vprintfmt((void*)kprintfputch, &TTY, fmt, ap); + + int rc = TTY.cnt; + + return rc; +} + +/* + * 内核格式化输出函数,除了支持正常的%d,%s等格式化输出外 + * 还支持ANSI转义序列中的CSI sequence(以"ESC(十六进制字符为\x1b)["开头的序列,用于控制光标和终端) + * 依据维基百科上的手册实现(en.wikipedia.org/wiki/ANSI_escape_code) + * 目前支持的功能有: + * \x1b[nA 将光标向上移动n(\x1b[A等价于\x1b[1A)行,如果移动中接触到边界则不继续移动 + * \x1b[nB 将光标向下移动n(\x1b[B等价于\x1b[1B)行,如果移动中接触到边界则不继续移动 + * \x1b[nC 将光标向右移动n(\x1b[C等价于\x1b[1C)列,如果移动中接触到边界则不继续移动 + * \x1b[nD 将光标向左移动n(\x1b[D等价于\x1b[1D)列,如果移动中接触到边界则不继续移动 + * \x1b[nE 将光标向下移动n(\x1b[E等价于\x1b[1E)行后移动到行首,如果移动中接触到边界则不继续移动 + * \x1b[nF 将光标向上移动n(\x1b[F等价于\x1b[1F)行后移动到行首,如果移动中接触到边界则不继续移动 + * \x1b[n;mH 将光标移动到n行m列(1行1列为终端左上角),n、m为空时默认为1,如果移动中接触到边界则不继续移动 + * 可以不输出";"特殊情况举例: + * \x1b[;5H等价于\x1b[1;5H + * \x1b[5H等价于\x1b[5;H等价于\x1b[5;1H + * \x1b[H等价于\x1b[1;1H + * \x1b[nJ 终端清屏操作(\x1b[J等价于\x1b[0J) + * 当n为0时,清除当前光标位置到终端末尾的所有字符输出 + * 当n为1时,清除当前光标位置到终端开头的所有字符输出 + * 当n为2时,清除当前终端的所有字符输出 + * \x1b[nK 当前行清除字符操作(\x1b[J等价于\x1b[0J) + * 当n为0时,清除当前光标位置到行末的所有字符输出 + * 当n为1时,清除当前光标位置到行首的所有字符输出 + * 当n为2时,清除光标位置所在行的所有字符输出 + * \x1b[nS 将整个终端显示向上移动(\x1b[S等价于\x1b[1S)行,底部字符用空白填充 + * \x1b[nT 将整个终端显示向下移动(\x1b[T等价于\x1b[1T)行,顶部字符用空白填充 + * \x1b[nm 控制终端之后要输出的颜色(\x1b[m等价于\x1b[0m): + * 当n为0时,将终端之后要输出的字符颜色置为默认色(白底黑字) + * 当n为1时,将终端之后要输出的字符颜色亮度提高(如果之前输出的是暗色) + * 当n为2时,将终端之后要输出的字符颜色亮度降低(如果之前输出的是亮色) + * 当n为30/40时,将终端之后要输出的字符的前景色/背景色置为黑色 + * 当n为31/41时,将终端之后要输出的字符的前景色/背景色置为红色 + * 当n为32/42时,将终端之后要输出的字符的前景色/背景色置为绿色 + * 当n为33/43时,将终端之后要输出的字符的前景色/背景色置为棕色 + * 当n为34/44时,将终端之后要输出的字符的前景色/背景色置为蓝色 + * 当n为35/45时,将终端之后要输出的字符的前景色/背景色置为洋红色 + * 当n为36/46时,将终端之后要输出的字符的前景色/背景色置为青色 + * 当n为37/47时,将终端之后要输出的字符的前景色/背景色置为银色 + * 当n为90/100时,将终端之后要输出的字符的前景色/背景色置为灰色 + * 当n为91/101时,将终端之后要输出的字符的前景色/背景色置为亮红色 + * 当n为92/102时,将终端之后要输出的字符的前景色/背景色置为亮绿色 + * 当n为93/103时,将终端之后要输出的字符的前景色/背景色置为黄色 + * 当n为94/104时,将终端之后要输出的字符的前景色/背景色置为亮蓝色 + * 当n为95/105时,将终端之后要输出的字符的前景色/背景色置为亮洋红色 + * 当n为96/106时,将终端之后要输出的字符的前景色/背景色置为亮青色 + * 当n为97/107时,将终端之后要输出的字符的前景色/背景色置为白色色 + */ +int +kprintf(const char *fmt, ...) +{ + va_list ap; + int rc; + + va_start(ap, fmt); + rc = vkprintf(fmt, ap); + va_end(ap); + + return rc; } \ No newline at end of file diff --git a/实验3-v0.2.md b/实验3-v0.2.md deleted file mode 100644 index 24dc490..0000000 --- a/实验3-v0.2.md +++ /dev/null @@ -1,293 +0,0 @@ -
- 实验三 进入保护模式 -
- -
- 谷建华 -
- -
- 2022-09-29 v0.2 -
- -#### 实验目的 - -1. 学习x86保护模式编程,以及如何从实模式进入到保护模式. -2. 学习elf文件的组成和格式、加载elf文件格式的kernel. -3. 学习汇编与C之间的相互调用. -4. 学习二进制文件与elf文件之间的区别. - -#### 实验预习内容 - -1. 保护模式之前需要做的准备工作. -2. ELF文件格式. -3. 应用程序二进制接口. - -#### 实验内容以及步骤 - -**2022年试点班需要完成1、2、3三个实验,提高实验想做就做不强求** - -1. 编译构建硬盘启动盘,并挂载到虚拟机上,证实执行流交给了kernel - (1) 修改`kernel.asm`,使之能够在虚拟机终端中显示`kernel`字符串(注意,这个时候bios中断**不能**使用了). - (2) 通过`make`编译,生成`a.img`硬盘镜像. - (3) 通过`make run`运行虚拟机并挂载`a.img`作为硬盘启动盘,观察并记录现象. - -2. 通过修改`initkernel.c`的代码,解析`kernel.bin`elf文件并结合elf文档阅读,回答下面问题: - (1) 凭什么知道`kernel.bin`是一个elf文件? - (2) 凭什么知道`kernel.bin`运行的环境的指令集为i386? - (3) `kernel.bin`一共被加载了几个程序段?加载的段在内存中是从什么地址开始?加载的长度有多长?每个段的读/写/可执行标志是怎么设置的? - -3. 通过修改`kprintf.asm`,完成kprintf的函数的汇编实现,函数调用的参数如下: - (1) 第0个参数为从终端中的第几个字符开始输出,第0个字符是终端0行0列,第1个字符是0行1列,第80个字符是1行0列,以此类推. - (2) 第1个参数为需要格式化输出的字符串,传进去的参数实际是这个字符串的首地址. - (3) 再之后的参数根据第1个参数的字符串决定,每次调用kprintf函数后首先默认输出黑底白字的字符,接下来几条是需要实现的. - - + `%c`在终端输出参数的对应的ASCII字符值(参数保证范围在0~127之间),在输出字符后输出的位置往下移动一位. - + `%f`更改之后输出的字符的前景色为参数的对应值(参数保证范围在0~15之间,颜色码对应值) - + `%b`更改之后输出的字符的背景色为参数的对应值(参数保证范围在0~15之间,颜色码对应值) - + 其余字符则按照其ASCII码输出(保证字符串不会出现%、换行符、退格符等乱七八糟的字符),在输出字符后输出的位置往下移动一位. - + 在`start.c`中存放一组测试样例,效果已经写明可以大家可以用于测试. - - (4) 在`start.c`中接入`cmatrix.c`中的启动函数`cmatrix_start`,如果编写正确,会在终端中看到以下效果 - - ![](./lab3-finish.png) - - (5) (自我提高内容,自己想做就做,不用写进报告,**禁止内卷**)增添一个新参数`%s`,对应的参数是一个结构体,结构体的格式如下: - - ```C - struct color_char{ - u8 foreground;//输出字符的前景色(0~15) - u8 background;//输出字符的背景色(0~15) - u8 print_char;//输出字符的ASCII码(0~127) - u16 print_pos;//输出字符在终端中的位置 - }; - ``` - 这种输出方式不影响接下来`%c`输出的前景色、背景色和输出在终端中的位置,相当于是一个独立的输出方式.检验你程序的正确性可以调用`make tests`命令生成专属的测试kernel. - -4. 简单的`nm`工具制作(自我提高内容,自己想做就做,不用写进报告,**禁止内卷**) - (1) 通过分析elf文件的文件头,找到elf文件中的符号表,输出每个符号对应的符号名、符号地址、大小和类型. - (2) 这个操作可以在我们的内核中写,也可以自己独立写一个C/Cpp程序,如何检验自己的程序是否正确,可以通过`nm`和`readelf -s`检查. - -5. 创建FAT32格式的硬盘启动盘(自我提高内容,自己想做就做,不用写进报告,**禁止内卷**) - (1) 之前硬盘启动盘的文件系统格式为FAT12,这次需要将FAT12格式变成FAT32格式,这需要熟读官方文档,了解FAT32的文件格式以及处理方式. - (2) 修改boot/loader代码实现正确加载. - (3) 编译制作硬盘启动盘,启动虚拟机并挂载硬盘启动盘,观察并记录现象. -#### 实验总结 - -1. 在loader阶段都完成了哪些主要功能?X86系统是如何进入保护模式的?在进入保护模式之前需要完成哪些准备工作? -2. C语言和汇编语言是如何互相调用的?参数如何传递? - -#### 实验参考 - -##### 进入保护模式 - -在进入了loader后,终于可以不受512字节的代码限制.进入loader之后我们需要干的一件事是:**进入保护模式**. - -为什么要进入保护模式?这个时候这个问题就自然而然问出来了,这不是实模式待着好好地嘛,去啥保护模式.正常情况是这样的,对于我们写的程序来说实模式貌似挺够我们用的,但是如果稍微写个大一点的程序呢?这个时候一个噩梦般的事情发生了:段偏移的寻址方式太弱了,虽说1MB的寻址能力挺强的,但是对于一个固定段,它的偏移寻址能力只有64KB大,只需要128个boot程序般大的程序文件会超过偏移寻址能力,这对程序员来说是极为头疼的,写程序逻辑本身就很麻烦了,还要关心寻址,这程序谁爱写谁写. - -而保护模式解决了这个寻址问题,它扩展了段偏移寻址模式的定义,将偏移范围支持到4GB大,刚好是一个int能够表达的范围,这不爽死XD,所以进入保护模式还是很有必要的. - -保护模式是怎么扩展段偏移寻址模式的定义?这帮硬件工程师商量来商量去最后给出了一种方式`gdt(global descriptor table)`全局描述符.全局描述符抽象来看是一个数据结构,这个数据结构不能说是整齐规整,也可以说是东拼西凑,具体可以到Orange教科书中查询相关的数据结构细节.与实模式的段地址不同,保护模式的“段地址”就是gdt的基地址,而gdt能够表示偏移寻址的范围最多能到4GB,这样就能够支持4GB的寻址. - -回忆实模式,它的基址是一个地址值,很容易用一个寄存器进行存储,但是保护模式的段地址是一个数据结构的基地址,而且由于段寄存器的历史遗留问题仍然只有16位,不能填入32位数据,所以硬件工程师就搞出一条命令`lgdt`,由于我们在一个固定缓冲区按顺序存储一些gdt,亦叫gdt表(数组),只需要通过`lgdt`指令告诉硬件gdt表的起始地址和长度就能让硬件提前分析gdt表,这样我们只需要将段寄存器改成gdt相对于gdt表的偏移量(亦称选择子)就可以轻松表示.(改成这种方式还能够控制权限,不过本篇不谈) - -在进入保护模式之前,根据硬件厂商的手册,我们需要做三件事 - -1. 加载gdt表 -2. 打开地址线A20 -3. cr0第0位置位 - -这三件事做完之后,通过一个32位长指令的长跳转跳转到`SelectorFlatC:LABEL_PM_START`后,我们执行环境从16位变成了32位,从实模式跳到了保护模式.其中`SelectorFlatC`是希望cs变成的gdt选择子,`LABEL_PM_START`是指希望跳到的地址(不受段偏移限制了). - -在进入了保护模式后,就可以忘掉落后的段偏移访存方式了,能够更加舒服地写汇编程序了.但惊喜不止于此,我们还能够通过C语言实现我们想要的功能,想必看完那么多汇编大家肯定不想再读汇编了,苦日子终于熬到头了,可以用C了,C比汇编可好读太多了. - -##### 汇编与C之间的交互 - -在进入了C语言的部分之后就需要面临一个问题,如何进行汇编与C之间的交互?这个问题放平时可能想都不会想,大一开始的C语言课程可能根本不会让你意识到程序背后本质可以抽象成一条条汇编指令的执行,就如大家写的汇编程序一样,所以C语言代码本身可以具象为汇编代码,C语言中的函数本身就是一条label标签,这样就可以通过jmp和call进行函数跳转/调用将执行流交给C语言程序. - -但是这么说依然很抽象,你依然会很难理解一个在C语言中的函数调用具体发生了什么,一个带参数的函数跳转是怎么执行的,中间经历了什么? - -1. 导入/导出符号 - -如何让汇编代码与C代码进行相互调用,首先需要了解什么是符号,符号说人话就是函数名,全局变量名等.我们在编写汇编代码的时候会写一些标签,这些标签成了我们跳转的目标,也可以成为我们访存的目标,从某种意义上,符号就是这些标签. - -先解决第一个问题,如何在汇编中调用C函数/全局变量,对于汇编来说,这个符号是外部的,所以需要引入这个符号,引入到代码中,这样编译器就知道这个就是外部的符号,在跳转/访存的时候就知道这个符号是从哪里来的了.虽然在汇编文件编译的时候不知道确切地址,在链接的时候就能够知道了. - -```c -;nasm汇编程序 -extern foo ; 这个时候通过引入foo符号实现函数的跳转 -call foo ; 通过调用foo函数实现打印 - -//C程序 -#include -void foo() -{ - printf("114514 1919810"); -} -``` - -然后解决第二个问题,如何在C中调用汇编中的函数/全局变量,对于汇编来说,这个符号不仅是汇编文件用,外部也要用,所以汇编文件就需要将符号导出,告诉外界,它们想要的符号在汇编文件里,这样在链接的时候其他文件也知道这个符号在哪. - -```c -;nasm汇编程序 -global foo ; 导出foo符号 -foo: - ... ; 这里是foo函数的实现 - -global foofoo ; 导出foofoo符号 -foofoo dd 0 ; 这个是个全局变量 - -//C程序 -void foo(); //这里类似汇编里面的extern,如果下面没有foo函数的实现就将foo这个函数的符号导入进来 -extern int foofoo; //将foofoo这个全局符号导入进来 - -void bar() -{ - foo(); //这样就能够调用foo函数 - foofoo=1;//这样就能够使用foofoo变量 -} -``` - -这样汇编与C语言代码之间的相互调用就完成了.如果你好奇原理是什么,可以看第3节符号表. - -2. 参数传递 - -刚才解决的是C语言函数与汇编函数相互调用的问题,但是假设有这么个情况,这个C语言函数是一个带输入参数的函数,比如`int foo(int a, int b)`,这个时候如果光调用`call`指令可不好使,`a,b`参数就被吞了,下面的`foo`函数拿着混乱的参数大搞破坏,这肯定是我们所不希望的. - -那么对于`foo`函数,它还有两个参数`a,b`,那么如何将`a,b`“告诉”子函数,对于i386指令集规定了参数传递方式:对于所有参数,按照从右往左的顺序压栈,通过栈传递中间参数. - -对于上面这个例子来说,nasm汇编在调用C函数之前需要先将`b`压栈,然后将`a`压栈,最后才是调用`call`指令跳转到`foo`函数的标签处,C函数同样遵循这个规范,所以会去栈里寻找`a,b`两个变量的值,离栈顶越近的元素在函数定义的时候参数越靠左. - -这种从右往左的压栈方式其实很好,比如`printf`,我们不知道要输入多少个参数,但是会根据其中的字符串中的信息`%d,%s`判断需要多少个参数,然后在栈中找就可以将传入的参数拿出.在这次的实验中大家需要实现一个类似`printf`的函数,来深刻理解这种传参方式. - -通过压栈就能解决参数传递的问题,这种传递方式本质叫**应用程序二进制接口**,在第4节中有介绍. - -3. 符号表 - -可能大家都忘完了大一学的大计基和C程序,这个时候就要需要回忆一下一个C语言程序的编译过程`预处理、编译、汇编、链接`.相信大家在大一的时候把这一过程当文科一样背,现在可能基本记不清这几步具体发生了什么.前面三个步骤还能勉强理解理解,这个链接可能是真的难以理解. - -假设有若干个可重定位文件(.o文件),那么可以使用GNU的工具`ld`对这些可重定位文件进行链接成一个可执行文件. - -```c -//1.c程序内容 -void foo() -{ - int bar=114514; -} -int main() -{ - foo(); -} -//2.c程序内容 -void foo() -{ - int bar=1919810; -} -``` - -将两个程序的内容用gcc(附上-c参数)命令编译成两个可重定位文件,然后尝试将两个可重定位文件用ld命令链接,然后你就会发现一个神奇的现象. - -```shell -$ gcc -c 1.c && gcc -c 2.c -$ ld 1.o 2.o -o test -ld: 2.o: in function `foo': -2.c:(.text+0x0): multiple definition of `foo'; 1.o:1.c:(.text+0x0): first defined here -ld: warning: cannot find entry symbol _start; defaulting to 0000000000401000 -``` - -你会发现不让你链接,原因是foo被两次定义了.那么原因是什么?它凭什么知道foo被两次定义了?我们作为写程序的人肯定知道被两次定义了,但是机器怎么知道?这就涉及到**符号表**了. - -每一个可重定位文件里面存放着一张符号表,它存放所有符号信息.那么如何获取符号信息?GNU里面有一个工具`nm`能够获取对应的符号信息. - -```shell -$ nm 1.o -0000000000000000 T foo -0000000000000012 T main -$ nm 2.o -0000000000000000 T foo -``` - -看到这个的时候,哦,确实有两个foo的符号,所以在链接的时候会发生符号冲突.符号表是用于链接时定位的,在可重定位文件里,可能某个跳转语句的目的地址还是未知的,在链接成可执行文件时才能具体填入地址. - -在大一上C程序的时候大家可能不知道有`nm`命令,所以对链接的学习是迷迷糊糊的(再加上考试是不会考这个的,这个考了不挂一片人),所以可能仍不会`extern static`等相关关键词的语义.在做这节实验的时候可以尝试结合`nm`复习这些关键词的语义,这会对你对链接的理解有很大帮助. - -4. 应用程序二进制接口(Application Binary Interface) - -ABI本质上是一种约定,它约束外部函数调用的参数规范,每种指令集都有它自己的一套调用规范,类似64-x86,risc-V指令集首先用的是寄存器传参再是栈传参,而32位的i386指令集用的是栈传参.所以只要按照ABI规范写就一定能够正确调用外部函数. - -目前我们的OS用的是32位i386指令集,采取的传参方式是栈传参,就是在调用函数之前将所有参数从右往左依次压入栈中,最后才是函数跳转,不管是调用者还是被调用者都遵循ABI,这样就可以实现正确的函数调用. - -但是实际上有那么多种ABI,我们肯定背不下那么多ABI规范,但是有一种最笨也是最有效的方式就是:读汇编,读汇编,读汇编.因为C语言程序本质上也可以抽象成一个汇编程序,而汇编程序是最基础的,它每条指令的执行会按部就班地执行,而且每句话都是实打实地,不会骗你半点,当你觉得你实现的汇编很对但是链接进去执行错误的时候最好的方式就是反汇编/gdb. - -那么如何反汇编呢?GNU也给了我们一个工具`objdump`,通过调用`objdump -d kernel.bin | less`命令就可以将kernel.bin这个可执行elf文件(也可以是可重定位文件)反汇编,然后就可以愉悦地查看汇编代码了,通过查看实际的汇编代码是很好的,它完全反应了程序指令运行行为,对着它研究ABI准没错. - -当学会了ABI,大家可能就觉得C语言可能也那么神秘,你也可以独立从一个C语言翻译成一段汇编代码,只是需要读文档+花费逝量时间即可. - -##### 二进制文件和ELF文件 - -在进入了保护模式之后剩下loader要做的事就是加载kernel,毕竟loader是一个32位与16位代码混合的二进制文件,有实模式寻址的约束,不太适合扩展32位代码当内核,不如新启一个程序文件作为kernel. - -而这次加载kernel文件并不能像加载loader文件一样直接加载到指定位置然后跳过去,可以通过`file`命令查看两个文件类型: - -```shell -$ file loader.bin | tr ',' '\n' -loader.bin: - code offset 0x1c7+3 - OEM-ID "ForrestY" - root entries 224 - sectors 2880 (volumes <=32 MB) - sectors/FAT 9 - sectors/track 18 - serial number 0x0 - label: "OrangeS0.02" - FAT (12 bit) -$ file kernel.bin | tr ',' '\n' -kernel.bin: ELF 32-bit LSB executable - Intel 80386 - version 1 (SYSV) - statically linked - with debug_info - not stripped -``` - -loader.bin本质上是一个二进制文件,不过带有引导信息(沿用了boot的引导信息所以会被识别成一个文件系统),所以可以通过跳转跳到文件开头就可以进行loader的执行. - -而kernel.bin本质上是一个elf可执行文件,什么是elf可执行文件? - -##### ELF文件格式简介 - -试想一个简单的场景,你写的代码有代码数据和普通数据,这两部分数据是不对等的,对于代码数据你希望它不会被改动,所以对这部分数据不应该附上写权限,普通数据应该附上写权限.很明显这两部分数据不应该混在一块,而是应该分成两段存放.而且每一段都有它希望被加载到的地址,整个一个elf文件的入口地址也要指定……这些东西是需要一个数据结构进行管理,维护每一个段所需求的信息,然后在文件开头维护每个段的信息以及整个程序的信息等,这个数据结构就叫ELF文件格式.如果你比较熟悉内核,熟悉execve系统调用,那么[这里](https://www.bilibili.com/video/BV1wL4y1L72C/)会让你对ELF文件格式有更深刻的理解(如果不熟悉别听,绝对听晕). - -ELF维护了若干个程序段,每个程序段存放了相对应的数据,通过对数据的迁移到指定地址,再跳到入口地址就能实现elf文件的加载执行. - -在知道ELF是啥之后,怎么解析ELF文件,将ELF文件的程序数据迁移就是个问题,讲理论概念谁不会讲,但是在具体做的时候经常会怀疑人生,理论突然就不起用了,那么这个时候该怎么办?**读文档**,对于ELF文件格式,linux里面有写一个专门关于它的文档,可以通过调用`man elf`指令进行查看ELF里面的细节,可以结合`initkernel.c`代码与文档一起阅读,分析其中的具体含义(不要惧怕阅读英语,你们从小学开始学的英语不是仅用来考试的).需要注意的是`man elf`中是不会给出参数的具体值的,需要阅读`/usr/include/elf.h`头文件获取具体的值. - -当然解析这些玩意肯定折磨人,不过我们万能的GNU又给我们提供了一个工具`readelf`,当你想分析一个elf文件格式的时候,通过调用`readelf -a`获取所有信息,`readelf -l`获取程序头信息,当你觉得你的C程序分析错的时候,用`readelf`能够及时比对. - -##### 加载kernel - -虽然说起来ELF格式很复杂,但是由于kernel没啥权限需求,实际上加载的时候的代码没几行,实际上就遍历所有程序头,然后根据每个程序头的要求将文件数据加载到指定的位置,最后用一个跳转跳入kernel的入口地址. - -在进入了kernel了之后,我们真正有意思的内核就来了,大家会逐渐实现一些有趣的机制,并用这些有趣的机制实现很多有趣的玩意. - -##### 显存 - -进入保护模式后,实模式下能够使用的BIOS中断就不能用了,那么如何往终端打印字符就成了一个难事,不过好在天无绝人之路,在内存的[0xB8000, 0xC0000)处给了我们一个能够往那黑漆漆的终端写字符的途径,我们的终端只有25行*80列,一共2000个字符,每个字符由两字节表示,相当于终端显示的是显存[0xB8000, 0xB8000 + 4000)这部分的内容,这两字节低字节表示ASCII字符内容,高字节字符分成两部分,低4位为前景色,高四位为背景色,颜色码与实验一中的颜色码一致(具体可以到Orange书中7.2.1节中查看). - -在保护模式中,显存选择子对应的段就是显存的内存.所以在loader中可以通过gs段寄存器对显存进行存取字符,这个可比中断高效多了. - -```nasm -; 段描述符 -LABEL_DESC_VIDEO: Descriptor 0B8000h, 0ffffh, DA_DRW | DA_DPL3 ; 显存首地址 - -; 段选择子 -SelectorVideo equ LABEL_DESC_VIDEO - LABEL_GDT + SA_RPL3 - -; 将选择子加载到gs段寄存器 -mov gs, SelectorVideo -mov ah, 00fh; 黑底白字 -mov al, 041h; 'A' -mov word [gs:0 * 2] ax; 往0行0列写入一个黑底白字的字符A -mov word [gs:1 * 2] ax; 往0行1列写入一个黑底白字的字符A -mov word [gs:80 * 2] ax; 往1行0列写入一个黑底白字的字符A -``` \ No newline at end of file diff --git a/实验四-v0.3.md b/实验四-v0.3.md new file mode 100644 index 0000000..6c954d5 --- /dev/null +++ b/实验四-v0.3.md @@ -0,0 +1,305 @@ +
+ 实验四 中断 +
+ +
+ 谷建华 +
+ +
+ 2022-10-14 v0.3 +
+ +#### 实验目的 + +1. 学习中断描述符,中断处理全流程(特别是执行流、堆栈、权限的切换),包括中断描述符表IDT的功能和设置 +2. 学习时钟中断和键盘中断的处理过程 +3. 学习分时任务调度 + +#### 实验预习内容 + +1. 中断描述表IDT +2. 8259A设置外设中断 +3. 分时任务调度原理 +4. 键盘中断的字模的获取和处理 + +#### 实验内容 + +1. 验证时钟中断的发生 + (1) 编译运行实验默认给的源码,观察并分析现象 + (2) 修改时钟中断处理程序,使之从输出`#`变成输出`i*(*为第几次中断)`,编译运行后观察并分析现象 +2. 修改时钟中断触发时的调度机制,让某个进程被分配到更少的时间片达成进程饥饿的目的(进程必须得活着的能够输出,饥饿不等于删掉进程),编译运行后观察并分析现象. +3. 阅读Orange第6章内容,修改硬件时钟频率,使之大约以1000Hz的频率触发时钟中断,编译运行后观察证实你新修改的频率要比原来的快(这个频率很难测量,只能通过对比的方式估计) +4. 阅读Orange第7章内容并结合实验参考,添加键盘中断,要求如下: + (1) 使之能够正确识别`a`\~`z`(不需要结合shift键区分大小写,就算是shift+`a`也识别为`a`而不是`A`),其余字符一概丢掉.将识别出来的字符通过调用`keyboard.c`中的`add_keyboard_buf`函数将字符添加到字符缓冲区中(需学生自行实现该函数). + (2) 修改`keyboard.c`中的`getch`函数,这是一个非阻塞的函数,即当缓冲区中有字符未输出时,输出缓冲区队头该字符,并将缓冲区队头字符弹出,当缓冲区中没有字符时输出255(u8意义下的-1). + (3) 我们准备了一个贪吃蛇(没错,有了时钟中断和键盘中断就能做一个小游戏了!),修改时钟频率至1000Hz,删除时钟中断处理函数中的所有`kprintf`,在`kernel_main`中仅创建一个单进程,进程入口函数会在`game.h`中给出.最后重新编译kernel就可以游玩了(不用将这一步写进报告). + +#### 实验总结 + +1. 操作系统内核的初始化阶段都完成哪些主要功能? +2. 刚刚进入时钟中断处理程序时系统用的是哪个堆栈,中断号是多少?执行流在哪个位置?在执行中断服务程序的过程中堆栈发生哪些变化?`call [irq_tabel + 4 * %1]`的功能是什么? +3. 外设(时钟、键盘等)中断的中断号是由谁决定的?在哪里决定的? + +#### 实验参考 + +在前几个实验,我们一直是在内核态,而我们的进程一般都是在用户态下执行的,这样进程做出出格的事情也不会伤到内核.那么接下来需要研究`kernel_main`函数是怎么进入用户态.这次实验的重点是从`restart`函数出发进入到用户态,然后又因为中断回到内核态这一整个过程. + +##### 1. 中断初始化 + +###### 如何区分当前执行流为用户态和内核态 + +平时都说用户态,内核态,但是怎么区分执行流目前的状态靠的就是段寄存器,可以发现段描述符的大小刚好是8字节,所以存储在段寄存器中的段选择子值假设是$ x $,那么$ \lfloor\frac{x}{8}\rfloor $就能够描述选择的是第几个段,即在二进制角度看段选择子的低三位就没有被用上,所以硬件工程师就考虑把这些位利用上,第0~1位用于权限,第2位用于标识段是全局段还是局部段. + +对于权限的划分各位需要阅读Orange教材,这里不细展开,总之靠着段选择子的第0~1位可以划分当前段的权限,当权限为用户态时执行流(CS)就是用户态. + +###### LDT初始化 + +LDT(local descriptor table)全称局部描述符表,跟GDT很类似,为什么需要LDT是因为在之前可能不同的任务它们的段寄存器可能会不同,为了区分,每个任务有它自己的独一套LDT,这样切换不同任务时标识更容易些.虽然理论上每个任务都有自己的独一套LDT,但是都是$ 2^{32} $寻址,限制都是靠分页做的(这个我们下个实验再说).所以我们只需要加载一次ldt就能满足所有用户态进程的需求. + +为了方便大家理解段之间的区别,这里我们约定GDT里面全是存储内核态会用到的段描述符(除了显存段),LDT里面存储用户态会用到的段描述符. + +```C +// 这句话初始化了ldt的段描述符 +init_segment(&gdt[5], (u32)ldt, sizeof(ldt) - 1, DA_LDT); +// 在加载了gdt后,ldt就可以通过传入段选择子的方法加载 +lgdt [gdt_ptr] ; 使用新的GDT +lldt [SELECTOR_LDT] ; SELECTOR_LDT = 0x28 = 40 = 5 * 8 +``` + +###### 中断 + +这一次实验我们要开始处理中断了,平时课上也讲过,执行流在用户态的时候肯定不能放心一直将执行流交给用户态(用户不能关中断IF位,要不然就永远无法响应中断了).比如时钟中断,每次执行固定时长后硬件会向内核发送一次中断,在触发中断异常/用户请求系统调用的时候能够回到内核态,不同的触发方式会执行不同的处理函数,那么如何区分这些不同的触发方式就需要IDT了. + +###### IDT初始化 + +IDT(Interrupt Descriptor Table)中断描述符表会根据中断的形式决定进入内核的中断处理函数,而区分形式是靠中断号判断,根据中断号找到对应的门描述符,根据中断描述符找到对应的中断处理函数入口和加载对应的CS段寄存器,将执行流交给内核. + +类似gdt, idt需要通过`lidt`命令将idt表的数据结构载入,该数据结构与上一个实验的`gdt_ptr`一致. + +```nasm + lidt [idt_ptr] +``` + +###### 8259A初始化 + +8259A简单来说是外设中断的实际处理硬件,时钟中断,键盘中断,鼠标中断等都是靠它给内核发送信号触发中断,它也需要初始化与中断描述符之间的联系. + +##### 2. 中断处理过程 + +###### 进入用户态 + +在内核中,每个进程需要维护一个用于存放进程用户态当前状态的寄存器表,当用户态因为某些原因陷入内核态时用户态的当前所有寄存器信息就存放在寄存器表中,当从内核态又回到用户态时就根据寄存器表恢复用户态当前的状态. + +```C +typedef struct s_stackframe { + u32 gs; + u32 fs; + u32 es; + u32 ds; + u32 edi; + u32 esi; + u32 ebp; + u32 kernel_esp; + u32 ebx; + u32 edx; + u32 ecx; + u32 eax; + u32 retaddr; + u32 eip; + u32 cs; + u32 eflags; + u32 esp; + u32 ss; +}STACK_FRAME; +``` + +这个就是我们这个实验会用到寄存器表,`gs`在低地址,`ss`在高地址,接下来结合源码分析进入用户态这个过程中寄存器的变化. + +在`kernel_main`中,我们需要对寄存器做一次初始化: + +```c +p_proc->regs.cs = (SELECTOR_FLAT_C & SA_RPL_MASK & SA_TI_MASK) + | SA_TIL | RPL_USER; +p_proc->regs.ds = (SELECTOR_FLAT_RW & SA_RPL_MASK & SA_TI_MASK) + | SA_TIL | RPL_USER; +p_proc->regs.es = (SELECTOR_FLAT_RW & SA_RPL_MASK & SA_TI_MASK) + | SA_TIL | RPL_USER; +p_proc->regs.fs = (SELECTOR_FLAT_RW & SA_RPL_MASK & SA_TI_MASK) + | SA_TIL | RPL_USER; +p_proc->regs.ss = (SELECTOR_FLAT_RW & SA_RPL_MASK & SA_TI_MASK) + | SA_TIL | RPL_USER; +p_proc->regs.gs = (SELECTOR_VIDEO & SA_RPL_MASK & SA_TI_MASK) + | RPL_USER; + +p_proc->regs.eip = (u32)entry[i]; +p_stack += STACK_PREPROCESS; +p_proc->regs.esp = (u32)p_stack; +p_proc->regs.eflags = 0x1202; /* IF=1, IOPL=1 */ +``` + +这里可以看到初始化的段寄存器中除了`gs`都有`SA_TIL`标志位,它的实际值是4,即二进制意义下的第2位,标志着这个段是选择的是ldt中的段,而ldt中的段都是用户态权限的,所以在进入到用户态时执行流权限就自动切换到用户态. + +再之后就是eip,这是执行流的寄存器,esp用于分配栈,eflags用于初始化flags信息. + +在`kernel_main`初始化完后会调用`restart`函数进入用户态,这是一个汇编接口函数,关键的代码如下: + +```nasm +restart: + mov esp, [p_proc_ready] + lea eax, [esp + P_STACKTOP] + mov dword [tss + TSS3_S_SP0], eax +restart_reenter: ; 我们的代码从这里开始分析,上面的等下会讲 + cli + dec dword [k_reenter] + pop gs + pop fs + pop es + pop ds + popad + add esp, 4 + iretd +``` + +先是关中断,我们肯定不希望在恢复用户态寄存器信息时被意外的中断干扰,再接下来是`k_reenter`减1(这个本参考不讲,自行阅读源码),之后开始恢复寄存器信息.先是恢复`gs`\~`ds`一共四个段寄存器信息.再是恢复`edi`\~`eax`这八个寄存器,需要注意的是`kernel_esp`比较特殊,它实际上不起恢复作用,因为现在的`esp`还不是用户态`esp`,而且`popad`指令会略过`esp`的恢复.再是`esp`加4跳过`retaddr`(这个变量是用于`save`这个函数,它存储的是`call save`时`ret`的地址),最后调用`iret`将`eip`\~`ss`这五个寄存器恢复(为什么让这五个寄存器单独用特殊指令恢复原因是这五个与执行流密切相关),由于eflags中IF位被置1中断被重新打开. + +###### 返回内核态 + +执行流肯定不能一直留在用户态,在接受中断的时候需要再次陷入内核态.再次陷入内核态后,硬件保证了在进入中断时eflags的中断IF位为0,不会受到其余中断的影响,这个时候内核调用了`call`函数保存`eax`\~`gs`寄存器(为什么不保存`ss`\~`eip`这五个寄存器在第3部分会讲到): + +```nasm +save: + pushad ; `. + push ds ; | + push es ; | 保存原寄存器值 + push fs ; | + push gs ; / + mov dx, ss + mov ds, dx + mov es, dx + + mov eax, esp ;eax = 进程表起始地址 + + inc dword [k_reenter] ;k_reenter++; + cmp dword [k_reenter], 0 ;if(k_reenter ==0) + jne .1 ;{ + mov esp, StackTop ; mov esp, StackTop <--切换到内核栈 + push restart ; push restart + jmp [eax + RETADR - P_STACKBASE]; return; +.1: ;} else { 已经在内核栈,不需要再切换 + push restart_reenter ; push restart_reenter + jmp [eax + RETADR - P_STACKBASE]; return; + ;} +``` + +在进入`call`函数中,`ret`的返回地址被存入了寄存器表中`retaddr`的位置,然后调用了`pushad`将`eax`\~`edi`存入寄存器表中,最后将其余段寄存器存入表中,这段代码最后的两个jmp是值得讲的,这个时候别傻乎乎用`ret`指令,返回地址实际上`retaddr`中存着,`ret`的话会把`restart`或`restart_reenter`当返回地址了. + +###### 屏蔽中断和置EOI + +```nasm +in al, INT_M_CTLMASK ; `. +or al, (1 << %1) ; | 屏蔽当前中断 +out INT_M_CTLMASK, al ; / +mov al, EOI ; `. 置EOI位 +out INT_M_CTL, al ; / +sti ; CPU在响应中断的过程中会自动关中断,这句之后就允许响应新的中断 +``` + +在保存完寄存器后,需要修改中断掩码使得不再相应相同类型的中断,保证在内核中不会被同类中断干扰.然后还得向`INT_M_CTL`端口发送EOI信号,告诉硬件已经做好准备了,可以接受下一个中断了(有可能在`sti`之后马上又被下一个中断打扰). + +###### 重新进入用户态 + +再接下来就是中断处理程序的调用了,在处理完中断后就可以准备返回用户态了: + +```nasm +cli +in al, INT_M_CTLMASK ; `. +and al, ~(1 << %1) ; | 恢复接受当前中断 +out INT_M_CTLMASK, al ; / +ret +``` + +跟上节做的事情相反,将目标中断恢复接受,然后使用ret指令,还记得`save`函数里面的`push restart`和`push restart_reenter`两个指令吗?这个push的地址值就是为了`ret`准备的,`ret`过后会重新回到`restart`,然后最终回到用户态. + +##### 3. TSS机制 + +TSS书上写的很玄乎,很难理解,但是实际上TSS没有那么难,举个实例就可以很清晰的知道TSS的作用,假设我们在用户态执行的程序突然受到一个中断要返回内核态,那么这个时候肯定不能就着用户态的esp存储寄存器信息,需要切换到一个特定的栈(内核栈)存储寄存器信息,那么这个内核栈的ss和esp需要预先存储到一个特定地方用于进入内核态时切换(没错,需要段寄存器,因为用户的段寄存器是低权限的,如果访问内核栈会违反保护模式qemu直接重开,你会看到终端不断闪现boot信息),而这个存放的位置就是TSS,TSS里面存放很多数据,看起来很吓人,但是实际上现在我们只会使用其中的`ss0`和`esp0`(0是内核权限级),当从用户态进入到内核态时,ss和esp会切换内核态的对应寄存器,这个时候就能正常执行内核程序. + +TSS是一个段,存放在gdt表中(标识为`DA_386TSS`).下面是gdt表中TSS段的初始化: + +```c +tss.ss0 = SELECTOR_FLAT_RW; //ss0的初始化在这里完成 +tss.iobase = sizeof(tss); /* 没有I/O许可位图 */ +init_segment(&gdt[4], (u32)&tss, sizeof(tss) - 1, DA_386TSS); +``` + +在初始化完TSS段之后需要通过`ltr`加载TSS选择子让硬件知晓TSS段. + +```nasm +_start: + ; 把 esp 从 LOADER 挪到 KERNEL + mov esp, StackTop ; 堆栈在 bss 段中 + + call cstart ; 在此函数中改变了gdt_ptr,让它指向新的GDT + + lgdt [gdt_ptr] ; 使用新的GDT + lldt [SELECTOR_LDT] + lidt [idt_ptr] + + jmp SELECTOR_KERNEL_CS:csinit +csinit: ; “这个跳转指令强制使用刚刚初始化的结构”——<> P90. + + xor eax, eax + mov ax, SELECTOR_TSS ; 选择TSS选择子 + ltr ax + + jmp kernel_main +``` + +在每次调用`restart`函数的时候,TSS中的`sp0`寄存器赋值为进程存储的寄存器表的顶部地址,这样保证进入内核态之后第一个压入的寄存器的值对应的是寄存器表中的`ss`. + +```nasm +restart: + mov esp, [p_proc_ready] + lea eax, [esp + P_STACKTOP] + mov dword [tss + TSS3_S_SP0], eax +restart_reenter: + ... +``` + +这样当再次陷入内核态时,首先将TSS中的`esp0`和`ss0`赋值到`esp`和`ss`寄存器,再之后`eip`\~`ss`这五个寄存器(其中`esp`和`ss`是用户态下的,虽然从逻辑上感觉不可思议,但是硬件总是可行的)会被压入栈中. + +##### 4. 时钟中断 + +时钟中断反正也挺简单的,Orange书上也有写,也就一个固定频率的晶振电路,触发指定次后向OS发送一个中断信号,这个时候执行流需要陷入内核然后处理时钟中断处理程序. + +##### 5. 键盘中断 + +这又是书上讲的很玄乎的一部分,但是实际上没那么玄乎,实验用不到那么多,在接受到键盘中断后,我们实际上需要解决两个问题:如何获取键盘输入的扫描码?如何将扫描码解析成正常ASCII码字符?解析后的ASCII码字符怎么用? + +从键盘输入上获取扫描码这个问题比较简单书上也讲了,存储在标号为`0x60`的端口里,可以通过inb(in_byte)函数将端口的值读出来.如果不及时读出来,你再怎么摁键盘也不会触发键盘中断,可以理解为第一次输入的扫描码直接把端口霸占住了,不让其他扫描码进来. + +但是实际上我们读入的是扫描码,是键盘上的一种编码,而我们需要将这种编码进行一步映射将扫描码映射成我们熟悉的ASCII字符,我们从端口里读入的是一个字节的数据,能表达0\~255之间的数,而ASCII字符仅能表达0\~127之间的数.最高位没有被用到,所以被硬件工程师重新利用,要知道我们摁下键盘实际上分摁下和弹起两个操作,对于同一个字符,它摁下与弹起的扫描码的区别在于摁下是最高位置0,而弹起是最高位置1,如果一直摁下会一直发送摁下的扫描码.对于`a`\~`z`这些字符,我们摁一次键会收到两个扫描码(摁下和弹起),但是在我们的日常理解里我们只关心摁下这个操作,所以在这次实验中,我们需要忽略掉弹起的扫描码,只关心摁下的扫描码,接下来需要解决的就是一个映射问题,`inc/keymap.h`里存放着一张扫描码到ASCII码的转换表,通过这张转换表就可以直接将扫描码转化为ASCII字符,这里并不需要考虑shift,ctrl这种特殊的控制字符,只需要实现实验要求的功能即可. + +在获取完ASCII码字符后,我们肯定不能把辛苦得来的字符丢掉,但是我们并不知道用户程序什么时候会来索取字符,所以需要一个缓冲区存储字符,在`kern/keyboard.c`放着一个简单的缓冲区: + +```c +#define KB_INBUF_SIZE 4 + +typedef struct kb_inbuf { + u8* p_head; + u8* p_tail; + int count; + u8 buf[KB_INBUF_SIZE]; +} KB_INPUT; + +static KB_INPUT kb_input = { + .p_head = kb_input.buf, + .p_tail = kb_input.buf, + .count = 0, +}; +``` + +这个数据结构本质上是一个队列,其中`p_head`指的是缓冲区的队首字符,`p_tail`指的是缓冲区的队尾字符,`count`是当前存储的字符数量,`buf`是缓冲区.需要注意的是让缓冲区满的时候所有添加的字符需要丢弃. + +当有这么一个缓冲区,触发键盘中断将解析来的字符存放到缓冲区中,当有用户程序需要索取时将缓冲区的队首字符弹出交给用户程序,就完成了整个交互. \ No newline at end of file