From fecc31e72b01688e6e43398baa954522c569003a Mon Sep 17 00:00:00 2001 From: yanguangshaonian Date: Tue, 11 Apr 2023 22:13:54 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A4=96=E4=B8=AD=E6=96=AD=E5=AE=8C=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 16 ++ .vscode/launch.json | 60 ++++++ .vscode/settings.json | 27 +++ .vscode/tasks.json | 51 +++++ bochs/PKGBUILD | 67 ++++++ bochs/bochsrc | 60 ++++++ bochs/bochsrc.gdb | 55 +++++ src/Makefile | 106 ++++++++++ src/boot/boot.asm | 280 +++++++++++++++++++++++++ src/boot/loader.asm | 288 ++++++++++++++++++++++++++ src/include/onix/assert.h | 14 ++ src/include/onix/console.h | 48 +++++ src/include/onix/debug.h | 11 + src/include/onix/global.h | 45 ++++ src/include/onix/interrupt.h | 63 ++++++ src/include/onix/io.h | 51 +++++ src/include/onix/onix.h | 18 ++ src/include/onix/printk.h | 7 + src/include/onix/stdarg.h | 10 + src/include/onix/stdlib.h | 6 + src/include/onix/string.h | 37 ++++ src/include/onix/task.h | 27 +++ src/include/onix/types.h | 36 ++++ src/include/onix/vprintf.h | 9 + src/kernel/debug.c | 16 ++ src/kernel/global.c | 23 +++ src/kernel/handle.asm | 190 +++++++++++++++++ src/kernel/interrupt.c | 107 ++++++++++ src/kernel/io.asm | 86 ++++++++ src/kernel/main.c | 34 ++++ src/kernel/printk.c | 18 ++ src/kernel/schdule.asm | 34 ++++ src/kernel/start.asm | 19 ++ src/kernel/task.c | 98 +++++++++ src/lib/assert.c | 45 ++++ src/lib/console.c | 326 +++++++++++++++++++++++++++++ src/lib/stdlib.c | 11 + src/lib/string.c | 169 +++++++++++++++ src/lib/vprintf.c | 385 +++++++++++++++++++++++++++++++++++ src/test/types.c | 7 + 40 files changed, 2960 insertions(+) create mode 100644 .gitignore create mode 100644 .vscode/launch.json create mode 100644 .vscode/settings.json create mode 100644 .vscode/tasks.json create mode 100644 bochs/PKGBUILD create mode 100644 bochs/bochsrc create mode 100644 bochs/bochsrc.gdb create mode 100644 src/Makefile create mode 100644 src/boot/boot.asm create mode 100644 src/boot/loader.asm create mode 100644 src/include/onix/assert.h create mode 100644 src/include/onix/console.h create mode 100644 src/include/onix/debug.h create mode 100644 src/include/onix/global.h create mode 100644 src/include/onix/interrupt.h create mode 100644 src/include/onix/io.h create mode 100644 src/include/onix/onix.h create mode 100644 src/include/onix/printk.h create mode 100644 src/include/onix/stdarg.h create mode 100644 src/include/onix/stdlib.h create mode 100644 src/include/onix/string.h create mode 100644 src/include/onix/task.h create mode 100644 src/include/onix/types.h create mode 100644 src/include/onix/vprintf.h create mode 100644 src/kernel/debug.c create mode 100644 src/kernel/global.c create mode 100644 src/kernel/handle.asm create mode 100644 src/kernel/interrupt.c create mode 100644 src/kernel/io.asm create mode 100644 src/kernel/main.c create mode 100644 src/kernel/printk.c create mode 100644 src/kernel/schdule.asm create mode 100644 src/kernel/start.asm create mode 100644 src/kernel/task.c create mode 100644 src/lib/assert.c create mode 100644 src/lib/console.c create mode 100644 src/lib/stdlib.c create mode 100644 src/lib/string.c create mode 100644 src/lib/vprintf.c create mode 100644 src/test/types.c diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3fdeeaa --- /dev/null +++ b/.gitignore @@ -0,0 +1,16 @@ + +*.img +*.ini +*.bin +*.map +*.o +*.out +*.zst +*.gz +*.lock +*.vmdk +*.log + +build +bochs/src +bochs/pkg \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..c196eb8 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,60 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "onix - Build and debug kernel", + "type": "cppdbg", + "request": "launch", + "program": "${workspaceFolder}/build/kernel.bin", + "args": [], + "stopAtEntry": false, + "cwd": "${fileDirname}", + "environment": [], + "externalConsole": false, + "MIMode": "gdb", + "miDebuggerServerAddress": "127.0.0.1:1234", + "setupCommands": [ + { + "description": "Enable pretty-printing for gdb", + "text": "-enable-pretty-printing", + "ignoreFailures": true + }, + { + "description": "Set Disassembly Flavor to Intel", + "text": "-gdb-set disassembly-flavor intel", + "ignoreFailures": true + } + ], + "miDebuggerPath": "/usr/bin/gdb" + }, + { + "name": "gcc - Build and debug active file", + "type": "cppdbg", + "request": "launch", + "program": "${fileDirname}/${fileBasenameNoExtension}.out", + "args": [], + "stopAtEntry": false, + "cwd": "${fileDirname}", + "environment": [], + "externalConsole": false, + "MIMode": "gdb", + "setupCommands": [ + { + "description": "Enable pretty-printing for gdb", + "text": "-enable-pretty-printing", + "ignoreFailures": true + }, + { + "description": "Set Disassembly Flavor to Intel", + "text": "-gdb-set disassembly-flavor intel", + "ignoreFailures": true + } + ], + "preLaunchTask": "C/C++: gcc build active file", + "miDebuggerPath": "/usr/bin/gdb" + }, + ] +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..598d26c --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,27 @@ +{ + "files.associations": { + "onix.h": "c", + "types.h": "c", + "cstring": "c", + "string.h": "c", + "sstream": "c", + "typeinfo": "c", + "io.h": "c", + "console.h": "c", + "system_error": "c", + "random": "c", + "algorithm": "c", + "stdarg.h": "c", + "*.tcc": "c", + "cstdio": "c", + "vprintf.h": "c", + "printk.h": "c", + "assert.h": "c", + "debug.h": "c", + "global.h": "c", + "task.h": "c", + "interrupt.h": "c", + "vector": "c", + "stdlib.h": "c" + } +} \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..ac72a87 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,51 @@ +{ + "tasks": [ + { + "type": "cppbuild", + "label": "C/C++: gcc 生成活动文件", + "command": "/usr/bin/gcc", + "args": [ + "-fdiagnostics-color=always", + "-m32", + "-g", + "-I${workspaceFolder}/src/include", + "${file}", + "-o", + "${fileDirname}/${fileBasenameNoExtension}.out" + ], + "options": { + "cwd": "${fileDirname}" + }, + "problemMatcher": [ + "$gcc" + ], + "group": { + "kind": "build", + "isDefault": true + }, + "detail": "调试器生成的任务。" + }, + { + "type": "cppbuild", + "label": "C/C++: clang 生成活动文件", + "command": "/usr/bin/clang", + "args": [ + "-fcolor-diagnostics", + "-fansi-escape-codes", + "-g", + "${file}", + "-o", + "${fileDirname}/${fileBasenameNoExtension}" + ], + "options": { + "cwd": "${fileDirname}" + }, + "problemMatcher": [ + "$gcc" + ], + "group": "build", + "detail": "编译器: /usr/bin/clang" + } + ], + "version": "2.0.0" +} \ No newline at end of file diff --git a/bochs/PKGBUILD b/bochs/PKGBUILD new file mode 100644 index 0000000..849db2e --- /dev/null +++ b/bochs/PKGBUILD @@ -0,0 +1,67 @@ +# 用于调试 C 程序 以下内容是从 Archlinux AUR 包和 bochs 包抠出来的,专门用于 32 位系统的调试, +# 默认的是 64 位操作系统所以会有一些问题 +# 安装使用 makepkg -si + +pkgname=bochs-gdb +pkgver=2.7 +pkgrel=1 +pkgdesc="A portable x86 PC emulation software package with gdbstub" +arch=('x86_64') +url="http://bochs.sourceforge.net/" +license=('LGPL') +depends=('gcc-libs' 'libxrandr' 'libxpm' 'gtk2') +source=("http://downloads.sourceforge.net/sourceforge/bochs/bochs-$pkgver.tar.gz") +sha256sums=('a010ab1bfdc72ac5a08d2e2412cd471c0febd66af1d9349bc0d796879de5b17a') + +prepare() { + cd "$srcdir/bochs-$pkgver" + # 4.X kernel is basically 3.20 + sed -i 's/2\.6\*|3\.\*)/2.6*|3.*|4.*)/' configure* +} + +build() { + cd "$srcdir/bochs-$pkgver" + + ./configure \ + --prefix=/usr \ + --without-wx \ + --with-x11 \ + --with-x \ + --with-term \ + --disable-docbook \ + --enable-cpu-level=6 \ + --enable-fpu \ + --enable-3dnow \ + --enable-disasm \ + --enable-long-phy-address \ + --enable-disasm \ + --enable-pcidev \ + --enable-usb \ + --enable-all-optimizations \ + --enable-gdb-stub \ + --with-nogui \ + --enable-plugins \ + # --enable-smp \ + # --enable-x86-debugger \ + # --enable-debugger \ + # --enable-x86-64 \ + # --enable-avx \ + # --enable-evex \ + sed -i 's/^LIBS = /LIBS = -lpthread/g' Makefile + make -j 1 +} + +package() { + cd "$srcdir/bochs-$pkgver" + make DESTDIR="$pkgdir" install + install -Dm644 .bochsrc "$pkgdir/etc/bochsrc-sample.txt" + + cd "$pkgdir/usr/bin/" + mv bochs bochs-gdb + rm -rf bochs-gdb-a20 + rm bximage + cd "$pkgdir/usr/" + rm -rfv share + cd "$pkgdir" + rm -rfv etc +} \ No newline at end of file diff --git a/bochs/bochsrc b/bochs/bochsrc new file mode 100644 index 0000000..f32dd4d --- /dev/null +++ b/bochs/bochsrc @@ -0,0 +1,60 @@ +# configuration file generated by Bochs +plugin_ctrl: unmapped=true, biosdev=true, speaker=true, extfpuirq=true, parallel=true, serial=true, iodebug=true, pcidev=false, usb_uhci=false +config_interface: textconfig +# 设置有gui的debug +display_library: x, options="gui_debug" +memory: host=32, guest=32 +romimage: file="/usr/share/bochs/BIOS-bochs-latest", address=0x00000000, options=none +vgaromimage: file="/usr/share/bochs/VGABIOS-lgpl-latest" +# 设置硬盘启动 +boot: disk +floppy_bootsig_check: disabled=0 +floppya: type=1_44 +# no floppyb +ata0: enabled=true, ioaddr1=0x1f0, ioaddr2=0x3f0, irq=14 +# 硬盘启动路径设置 +ata0-master: type=disk, path="../build/master.img", mode=flat +ata0-slave: type=none +ata1: enabled=true, ioaddr1=0x170, ioaddr2=0x370, irq=15 +ata1-master: type=none +ata1-slave: type=none +ata2: enabled=false +ata3: enabled=false +optromimage1: file=none +optromimage2: file=none +optromimage3: file=none +optromimage4: file=none +optramimage1: file=none +optramimage2: file=none +optramimage3: file=none +optramimage4: file=none +pci: enabled=1, chipset=i440fx, slot1=none, slot2=none, slot3=none, slot4=none, slot5=none +vga: extension=vbe, update_freq=5, realtime=1, ddc=builtin +cpu: count=1:1:1, ips=4000000, quantum=16, model=bx_generic, reset_on_triple_fault=1, cpuid_limit_winnt=0, ignore_bad_msrs=1, mwait_is_nop=0 +cpuid: level=6, stepping=3, model=3, family=6, vendor_string="AuthenticAMD", brand_string="AMD Athlon(tm) processor" +cpuid: mmx=true, apic=xapic, simd=sse2, sse4a=false, misaligned_sse=false, sep=true +cpuid: movbe=false, adx=false, aes=false, sha=false, xsave=false, xsaveopt=false, avx_f16c=false +cpuid: avx_fma=false, bmi=0, xop=false, fma4=false, tbm=false, x86_64=true, 1g_pages=false +cpuid: pcid=false, fsgsbase=false, smep=false, smap=false, mwait=true +print_timestamps: enabled=0 +debugger_log: - +magic_break: enabled=1 +port_e9_hack: enabled=0 +private_colormap: enabled=0 +clock: sync=none, time0=local, rtc_sync=0 +# no cmosimage +log: - +logprefix: %t%e%d +debug: action=ignore +info: action=report +error: action=report +panic: action=ask +keyboard: type=mf, serial_delay=250, paste_delay=100000, user_shortcut=none +mouse: type=ps2, enabled=false, toggle=ctrl+mbutton +speaker: enabled=true, mode=system +parport1: enabled=true, file=none +parport2: enabled=false +com1: enabled=true, mode=null +com2: enabled=false +com3: enabled=false +com4: enabled=false diff --git a/bochs/bochsrc.gdb b/bochs/bochsrc.gdb new file mode 100644 index 0000000..24564af --- /dev/null +++ b/bochs/bochsrc.gdb @@ -0,0 +1,55 @@ +# configuration file generated by Bochs +plugin_ctrl: pcidev=false, speaker=true, parallel=true, biosdev=true, extfpuirq=true, usb_uhci=false, serial=true, unmapped=true +config_interface: textconfig +display_library: x +memory: host=32, guest=32 +romimage: file="/usr/share/bochs/BIOS-bochs-latest", address=0x00000000, options=none +vgaromimage: file="/usr/share/bochs/VGABIOS-lgpl-latest" +boot: disk +floppy_bootsig_check: disabled=0 +floppya: type=1_44 +# no floppyb +ata0: enabled=true, ioaddr1=0x1f0, ioaddr2=0x3f0, irq=14 +ata0-master: type=disk, path="../build/master.img", mode=flat +ata0-slave: type=none +ata1: enabled=true, ioaddr1=0x170, ioaddr2=0x370, irq=15 +ata1-master: type=none +ata1-slave: type=none +ata2: enabled=false +ata3: enabled=false +optromimage1: file=none +optromimage2: file=none +optromimage3: file=none +optromimage4: file=none +optramimage1: file=none +optramimage2: file=none +optramimage3: file=none +optramimage4: file=none +pci: enabled=1, chipset=i440fx, slot1=none, slot2=none, slot3=none, slot4=none, slot5=none +vga: extension=vbe, update_freq=5, realtime=1, ddc=builtin +cpu: count=1, ips=4000000, model=bx_generic, reset_on_triple_fault=1, cpuid_limit_winnt=0, ignore_bad_msrs=1, mwait_is_nop=0 +cpuid: level=6, stepping=3, model=3, family=6, vendor_string="AuthenticAMD", brand_string="AMD Athlon(tm) processor" +cpuid: mmx=true, apic=xapic, simd=sse2, sse4a=false, misaligned_sse=false, sep=true +cpuid: movbe=false, adx=false, aes=false, sha=false, xsave=false, xsaveopt=false, smep=false +cpuid: smap=false, mwait=true +print_timestamps: enabled=0 +gdbstub: enabled=1, port=1234, text_base=0, data_base=0, bss_base=0 +port_e9_hack: enabled=0 +private_colormap: enabled=0 +clock: sync=none, time0=local, rtc_sync=0 +# no cmosimage +log: - +logprefix: %t%e%d +debug: action=ignore +info: action=report +error: action=report +panic: action=ask +keyboard: type=mf, serial_delay=250, paste_delay=100000, user_shortcut=none +mouse: type=ps2, enabled=false, toggle=ctrl+mbutton +speaker: enabled=true, mode=system +parport1: enabled=true, file=none +parport2: enabled=false +com1: enabled=true, mode=null +com2: enabled=false +com3: enabled=false +com4: enabled=false diff --git a/src/Makefile b/src/Makefile new file mode 100644 index 0000000..936ea63 --- /dev/null +++ b/src/Makefile @@ -0,0 +1,106 @@ +BUILD_PATH:=../build +SRC:= ./ + +# 内核的执行位置 +KERNEL_ENTRY_POINT := 0x10000 + +# 内核编译 .c文件需要传递给gcc的变量 +CFLAG := -m32 # 32位程序 +CFLAG += -fno-builtin # 不需要gcc内置函数(比如memcpy, 需要我们自己写) +CFLAG += -nostdinc # 不需要标准头文件 +CFLAG += -fno-pic # 不需要位置无关的代码 +CFLAG += -fno-pie # 不需要位置无关的可执行程序 +CFLAG += -nostdlib # 不需要标准库 +CFLAG += -fno-stack-protector # 不需要栈保护 +CFLAG += -fno-common +CFLAG := $(strip ${CFLAG}) + +DEBUG := -g # 需要debug +INCLUDE := -I$(SRC)/include # 头文件引入目录 + + +# bootloader的编译 +$(BUILD_PATH)/boot/%.bin:$(SRC)/boot/%.asm + $(shell mkdir -p $(dir $@)) + nasm -f bin $< -o $@ + +# kernel还有其他asm的编译, 把start.asm 编译成elf格式的 start.o +$(BUILD_PATH)/%.o:$(SRC)/%.asm + $(shell mkdir -p $(dir $@)) + nasm -f elf32 $(DEBUG) $< -o $@ + +# kernel.c还有其他 .c的编译 +$(BUILD_PATH)/%.o:$(SRC)/%.c + $(shell mkdir -p $(dir $@)) + gcc $(CFLAG) $(DEBUG) $(INCLUDE) -c $< -o $@ + +# kernel的编译, 需要依赖上面的 start.o +# 使用链接器将所有依赖的($^)的文件链接成一个静态链接的文件 +# 且重新编排指定程序的入口地址(_start符号)为 KERNEL_ENTRY_POINT 定义的地址,并将生成的可执行文件输出为 $@ 定义的文件名 +$(BUILD_PATH)/kernel.bin:$(BUILD_PATH)/kernel/start.o \ + $(BUILD_PATH)/kernel/main.o\ + $(BUILD_PATH)/kernel/io.o\ + $(BUILD_PATH)/lib/string.o\ + $(BUILD_PATH)/lib/console.o\ + $(BUILD_PATH)/lib/vprintf.o\ + $(BUILD_PATH)/kernel/printk.o\ + $(BUILD_PATH)/lib/assert.o\ + $(BUILD_PATH)/kernel/debug.o\ + $(BUILD_PATH)/kernel/global.o\ + $(BUILD_PATH)/kernel/task.o\ + $(BUILD_PATH)/kernel/schdule.o\ + $(BUILD_PATH)/kernel/handle.o\ + $(BUILD_PATH)/kernel/interrupt.o\ + $(BUILD_PATH)/lib/stdlib.o\ + + $(shell mkdir -p $(dir $@)) + ld -m elf_i386 -static $^ -o $@ -Ttext $(KERNEL_ENTRY_POINT) + +# elf文件从磁盘进入内存还需要特殊处理, 使用这种方式, 直接读入内存 +# objcopy -O binary 输出文件中只包含了输入文件的纯二进制数据,没有任何 ELF 文件格式的头部信息 +# 如数据段, 代码段等 +$(BUILD_PATH)/system.bin: $(BUILD_PATH)/kernel.bin + objcopy -O binary $< $@ + +# 导出kernel的符号表 +$(BUILD_PATH)/system.map: $(BUILD_PATH)/kernel.bin + nm $< | sort > $@ + + +# 创建镜像文件 +$(BUILD_PATH)/master.img: $(BUILD_PATH)/boot/boot.bin \ + $(BUILD_PATH)/boot/loader.bin \ + $(BUILD_PATH)/system.bin \ + $(BUILD_PATH)/system.map + + yes | bximage -q -hd=16 -func=create -sectsize=512 -imgmode=flat $@ + dd if=$(BUILD_PATH)/boot/boot.bin of=$@ bs=512 count=1 conv=notrunc + dd if=$(BUILD_PATH)/boot/loader.bin of=$@ bs=512 seek=2 count=4 conv=notrunc + dd if=$(BUILD_PATH)/system.bin of=$@ bs=512 seek=10 count=200 conv=notrunc + +# 清理 +.PHONY:clean +clean: + @rm -rf $(BUILD_PATH) + +test: $(BUILD_PATH)/master.img + +# 启动系统 +.PHONY:bochs +bochs: $(BUILD_PATH)/master.img + @rm -rf $(BUILD_PATH)/*lock + bochs -q -f ../bochs/bochsrc + +# 启动系统 +.PHONY:bochsg +bochsg: $(BUILD_PATH)/master.img + @rm -rf $(BUILD_PATH)/*lock + bochs-gdb -q -f ../bochs/bochsrc.gdb + +.PHONY: qemu +qemu: $(BUILD_PATH)/master.img + qemu-system-i386 \ + -s -S \ + -m 64M \ + -boot c \ + -hda $< \ No newline at end of file diff --git a/src/boot/boot.asm b/src/boot/boot.asm new file mode 100644 index 0000000..976b5fe --- /dev/null +++ b/src/boot/boot.asm @@ -0,0 +1,280 @@ +; 声明这段代码的位置运行时会在0x7c00, 直接取址会加上0x7c00 +; xchg bx, bx ; bochs 的魔数, 代码执行到这里会停下 + +[org 0x7c00] + +start: + init: + ; 设置屏幕模式微文本模式, 清除屏幕 + mov ax, 3 + int 0x10 + + ; 初始化段寄存器 + mov ax, 0 + mov bx, ax + mov cx, ax + mov dx, ax + + mov ds, ax + mov ss, ax + mov es, ax + + mov si, ax + mov di, ax + + ; 修改栈顶为0x7c00, 使其向下增长 + mov sp, 0x7c00 + + ; 读取loader.bin + mov edi, 0x1000 ; 读取到目标内存地址(32位地址空间) + mov ebx, 2 ; 从第 n 个扇区开始读(32位 扇区最大有 2的27次方个) + mov cl, 4 ; 读 1个 扇区(8位 每次最多读取256个扇区) + call read_disk + + ; 校验上方读入的数据 + cmp word es:[0x1000], 0xa0a0 + jnz error + + ; mov edi, 0x1000 ; 把內存中什么地方的数据写出来 + ; mov ebx, 1 ; 从第 n 个扇区开始写(32位 扇区最大有 2的27次方个) + ; mov cl, 1 ; 写 1个 扇区(8位 每次最多读取256个扇区) + ; call write_disk + + mov si, booting + call print + + + jmp 0x1002 + + + ; 阻塞, 一直跳转到当前行 + jmp $ + ret + + +read_disk: + push edx + push eax + + ; ecx寄存器校验, 使其最大值为256 + and ecx,0b1111_1111 + + ; 设置读取扇区的数量 0x1f2 + mov dx, 0x1f2 + mov al, cl ; cl:1, al:1 + out dx, al ; out 0x1f2, 1 + + mov eax, ebx ; eax: 0 + + ; 起始扇区的 0~7位 设置 0x1f3 + inc dx + out dx, al ; al: 0 + + ; 起始扇区的 8~15位 设置 0x1f4 + inc dx + shr eax, 8 + out dx, al ; al:0 + + ; 起始扇区的 16~23位 设置 0x1f5 + inc dx + shr eax, 8 + out dx, al ; al:0 + + ; 起始扇区的 24~27位 设置 0x1f6 + inc dx + shr eax, 8 ; al: 0 + + ; 高4位设为0, 低四位保持不变 + and al, 0b0000_1111 ; al: 0000_0000 + + ; 读取模式为 LBA模式 + or al, 0b1110_0000 ; al: 1110_0000 + out dx, al + + ; 从硬盘读 0x1f7 + inc dx + mov al, 0x20 ; al: 0x20 + + out dx, al + + + ; 等待读取完毕 + call .waits + ; 读取到指定为止 + call .reads + jmp .end + + + .reads: + push cx + ; 读取每个扇区的512字节, 每次读2字节读256次 + mov cx, 256 + mov dx, 0x1f0 + + .readw: + in ax, dx + jmp $+2 + jmp $+2 + jmp $+2 + mov es:[edi], ax + add edi, 2 + loop .readw + pop cx + ; 如果读取了多个扇区 继续循环 + loop .reads + ret + + ; 循环检查磁盘状态 + .waits: + mov dx, 0x1f7 + in al,dx + jmp $+2 + jmp $+2 + jmp $+2 + + ; 如果 第三位是1, 说明准备好了 + and al, 0b1000_1000 + cmp al, 0b0000_1000 + jnz .waits + ret + + .end: + pop eax + pop edx + ret + + + +write_disk: + push edx + push eax + + ; ecx寄存器校验, 使其最大值为256 + and ecx,0b1111_1111 + + ; 设置写入扇区的数量 0x1f2 + mov dx, 0x1f2 + mov al, cl ; cl:1, al:1 + out dx, al ; out 0x1f2, 1 + + mov eax, ebx ; eax: 0 + + ; 起始扇区的 0~7位 设置 0x1f3 + inc dx + out dx, al ; al: 0 + + ; 起始扇区的 8~15位 设置 0x1f4 + inc dx + shr eax, 8 + out dx, al ; al:0 + + ; 起始扇区的 16~23位 设置 0x1f5 + inc dx + shr eax, 8 + out dx, al ; al:0 + + ; 起始扇区的 24~27位 设置 0x1f6 + inc dx + shr eax, 8 ; al: 0 + + ; 高4位设为0, 低四位保持不变 + and al, 0b0000_1111 ; al: 0000_0000 + + ; 模式为 LBA模式 + or al, 0b1110_0000 ; al: 1110_0000 + out dx, al + + ; 从硬盘设置写 0x1f7 + inc dx + mov al, 0x30 ; al: 0x20 + + out dx, al + + + ; 写入到指定为止 + call .writes + jmp .end + + + .writes: + ; 等待硬盘空闲 + call .waits + + push cx + ; 写入每个扇区的512字节, 每次写2字节读256次 + mov cx, 256 + mov dx, 0x1f0 + + .writew: + mov ax, es:[edi] + out dx, ax + jmp $+2 + jmp $+2 + jmp $+2 + mov es:[edi], ax + add edi, 2 + loop .writew + pop cx + ; 如果写了多个扇区 继续循环 + loop .writes + ret + + ; 循环检查磁盘状态 + .waits: + mov dx, 0x1f7 + in al,dx + jmp $+2 + jmp $+2 + jmp $+2 + + ; 如果 第三位是1, 说明准备好了 + and al, 0b1000_0000 ; 写入,不需要校验第三位了, 秩序要校验第七位 + cmp al, 0b0000_0000 + jnz .waits + ret + + .end: + pop eax + pop edx + ret + + + +; 一个print函数, 调用时 把字符串的地址 mov到si寄存器即可 +print: + push ax + mov ah, 0x0e + .show: + mov al,[si] + cmp al, 0 + jz .end + int 0x10 + inc si + jmp .show + .end: + pop ax + ret + + +error: + mov si, .error_msg + call print + hlt ; 让 CPU 停止 + jmp $ + ret + .error_msg db "Booting Error!!!", 10, 13, 0 + + + + +booting: + db "Booting Start ...", 10, 13, 0 ; 结尾 \n \r 0 + + +padding: + ; 填充数据, 引导扇区必须为512字节, 最后两个字节是魔数, 除了代码之外必须用0填充 + times 510 - ($ - $$) db 0 ; 最后俩字节是0xaa55, 所以一共需要填充 510 - (当前行位置 - 开始的的位置) = 510 - 代码段的大小 + + +; 魔数 +dw 0xaa55 \ No newline at end of file diff --git a/src/boot/loader.asm b/src/boot/loader.asm new file mode 100644 index 0000000..d9c8fed --- /dev/null +++ b/src/boot/loader.asm @@ -0,0 +1,288 @@ +[org 0x1000] + +; 校验用的, 这里会被读到 0x1000处 +dw 0xa0a0 + +mov si, loading_log +call print +call detect_memory +call prepare_protect_mode + +jmp $ + + + +detect_memory: + ; 设置检测内存的buffer位置 + mov ax, 0 + mov es, ax + mov edi, mem_task_buffer + + ; 固定签名 + mov edx, 0x534d4150 + + ; 置为0, 每次系统调用会修改这个寄存器 + xor ebx, ebx + + ; 保存的目的地 + mov di, mem_task_buffer + + .next: + ; 子功能号 + mov eax, 0xe820 + ; ards 结构的大小 (字节) + mov cx, 20 + ; 调用中断 + int 0x15 + ; 如果cf置位, 表示出错了 + jc error + + ; 计算下一个内存结构体保存的首地址 + add di, cx + + inc word [mem_task_count] + + ; 不为0 说明检查未完成 + cmp ebx, 0 + jnz .next + + + mov si, detect_memory_log + call print + ret + + ; ; 循环结构体内的值(我们只读取低32位相关的信息, 高32位的暂时不需要) + ; mov cx, [mem_task_count] + ; ; 初始偏移量 + ; mov si, 0 + ; .show + ; mov eax, [mem_task_buffer + si] ; 基地址 低32位 + ; mov ebx, [mem_task_buffer + si + 8] ; 内存长度的低32位 + ; mov edx, [mem_task_buffer + si + 16] ; 本段内存类型 1: 可以使用, 2: 内存使用或者被保留中, 其他: 未定义 + + ; add si, 20 + ; ; xchg bx, bx ; bochs 的魔数, 代码执行到这里会停下 + ; loop .show + + +print: + push ax + mov ah, 0x0e + .show: + mov al,[si] + cmp al, 0 + jz .end + int 0x10 + inc si + jmp .show + .end: + pop ax + ret + +prepare_protect_mode: + + cli ; 关闭中断 + + ; ; 打开 A20线 + ; mov al, 0xdd + ; out 0x64, al + + ; 打开 A20 线 + in al, 0x92 + or al, 0b10 + out 0x92, al + + ; 进入保护模式 + mov eax, cr0 + or eax, 0b1 + mov cr0, eax + + ; 加载 gdt + lgdt [gdt_ptr] + + ; 长跳转, 刷新缓存, 跳进保护模式 + jmp dword code_selecter:protect_mode + + ret + +[bits 32] +protect_mode: + ; 这里已经进入了保护模式 + + ; 初始化段寄存器(将代码段之外的寄存器都设置为 数据段) + mov ax, data_selecter + mov ds, ax + mov ss, ax + mov es, ax + mov gs, ax + mov fs, ax + + ; 修改自己设置的栈顶 + mov esp, 0x10000 + + ; 进入保护模式之后, 实模式的print就不能用了 + mov byte [0xb8000], 'P' + + ; 加载kernel, 把从10扇区 读取200个扇区的数据, 读到0x10000 + mov edi, 0x10000 ; + mov ebx, 10 + mov cl, 200 + call read_disk + + jmp dword code_selecter:0x10000 + ud2 ; 执行到这里直接出错(不可能执行到这里) + jmp $ + +read_disk: + push edx + push eax + + ; ecx寄存器校验, 使其最大值为256 + and ecx,0b1111_1111 + + ; 设置读取扇区的数量 0x1f2 + mov dx, 0x1f2 + mov al, cl ; cl:1, al:1 + out dx, al ; out 0x1f2, 1 + + mov eax, ebx ; eax: 0 + + ; 起始扇区的 0~7位 设置 0x1f3 + inc dx + out dx, al ; al: 0 + + ; 起始扇区的 8~15位 设置 0x1f4 + inc dx + shr eax, 8 + out dx, al ; al:0 + + ; 起始扇区的 16~23位 设置 0x1f5 + inc dx + shr eax, 8 + out dx, al ; al:0 + + ; 起始扇区的 24~27位 设置 0x1f6 + inc dx + shr eax, 8 ; al: 0 + + ; 高4位设为0, 低四位保持不变 + and al, 0b0000_1111 ; al: 0000_0000 + + ; 读取模式为 LBA模式 + or al, 0b1110_0000 ; al: 1110_0000 + out dx, al + + ; 从硬盘读 0x1f7 + inc dx + mov al, 0x20 ; al: 0x20 + + out dx, al + + + ; 等待读取完毕 + call .waits + ; 读取到指定为止 + call .reads + jmp .end + + + .reads: + push cx + ; 读取每个扇区的512字节, 每次读2字节读256次 + mov cx, 256 + mov dx, 0x1f0 + + .readw: + in ax, dx + jmp $+2 + jmp $+2 + jmp $+2 + mov es:[edi], ax + add edi, 2 + loop .readw + pop cx + ; 如果读取了多个扇区 继续循环 + loop .reads + ret + + ; 循环检查磁盘状态 + .waits: + mov dx, 0x1f7 + in al,dx + jmp $+2 + jmp $+2 + jmp $+2 + + ; 如果 第三位是1, 说明准备好了 + and al, 0b1000_1000 + cmp al, 0b0000_1000 + jnz .waits + ret + + .end: + pop eax + pop edx + ret + +error: + mov si, .error_msg + call print + hlt ; 让 CPU 停止 + jmp $ + ret + .error_msg db "Loader Error!!!", 10, 13, 0 + +mem_task_count: + dw 0 + +; 用来存放检测内存结果的结构体 +mem_task_buffer: + times 20*10 db 0 + + +; 定义gdt +base equ 0 ; 段地址 +limit equ 0xfffff ; 段界限数量, 和粒度搭配 如果粒度是4k, 那么 0xfffff*4096 最大的大小 + +; 段选择子 +code_selecter equ 1 << 3 ; 选择子 前13位 代表索引, 后面3位暂时不需要 +data_selecter equ 2 << 3 ; + +; gdt表的指针 +gdt_ptr: + dw gdt_end - gdt_start - 1 ; 16位表示gdt的总大小, 每个段描述符8字节, 2**16/8=8192刚好可以表示8292; (2的13次方个刚好是段选择子的最大索引) + dd gdt_start ; gdt表起始位置 + + +; gdt 表的详情 +gdt_start: + gdt_base: + times 8 db 0 ; 第0个段描述符 不能被选择也不可使用和访问, 否则会cpu发出异常 + gdt_code: + dw limit ; 段界限 dw 0~15 + dw base ; 段基址 dw 0~15 + db base >> 16 ; 段基址16~23 + db 0b_1_00_1_1010 ; P_DPL_S_TYPE(代码段, 非依从, 可读, 没有被cpu访问过) + db 0b_1_1_0_0_0000 | limit >> 16 ; G_D/B_L_AVL | 段界限16~19 + db base >> 24 ; 段基址24~31 + gdt_data: + dw limit ; 段界限 dw 0~15 + dw base ; 段基址 dw 0~15 + db base >> 16 ; 段基址16~23 + db 0b_1_00_1_0010 ; P_DPL_S_TYPE(0010 表示"数据段,数据可写, 数据向上拓展") + db 0b_1_1_0_0_0000 | limit >> 16 ; G_D/B_L_AVL | 段界限16~19 + db base >> 24 ; 段基址24~31 + gdt_padding: + times 2<<16-($-gdt_start) db 0 +gdt_end: + + + + +loading_log: + db 'Loader Start', 13, 10, 0 + +detect_memory_log: + db 'detect_memory end', 13, 10, 0 + + diff --git a/src/include/onix/assert.h b/src/include/onix/assert.h new file mode 100644 index 0000000..03c8e0b --- /dev/null +++ b/src/include/onix/assert.h @@ -0,0 +1,14 @@ +#ifndef ONIX_ASSERT_H +#define ONIX_ASSERT_H + +void assertion_failure(char *exp, char *file, char *base, int line); + +#define assert(exp) \ + if (exp) \ + ; \ + else \ + assertion_failure(#exp, __FILE__, __BASE_FILE__, __LINE__) + +void panic(const char *fmt, ...); + +#endif \ No newline at end of file diff --git a/src/include/onix/console.h b/src/include/onix/console.h new file mode 100644 index 0000000..b4985c0 --- /dev/null +++ b/src/include/onix/console.h @@ -0,0 +1,48 @@ +#ifndef ONIX_CONSOLE_H +#define ONIX_CONSOLE_H + +#include +#include +#include + + + +#define CRT_ADDR_PORT 0x3d4 // crt位置寄存器 port +#define CRT_DATA_PORT 0x3d5 // crt数据寄存器 port + +#define CRT_CURSOR_H_VALUE 0xe // 当前光标所在字符距离0xb800 高位 value +#define CRT_CURSOR_L_VALUE 0xf // 当前光标所在字符距离0xb800 低位 value + +#define CRT_MEM_H_VALUE 0xC // 当前屏幕字符起始位置距离0xb800 - 高位 +#define CRT_MEM_L_VALUE 0xD // 当前屏幕显示字符起始位置距离0xb800 - 低位 + +#define CRT_MEM_START 0xB8000 // 显卡内存起始位置 +#define CRT_MEM_SIZE 0x4000 // 显卡内存大小 +#define CRT_MEM_END (CRT_MEM_START + CRT_MEM_SIZE) // 显卡内存结束位置 + +#define WIDTH 80 // 屏幕文本列数 +#define HEIGHT 25 // 屏幕文本行数 + +#define ROW_SIZE (WIDTH * 2) // 每行字节数 +#define SCR_SIZE (ROW_SIZE * HEIGHT) // 一个屏幕容纳的字节数 + + +// 特殊字符 +#define NUL 0x00 +#define ENQ 0x05 +#define ESC 0x1B // ESC +#define BEL 0x07 // \a +#define BS 0x08 // \b 退格键 +#define HT 0x09 // \t +#define LF 0x0A // \n 换行 +#define VT 0x0B // \v +#define FF 0x0C // \f +#define CR 0x0D // \r 回到开头的位置 +#define DEL 0x7F // 删除当前位置的字符, 但是不退格 + + +console_init(); +console_clear(); +console_write(u8* buf, usize count); + +#endif \ No newline at end of file diff --git a/src/include/onix/debug.h b/src/include/onix/debug.h new file mode 100644 index 0000000..083beff --- /dev/null +++ b/src/include/onix/debug.h @@ -0,0 +1,11 @@ +#ifndef ONIX_DEBUG_H +#define ONIX_DEBUG_H + +void debugk(char *file, int line, const char *fmt, ...); + +#define BMB asm volatile("xchgw %bx, %bx") // bochs magic breakpoint +#define DEBUGK(fmt, args...) debugk(__BASE_FILE__, __LINE__, fmt, ##args) + +// #define LOGK(fmt, args...) DEBUGK(fmt, ##args) + +#endif \ No newline at end of file diff --git a/src/include/onix/global.h b/src/include/onix/global.h new file mode 100644 index 0000000..1cd9ba1 --- /dev/null +++ b/src/include/onix/global.h @@ -0,0 +1,45 @@ +#ifndef ONIX_GLOBAL_H +#define ONIX_GLOBAL_H +#include + +// 我们只使用128个, 当然全局描述符最大可以有 2**13 8192个 +#define GDT_SIZE 128 +#define CODE_SELECT 1 << 3 // 选择子 +#define DATA_SELECT 2 << 3 // 选择子 + +// 全局描述符 +typedef struct descriptor_t /* 共 8 个字节 */ +{ + unsigned short limit_low; // 段界限 0 ~ 15 位 + unsigned int base_low : 24; // 基地址 0 ~ 23 位 16M + unsigned char type : 4; // 段类型 + unsigned char segment : 1; // 1 表示代码段或数据段,0 表示系统段 + unsigned char DPL : 2; // Descriptor Privilege Level 描述符特权等级 0 ~ 3 + unsigned char present : 1; // 存在位,1 在内存中,0 在磁盘上 + unsigned char limit_high : 4; // 段界限 16 ~ 19; + unsigned char available : 1; // 该安排的都安排了,送给操作系统吧 + unsigned char long_mode : 1; // 64 位扩展标志 + unsigned char big : 1; // 32 位 还是 16 位; + unsigned char granularity : 1; // 粒度 4KB 或 1B + unsigned char base_high; // 基地址 24 ~ 31 位 +} _packed descriptor_t; + +// 段选择子 +typedef struct selector_t +{ + u8 RPL : 2; // Request Privilege Level + u8 TI : 1; // Table Indicator + u16 index : 13; +} selector_t; + + +// 全局描述符表指针 +typedef struct pointer_t +{ + u16 limit; + u32 base; +} _packed pointer_t; + + +void gdt_init(); +#endif diff --git a/src/include/onix/interrupt.h b/src/include/onix/interrupt.h new file mode 100644 index 0000000..0d89c12 --- /dev/null +++ b/src/include/onix/interrupt.h @@ -0,0 +1,63 @@ +#ifndef ONIX_INTERRUPT_H +#define ONIX_INTERRUPT_H + +#include +#include + +// 在实模式中0x000~0x3ff是中断向量表, 一共1024字节, 每个中断描述符占4个字节 段地址2个字节, 偏移地址2个字节 一共可以存放256个 +// 保护模式下, 一个中断描述符 gate_t占 8个字节, 除了函数的段内偏移地址, 还有一些属性 + +#define IDT_SIZE 256 +#define ENTRY_SIZE 0x30 // 我们实现的中断数量 +#define LOGK(fmt, args...) DEBUGK(fmt, ##args) + +#define PIC_M_CTRL 0x20 // 主片的控制端口 +#define PIC_M_DATA 0x21 // 主片的数据端口 +#define PIC_S_CTRL 0xa0 // 从片的控制端口 +#define PIC_S_DATA 0xa1 // 从片的数据端口 +#define PIC_EOI 0x20 // 通知中断控制器中断结束 + + +typedef struct gate_t{ + u16 offset0; // 段内偏移 0 ~ 15 位 + u16 selector; // 代码段选择子 + u8 reserved; // 保留不用 + u8 type : 4; // 任务门/中断门/陷阱门 + u8 segment : 1; // segment = 0 表示系统段 + u8 DPL : 2; // 使用 int 指令访问的最低权限 + u8 present : 1; // 是否有效 + u16 offset1; // 段内偏移 16 ~ 31 位 +} _packed gate_t; + + +// 初始化保护模式的中断向量表 +void interrupt_init(); + + +static char *messages[] = { + "#DE Divide Error\0", + "#DB RESERVED\0", + "-- NMI Interrupt\0", + "#BP Breakpoint\0", + "#OF Overflow\0", + "#BR BOUND Range Exceeded\0", + "#UD Invalid Opcode (Undefined Opcode)\0", + "#NM Device Not Available (No Math Coprocessor)\0", + "#DF Double Fault\0", + " Coprocessor Segment Overrun (reserved)\0", + "#TS Invalid TSS\0", + "#NP Segment Not Present\0", + "#SS Stack-Segment Fault\0", + "#GP General Protection\0", + "#PF Page Fault\0", + "-- (Intel reserved. Do not use.)\0", + "#MF x87 FPU Floating-Point Error (Math Fault)\0", + "#AC Alignment Check\0", + "#MC Machine Check\0", + "#XF SIMD Floating-Point Exception\0", + "#VE Virtualization Exception\0", + "#CP Control Protection Exception\0", +}; + + +#endif \ No newline at end of file diff --git a/src/include/onix/io.h b/src/include/onix/io.h new file mode 100644 index 0000000..0fce79a --- /dev/null +++ b/src/include/onix/io.h @@ -0,0 +1,51 @@ +#ifndef ONIX_IO_H +#define ONIX_IO_H + +#include + +// 这个文件只是定义了头文件, 真正实现的是 io.asm + +extern u8 in_8(u16 port); // 从端口读出一个字节 +extern u16 in_16(u16 port); // 从端口读出一个字 + + +extern void out_8(u16 port, u8 value); // 写入到 端口内 一个字节 +extern void out_16(u16 port, u8 value); // 写入到 端口内 一个字 + + +#endif + + +// #define CRT_ADDR_PORT 0x3d4 // 位置寄存器 port +// #define CRT_DATA_PORT 0x3d5 // 数据寄存器 port + +// #define CRT_CURSOR_H_VALUE 0xe // 光标高位 value +// #define CRT_CURSOR_L_VALUE 0xf // 光标低位 value + // { + // // 读取光标位置的 高8位 + // out_8(CRT_ADDR_PORT, CRT_CURSOR_H_VALUE); + // u8 pos_h = in_8(CRT_DATA_PORT); + + // // 读取光标位置的 低8位 + // out_8(CRT_ADDR_PORT, CRT_CURSOR_L_VALUE); + // u8 pos_l = in_8(CRT_DATA_PORT); + + // // 获得光标位置 + // u16 pos = (pos_h << 8) | pos_l; + // } + + + // { + // // 设置光标位置到666处 + // u16 pos = 444; + + // // 设置高地址 + // out_8(CRT_ADDR_PORT, CRT_CURSOR_H_VALUE); + // u8 pos_h = pos >> 8; + // out_8(CRT_DATA_PORT, pos_h); + + // // 设置低地址 + // u8 pos_l = pos & 0xff; + // out_8(CRT_ADDR_PORT, CRT_CURSOR_L_VALUE); + // out_8(CRT_DATA_PORT, pos_l); + // } \ No newline at end of file diff --git a/src/include/onix/onix.h b/src/include/onix/onix.h new file mode 100644 index 0000000..cab035e --- /dev/null +++ b/src/include/onix/onix.h @@ -0,0 +1,18 @@ +#ifndef ONIX_H +#define ONIX_H +#include +#include +#include +#include +#include +#include +#include +#include +#include > +#include > +#include > + + +void kernel_init(); + +#endif \ No newline at end of file diff --git a/src/include/onix/printk.h b/src/include/onix/printk.h new file mode 100644 index 0000000..2ec6e76 --- /dev/null +++ b/src/include/onix/printk.h @@ -0,0 +1,7 @@ +#ifndef ONIX_PRINTK_H +#define ONIX_PRINTK_H +#include + +u32 printk(const u8 *fmt, ...); + +#endif \ No newline at end of file diff --git a/src/include/onix/stdarg.h b/src/include/onix/stdarg.h new file mode 100644 index 0000000..3a60b72 --- /dev/null +++ b/src/include/onix/stdarg.h @@ -0,0 +1,10 @@ +#ifndef ONIX_STDARG_H +#define ONIX_STDARG_H + +typedef char *va_list; + +#define va_start(ap, v) (ap = (va_list)&v + sizeof(char *)) +#define va_arg(ap, t) (*(t *)((ap += sizeof(char *)) - sizeof(char *))) +#define va_end(ap) (ap = (va_list)0) + +#endif \ No newline at end of file diff --git a/src/include/onix/stdlib.h b/src/include/onix/stdlib.h new file mode 100644 index 0000000..161d245 --- /dev/null +++ b/src/include/onix/stdlib.h @@ -0,0 +1,6 @@ +#ifndef ONIX_STDLIB_H +#define ONIX_STDLIB_H +#include +void delay (u32 count); +void hang(); +#endif \ No newline at end of file diff --git a/src/include/onix/string.h b/src/include/onix/string.h new file mode 100644 index 0000000..ab61fbf --- /dev/null +++ b/src/include/onix/string.h @@ -0,0 +1,37 @@ +#ifndef ONIX_STRING_H +#define ONIX_STRING_H + +#include + +// 字符串 src copy 到 dest +strcpy(u8* dest, const u8* src); +// 字符串 src copy count 个字符 到 dest +strncpy(u8* dest, const u8* src, usize count); +// 字符串 dest += src +strcat(u8* dest, const u8* src); +// 统计字符串长度 +usize strlen(const u8* str); +// 比较字符的大小, 返回值 -1/0/1 ("acd", "abd") 返回 -1, 因为 acd < abd +i8 strcmp(const u8* lhs, const u8* rhs); +// 左边第一个 指定字符串中找都指定字符所在的指针地址, 如果没有找到, 返回 nullptr +u8* strchr_l(const u8* str, u8 ch); +// 右边第一个 指定字符串中找都指定字符所在的指针地址, 如果没有找到, 返回 nullptr +u8* strchr_r(const u8* str, u8 ch); +// u8* strsep(const u8* str); +// u8* strrsep(const u8* str); + +// 比较指定字节内存 +i8 memcmp(const u8* lhs, const u8* rhs, usize count); +// 将指定区域赋值为 ch +memset(u8* src, u8 ch, usize count); +// 从src copy指定字节的数据 到dest +memcpy(u8* dest, const u8* src, usize count); +// 从内存总找到, 第一个 ch字符 从左到右 +u8* memchr(const u8* src, u8 ch, usize count); + + + +#endif + + + diff --git a/src/include/onix/task.h b/src/include/onix/task.h new file mode 100644 index 0000000..c58ea70 --- /dev/null +++ b/src/include/onix/task.h @@ -0,0 +1,27 @@ +#ifndef ONIX_TASK_H +#define ONIX_TASK_H + +#include + +#define PAGE_SIZE 0x1000 // 每页的内存大小 4k + +typedef u32 target_t(); // 函数的入口地址 + + +typedef struct task_t{ + u8* stack; // 内核线程的栈顶 +} task_t; + +// ABI 需要保存的寄存器(字段顺序 不要变!) +typedef struct task_frame_t{ + u32 edi; + u32 esi; + u32 ebx; + u32 ebp; + void (*eip)(void); // 一个函数指针 +} task_frame_t; + +// 初始化任务的函数 +void task_init(); + +#endif \ No newline at end of file diff --git a/src/include/onix/types.h b/src/include/onix/types.h new file mode 100644 index 0000000..43e05fb --- /dev/null +++ b/src/include/onix/types.h @@ -0,0 +1,36 @@ +#ifndef ONIX_TYPES_H +#define ONIX_TYPES_H + +typedef char i8; +typedef unsigned char u8; +typedef short i16; +typedef unsigned short u16; +typedef int i32; +typedef unsigned int u32; +typedef long long i64; +typedef unsigned long long u64; +typedef i32 isize; +typedef u32 usize; + + +#define bool _Bool +#define true 1 +#define false 0 + + +#define EOF -1 +#define EOS '\0' + +#define nullptr ((void*) 0) + +#define _packed __attribute__((packed)) // 用于定义特殊的结构体, 使用该属性可以使得变量或者结构体成员使用最小的对齐方式 + // 即对变量是一字节对齐, 对域(field)是位对齐 + +// 用于省略函数的栈帧 +#define _ofp __attribute__((optimize("omit-frame-pointer"))) + + + + + +#endif diff --git a/src/include/onix/vprintf.h b/src/include/onix/vprintf.h new file mode 100644 index 0000000..5825be8 --- /dev/null +++ b/src/include/onix/vprintf.h @@ -0,0 +1,9 @@ +#ifndef ONIX_VPRINTF_H +#define ONIX_VPRINTF_H + +#include + +int vsprintf(char *buf, const char *fmt, va_list args); +int sprintf(char *buf, const char *fmt, ...); + +#endif \ No newline at end of file diff --git a/src/kernel/debug.c b/src/kernel/debug.c new file mode 100644 index 0000000..cb7a777 --- /dev/null +++ b/src/kernel/debug.c @@ -0,0 +1,16 @@ +#include +#include +#include +#include + +static char buf[1024]; + +void debugk(char *file, int line, const char *fmt, ...){ + // 先格式化自定义的一些文本信息 + va_list args; + va_start(args, fmt); + vsprintf(buf, fmt, args); + va_end(args); + + printk("[%s] [%d] %s \n", file, line, buf); +} \ No newline at end of file diff --git a/src/kernel/global.c b/src/kernel/global.c new file mode 100644 index 0000000..0841038 --- /dev/null +++ b/src/kernel/global.c @@ -0,0 +1,23 @@ +#include +#include +#include + + +descriptor_t gdt[GDT_SIZE]; // 内核全部的全局描述符表 +pointer_t gdt_ptr; // 内核全局描述符表指针 + +// 初始化全局描述符表 为我们 c语言 的数组 +// 把loader中的全局描述符表 copy 到内核中 +void gdt_init(){ + // 把loader内的的全局描述符表指针加载出来 + asm volatile("sgdt gdt_ptr"); + // 把全部的全局描述符表 copy出来 + memcpy(&gdt, (u8*)gdt_ptr.base, gdt_ptr.limit + 1); + + // + gdt_ptr.base = (usize)&gdt; + gdt_ptr.limit = sizeof(gdt) - 1; + + // 把内核的gdt表加载进去 + asm volatile("lgdt gdt_ptr"); +} \ No newline at end of file diff --git a/src/kernel/handle.asm b/src/kernel/handle.asm new file mode 100644 index 0000000..b6942be --- /dev/null +++ b/src/kernel/handle.asm @@ -0,0 +1,190 @@ +[bits 32] +; 这里调用关系复杂, 解释一下, INTERRUPT_HANDLER这个宏生成和handler_entry_table表里对应的函数, handler_entry_table表里存放呃是宏生成的每个函数的位置的指针 +; 然后interrupt.c语言初始化中断向量表的时候, 把handler_entry_table里面函数的指针, 加载到idt里面, +; 在异常或者中断发生的时候, 有的中断会push进去一个eflag 有的不会, 这里 通过宏整理判断 没有push的都会push进去一点东西, 保证参数数量 统一一点 +; 然后跳入interrupt_entry中执行, 根据中断号, 去调用interrupt.c定义的 handler_table, 然后iret + +; 导入c语言 整理过的中断向量表 +; c语言整理的其实也是下面的 handler_entry_table +extern handler_table + +; 中断发生时栈的布局为: 低地址(栈顶) %1(中断向量号) gp错误码(如果有) ip cs eflags 低高地址(栈底) +section .text + +; ## gp错误码 +; !!注意, 只有硬中断才会压入gp错误码, 而我们手动调用int 不会触发, (ps: int 一个我们没有定义中断处理函数的中断, 会调用0x0d,由我们软中断->硬中断) +; 这个宏主要是为 没有gp错误码的异常, 随便压入一个参数到栈中为了保持中断时的一致,后面容易进行弹栈操作, 然后push到终端处理函数的入口 +; gp错误码的格式 0b0000000000000_01_0, 第1位是判断有内部还是由外部触发, 第2~3位表示是 IDT(01或者11) 还是GDT(00)还是LDT(10) 4~16位表示选择子索引 + +; int 0x80 的异常码 就是 0x00000402 换成2进制就是 10000000_01_0, 其中二进制的10000000 换算16进制就是0x80, +; 由于0x80 我们没有中断处理函数, 手动软中断出发之后, 又会立即硬中断所以会又触发0x0d一般性保护异常, 把异常压入栈 +; 在80286中 gp错误码是16个bit, 现在为了兼容 扩充到了32位 + +; ## eflag ; sti打开中断这个指令会把 if位 置为1, +; 如果使用sti指令 后压入栈的 eflags就是 0x212, 如果关闭中断则压入栈的eflags就变成了0x012, 0x212或者0x012 这俩数, 他们的bit位的第九位存在差别 +; 此时压入栈的是0x212, 而eflag寄存器则是0x12, 因为我们初始化中断描述符的时候, 设置的是中断门 所以第九位被置为了0保存在了eflag寄存器中 + +; 时钟中断时 0x20 这个也没有错误码 +%macro INTERRUPT_HANDLER 2 +interrupt_handler_%1: + xchg bx, bx ; 这时候观察栈, 如果有状态码 +%ifn %2 + push 0x20222202 ; 这个是给没有错误码的调用添加一个自己定义的魔数 +%endif + push %1; 压入宏的中断向量,跳转到中断入口 + jmp interrupt_entry +%endmacro + + +interrupt_entry: + mov eax, [esp] + ; 调用中断处理函数 handler_table 中存储了中断处理函数的指针, 并把 上方push %1 中断号传入中断处理函数的指针(所以这里 *4) + call [handler_table + eax * 4] + ; 弹栈, 弹出 %1, 弹出0x20222202, 后面调用iret + + add esp, 8 + iret + +; 下面的功能, 主要是用 用宏生成代码 +; 异常处理 +INTERRUPT_HANDLER 0x00, 0; divide by zero +INTERRUPT_HANDLER 0x01, 0; debug +INTERRUPT_HANDLER 0x02, 0; non maskable interrupt +INTERRUPT_HANDLER 0x03, 0; breakpoint + +INTERRUPT_HANDLER 0x04, 0; overflow +INTERRUPT_HANDLER 0x05, 0; bound range exceeded +INTERRUPT_HANDLER 0x06, 0; invalid opcode +INTERRUPT_HANDLER 0x07, 0; device not avilable + +INTERRUPT_HANDLER 0x08, 1; double fault +INTERRUPT_HANDLER 0x09, 0; coprocessor segment overrun +INTERRUPT_HANDLER 0x0a, 1; invalid TSS +INTERRUPT_HANDLER 0x0b, 1; segment not present + +INTERRUPT_HANDLER 0x0c, 1; stack segment fault +INTERRUPT_HANDLER 0x0d, 1; general protection fault +INTERRUPT_HANDLER 0x0e, 1; page fault +INTERRUPT_HANDLER 0x0f, 0; reserved + +INTERRUPT_HANDLER 0x10, 0; x87 floating point exception +INTERRUPT_HANDLER 0x11, 1; alignment check +INTERRUPT_HANDLER 0x12, 0; machine check +INTERRUPT_HANDLER 0x13, 0; SIMD Floating - Point Exception + +INTERRUPT_HANDLER 0x14, 0; Virtualization Exception +INTERRUPT_HANDLER 0x15, 1; Control Protection Exception +INTERRUPT_HANDLER 0x16, 0; reserved +INTERRUPT_HANDLER 0x17, 0; reserved + +INTERRUPT_HANDLER 0x18, 0; reserved +INTERRUPT_HANDLER 0x19, 0; reserved +INTERRUPT_HANDLER 0x1a, 0; reserved +INTERRUPT_HANDLER 0x1b, 0; reserved + +INTERRUPT_HANDLER 0x1c, 0; reserved +INTERRUPT_HANDLER 0x1d, 0; reserved +INTERRUPT_HANDLER 0x1e, 0; reserved +INTERRUPT_HANDLER 0x1f, 0; reserved + +; 外中断 +INTERRUPT_HANDLER 0x20, 0; clock 时钟中断 +INTERRUPT_HANDLER 0x21, 0; keyboard 键盘中断 +INTERRUPT_HANDLER 0x22, 0 +INTERRUPT_HANDLER 0x23, 0; com2 串口2 +INTERRUPT_HANDLER 0x24, 0; com1 串口1 +INTERRUPT_HANDLER 0x25, 0 +INTERRUPT_HANDLER 0x26, 0 +INTERRUPT_HANDLER 0x27, 0 +INTERRUPT_HANDLER 0x28, 0; rtc 实时时钟 +INTERRUPT_HANDLER 0x29, 0 +INTERRUPT_HANDLER 0x2a, 0 +INTERRUPT_HANDLER 0x2b, 0 +INTERRUPT_HANDLER 0x2c, 0 +INTERRUPT_HANDLER 0x2d, 0 +INTERRUPT_HANDLER 0x2e, 0; harddisk1 硬盘主通道 +INTERRUPT_HANDLER 0x2f, 0; harddisk2 硬盘从通道 + + +; 下面的数组记录了每个中断入口函数的指针, 每个4个字节 dd +; 这里每个指针就是 INTERRUPT_HANDLER 宏生成的 函数的入口地址 +; 后面还需要再导出handler_entry_table 到c语言 +section .data +global handler_entry_table +handler_entry_table: + dd interrupt_handler_0x00 + dd interrupt_handler_0x01 + dd interrupt_handler_0x02 + dd interrupt_handler_0x03 + dd interrupt_handler_0x04 + dd interrupt_handler_0x05 + dd interrupt_handler_0x06 + dd interrupt_handler_0x07 + dd interrupt_handler_0x08 + dd interrupt_handler_0x09 + dd interrupt_handler_0x0a + dd interrupt_handler_0x0b + dd interrupt_handler_0x0c + dd interrupt_handler_0x0d + dd interrupt_handler_0x0e + dd interrupt_handler_0x0f + dd interrupt_handler_0x10 + dd interrupt_handler_0x11 + dd interrupt_handler_0x12 + dd interrupt_handler_0x13 + dd interrupt_handler_0x14 + dd interrupt_handler_0x15 + dd interrupt_handler_0x16 + dd interrupt_handler_0x17 + dd interrupt_handler_0x18 + dd interrupt_handler_0x19 + dd interrupt_handler_0x1a + dd interrupt_handler_0x1b + dd interrupt_handler_0x1c + dd interrupt_handler_0x1d + dd interrupt_handler_0x1e + dd interrupt_handler_0x1f + + ; 外中断 + dd interrupt_handler_0x20 + dd interrupt_handler_0x21 + dd interrupt_handler_0x22 + dd interrupt_handler_0x23 + dd interrupt_handler_0x24 + dd interrupt_handler_0x25 + dd interrupt_handler_0x26 + dd interrupt_handler_0x27 + dd interrupt_handler_0x28 + dd interrupt_handler_0x29 + dd interrupt_handler_0x2a + dd interrupt_handler_0x2b + dd interrupt_handler_0x2c + dd interrupt_handler_0x2d + dd interrupt_handler_0x2e + dd interrupt_handler_0x2f + + +; [bits 32] +; ; 中断处理函数入口 + +; section .text + +; extern printk ; 导入一个外部符号进来 + +; global interrupt_handler ; 导出一个符号 + +; interrupt_handler: + +; ; 传入参数 +; push message +; call printk + +; ; 恢复上面的 push message之前的栈 +; add esp, 4 + +; iret + + +; section .data +; message: +; db "default interrupt", 10, 13, 0 \ No newline at end of file diff --git a/src/kernel/interrupt.c b/src/kernel/interrupt.c new file mode 100644 index 0000000..7f700cb --- /dev/null +++ b/src/kernel/interrupt.c @@ -0,0 +1,107 @@ +#include +#include +#include // // 全局描述符表 指针 + +// 全局中断向量表 一共IDT_SIZE个中断符号 +gate_t idt[IDT_SIZE]; +pointer_t idt_ptr; + +// 这个会被handle.asm导入, 里面放的是我们定义的中断处理函数 +u8* handler_table[IDT_SIZE]; + +// 从handle.asm 导入一共 ENTRY_SIZE个 存放了统一的中断描述符处理函数的地址 +// 为了让我们从c语言加载 idt表, 中断时 会先进入这个指针所在函数内, 然后继续跳入到 handler_table中 +extern handler_entry_table[ENTRY_SIZE]; + +// 异常 中断处理函数默认函数 +void exception_handle(u8 handle_num){ + char *msg; + if (handle_num < 22) { + msg = messages[handle_num]; + } else{ + msg = messages[15]; + } + printk("Exception as [0x%02X] %s\n", handle_num, msg); + hang(); +} + +// 通知中断控制器,中断处理结束 +void send_eoi(u8 handle_num) +{ + if (handle_num >= 0x20 && handle_num < 0x28) + { + out_8(PIC_M_CTRL, PIC_EOI); + } + if (handle_num >= 0x28 && handle_num < 0x30) + { + out_8(PIC_M_CTRL, PIC_EOI); + out_8(PIC_S_CTRL, PIC_EOI); + } +} + +u32 counter = 0; +// 外中断 中断处理函数默认函数 +void default_handler(u8 handle_num) +{ + send_eoi(handle_num); + counter += 1; + DEBUGK("[%x] default interrupt called... %d", handle_num, counter); +} + + +void idt_init(){ + for (usize i = 0; i < ENTRY_SIZE; i++){ + // 得到中断处理函数 + void* handle = handler_entry_table[i]; + + idt[i].offset0 = (usize)handle; + idt[i].offset1 = ((usize)handle) >> 16; + idt[i].selector = CODE_SELECT; // 代码段 + idt[i].reserved = 0; // 保留不用 + idt[i].type = 0b1110; // 任务门, 中断门, 陷阱门, 这里使用中断门, 他比陷阱门多了一个eflag, 任务门最复杂性能也不咋地 + idt[i].segment = 0; // 系统级别的中断 + idt[i].DPL = 0; // 内核态权限才能调用 + idt[i].present = 1; // 在内存中是有效的 + } + + // 加载idt表 + idt_ptr.base = (usize)idt; + idt_ptr.limit = sizeof(idt)-1; + asm volatile("lidt idt_ptr\n"); + + // 修改 handler_table, 使其调用int中断执行handle.asm统一处理函数之后, 执行我们自己的这里的c处理程序 + // 异常处理函数设置 + for (usize i = 0; i < 0x20; i++){ + handler_table[i] = exception_handle; + } + + // 外中断处理函数设置 + for (usize i = 0x20; i < ENTRY_SIZE; i++){ + handler_table[i] = default_handler; + } +} + +// 初始化中断控制器 +void pic_init(){ + out_8(PIC_M_CTRL, 0b00010001); // ICW1: 边沿触发, 级联 8259, 需要ICW4. + out_8(PIC_M_DATA, 0x20); // ICW2: 起始中断向量号 0x20 + out_8(PIC_M_DATA, 0b00000100); // ICW3: IR2接从片. + out_8(PIC_M_DATA, 0b00000001); // ICW4: 8086模式, 正常EOI + + out_8(PIC_S_CTRL, 0b00010001); // ICW1: 边沿触发, 级联 8259, 需要ICW4. + out_8(PIC_S_DATA, 0x28); // ICW2: 起始中断向量号 0x28 + out_8(PIC_S_DATA, 2); // ICW3: 设置从片连接到主片的 IR2 引脚 + out_8(PIC_S_DATA, 0b00000001); // ICW4: 8086模式, 正常EOI + + out_8(PIC_M_DATA, 0b11111110); // 只打开主片的第0个中断也就是 的主片的0x20时钟中断 + out_8(PIC_S_DATA, 0b11111111); // 关闭从片所有pic中断 +} + + +// 初始化保护模式的中断向量表 +void interrupt_init(){ + pic_init(); + idt_init(); +} + + diff --git a/src/kernel/io.asm b/src/kernel/io.asm new file mode 100644 index 0000000..b7a4a27 --- /dev/null +++ b/src/kernel/io.asm @@ -0,0 +1,86 @@ +[bits 32] + + + +section .text ; 代码段 + +global in_8, in_16, out_8 ; 将符号公开导入, 外部可以使用 + +in_8: + ; 栈帧保存 + push ebp + mov ebp, esp + + + xor eax, eax + mov edx, [ebp + 8] ; +0 是本身ebp的值, +4 是call in_8所在代码的下一行, +8 是第一个参数port + + in al, dx ; 将 port 的数据 读取1个字节 (8个比特位 al) 到al, 根据调用约定, eax作为返回值 + + jmp $+2 + jmp $+2 + jmp $+2 + + ; 恢复栈帧 + leave + ret + + +in_16: + ; 栈帧保存 + push ebp + mov ebp, esp + + + xor eax, eax + mov edx, [ebp + 8] + + in ax, dx + + jmp $+2 + jmp $+2 + jmp $+2 + + ; 恢复栈帧 + leave + ret + + + +out_8: + ; 栈帧保存 + push ebp + mov ebp, esp + + + mov edx, [ebp + 8] ; port + mov eax, [ebp + 12] ; value + + out dx, al ; 将 value 的8比特写入到 port + + jmp $+2 + jmp $+2 + jmp $+2 + + ; 恢复栈帧 + leave + ret + +out_16: + ; 栈帧保存 + push ebp + mov ebp, esp + + + mov edx, [ebp + 8] ; port + mov eax, [ebp + 12] ; value + + out dx, ax ; 将 value 的 16比特写入到 port + + jmp $+2 + jmp $+2 + jmp $+2 + + ; 恢复栈帧 + leave + ret diff --git a/src/kernel/main.c b/src/kernel/main.c new file mode 100644 index 0000000..b5bfaf8 --- /dev/null +++ b/src/kernel/main.c @@ -0,0 +1,34 @@ + +#include + + + + +void kernel_init(){ + // 初始化控制台 + console_init(); + + // 初始化全局描述符 + gdt_init(); + + // 初始化任务 + // task_init(); + + // 初始化中断 + interrupt_init(); + + asm volatile( + "sti\n" // 打开cpu的中断中断 + "movl %eax, %eax\n" + ); + + // u32 counter = 0; + // while(true){ + // counter += 1; + // DEBUGK("looping in kernel init %d", counter); + // delay(10000000); + // } + +} + + diff --git a/src/kernel/printk.c b/src/kernel/printk.c new file mode 100644 index 0000000..6d5503f --- /dev/null +++ b/src/kernel/printk.c @@ -0,0 +1,18 @@ + +#include +#include +#include + +static u8* buf[1024]; + +u32 printk(const u8 *fmt, ...){ + va_list args; + u32 i; + + va_start(args, fmt); + + i = vsprintf(buf, fmt, args); + va_end(args); + console_write(buf, i); + return i; +} \ No newline at end of file diff --git a/src/kernel/schdule.asm b/src/kernel/schdule.asm new file mode 100644 index 0000000..d6b020b --- /dev/null +++ b/src/kernel/schdule.asm @@ -0,0 +1,34 @@ +[bits 32] + +global task_switch + +task_switch: + ; 保存栈帧 + push ebp + mov ebp, esp + + ; 调用ABI约定, 保存寄存器 + push ebx + push esi + push edi + + mov eax, esp ;先得到当前调用栈 + and eax, 0xfffff000 ; 当前任务的最开始的地方, 也就是线程栈的开始这里保存了4字节的当前线程的栈顶指针 + + ; 保存当前调用栈的的 esp 栈顶 到 task_t->stack 也就是0x1000 或者0x2000处的地方 + mov [eax], esp + + ; 得到传进来的参数 next, ebp保存的是current在进入函数之后最开始的栈顶, ebp+0保存的本来的ebp, ebp+4保存的call task_switch之后下一行地址 + ; ebp +8 得到是传进来的参数 即 next线程栈的最开始的地址(把栈顶,0x1000或者0x2000保存的4字节栈顶 传入到 esp中) + mov eax, [ebp + 8] + + ; next的 的栈顶指针 + mov esp, [eax] + + pop edi + pop esi + pop ebx + pop ebp + + ; 此时 栈顶是 call task_switch 的下一行代码的位置, ret即可 + ret diff --git a/src/kernel/start.asm b/src/kernel/start.asm new file mode 100644 index 0000000..c5797f0 --- /dev/null +++ b/src/kernel/start.asm @@ -0,0 +1,19 @@ +[bits 32] + +; 导入外部符号 +extern kernel_init + +; 导出符号 +global _start + + +_start: + call kernel_init + ; int 0x0d + ; mov bx, 0 + ; div bx + + jmp $ + + + \ No newline at end of file diff --git a/src/kernel/task.c b/src/kernel/task.c new file mode 100644 index 0000000..1ff26d5 --- /dev/null +++ b/src/kernel/task.c @@ -0,0 +1,98 @@ +#include +#include + +extern void task_switch(task_t* next); + +task_t* task_a_stack = (task_t*) (PAGE_SIZE * 1); // A线程栈开始的位置 +task_t* task_b_stack = (task_t*) (PAGE_SIZE * 2); // B线程栈开始的位置 + + + +static void task_create(task_t* task_struct, usize target_handle){ + usize stack = (usize)task_struct + PAGE_SIZE; // 得到线程栈的栈底 + stack -= sizeof(task_frame_t); // 线程本身的信息留出来空间 + task_frame_t* frame = (task_frame_t*) stack; // 上面留出来的空间, 创建frame + + frame->edi = 0x11111111; + frame->esi = 0x22222222; + frame->ebx = 0x33333333; + frame->ebp = 0x44444444; + frame->eip = (u8*) target_handle; // ip指向指定的 函数 + + task_struct->stack = (u8*)stack; // 把减去了 frame大小的栈的栈底, 赋值给 task结构 的首地址, 后面会被 schdule 间接取址 + int a = 123; +} + +task_t* running_task(){ + asm volatile( + "movl %esp, %eax\n" // 当前 栈顶 + "andl $0xfffff000, %eax\n" // 得到栈底; 得到当前线程开始的位置, 即task_create中创建的 frame, + // 由于我们每个任务 占用 1个页, 1个页的大小是0x1000, 所以我们每个线程开始的地方 + // 刚好是 0x1000的整倍数, 直接把低3位置位0 即可得到每个任务开始的地方也就是保存了task_frame_t和后续栈的地方 + ); + +} + +void schdule(){ + task_t* current = running_task(); + // 这里实验的, 如果当前是任务a 就切换到任务b, 如果是任务b 就切换到任务a + task_t* next; + if (current == task_a_stack) { + next = task_b_stack; + } else { + next = task_a_stack; + } + task_switch(next); // 调用汇编实现的切换函数 +} + + +void thread_a(){ + while(true){ + printk("A"); + schdule(); + } +} +void thread_b(){ + while(true){ + printk("B"); + schdule(); + } +} + + +void task_init(){ + // test(); + +} + + +void test(){ + task_create(task_a_stack, thread_a); + task_create(task_b_stack, thread_b); + schdule(); + +} + +/* +线程栈的内存分布 + + + +| | | | +| ------ | ------ | ------------ | +| eip | 0x1fff | Function_ptr | +| ebp | | 0x44444444 | +| ebx | | 0x33333333 | +| esi | | 0x22222222 | +| edi | | 0x11111111 | +| 栈底 | 0x1f00 | | +| | | ... | +| | | ... | +| 栈顶 | 0x1100 | | +| | | | +| ... | | | +| 保存栈顶 | 0x1000 | 0x1100 | + +*/ + + diff --git a/src/lib/assert.c b/src/lib/assert.c new file mode 100644 index 0000000..009ffce --- /dev/null +++ b/src/lib/assert.c @@ -0,0 +1,45 @@ +#include +#include +#include +#include +#include + +static u8 buf[1024]; + +// 强制阻塞 +static void spin(char *name) +{ + printk("spinning in %s ...\n", name); + while (true) + ; +} + +void assertion_failure(char *exp, char *file, char *base, int line) +{ + printk( + "\n--> assert(%s) failed!!!\n" + "--> file: %s \n" + "--> base: %s \n" + "--> line: %d \n", + exp, file, base, line); + + spin("assertion_failure()"); + + // 不可能走到这里,否则出错; + asm volatile("ud2"); +} + +void panic(const char *fmt, ...) +{ + // 先格式化用户的自定义的输出信息 + va_list args; + va_start(args, fmt); + int i = vsprintf(buf, fmt, args); + va_end(args); + + printk("!!! panic !!!\n--> %s \n", buf); + spin("panic()"); + + // 不可能走到这里,否则出错; + asm volatile("ud2"); +} \ No newline at end of file diff --git a/src/lib/console.c b/src/lib/console.c new file mode 100644 index 0000000..eafefee --- /dev/null +++ b/src/lib/console.c @@ -0,0 +1,326 @@ +#include + +static u8 char_attr = 7; // 字符默认样式 +static u16 space = 0x0720; // 带有样式的空格 +struct CURSOR_REL {u8 x; u8 y} ; // 光标距离 当前屏幕的 x和y 单位为字符 + + +// 得到当前显示器这一屏的开始的内存位置 +static usize get_current_screen_mem_status(){ + // 首先 获得当前显示器 距离 0xb800 多少个字符 + // 高八位 + out_8(CRT_ADDR_PORT, CRT_MEM_H_VALUE); + u8 h = in_8(CRT_DATA_PORT); + + // 低8位 + out_8(CRT_ADDR_PORT, CRT_MEM_L_VALUE); + u8 l = in_8(CRT_DATA_PORT); + + u16 char_pos = (h << 8) | l; + + // 累加 得到当前显示器的内存地址 + usize mem_pos = char_pos << 1; // 一个字符 2字节 + return CRT_MEM_START + mem_pos; +} + +// 设置显示器当前屏需要展示的字符的内存位置 为新的一屏 +static set_current_screen_mem_status(usize screen){ + + // 得到距离 crt开始的内存的距离 + usize screen_rel = screen-CRT_MEM_START; + + // 高八位字符设置 + out_8(CRT_ADDR_PORT, CRT_MEM_H_VALUE); + out_8(CRT_DATA_PORT, (screen_rel >> 1) >> 8); + + // 低8位 + out_8(CRT_ADDR_PORT, CRT_MEM_L_VALUE); + out_8(CRT_DATA_PORT, (screen_rel >> 1)); +} + +// 得到当前光标在内存中的位置 +static usize get_current_cursor_mem_status(){ + // 首先 获得当前当前光标 距离 0xb800 多少个字符 + // 高八位 + out_8(CRT_ADDR_PORT, CRT_CURSOR_H_VALUE); + u8 h = in_8(CRT_DATA_PORT); + + // 低8位 + out_8(CRT_ADDR_PORT, CRT_CURSOR_L_VALUE); + u8 l = in_8(CRT_DATA_PORT); + + u16 char_pos = (h << 8) | l; + + // 累加 得到当前光标的内存地址 + usize mem_pos = char_pos << 1; // 一个字符 2字节 + return CRT_MEM_START + mem_pos; +} + +// 设置光标的新的内存位置 +static set_current_cursor_mem_status(usize cursor){ + // 得到距离 crt开始的内存的距离 + usize cursor_rel = cursor-CRT_MEM_START; + + // 高八位字符设置 + out_8(CRT_ADDR_PORT, CRT_CURSOR_H_VALUE); + out_8(CRT_DATA_PORT, (cursor_rel >> 1) >> 8); + + // 低8位 + out_8(CRT_ADDR_PORT, CRT_CURSOR_L_VALUE); + out_8(CRT_DATA_PORT, (cursor_rel >> 1)); +} + +// 得当当前距离屏幕的相对坐标 +static struct CURSOR_REL get_current_cursor_x_and_y(){ + // 获得当前 光标内存位置 + usize cursor_mem_abs = get_current_cursor_mem_status(); + // 获得当前显示器内存位置 + usize screen_mem_abs = get_current_screen_mem_status(); + + // 当前光标距离当前屏幕首地址几个字符 + usize char_count = (cursor_mem_abs - screen_mem_abs) >> 1; + + u8 x = char_count % WIDTH; + u8 y = char_count / WIDTH; + struct CURSOR_REL tmp = {x,y}; + return tmp; +} + +// 向上滚动 count行 +static scroll_up(usize* cursor_mem_abs_ptr){ + usize count_bytes = (1 * 2 * 80 * 1); + + // 当前屏幕的内存位置 + usize screen_mem_abs = get_current_screen_mem_status(); + + // 新的内存屏幕的位置 + usize new_screen_mem_abs_start = screen_mem_abs + count_bytes; + + // 显存检测, 如果写入超过显存 + if (*cursor_mem_abs_ptr >= CRT_MEM_END) { + + // 把当前屏幕数据copy到 起始 位置 + memcpy((u8*)CRT_MEM_START, (u8*)screen_mem_abs, (1 * 2 * WIDTH * HEIGHT)); + + // 清空现在当前屏幕, 内存之下所有的数据(!有问题, 用下面的循环) + // memset((u8*)CRT_MEM_START+(1 * 2 * WIDTH * HEIGHT), 0, CRT_MEM_END-(CRT_MEM_START+(1 * 2 * WIDTH * HEIGHT))); + + // 结束位置 + u8* e = (u8*)CRT_MEM_START + (1 * 2 * WIDTH * HEIGHT); + while (true){ + if (e >= CRT_MEM_END) { + break; + } + *e = space; + e += 2; + } + + + // 设置 new_screen_mem_abs_start 为 新地址 + new_screen_mem_abs_start = CRT_MEM_START + count_bytes; + + // 光标也挪过去(cursor_mem_abs_ptr 是指针类型指针) + (*cursor_mem_abs_ptr) -= (screen_mem_abs - CRT_MEM_START); + } + + // 设置屏幕 + set_current_screen_mem_status(new_screen_mem_abs_start); +} + + +console_write(u8* buf, usize count){ + // 当前光标内存位置 + u8* cursor_mem_abs_ptr = (u8*)get_current_cursor_mem_status(); + + // 当前屏幕的内存位置 + u8* screen_mem_abs_ptr = (u8*)get_current_screen_mem_status(); + + // 当前光标相对于屏幕坐标系 + struct CURSOR_REL cursor_rel_xy = get_current_cursor_x_and_y(); + + for (usize i = 0; i < count; i++){ + + u8 ch = buf[i]; + if (ch == '~'){ + int f = 123; + } + + switch (ch){ + case EOS: + break; + case BEL: + break; + case BS: + // 如果光标内存位置==屏幕内存位置, 说明在开头 + if (cursor_mem_abs_ptr == screen_mem_abs_ptr) { + break; + } + + // 前一个字符的 内存位置 + cursor_mem_abs_ptr -= 2; + // 修改为空字符 + *cursor_mem_abs_ptr = space; + break; + case HT: + break; + case LF: + // 光标换行 + cursor_mem_abs_ptr += (WIDTH << 1); + cursor_rel_xy.y += 1; + // 换行是否需要滚动 + if(cursor_rel_xy.y >= HEIGHT) { + scroll_up(&cursor_mem_abs_ptr); + cursor_rel_xy.y -= 1; + } + goto in_cr; + break; + case VT: + break; + case FF: + goto in_cr; + break; + case CR: + // 使光标回到开始的位置 + in_cr: + cursor_mem_abs_ptr -= (cursor_rel_xy.x << 1); + cursor_rel_xy.x = 0; + break; + case DEL: + // 直接替换当前字符为空白 + *cursor_mem_abs_ptr = space; + break; + default: + cursor_rel_xy.x += 1; + + // 如果需要换行 + if(cursor_rel_xy.x >= WIDTH) { + cursor_rel_xy.x =0; + cursor_rel_xy.y += 1; + + // 即将在下一行写入字符, 要保证下一行在 最大高度HEIGHT 之内 + // cursor_rel_xy 内保存的是idx, 需要+1得到当前屏幕的行数 和HEIGHT 比较如果相等, 说明当前屏幕已经写满了 + if(cursor_rel_xy.y >= HEIGHT) { + scroll_up(&cursor_mem_abs_ptr); + cursor_rel_xy.y -= 1; + } + } + + // 替换字符和样式 + *cursor_mem_abs_ptr = ch; + + cursor_mem_abs_ptr += 1; + *cursor_mem_abs_ptr = char_attr; + cursor_mem_abs_ptr += 1; + + break; + } + } + + // // 如果最后一个字符显示完毕后刚好是屏幕的最后一个字符 + // if(cursor_rel_xy.x >= WIDTH) { + // cursor_rel_xy.x =0; + // cursor_rel_xy.y += 1; + + // // 即将在下一行写入字符, 要保证下一行在 最大高度HEIGHT 之内 + // // cursor_rel_xy 内保存的是idx, 需要+1得到当前屏幕的行数 和HEIGHT 比较如果相等, 说明当前屏幕已经写满了 + // if(cursor_rel_xy.y >= HEIGHT) { + // scroll_up(&cursor_mem_abs_ptr); + // cursor_rel_xy.y -= 1; + // } + // } + + // 重新设置光标位置 + set_current_cursor_mem_status((usize)cursor_mem_abs_ptr); +} + +console_clear(){ + // 整个显存内存区域改成 有样式的空格 + usize crt_mem_start = CRT_MEM_START; + u8* crt_mem_start_ptr = (u8*)crt_mem_start; + for (usize i = 0; i < CRT_MEM_SIZE; i++){ + *crt_mem_start_ptr = space; + crt_mem_start_ptr += 2; + } + + // 光标重置到当前屏幕的第一个字符 + set_current_cursor_mem_status(get_current_screen_mem_status()); +} + +console_init(){ + // test_console(); + console_clear(); +} + + +test_console(){ + struct CURSOR_REL cursor_rel_xy = get_current_cursor_x_and_y(); + int a = 0; + + // 当前位置 + usize screen_mem_abs = get_current_screen_mem_status(); + + // 设置从第二行开始显示 (2*80)表示一行的字节数量 + usize screen = CRT_MEM_START + (2*80)*1; + set_current_screen_mem_status(screen); + // 当前位置 + screen_mem_abs = get_current_screen_mem_status(); + + // 当前光标位置 + usize cursor_mem_abs = get_current_cursor_mem_status(); + // 设置新的光位置视在 第一行的 下标为3的字符, 当然, 这是不可见的, 因为 上面第一行已经不显示了 + cursor_mem_abs = CRT_MEM_START + (1 * 2) * 3; + set_current_cursor_mem_status(cursor_mem_abs); + + // 设置光标在第二行 下标为4的 字符处(这里屏幕中的第一行, 就是内存中的第二行) + cursor_mem_abs = CRT_MEM_START + (1 * 2) * 80 + (1 * 2) * 4; + set_current_cursor_mem_status(cursor_mem_abs); + + // 得到当前光标的相对位置 + struct CURSOR_REL cur = get_current_cursor_x_and_y(); + + + + console_clear(); + char* message = "123456781234567812345672812345678123456781234567812345678123456781234567812345678\n"; + for (usize i = 0; i < 49; i++){ + console_write(message, strlen(message)); + } + char* message1= "123456\n"; + console_write(message1, strlen(message1)); + console_write(message1, strlen(message1)); + console_write(message1, strlen(message1)); + + cursor_rel_xy = get_current_cursor_x_and_y(); + a = 0; + + console_write(message1, strlen(message1)); + + cursor_rel_xy = get_current_cursor_x_and_y(); + a = 0; + + console_write(message1, strlen(message1)); + console_write(message1, strlen(message1)); + console_write(message1, strlen(message1)); + console_write(message1, strlen(message1)); + console_write(message1, strlen(message1)); + + char* message2= "111~\n"; + console_write(message2, strlen(message2)); + + + + // screen_mem_abs = get_current_screen_mem_status(); + // cursor_rel_xy = get_current_cursor_x_and_y(); + + + // usize tmp = (6 * 2) + (24 * 80 * 2); + // set_current_cursor_mem_status(screen_mem_abs + tmp); + + // char* message2= "1234567\n"; + // console_write(message2, strlen(message2)); + // console_write(message2, strlen(message2)); + // console_write(message2, strlen(message2)); + // console_write(message2, strlen(message2)); + // console_write(message2, strlen(message2)); + // console_write(message2, strlen(message2)); +} \ No newline at end of file diff --git a/src/lib/stdlib.c b/src/lib/stdlib.c new file mode 100644 index 0000000..4440697 --- /dev/null +++ b/src/lib/stdlib.c @@ -0,0 +1,11 @@ +#include + +void delay(usize count){ + for (usize i = 0; i < count; i++); +} + +void hang(){ + while (true){ + + } +} \ No newline at end of file diff --git a/src/lib/string.c b/src/lib/string.c new file mode 100644 index 0000000..86cabb5 --- /dev/null +++ b/src/lib/string.c @@ -0,0 +1,169 @@ +#include + +strcpy(u8* dest, const u8* src){ + while (true){ + *dest = *src; + if (*src == EOS){ + break; + } + dest += 1; + src += 1; + } +} + +strncpy(u8* dest, const u8* src, usize count){ + for (usize i = 0; i < count; i++){ + if (*src == EOS){ + break; + } + *dest = *src; + dest += 1; + src += 1; + } + *dest = EOS; +} + +strcat(u8* dest, const u8* src){ + // 找到dest 的结尾 + while (true) { + if (*dest == EOS) { + break; + } + dest += 1; + } + + // 从结尾开始拼接 + while (true){ + *dest = *src; + if (*src == EOS){ + break; + } + dest += 1; + src += 1; + } +} + +usize strlen(const u8* src){ + u8* start = src; + while (*src != EOS){ + src += 1; + } + return src - start; +} + +i8 strcmp(const u8* lhs, const u8* rhs){ + while (*lhs == *rhs && *lhs != EOS && *rhs != EOS){ + lhs += 1; + rhs += 1; + } + + // 比较不相同的一位 + if (*lhs < *rhs) { + return -1; + } else if (*lhs == *rhs){ + return 0; + } + return 1; +} + +u8* strchr_l(const u8* src, u8 ch) { + while (true){ + if (*src == EOS) { + return nullptr; + } else if (*src == ch){ + return src; + } + src += 1; + } +} + +u8* strchr_r(const u8* src, u8 ch) { + u8* last_ptr = nullptr; + while (true){ + if (*src == EOS) { + return last_ptr; + } else if (*src == ch){ + last_ptr = src; + } + src += 1; + } +} + +i8 memcmp(const u8* lhs, const u8* rhs, usize count){ + if (count == 0){ + return 0; + } + + for (usize i = 0; i < count; i++){ + if (*lhs != *rhs) { + break; + } + lhs += 1; + rhs += 1; + } + if (*lhs < *rhs) { + return -1; + } else if (*lhs == *rhs){ + return 0; + } + return 1; +} + +memset(u8* src, u8 ch, usize count){ + for (usize i = 0; i < count; i++){ + *src = ch; + src += 1; + } +} + +memcpy(u8* dest, const u8* src, usize count){ + for (usize i = 0; i < count; i++){ + *dest = *src; + dest += 1; + src += 1; + } +} + +u8* memchr(const u8* src, u8 ch, usize count){ + for (usize i = 0; i < count; i++){ + if (*src == ch){ + return src; + } + src += 1; + } + return nullptr; +} + + + +void test_string(){ + u8 message[] = "hello~"; + u8 buffer[1024]; + + u8* video = (u8*)0xb8000; + for (usize i = 0; i < sizeof(message); i++){ + video[i * 2] = message[i]; + } + + int res; + res = strcmp(message, buffer); // 1 + strcpy(buffer, message); + res = strcmp(message, buffer); // 0 + strcat(buffer, message); // 拼接一下 + res = strcmp(message, buffer); // -1 + res = strlen(buffer); // 12 + res = strlen(message); // 6 + res = sizeof(message); // 7 sizeof 把 0 也统计了 + + + usize l_pos = strchr_l(message, 'l') - message; // 2 + l_pos = strchr_r(message, 'l') - message; // 3 这是最后一个l 他在指针开始下标为 3 的位置 + + memset(buffer, 0, sizeof(buffer)); // 重置为0 + + res = memcmp(message, buffer, sizeof(message)); // 比较内存的大小, buff重置了之后, 这里结果应该是 1 + + memcpy(buffer, message, sizeof(message)); + res = memcmp(message, buffer, sizeof(message)); // copy完 再比较一下 应该是 0 + +} \ No newline at end of file diff --git a/src/lib/vprintf.c b/src/lib/vprintf.c new file mode 100644 index 0000000..3a62d26 --- /dev/null +++ b/src/lib/vprintf.c @@ -0,0 +1,385 @@ +#include +#include +#include + + +#define ZEROPAD 1 // 填充零 +#define SIGN 2 // unsigned/signed long +#define PLUS 4 // 显示加 +#define SPACE 8 // 如是加,则置空格 +#define LEFT 16 // 左调整 +#define SPECIAL 32 // 0x +#define SMALL 64 // 使用小写字母 + +#define is_digit(c) ((c) >= '0' && (c) <= '9') + +// 将字符数字串转换成整数,并将指针前移 +static int skip_atoi(const char **s) +{ + int i = 0; + while (is_digit(**s)) + i = i * 10 + *((*s)++) - '0'; + return i; +} + +// 将整数转换为指定进制的字符串 +// str - 输出字符串指针 +// num - 整数 +// base - 进制基数 +// size - 字符串长度 +// precision - 数字长度(精度) +// flags - 选项 +static char *number(char *str, unsigned long num, int base, int size, int precision, int flags) +{ + char c, sign, tmp[36]; + const char *digits = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + int i; + int index; + char *ptr = str; + + // 如果 flags 指出用小写字母,则定义小写字母集 + if (flags & SMALL) + digits = "0123456789abcdefghijklmnopqrstuvwxyz"; + + // 如果 flags 指出要左对齐,则屏蔽类型中的填零标志 + if (flags & LEFT) + flags &= ~ZEROPAD; + + // 如果进制基数小于 2 或大于 36,则退出处理 + // 也即本程序只能处理基数在 2-32 之间的数 + if (base < 2 || base > 36) + return 0; + + // 如果 flags 指出要填零,则置字符变量 c='0',否则 c 等于空格字符 + c = (flags & ZEROPAD) ? '0' : ' '; + + // 如果 flags 指出是带符号数并且数值 num 小于 0,则置符号变量 sign=负号,并使 num 取绝对值 + if (flags & SIGN && (long)num < 0) + { + sign = '-'; + num = -(long)num; + } + else + // 否则如果 flags 指出是加号,则置 sign=加号,否则若类型带空格标志则 sign=空格,否则置 0 + sign = (flags & PLUS) ? '+' : ((flags & SPACE) ? ' ' : 0); + + // 若带符号,则宽度值减 1 + if (sign) + size--; + + // 若 flags 指出是特殊转换,则对于十六进制宽度再减少 2 位(用于0x) + if (flags & SPECIAL) + { + if (base == 16) + size -= 2; + // 对于八进制宽度减 1(用于八进制转换结果前放一个零) + else if (base == 8) + size--; + } + + i = 0; + // 如果数值 num 为 0,则临时字符串='0';否则根据给定的基数将数值 num 转换成字符形式 + if (num == 0) + tmp[i++] = '0'; + else + while (num != 0) + { + index = num % base; + num /= base; + tmp[i++] = digits[index]; + } + + // 若数值字符个数大于精度值,则精度值扩展为数字个数值 + if (i > precision) + precision = i; + + // 宽度值 size 减去用于存放数值字符的个数 + size -= precision; + + // 从这里真正开始形成所需要的转换结果,并暂时放在字符串 str 中 + + // 若 flags 中没有填零(ZEROPAD) 和左对齐(左调整)标志 + // 则在 str 中首先填放剩余宽度值指出的空格数 + if (!(flags & (ZEROPAD + LEFT))) + while (size-- > 0) + *str++ = ' '; + + // 若需带符号位,则存入符号 + if (sign) + *str++ = sign; + + // 若 flags 指出是特殊转换 + if (flags & SPECIAL) + { + // 则对于八进制转换结果头一位放置一个'0' + if (base == 8) + *str++ = '0'; + // 对于十六进制则存放'0x' + else if (base == 16) + { + *str++ = '0'; + *str++ = digits[33]; + } + } + // 若 flags 中没有左调整(左对齐)标志, 则在剩余宽度中存放 c 字符('0'或空格) + if (!(flags & LEFT)) + while (size-- > 0) + *str++ = c; + + // 此时 i 存有数值 num 的数字个数 + + // 若数字个数小于精度值,则 str 中放入(精度值-i)个'0' + while (i < precision--) + *str++ = '0'; + + // 将转数值换好的数字字符填入 str 中,共 i 个 + while (i-- > 0) + *str++ = tmp[i]; + + // 若宽度值仍大于零 + // 则表示 flags 标志中有左对齐标志标志 + // 则在剩余宽度中放入空格 + while (size-- > 0) + *str++ = ' '; + return str; +} + +int vsprintf(char *buf, const char *fmt, va_list args) +{ + int len; + int i; + + // 用于存放转换过程中的字符串 + char *str; + char *s; + int *ip; + + // number() 函数使用的标志 + int flags; + + int field_width; // 输出字段宽度 + int precision; // min 整数数字个数;max 字符串中字符个数 + int qualifier; // 'h', 'l' 或 'L' 用于整数字段 + + // 首先将字符指针指向 buf + // 然后扫描格式字符串, + // 对各个格式转换指示进行相应的处理 + for (str = buf; *fmt; ++fmt) + { + // 格式转换指示字符串均以 '%' 开始 + // 这里从 fmt 格式字符串中扫描 '%',寻找格式转换字符串的开始 + // 不是格式指示的一般字符均被依次存入 str + if (*fmt != '%') + { + *str++ = *fmt; + continue; + } + + // 下面取得格式指示字符串中的标志域,并将标志常量放入 flags 变量中 + flags = 0; + repeat: + // 掉过第一个 % + ++fmt; + switch (*fmt) + { + // 左对齐调整 + case '-': + flags |= LEFT; + goto repeat; + // 放加号 + case '+': + flags |= PLUS; + goto repeat; + // 放空格 + case ' ': + flags |= SPACE; + goto repeat; + // 是特殊转换 + case '#': + flags |= SPECIAL; + goto repeat; + // 要填零(即'0'),否则是空格 + case '0': + flags |= ZEROPAD; + goto repeat; + } + + // 取当前参数字段宽度域值,放入 field_width 变量中 + field_width = -1; + + // 如果宽度域中是数值则直接取其为宽度值 + if (is_digit(*fmt)) + field_width = skip_atoi(&fmt); + + // 如果宽度域中是字符 '*',表示下一个参数指定宽度 + else if (*fmt == '*') + { + ++fmt; + // 因此调用 va_arg 取宽度值 + field_width = va_arg(args, int); + + // 若此时宽度值小于 0,则该负数表示其带有标志域 '-' 标志(左对齐) + if (field_width < 0) + { + // 因此还需在标志变量中添入该标志,并将字段宽度值取为其绝对值 + field_width = -field_width; + flags |= LEFT; + } + } + + // 取格式转换串的精度域,并放入 precision 变量中 + precision = -1; + + // 精度域开始的标志是'.' 其处理过程与上面宽度域的类似 + if (*fmt == '.') + { + ++fmt; + // 如果精度域中是数值则直接取其为精度值 + if (is_digit(*fmt)) + precision = skip_atoi(&fmt); + + // 如果精度域中是字符'*',表示下一个参数指定精度 + else if (*fmt == '*') + { + // 因此调用 va_arg 取精度值 + precision = va_arg(args, int); + } + // 若此时宽度值小于 0,则将字段精度值取为其绝对值 + if (precision < 0) + precision = 0; + } + + // 下面这段代码分析长度修饰符,并将其存入 qualifer 变量 + qualifier = -1; + if (*fmt == 'h' || *fmt == 'l' || *fmt == 'L') + { + qualifier = *fmt; + ++fmt; + } + + // 下面分析转换指示符 + switch (*fmt) + { + + // 如果转换指示符是'c',则表示对应参数应是字符 + case 'c': + // 此时如果标志域表明不是左对齐, + if (!(flags & LEFT)) + // 则该字段前面放入 (宽度域值 - 1) 个空格字符,然后再放入参数字符 + while (--field_width > 0) + *str++ = ' '; + *str++ = (unsigned char)va_arg(args, int); + // 如果宽度域还大于 0,则表示为左对齐 + // 则在参数字符后面添加 (宽度值-1) 个空格字符 + while (--field_width > 0) + *str++ = ' '; + break; + + // 如果转换指示符是 's',则表示对应参数是字符串 + case 's': + s = va_arg(args, char *); + // 首先取参数字符串的长度 + len = strlen(s); + // 若其超过了精度域值, 则扩展精度域=字符串长度 + if (precision < 0) + precision = len; + else if (len > precision) + len = precision; + + // 此时如果标志域表明不是左对齐 + if (!(flags & LEFT)) + // 则该字段前放入 (宽度值-字符串长度) 个空格字符 + while (len < field_width--) + *str++ = ' '; + // 然后再放入参数字符串 + for (i = 0; i < len; ++i) + *str++ = *s++; + // 如果宽度域还大于 0,则表示为左对齐 + // 则在参数字符串后面,添加(宽度值-字符串长度)个空格字符 + while (len < field_width--) + *str++ = ' '; + break; + + // 如果格式转换符是'o',表示需将对应的参数转换成八进制数的字符串 + case 'o': + str = number(str, va_arg(args, unsigned long), 8, + field_width, precision, flags); + break; + + // 如果格式转换符是'p',表示对应参数的一个指针类型 + case 'p': + // 此时若该参数没有设置宽度域,则默认宽度为 8,并且需要添零 + if (field_width == -1) + { + field_width = 8; + flags |= ZEROPAD; + } + str = number(str, + (unsigned long)va_arg(args, void *), 16, + field_width, precision, flags); + break; + + // 若格式转换指示是 'x' 或 'X' + // 则表示对应参数需要打印成十六进制数输出 + case 'x': + // 'x'表示用小写字母表示 + flags |= SMALL; + case 'X': + str = number(str, va_arg(args, unsigned long), 16, + field_width, precision, flags); + break; + + // 如果格式转换字符是'd', 'i' 或 'u',则表示对应参数是整数 + case 'd': + case 'i': + // 'd', 'i'代表符号整数,因此需要加上带符号标志 + flags |= SIGN; + // 'u'代表无符号整数 + case 'u': + str = number(str, va_arg(args, unsigned long), 10, + field_width, precision, flags); + break; + + // 若格式转换指示符是 'n' + // 表示要把到目前为止转换输出的字符数保存到对应参数指针指定的位置中 + case 'n': + // 首先利用 va_arg() 取得该参数指针 + ip = va_arg(args, int *); + // 然后将已经转换好的字符数存入该指针所指的位置 + *ip = (str - buf); + break; + + default: + // 若格式转换符不是 '%',则表示格式字符串有错 + if (*fmt != '%') + // 直接将一个 '%' 写入输出串中 + *str++ = '%'; + // 如果格式转换符的位置处还有字符,则也直接将该字符写入输出串中 + // 然后继续循环处理格式字符串 + if (*fmt) + *str++ = *fmt; + else + // 否则表示已经处理到格式字符串的结尾处,则退出循环 + --fmt; + break; + } + } + // 最后在转换好的字符串结尾处添上字符串结束标志 + *str = '\0'; + + // 返回转换好的字符串长度值 + i = str - buf; + // todo + assert(i < 1024); + return i; +} + +// 结果按格式输出字符串到 buf +int sprintf(char *buf, const char *fmt, ...) +{ + va_list args; + va_start(args, fmt); + int i = vsprintf(buf, fmt, args); + va_end(args); + return i; +} \ No newline at end of file diff --git a/src/test/types.c b/src/test/types.c new file mode 100644 index 0000000..12bbc29 --- /dev/null +++ b/src/test/types.c @@ -0,0 +1,7 @@ +#include + +void main(){ + printf("size of u8 %d\n", sizeof(u8)); + printf("size of u16 %d\n", sizeof(u16)); + printf("size of u32 %d\n", sizeof(u32)); +}