before lab4

This commit is contained in:
ridethepig 2022-10-18 18:23:01 +08:00
parent f8828d6c4e
commit 4b58f8019d
48 changed files with 3265 additions and 1539 deletions

5
.gitignore vendored
View File

@ -1,4 +1 @@
*.bin obj
*.img
obj/
.vscode

13
.vscode/c_cpp_properties.json vendored Normal file
View File

@ -0,0 +1,13 @@
{
"configurations": [
{
"name": "Linux",
"includePath": [
"${workspaceFolder}/inc"
],
"defines": [],
"cStandard": "gnu99"
}
],
"version": 4
}

22
.vscode/launch.json vendored Normal file
View File

@ -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",
}
]
}

10
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,10 @@
{
"editor.detectIndentation": false,
"editor.tabSize": 8,
"editor.insertSpaces": false,
"files.associations": {
"*.h": "c",
"*.c": "c",
"*.asm": "nasm"
}
}

View File

@ -1,11 +1,5 @@
# #
# make的主文件 # 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 NM := nm
# 预定义,-Dwdnmd相当于在C程序中`#ifdef wdnmd`为真
DEFS := DEFS :=
# gcc的相关命令参数 # gcc的相关命令参数
@ -44,6 +37,8 @@ CFLAGS += -I $(INCDIR) -MD
CFLAGS += -fno-stack-protector CFLAGS += -fno-stack-protector
# -std=gnu99 规定编译的语言规范为gnu99 # -std=gnu99 规定编译的语言规范为gnu99
CFLAGS += -std=gnu99 CFLAGS += -std=gnu99
# -fno-pie 不创建动态链接库
CFLAGS += -fno-pie
# -static 编译静态程序 # -static 编译静态程序
# -m32 编译32位程序 # -m32 编译32位程序
CFLAGS += -static -m32 CFLAGS += -static -m32
@ -55,6 +50,11 @@ CFLAGS += -Wall -Wno-format -Wno-unused -Werror
# ld链接器的相关命令参数 # ld链接器的相关命令参数
# -m elf_i386 链接的格式为i386 # -m elf_i386 链接的格式为i386
LDFLAGS := -m elf_i386 LDFLAGS := -m elf_i386
# -nostdlib 不链接gcc的标准库用库只能用命令行的
LDFLAGS += -nostdlib
# 获取gcc的库文件除法取模会用到
GCC_LIB := $(shell $(CC) $(CFLAGS) -print-libgcc-file-name)
# 记录每个OBJDIR里存放的每个子文件夹 # 记录每个OBJDIR里存放的每个子文件夹
# 对于这个系统来说,最后的值为./obj/kern和./obj/boot # 对于这个系统来说,最后的值为./obj/kern和./obj/boot
@ -64,31 +64,28 @@ OBJDIRS :=
# all的依赖会在kern/Makefrag中填充 # all的依赖会在kern/Makefrag中填充
all: all:
# xv6黑科技获取编译命令如果命令较新则重新编译所有文件 # xv6黑科技获取编译命令如果命令较新则重新编译所有文件
.PRECIOUS: $(OBJDIR)/.vars.% .PRECIOUS: $(OBJDIR)/.vars.%
$(OBJDIR)/.vars.%: FORCE $(OBJDIR)/.vars.%: FORCE
@echo "$($*)" | cmp -s $@ || echo "$($*)" > $@ @echo "$($*)" | cmp -s $@ || echo "$($*)" > $@
.PHONY: FORCE .PHONY: FORCE
# 导入两个文件两个文件分别编写方便管理也让主makefile更加清晰 # 导入两个文件两个文件分别编写方便管理也让主makefile更加舒服
include boot/Makefrag include boot/Makefrag
include kern/Makefrag include kern/Makefrag
# FAT12镜像文件 # FAT32镜像文件
IMAGE = $(OBJDIR)/kern/a.img 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 $(IMAGE): $(OBJDIR)/boot/boot.bin $(OBJDIR)/boot/loader.bin $(OBJDIR)/kern/kernel.bin
@dd if=/dev/zero of=$@ bs=512 count=2880 @stat $@ 1>/dev/null 2>/dev/null || dd if=/dev/zero of=$@ bs=512 count=102400
@mkfs.vfat $@ @mkfs.vfat -F 32 -s8 $@
@dd if=$(OBJDIR)/boot/boot.bin of=$@ bs=512 count=1 conv=notrunc @dd if=$< of=$@ bs=1 count=423 seek=90 conv=notrunc
@sudo mount -o loop $@ /mnt @sudo mount -o loop $@ /mnt
@sudo cp $(OBJDIR)/boot/loader.bin /mnt -v @sudo cp $(OBJDIR)/boot/loader.bin /mnt -v
@sudo cp $(OBJDIR)/kern/kernel.bin /mnt -v @sudo cp $(OBJDIR)/kern/kernel.bin /mnt -v
@sudo umount /mnt @sudo umount /mnt
all: $(IMAGE)
clean: clean:
@rm -rf $(OBJDIR) @rm -rf $(OBJDIR)
@ -111,32 +108,20 @@ gdb-no-graphic: $(IMAGE)
-s -S \ -s -S \
# 调试的内核代码elf # 调试的内核代码elf
KERNBIN := $(OBJDIR)/kern/kernel.bin KERNDBG := $(OBJDIR)/kern/kernel.dbg
monitor: $(KERNBIN) monitor: $(IMAGE)
@gdb -tui \ @gdb \
-ex 'set confirm off' \ -ex 'set confirm off' \
-ex 'target remote localhost:1234' \ -ex 'target remote localhost:1234' \
-ex 'file $(KERNBIN)' -ex 'file $(KERNDBG)'
debug: $(KERNBIN) # 黑科技时间,获取每个.c对应的头文件依赖
@qemu-system-i386 \ # 挺难整明白的不建议一开始整明白反正从xv6上抄的不明觉厉
-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黑科技获取头文件依赖如果头文件更新则会重新编译所有文件
$(OBJDIR)/.deps: $(foreach dir, $(OBJDIRS), $(wildcard $(OBJDIR)/$(dir)/*.d)) $(OBJDIR)/.deps: $(foreach dir, $(OBJDIRS), $(wildcard $(OBJDIR)/$(dir)/*.d))
@mkdir -p $(@D) @mkdir -p $(@D)
@perl mergedep.pl $@ $^ @perl mergedep.pl $@ $^
-include $(OBJDIR)/.deps -include $(OBJDIR)/.deps
.PHONY: all clean run gdb gdb-no-graphic monitor debug .PHONY: all clean run gdb gdb-no-graphic monitor

View File

@ -8,24 +8,21 @@ OBJDIRS += boot
LOADER_OBJS := $(OBJDIR)/boot/loader.o $(OBJDIR)/boot/loadkernel.o LOADER_OBJS := $(OBJDIR)/boot/loader.o $(OBJDIR)/boot/loadkernel.o
LOADER_LINKER := boot/linker.ld LOADER_LINKER := boot/linker.ld
# 根据源文件pattern判断编译的方式 $(OBJDIR)/boot/%.o: boot/%.c $(OBJDIR)/.vars.CFLAGS
$(OBJDIR)/boot/%.o: boot/%.c
@echo + cc $< @echo + cc $<
@mkdir -p $(@D) @mkdir -p $(@D)
@$(CC) $(CFLAGS) -fno-pie -c -o $@ $< @$(CC) $(CFLAGS) -c -o $@ $<
$(OBJDIR)/boot/%.o: boot/%.asm $(OBJDIR)/boot/%.o: boot/%.asm
@echo + as obj $< @echo + as obj $<
@mkdir -p $(@D) @mkdir -p $(@D)
@$(AS) -f elf -o $@ $< @$(AS) -f elf -o $@ $<
# 对于boot它是特殊的需要特殊编译
$(OBJDIR)/boot/boot.bin: boot/boot.asm $(OBJDIR)/boot/boot.bin: boot/boot.asm
@echo + as bin $< @echo + as bin $<
@mkdir -p $(@D) @mkdir -p $(@D)
@$(AS) -o $@ $< @$(AS) -o $@ $<
# loader通过一个特定的脚本进行链接反正可以先不管将核心放到内核代码上来
$(OBJDIR)/boot/loader.bin: $(LOADER_OBJS) $(LOADER_LINKER) $(OBJDIR)/boot/loader.bin: $(LOADER_OBJS) $(LOADER_LINKER) $(OBJDIR)/.vars.LDFLAGS
@echo + ld $@ @echo + ld $@
@mkdir -p $(@D)
@$(LD) $(LDFLAGS) -s -T $(LOADER_LINKER) --oformat binary -o $@ $(LOADER_OBJS) @$(LD) $(LDFLAGS) -s -T $(LOADER_LINKER) --oformat binary -o $@ $(LOADER_OBJS)

View File

@ -1,336 +1,215 @@
org 07c00h ; Boot 状态, Bios 将把 Boot Sector 加载到 0:7C00h 处并开始执行 ;==============================================================================================================================
BaseOfStack equ 0x07c00 ; Boot状态下堆栈基地址
STACK_ADDR equ BaseOfStack-28 ; 堆栈栈顶
; 下面这部分不仅是boot的内容也是Fat12文件系统的引导部分 BaseOfBoot equ 1000h ; added by mingxuan 2020-9-12
; boot需要文件系统的加持(可以没有但是如果这么干的话boot可扩展性为0磁盘内容位置稍微变动就不能用了)才能正确的将loader的内容导入到内存里执行 OffsetOfBoot equ 7c00h ; load Boot sector to BaseOfBoot:OffsetOfBoot
;================================================================================================ OffsetOfActiPartStartSec equ 7e00h ; 活动分区的起始扇区号相对于BaseOfBoot的偏移量 ;added by mingxuan 2020-9-12
; Fat12规定前3字节是跳转代码后面再是文件系统信息 ; 该变量来自分区表保存在该内存地址用于在os_boot和loader中查找FAT32文件
; 如果是短跳转(2字节指令机器码以0xEB开头)就需要后面加一个nop填充到3字节 BOOT_FAT32_INFO equ 0x5A ; 位于boot中的FAT32配置信息的长度
; 如果是长跳转(3字节指令机器码以0xE9开头)就不需要添加nop了
jmp Main ; Start to boot.
; nop ; 这个 nop 不可少
; 下面是 FAT12 磁盘的头 OSLOADER_SEG equ 0x1000 ; 段地址
; 正常情况下boot是要对磁盘头的数据进行解析的 OSLOADER_OFF equ 0x5000 ; 段偏移
; 但是出于简单考虑,直接将磁盘头硬编码进来,如果要分析可就太麻烦了,汇编本来就看的头大,还搞那么多未知元 OSLOADER_ADDR equ 0x15000 ; loader地址
; 里面很多信息实际上在boot里用不上请各位别对里面的参数纠结太多用到了再查也不迟 BUF_OFF equ 0x0000 ; 加载FAT表的临时存储
BS_OEMName DB 'ForrestY' ; OEM String, 必须 8 个字节 BUF_ADDR equ 0x10000 ; 加载FAT表的临时存储
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个字节
; 文件系统信息存放完毕后后面的内容就可以自由调整了,撒花! DIR_PER_SECTOR equ 0x10 ; 每个扇区所容纳的目录 BYTE
; 原先Orange的代码是按照 主函数->常量->变量->子函数的顺序给出,逻辑很乱 ; 扩展磁盘服务所使用的地址包
; 这次尝试用C语言风格按照 常量->变量->子函数->主函数的顺序给出,更加符合正常的编程逻辑 DAP_SECTOR equ 8 ; 起始扇区号
;============================================================================ DAP_BUFFER_SEG equ 10 ; 缓冲区地址
;常量 DAP_BUFFER_OFF equ 12 ; 缓冲区地址
;================================================================================================ DAP_READ_SECTORS equ 14 ; 要处理的扇区数
; boot的内存模型实际上比较简单区间左闭右开 DAP_PACKET_SIZE equ 16 ; 包的大小为24字节
; (0x500~0x7c00) 栈 CURRENT_CLUSTER equ 20 ; 当前正在处理的簇号 DWORD
; (0x7c00~0x7e00) 引导扇区 FAT_START_SECTOR equ 24 ; FAT表的起始扇区号 ;added by mingxuan 2020-9-17
; (0x90000~0x90400) 缓冲区GetNextCluster函数会用到它 DATA_START_SECTOR equ 28 ; 数据区起始扇区号 ;added by mingxuan 2020-9-17
; (0x90400~?) 加载区loader代码会加载到这里
BaseOfStack equ 07c00h ; Boot状态下堆栈基地址(栈底, 从这个位置向低地址生长)
BaseOfLoader equ 09000h ; LOADER.BIN 被加载到的位置 ---- 段地址
OffsetOfLoader equ 0400h ; LOADER.BIN 被加载到的位置 ---- 偏移地址
; 这部分请看手册 ; 目录项结构
RootDirSectors equ 14 OFF_START_CLUSTER_HIGH equ 20 ; 起始簇号高位 WORD
SectorNoOfRootDirectory equ 19 OFF_START_CLUSTER_LOW equ 26 ; 起始簇号低位 WORD
SectorNoOfFAT1 equ 1
DeltaSectorNo equ 31
;================================================================================================
;============================================================================ ; 相关常量
;变量 DIR_NAME_FREE equ 0x00 ; 该项是空闲的
;---------------------------------------------------------------------------- DIR_ENTRY_SIZE equ 32 ; 每个目录项的尺寸
LeftRootDirSectors dw RootDirSectors ; 还未搜索的根目录扇区数 ; 簇属性
RootDirSectorNow dw SectorNoOfRootDirectory ; 目前正在搜索的根目录扇区 CLUSTER_MASK equ 0FFFFFFFH ; 簇号掩码
BufferPacket times 010h db 0 ; ReadSector函数会用到的用于向int 13h中断的一个缓冲区 CLUSTER_LAST equ 0FFFFFF8H ;0xFFFFFFF8-0xFFFFFFFF表示文件的最后一个簇
;============================================================================ ; added by mingxuan 2020-9-12
;字符串 BPB_BytesPerSec equ (OffsetOfBoot + 0xb) ;每扇区字节数
;---------------------------------------------------------------------------- BPB_SecPerClu equ (OffsetOfBoot + 0xd) ;每簇扇区数
LoaderFileName db "LOADER BIN", 0 ; LOADER.BIN 的文件名(为什么中间有空格请RTFM) BPB_RsvdSecCnt equ (OffsetOfBoot + 0xe) ;保留扇区数
; 为简化代码, 下面每个字符串的长度均为 MessageLength BPB_NumFATs equ (OffsetOfBoot + 0x10) ;FAT表数
MessageLength equ 9 BPB_RootEntCnt equ (OffsetOfBoot + 0x11) ;FAT32不使用
BootMessage: db "Booting " ; 9字节, 不够则用空格补齐. 序号 0 BPB_TotSec16 equ (OffsetOfBoot + 0x13) ;扇区总数
Message1 db "Ready. " ; 9字节, 不够则用空格补齐. 序号 1 BPB_Media equ (OffsetOfBoot + 0x15) ;介质描述符
Message2 db "Read Fail" ; 9字节, 不够则用空格补齐. 序号 2 BPB_FATSz16 equ (OffsetOfBoot + 0x16) ;每个FAT表的大小扇区数(FAT32不使用)
Message3 db "No Loader" ; 9字节, 不够则用空格补齐. 序号 3 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
;---------------------------------------------------------------------------- mov bp, BaseOfStack
; 函数名: DispStr mov sp, STACK_ADDR
;----------------------------------------------------------------------------
; 作用:
; 显示一个字符串, 函数开始时 dh 中应该是字符串序号(从0开始)
DispStr:
push bp
mov bp, sp
pusha
push es
mov ax, MessageLength movzx eax, word [BPB_RsvdSecCnt]
mul dh mov [bp - FAT_START_SECTOR], eax
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
pop es ; 计算数据区起始扇区号 ; added by mingxuan 2020-9-17
popa movzx cx, byte [BPB_NumFATs]
pop bp _CALC_DATA_START:
ret add eax, [BPB_FATSz32]
loop _CALC_DATA_START
;---------------------------------------------------------------------------- mov [bp - DATA_START_SECTOR], eax
; 函数名: DispDot
;----------------------------------------------------------------------------
; 作用:
; 打印一个点
DispDot:
push bp
mov bp, sp
pusha
mov ah, 0Eh ; `. 每读一个扇区就在 "Booting " 后面 mov word [bp - DAP_PACKET_SIZE], 10h
mov al, '.' ; | 打一个点, 形成这样的效果: mov word [bp - DAP_BUFFER_SEG], OSLOADER_SEG
mov bl, 0Fh ; | Booting ......
int 10h ; /
popa jmp _SEARCH_LOADER
pop bp
ret
;---------------------------------------------------------------------------- LoaderName db "LOADER BIN" ; 第二阶段启动程序 FDOSLDR.BIN
; 函数名: 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
ReadSector: ReadSector:
push bp pusha
mov bp, sp
pusha
mov si, BufferPacket mov ah, 42h ;ah是功能号扩展读对应0x42
mov word [si + 0], 010h ; buffer_packet_size lea si, [bp - DAP_PACKET_SIZE] ;使用扩展int13时DAP结构体首地址传给si
mov word [si + 2], cx ; sectors mov dl, [BS_DrvNum] ;modified by mingxuan 2020-9-17
mov word [si + 4], bx ; buffer-offset int 13h
mov word [si + 6], es ; buffer-segment jc $
mov word [si + 8], ax ; start_sectors
mov dl, [BS_DrvNum] ; 驱动号 popa
mov ah, 42h ; 扩展读 ret
int 13h
jc .ReadFail ; 读取失败简单考虑就默认bios坏了
popa InlineReadDataSector:
pop bp sub eax, 2
ret movzx edx, byte [BPB_SecPerClu]
.ReadFail: mul edx
mov dh, 2 add eax, [bp - DATA_START_SECTOR]
call DispStr mov [bp - DAP_SECTOR], eax
jmp $ ; 如果cf位置1就意味着读入错误这个时候建议直接开摆
;---------------------------------------------------------------------------- movzx edx, byte [BPB_SecPerClu]
; 函数名: GetNextCluster mov [bp - DAP_READ_SECTORS], dx
;----------------------------------------------------------------------------
; 作用:
; ax存放的是当前的簇(cluster)号根据当前的簇号在fat表里查找找到下一个簇的簇号并将返回值存放在ax
GetNextCluster:
push bp
mov bp, sp
pusha
mov bx, 3 ; 一个FAT项长度为1.5字节 mov [bp - DAP_BUFFER_OFF], bx
mul bx call ReadSector
mov bx, 2 ; ax = floor(clus_number * 1.5) ret
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 bx, dx ; 将偏移量搬回bx ; 根据簇号计算扇区号
mov ax, [es:bx] ; Comments, added by mingxuan 2020-9-10
pop bx ; 取回奇数标识信息 ;====================================================================
cmp bx, 0 ; 如果是第奇数个FAT项还得右移四位 ; 检查是否还有下一个簇(读取FAT表的相关信息)
jz EvenCluster ; 可能是微软(FAT是微软创建的)第一个亲儿子的原因,有它的历史局限性 ; N = 数据簇号
shr ax, 4 ; 当时的磁盘很脆弱经常容易写坏所以需要两张FAT表备份而且人们能够制作的存储设备的容量很小 ; FAT_BYTES(在FAT表中的偏移) = N*4 (FAT32)
EvenCluster: ; FAT_SECTOR = FAT_BYTES / BPB_BytesPerSec
and ax, 0FFFh ; 读完需要与一下因为高位是未定义的防止ax值有误 ; FAT_OFFSET = FAT_BYTES % BPB_BytesPerSec
mov word [bp - 2], ax ; 这里用了一个技巧这样在popa的时候ax也顺便更新了 ;====================================================================
NextCluster: ;added by yangxiaofeng 2021-12-1
pushad
popa mov eax, [bp - CURRENT_CLUSTER]
pop bp mov ebx, 4
ret mul ebx
movzx ebx, word [BPB_BytesPerSec]
div ebx
add eax, [bp - FAT_START_SECTOR]
mov [bp - DAP_SECTOR], eax
;---------------------------------------------------------------------------- mov word [bp - DAP_READ_SECTORS], 1
; 函数名: StringCmp
;----------------------------------------------------------------------------
; 作用:
; 比较 ds:si 和 es:di 处的字符串比较长度为11仅为loader.bin所用
; 如果两个字符串相等ax返回1否则ax返回0
StringCmp:
push bp
mov bp, sp
pusha
mov cx, 11 ; 比较长度为11 mov word [bp - DAP_BUFFER_OFF], BUF_OFF
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里都不会变了
; 清屏 call ReadSector
mov ax, 0600h ; AH = 6, AL = 0h mov bx, dx
mov bx, 0700h ; 黑底白字(BL = 07h) mov eax, [es:bx + BUF_OFF]
mov cx, 0 ; 左上角: (0, 0) mov [bp - DATA_START_SECTOR - 6], eax
mov dx, 0184fh ; 右下角: (80, 50)
int 10h ; int 10h
mov dh, 0 ; "Booting " popad
call DispStr ; 显示字符串 ret
mov ah, 0 ; ┓ _SEARCH_LOADER:
mov dl, [BS_DrvNum] ; ┣ 硬盘复位 mov eax, [BPB_RootClus]
int 13h ; ┛
; 下面在 A 盘的根目录寻找 LOADER.BIN _NEXT_ROOT_CLUSTER: ;这个时候eax拿着的是当前的簇号
FindLoaderInRootDir: mov [bp - CURRENT_CLUSTER], eax
mov ax, [RootDirSectorNow]; ax <- 现在正在搜索的扇区号 mov bx, BUF_OFF
mov bx, OffsetOfLoader ; es:bx = BaseOfLoader:OffsetOfLoader
mov cx, 1
call ReadSector
mov si, LoaderFileName ; ds:si -> "LOADER BIN" call InlineReadDataSector
mov di, OffsetOfLoader ; es:di -> BaseOfLoader:400h = BaseOfLoader*10h+400h
mov dx, 10h ; 32(目录项大小) * 16(dx) = 512(BPB_BytsPerSec)
CompareFilename: mov di, BUF_OFF
call StringCmp ; 这里dl拿着的是BPB_SecPerClu
cmp ax, 1 _NEXT_ROOT_SECTOR:
jz LoaderFound ; ax == 1 -> 比对成了 mov bl, DIR_PER_SECTOR
dec dx
cmp dx, 0
jz GotoNextRootDirSector ; 该扇区的所有目录项都探索完了,去探索下一个扇区
add di, 20h ; 32 -> 目录项大小
jmp CompareFilename
GotoNextRootDirSector: _NEXT_ROOT_ENTRY:
inc word [RootDirSectorNow] ; 改变正在搜索的扇区号 mov si, LoaderName
dec word [LeftRootDirSectors] ; ┓ mov cx, 11
cmp word [LeftRootDirSectors], 0 ; ┣ 判断根目录区是不是已经读完 repe cmpsb
jz NoLoader ; ┛ 如果读完表示没有找到 LOADER.BIN就直接开摆 and di, DIR_ENTRY_SIZE
jmp FindLoaderInRootDir jcxz _FOUND_LOADER
add di, DIR_ENTRY_SIZE
NoLoader: dec bl
mov dh, 3 jnz _NEXT_ROOT_ENTRY
call DispStr
jmp $
LoaderFound: ; 找到 LOADER.BIN 后便来到这里继续 dec dl
add di, 01Ah ; 0x1a = 28 这个 28 在目录项里偏移量对应的数据是起始簇号RTFM jz _CHECK_NEXT_ROOT_CLUSTER
mov dx, word [es:di] ; 起始簇号占2字节读入到dx里
mov bx, OffsetOfLoader ; es:bx = BaseOfLoader:OffsetOfLoader
LoadLoader: jmp _NEXT_ROOT_SECTOR
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
times 510-($-$$) db 0 ; 填充剩下的空间使生成的二进制代码恰好为512字节 _CHECK_NEXT_ROOT_CLUSTER: ; 检查是否还有下一个簇
dw 0xaa55 ; 结束标志 call NextCluster ;added by yangxiaofeng 2021-12-1
cmp eax, CLUSTER_LAST ;CX >= 0FFFFFF8H则意味着没有更多的簇了
jb _NEXT_ROOT_CLUSTER
jmp $
_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
call InlineReadDataSector
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 ; 结束标志

View File

@ -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 ; 诶……徒增那么多判断看看隔壁FAT16FAT32exFAT不会发生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

View File

@ -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

View File

@ -1,8 +1,8 @@
OUTPUT_FORMAT("elf32-i386", "elf32-i386", "elf32-i386") OUTPUT_FORMAT("elf32-i386", "elf32-i386", "elf32-i386")
OUTPUT_ARCH(i386) OUTPUT_ARCH(i386)
ENTRY(_start) ENTRY(_start)
BASE_ADDRESS = 0x400; BASE_ADDRESS = 0x5000;
LOADER_SEG_PHYADDR = 0x90000; LOADER_SEG_PHYADDR = 0x10000;
SECTIONS SECTIONS
{ {

View File

@ -5,17 +5,23 @@
; Forrest Yu, 2005 ; 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] [SECTION .text.s16]
ALIGN 16 ALIGN 16
[BITS 16] [BITS 16]
%include "./boot/inc/load.inc"
global _start global _start
_start: _start:
jmp Main ; Start jmp Main ; Start
%include "./boot/inc/fat12.inc"
%include "./boot/inc/pm.inc" %include "./boot/inc/pm.inc"
ALIGN 4 ALIGN 4
@ -45,9 +51,7 @@ Main: ; <--- 从这里开始 *************
mov cx, 02000h mov cx, 02000h
mov ah, 01h mov ah, 01h
int 10h ; 隐藏光标 int 10h
call LoadKernelFile
; 下面准备跳入保护模式 ------------------------------------------- ; 下面准备跳入保护模式 -------------------------------------------

View File

@ -1,63 +1,7 @@
/* #include "type.h"
* #include "x86.h"
*/ #include "elf.h"
typedef int i32; #include "fat32.h"
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;
/* /*
* *
@ -87,7 +31,7 @@ write_to_terminal(u16 disp_pos, u16 content)
/* /*
* *
*/ */
static void void
clear_screen() clear_screen()
{ {
for (int i = 0; i < TERMINAL_ROW; i++) for (int i = 0; i < TERMINAL_ROW; i++)
@ -96,7 +40,7 @@ clear_screen()
DEFAULT_COLOR | ' '); DEFAULT_COLOR | ' ');
} }
static void * void *
memset(void *v, int c, size_t n) memset(void *v, int c, size_t n)
{ {
char *p; char *p;
@ -110,26 +54,116 @@ memset(void *v, int c, size_t n)
return v; return v;
} }
static void * int
memcpy(void *dst, const void *src, size_t n) strncmp(const char *p, const char *q, size_t n)
{ {
const char *s; while (n > 0 && *p && *p == *q)
char *d; n--, p++, q++;
if (n == 0)
return 0;
else
return (int) ((unsigned char) *p - (unsigned char) *q);
}
s = src; #define SECTSIZE 512
d = dst; #define BUF_ADDR 0x30000
#define ELF_ADDR 0x40000
if (s < d && s + n > d) { void
s += n; waitdisk(void)
d += n; {
while (n-- > 0) // wait for disk reaady
*--d = *--s; while ((inb(0x1F7) & 0xC0) != 0x40)
} else { /* do nothing */;
while (n-- > 0) }
*d++ = *s++;
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(); clear_screen();
for (char *s = "----start loading kernel elf----", *st = s; *s; s++) 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);
Elf32_Ehdr *kernel_ehdr = (Elf32_Ehdr *)KERNEL_ELF; readsect((void *)&bpb, 0);
u32* elf_magic = (u32*)KERNEL_ELF;
if (*elf_magic != 0x464c457f) { if (bpb.BPB_SecPerClus != 8 || bpb.BPB_BytsPerSec != SECTSIZE)
// make sure it's a elf file goto bad;
for (char *s = "not a elf file", *st = s; *s; s++)
write_to_terminal(80 + s - st, DEFAULT_COLOR | *s); fat_start_sec = bpb.BPB_RsvdSecCnt;
while (1); 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) { if (elf_clus == 0)
for (char *s = "not a x86 executable", *st = s; *s; s++) goto bad;
write_to_terminal(80 + s - st, DEFAULT_COLOR | *s);
while (1);
}
Elf32_Phdr *kernel_phdr = (void *)kernel_ehdr + kernel_ehdr->e_phoff; read_data_sec((void *)ELF_ADDR, elf_clus);
for (u32 i = 0; i < kernel_ehdr->e_phnum; i++, kernel_phdr++)
{ struct Elf *eh = (void *)ELF_ADDR;
if (kernel_phdr->p_type != PT_LOAD) 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; continue;
// 将elf的文件数据复制到指定位置 readseg(ph->p_va, ph->p_filesz, ph->p_offset);
memcpy(
(void *)kernel_phdr->p_vaddr,
(void *)kernel_ehdr + kernel_phdr->p_offset,
kernel_phdr->p_filesz);
// 将后面的字节清零(p_memsz >= p_filesz)
memset( memset(
(void *)kernel_phdr->p_vaddr + kernel_phdr->p_filesz, (void *)ph->p_va + ph->p_filesz,
0, 0,
kernel_phdr->p_memsz - kernel_phdr->p_filesz); 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
} }

18
inc/assert.h Normal file
View File

@ -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 */

View File

@ -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 */

67
inc/elf.h Normal file
View File

@ -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 */

55
inc/fat32.h Normal file
View File

@ -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 */

8
inc/keyboard.h Normal file
View File

@ -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 */

323
inc/keymap.h Normal file
View File

@ -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 */

41
inc/process.h Normal file
View File

@ -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

View File

@ -13,7 +13,164 @@ typedef struct s_descriptor { /* 共 8 个字节 */
u8 base_high; /* Base */ u8 base_high; /* Base */
}DESCRIPTOR; }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 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 */ #endif /* MINIOS_PROTECT_H */

12
inc/stdarg.h Normal file
View File

@ -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 */

24
inc/stdio.h Normal file
View File

@ -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 */

View File

@ -3,7 +3,15 @@
#include "type.h" #include "type.h"
void * memset(void *v, int c, size_t n); int strlen(const char *s);
void * memcpy(void *dst, const void *src, size_t n); 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 */ #endif /* MINIOS_STRING_H */

View File

@ -6,10 +6,13 @@
/* /*
* *
*/ */
#define TERMINAL_ADDR 0xB8000
#define TERMINAL_COLUMN 80 #define TERMINAL_COLUMN 80
#define TERMINAL_ROW 25 #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) #define DEFAULT_COLOR FOREGROUND(WHITE) | BACKGROUND(BLACK)
void clear_screen();
void kprintf(u16 disp_pos, const char* format, ...);
#endif /* MINIOS_TERMINAL_H */ #endif /* MINIOS_TERMINAL_H */

9
inc/time.h Normal file
View File

@ -0,0 +1,9 @@
#ifndef MINIOS_TIME_H
#define MINIOS_TIME_H
#include "type.h"
void timecounter_inc();
size_t clock();
#endif

79
inc/trap.h Normal file
View File

@ -0,0 +1,79 @@
#ifndef MINIOS_TRAP_H
#define MINIOS_TRAP_H
#define INT_M_CTL 0x20 /* I/O port for interrupt controller <Master> */
#define INT_M_CTLMASK 0x21 /* setting bits in this port disables ints <Master> */
#define INT_S_CTL 0xA0 /* I/O port for second interrupt controller <Slave> */
#define INT_S_CTLMASK 0xA1 /* setting bits in this port disables ints <Slave> */
/* 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 */

View File

@ -1,7 +1,12 @@
#ifndef MINIOS_TYPE_H #ifndef MINIOS_TYPE_H
#define 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 int i32;
typedef unsigned int u32; typedef unsigned int u32;
@ -10,6 +15,9 @@ typedef unsigned short u16;
typedef char i8; typedef char i8;
typedef unsigned char u8; typedef unsigned char u8;
typedef i32 intptr_t;
typedef u32 uintptr_t;
// 通常描述一个对象的大小,会根据机器的型号变化类型 // 通常描述一个对象的大小,会根据机器的型号变化类型
typedef u32 size_t; typedef u32 size_t;
// signed size_t 通常描述系统调用返回值,会根据机器的型号变化类型 // signed size_t 通常描述系统调用返回值,会根据机器的型号变化类型

114
inc/x86.h Normal file
View File

@ -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

View File

@ -5,22 +5,23 @@
OBJDIRS += kern OBJDIRS += kern
KERN_ENTRY := 0x30400 KERN_ENTRY_ADDR := 0x200000
KERN_SRCFILES:= kern/astart.asm \
# kernel的所有源码文件如果要添加什么新文件就只需要在这里添加即可其余makefile都不需要动 kern/atrap.asm \
KERN_SRCFEILS:= kern/kernel.asm \ kern/keyboard.c \
kern/main.c \
kern/start.c \ kern/start.c \
kern/cmatrix.c \ kern/time.c \
kern/kprintf.asm\ kern/trap.c \
lib/libch4Core.a\
lib/terminal.c \ lib/terminal.c \
lib/printfmt.c \
lib/string.c lib/string.c
# 根据KERN_SRCFEILS获取所有需要的可重定位文件 KERN_OBJFILES := $(patsubst %.c, $(OBJDIR)/%.o, $(KERN_SRCFILES))
KERN_OBJFILES := $(patsubst %.c, $(OBJDIR)/%.o, $(KERN_SRCFEILS))
KERN_OBJFILES := $(patsubst %.asm, $(OBJDIR)/%.o, $(KERN_OBJFILES)) KERN_OBJFILES := $(patsubst %.asm, $(OBJDIR)/%.o, $(KERN_OBJFILES))
KERN_OBJFILES := $(patsubst $(OBJDIR)/lib/%, $(OBJDIR)/kern/%, $(KERN_OBJFILES)) KERN_OBJFILES := $(patsubst $(OBJDIR)/lib/%, $(OBJDIR)/kern/%, $(KERN_OBJFILES))
# 根据源文件pattern判断编译的方式
$(OBJDIR)/kern/%.o: kern/%.c $(OBJDIR)/.vars.CFLAGS $(OBJDIR)/kern/%.o: kern/%.c $(OBJDIR)/.vars.CFLAGS
@echo + cc $< @echo + cc $<
@mkdir -p $(@D) @mkdir -p $(@D)
@ -36,7 +37,14 @@ $(OBJDIR)/kern/%.o: kern/%.asm $(OBJDIR)/.vars.CFLAGS
@mkdir -p $(@D) @mkdir -p $(@D)
@$(AS) -f elf -o $@ $< @$(AS) -f elf -o $@ $<
# 将所有可重定位文件链接成kernel.bin $(OBJDIR)/kern/%.a: lib/%.a $(OBJDIR)/.vars.CFLAGS
$(OBJDIR)/kern/kernel.bin: $(KERN_OBJFILES) @echo + cp lib $<
@mkdir -p $(@D)
@cp $< $(@D)
$(OBJDIR)/kern/kernel.bin: $(KERN_OBJFILES) $(OBJDIR)/.vars.LDFLAGS
@echo + ld $@ @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

77
kern/astart.asm Normal file
View File

@ -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 (04G) ┃ 8h = cs
; ┣━━━━━━━━━━━━━━━━━━┫
; ┃ DESC_FLAT_RW (04G) ┃ 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

270
kern/atrap.asm Normal file
View File

@ -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 <Master>
INT_M_CTLMASK equ 0x21 ; setting bits in this port disables ints <Master>
INT_S_CTL equ 0xA0 ; I/O port for second interrupt controller <Slave>
INT_S_CTLMASK equ 0xA1 ; setting bits in this port disables ints <Slave>
; 中断和异常 -- 硬件中断
; ---------------------------------
%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

View File

@ -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
}
}
}

10
kern/game.h Normal file
View File

@ -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

View File

@ -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 (04G) ┃ 8h = cs
; ┣━━━━━━━━━━━━━━━━━━┫
; ┃ DESC_FLAT_RW (04G) ┃ 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: ; “这个跳转指令强制使用刚刚初始化的结构”——<<OS:D&I 2nd>> P90.
push 0
popfd ; Pop top of stack into EFLAGS
hlt
STR_kernel: db 'kernel'

36
kern/keyboard.c Normal file
View File

@ -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;
}

View File

@ -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 $

101
kern/main.c Normal file
View File

@ -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);
}

View File

@ -1,57 +1,218 @@
#include "type.h" #include "type.h"
#include "protect.h" #include "protect.h"
#include "string.h" #include "stdio.h"
#include "cmatrix.h" #include "assert.h"
#include "terminal.h" #include "trap.h"
#include "x86.h"
u8 gdt_ptr[6]; /* 0~15:Limit 16~47:Base */ struct Pseudodesc gdt_ptr, idt_ptr;
DESCRIPTOR gdt[GDT_SIZE];
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");
}
/*
* panicCPU核
*/
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() void cstart()
{ {
/* 将 LOADER 中的 GDT 复制到新的 GDT 中 */ // 清屏并将光标置为开头
memcpy(&gdt, /* New GDT */ // ANSI转义序列由\x1b(ascii码27为Esc)开头的序列
(void *)(*((u32 *)(&gdt_ptr[2]))), /* Base of Old GDT */ // 能够控制光标控制终端linux上也在用这个控制终端
*((u16 *)(&gdt_ptr[0])) + 1 /* Limit of Old GDT */ // 目前支持的功能在inc/terminal.c中的kprintf函数有写
); // \x1b[2J 清屏
/* gdt_ptr[6] 共 6 个字节0~15:Limit 16~47:Base。用作 sgdt/lgdt 的参数。*/ // \x1b[H 将光标放置到左上角
u16 *p_gdt_limit = (u16 *)(&gdt_ptr[0]); kprintf("\x1b[2J\x1b[H");
u32 *p_gdt_base = (u32 *)(&gdt_ptr[2]);
*p_gdt_limit = GDT_SIZE * sizeof(DESCRIPTOR) - 1; kprintf("---loading gdt ");
*p_gdt_base = (u32)&gdt; init_gdt();
// 在终端的第1行依次打出N(黑底白字)W(黑底蓝字)P(白底蓝字)U(白底黑字) kprintf("ldt ");
kprintf(TERMINAL_POS(1, 0), init_ldt();
"N%fW%bP%fU%f%b ", LIGHT_BLUE, WHITE, BLACK, WHITE, BLACK); kprintf("8259A ");
// 在终端的第2行依次输出白底绿紫相间的字符 init_8259A();
for (char *s = "even old new york once Amsterdam", kprintf("idt ");
*st = s ; *s ; s += 4) { init_idt();
kprintf(TERMINAL_POS(2, s - st), kprintf("---\n");
"%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
//在终端第34行分别输出两句话而且两行的格式相同
//第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
} }

22
kern/time.c Normal file
View File

@ -0,0 +1,22 @@
#include "type.h"
#include "time.h"
static size_t timecounter;
/*
*
*/
void
timecounter_inc()
{
timecounter++;
}
/*
*
*/
size_t
clock()
{
return timecounter;
}

110
kern/trap.c Normal file
View File

@ -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;
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

BIN
lib/libch4Core.a Normal file

Binary file not shown.

267
lib/printfmt.c Normal file
View File

@ -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;
}

View File

@ -1,5 +1,78 @@
#include "string.h" #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 * void *
memset(void *v, int c, size_t n) memset(void *v, int c, size_t n)
{ {

View File

@ -1,5 +1,10 @@
#include "assert.h"
#include "stdio.h"
#include "string.h"
#include "stdarg.h"
#include "terminal.h" #include "terminal.h"
#include "cmatrix.h" #include "type.h"
#include "x86.h"
inline static void inline static void
write_to_terminal(u16 disp_pos, u16 content) 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) "mov %1, %%gs:(%0)" ::"r"(disp_pos * 2), "r"(content)
: "memory"); : "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 | ' '; u16 content = DEFAULT_COLOR | ' ';
for (int i = 0; i < TERMINAL_ROW; i++) if (b->param1 == CLEAR_CURSOR2END) {
for (int j = 0; j < TERMINAL_COLUMN; j++) u16 disp_pos = cursor_pos(b);
write_to_terminal(TERMINAL_POS(i, j), content); 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列11nm为空时默认为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;
} }

View File

@ -1,293 +0,0 @@
<div align='center'>
<font size='6'>实验三 进入保护模式</font>
</div>
<div align='center'>
<font size='4'>谷建华</font>
</div>
<div align='center'>
<font size='4'>2022-09-29 v0.2</font>
</div>
#### 实验目的
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<stdio.h>
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规范,但是有一种最笨也是最有效的方式就是:<font size='4'>读汇编</font>,<font size='5'>读汇编</font>,<font size='6'>读汇编</font>.因为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
```

305
实验四-v0.3.md Normal file
View File

@ -0,0 +1,305 @@
<div align='center'>
<font size='6'>实验四 中断</font>
</div>
<div align='center'>
<font size='4'>谷建华</font>
</div>
<div align='center'>
<font size='4'>2022-10-14 v0.3</font>
</div>
#### 实验目的
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`函数,这是一个非阻塞的函数,即当缓冲区中有字符未输出时,输出缓冲区队头该字符,并将缓冲区队头字符弹出,当缓冲区中没有字符时输出255u8意义下的-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初始化
LDTlocal 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初始化
IDTInterrupt 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: ; “这个跳转指令强制使用刚刚初始化的结构”——<<OS:D&I 2nd>> 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`的端口里,可以通过inbin_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`是缓冲区.需要注意的是让缓冲区满的时候所有添加的字符需要丢弃.
当有这么一个缓冲区,触发键盘中断将解析来的字符存放到缓冲区中,当有用户程序需要索取时将缓冲区的队首字符弹出交给用户程序,就完成了整个交互.