外中断完成

master
阳光少年 1 year ago
commit fecc31e72b

16
.gitignore vendored

@ -0,0 +1,16 @@
*.img
*.ini
*.bin
*.map
*.o
*.out
*.zst
*.gz
*.lock
*.vmdk
*.log
build
bochs/src
bochs/pkg

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

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

51
.vscode/tasks.json vendored

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

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

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

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

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

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

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

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

@ -0,0 +1,48 @@
#ifndef ONIX_CONSOLE_H
#define ONIX_CONSOLE_H
#include <onix/types.h>
#include <onix/io.h>
#include <onix/string.h>
#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

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

@ -0,0 +1,45 @@
#ifndef ONIX_GLOBAL_H
#define ONIX_GLOBAL_H
#include <onix/types.h>
// 我们只使用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

@ -0,0 +1,63 @@
#ifndef ONIX_INTERRUPT_H
#define ONIX_INTERRUPT_H
#include <onix/types.h>
#include <onix/debug.h>
// 在实模式中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

@ -0,0 +1,51 @@
#ifndef ONIX_IO_H
#define ONIX_IO_H
#include <onix/types.h>
// 这个文件只是定义了头文件, 真正实现的是 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);
// }

@ -0,0 +1,18 @@
#ifndef ONIX_H
#define ONIX_H
#include <onix/types.h>
#include <onix/io.h>
#include <onix/string.h>
#include <onix/console.h>
#include <onix/assert.h>
#include <onix/printk.h>
#include <onix/debug.h>
#include <onix/global.h>
#include <onix/task.h>>
#include <onix/interrupt.h>>
#include <onix/stdlib.h>>
void kernel_init();
#endif

@ -0,0 +1,7 @@
#ifndef ONIX_PRINTK_H
#define ONIX_PRINTK_H
#include <onix/types.h>
u32 printk(const u8 *fmt, ...);
#endif

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

@ -0,0 +1,6 @@
#ifndef ONIX_STDLIB_H
#define ONIX_STDLIB_H
#include <onix/types.h>
void delay (u32 count);
void hang();
#endif

@ -0,0 +1,37 @@
#ifndef ONIX_STRING_H
#define ONIX_STRING_H
#include <onix/types.h>
// 字符串 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

@ -0,0 +1,27 @@
#ifndef ONIX_TASK_H
#define ONIX_TASK_H
#include <onix/types.h>
#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

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

@ -0,0 +1,9 @@
#ifndef ONIX_VPRINTF_H
#define ONIX_VPRINTF_H
#include <onix/stdarg.h>
int vsprintf(char *buf, const char *fmt, va_list args);
int sprintf(char *buf, const char *fmt, ...);
#endif

@ -0,0 +1,16 @@
#include <onix/debug.h>
#include <onix/stdarg.h>
#include <onix/vprintf.h>
#include <onix/printk.h>
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);
}

@ -0,0 +1,23 @@
#include <onix/debug.h>
#include <onix/string.h>
#include <onix/global.h>
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");
}

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

@ -0,0 +1,107 @@
#include <onix/interrupt.h>
#include <onix/onix.h>
#include <onix/global.h> // // 全局描述符表 指针
// 全局中断向量表 一共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();
}

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

@ -0,0 +1,34 @@
#include <onix/onix.h>
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);
// }
}

@ -0,0 +1,18 @@
#include <onix/console.h>
#include <onix/stdarg.h>
#include <onix/vprintf.h>
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;
}

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

@ -0,0 +1,19 @@
[bits 32]
; 导入外部符号
extern kernel_init
; 导出符号
global _start
_start:
call kernel_init
; int 0x0d
; mov bx, 0
; div bx
jmp $

@ -0,0 +1,98 @@
#include <onix/task.h>
#include <onix/printk.h>
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 |
*/

@ -0,0 +1,45 @@
#include <onix/assert.h>
#include <onix/stdarg.h>
#include <onix/types.h>
#include <onix/printk.h>
#include <onix/vprintf.h>
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");
}

@ -0,0 +1,326 @@
#include <onix/console.h>
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));
}

@ -0,0 +1,11 @@
#include <onix/stdlib.h>
void delay(usize count){
for (usize i = 0; i < count; i++);
}
void hang(){
while (true){
}
}

@ -0,0 +1,169 @@
#include <onix/string.h>
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
}

@ -0,0 +1,385 @@
#include <onix/string.h>
#include <onix/stdarg.h>
#include <onix/assert.h>
#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;
}

@ -0,0 +1,7 @@
#include <onix/types.h>
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));
}
Loading…
Cancel
Save