From 6af31b5b894e7d8e19b2e2fc0afec0f763f97bb0 Mon Sep 17 00:00:00 2001 From: Jack Halford Date: Sat, 14 Dec 2019 22:46:48 +0100 Subject: [PATCH] Brendan's Tutorial Step 1: done --- .gdbinit | 5 ++ build.zig | 2 +- qemu.sh | 60 ++++++++++------------- src/arch/x86/idt.zig | 5 ++ src/arch/x86/lib/instructions.zig | 26 ++++------ src/arch/x86/main.zig | 1 - src/arch/x86/pmem.zig | 2 +- src/arch/x86/switch_tasks.s | 80 +++++++++++++++++-------------- src/base.zig | 4 -- src/console.zig | 11 +++++ src/main.zig | 11 ++--- src/task.zig | 59 +++++++++++++++++------ src/vga.zig | 24 +++++----- 13 files changed, 163 insertions(+), 127 deletions(-) create mode 100644 .gdbinit delete mode 100644 src/base.zig diff --git a/.gdbinit b/.gdbinit new file mode 100644 index 0000000..c6eda73 --- /dev/null +++ b/.gdbinit @@ -0,0 +1,5 @@ +file build/kernel +target remote localhost:4242 +set arch i386 +set step-mode on +directory src diff --git a/build.zig b/build.zig index 47237cd..8a76a52 100644 --- a/build.zig +++ b/build.zig @@ -11,7 +11,7 @@ pub fn build(b: *Builder) void { kernel.addAssemblyFile("src/arch/x86/gdt.s"); kernel.addAssemblyFile("src/arch/x86/isr.s"); kernel.addAssemblyFile("src/arch/x86/paging.s"); - // kernel.addAssemblyFile("src/arch/x86/switch_tasks.s"); + kernel.addAssemblyFile("src/arch/x86/switch_tasks.s"); kernel.setBuildMode(b.standardReleaseOptions()); kernel.setTarget(builtin.Arch.i386, builtin.Os.freestanding, builtin.Abi.none); diff --git a/qemu.sh b/qemu.sh index 0abadd2..9bf303a 100755 --- a/qemu.sh +++ b/qemu.sh @@ -1,48 +1,38 @@ QEMU_SOCKET=/tmp/qemu.sock -QEMU_MONITOR="socat - UNIX-CONNECT:${QEMU_SOCKET}" +QEMU_MONITOR="sudo socat - UNIX-CONNECT:${QEMU_SOCKET}" QEMU_GDB_PORT=4242 KERNEL=build/kernel start() { - sudo pkill -9 qemu - sudo qemu-system-i386 \ - -gdb tcp::${QEMU_GDB_PORT} \ - -monitor unix:${QEMU_SOCKET},server,nowait \ - -enable-kvm \ - -m 1337M \ - -curses \ - -append "Hello" \ - -drive file=disk.img,if=virtio\ - -kernel ${KERNEL} - # -no-reboot \ - # -device virtio-net,netdev=network0 -netdev tap,id=network0,ifname=tap0,script=no,downscript=no \ - # build/kernel.iso + sudo pkill -9 qemu + sudo qemu-system-i386 \ + -gdb tcp::${QEMU_GDB_PORT} \ + -monitor unix:${QEMU_SOCKET},server,nowait \ + -enable-kvm \ + -m 1337M \ + -curses \ + -kernel ${KERNEL} + # -drive file=disk.img,if=virtio\ + # -no-reboot \ + # -device virtio-net,netdev=network0 -netdev tap,id=network0,ifname=tap0,script=no,downscript=no \ + # -S \ + # build/kernel.iso - # this allows to monitor with ^a-c, but doesn't - # play nice with irqs apparently... - # -serial mon:stdio \ + # this allows to monitor with ^a-c, but doesn't + # play nice with irqs apparently... + # -serial mon:stdio \ } -monitor() { - if [ "$1" == "" ]; then - sudo ${QEMU_MONITOR} - else - echo "$1" | sudo ${QEMU_MONITOR} >/dev/null - fi -} +monitor() { sudo ${QEMU_MONITOR}; } +monitor-exec() { echo "$1" | sudo ${QEMU_MONITOR} >/dev/null; } + +quit() { monitor-exec quit; } reload() { - monitor stop - # monitor "change ide1-cd0 ${KERNEL}" - monitor system_reset - monitor cont -} - -gdb() { - gdb -q \ - -symbols "${KERNEL}" \ - -ex "target remote :${QEMU_GDB_PORT}" \ - -ex "set arch i386" + monitor-exec stop + # monitor "change ide1-cd0 ${KERNEL}" + monitor-exec system_reset + monitor-exec cont } "$@" diff --git a/src/arch/x86/idt.zig b/src/arch/x86/idt.zig index 64d9381..e8c3d5d 100644 --- a/src/arch/x86/idt.zig +++ b/src/arch/x86/idt.zig @@ -59,7 +59,12 @@ pub fn initialize() void { isr.install_syscalls(); interrupt.registerIRQ(0, interrupt.pit_handler); interrupt.registerIRQ(1, kernel.ps2.keyboard_handler); + interrupt.register(3, test_b); // load IDT lidt(@ptrToInt(&idtr)); } + +pub fn test_b() void { + kernel.println("int3!"); +} diff --git a/src/arch/x86/lib/instructions.zig b/src/arch/x86/lib/instructions.zig index a912abf..94c4c88 100644 --- a/src/arch/x86/lib/instructions.zig +++ b/src/arch/x86/lib/instructions.zig @@ -1,9 +1,3 @@ -//// -// Load a new Task Register. -// -// Arguments: -// desc: Segment selector of the TSS. -// pub inline fn ltr(desc: u16) void { asm volatile ("ltr %[desc]" : @@ -11,27 +5,25 @@ pub inline fn ltr(desc: u16) void { ); } -//// -// Completely stop the computer. -// pub inline fn hang() noreturn { - asm volatile ("cli"); + cli(); while (true) asm volatile ("hlt"); } -pub inline fn sti() void { +//TODO: inline this +pub fn cli() void { + asm volatile ("cli"); +} + +//TODO: inline this +pub fn sti() void { asm volatile ("sti"); } + pub inline fn int3() void { asm volatile ("int3"); } -//// -// Load a new Interrupt Descriptor Table. -// -// Arguments: -// idtr: Address of the IDTR register. -// pub inline fn lidt(idtr: usize) void { asm volatile ("lidt (%[idtr])" : diff --git a/src/arch/x86/main.zig b/src/arch/x86/main.zig index b5d8ec4..a06fb55 100644 --- a/src/arch/x86/main.zig +++ b/src/arch/x86/main.zig @@ -7,7 +7,6 @@ pub fn x86_main(info: *const kernel.multiboot.MultibootInfo) void { idt.initialize(); pmem.initialize(info); paging.initialize(); - // enable interrupts sti(); } diff --git a/src/arch/x86/pmem.zig b/src/arch/x86/pmem.zig index 05046f4..fbd63c4 100644 --- a/src/arch/x86/pmem.zig +++ b/src/arch/x86/pmem.zig @@ -16,7 +16,7 @@ pub inline fn pageAlign(address: u32) u32 { // Return the amount of variable elements (in bytes). // -pub inline fn available() usize { +pub fn available() usize { return stack_index * PAGE_SIZE; } diff --git a/src/arch/x86/switch_tasks.s b/src/arch/x86/switch_tasks.s index 12ff95e..85aebc5 100644 --- a/src/arch/x86/switch_tasks.s +++ b/src/arch/x86/switch_tasks.s @@ -1,47 +1,55 @@ -;https://wiki.osdev.org/Multitasking_Systems +//https://wiki.osdev.org/Multitasking_Systems -;C declaration: -; void switch_tasks(thread_control_block *next_thread); -; -;WARNING: Caller is expected to disable IRQs before calling, and enable IRQs again after function returns +//C declaration: +// void switch_tasks(thread_control_block *next_thread)// +// +//WARNING: Caller is expected to disable IRQs before calling, and enable IRQs again after function returns + +.type switch_tasks, @function +.global switch_tasks switch_tasks: + push %ebp + mov %esp, %ebp + mov +12(%esp), %eax + mov %esp, (%eax) //save old esp + mov +8(%esp), %eax + mov %eax, %esp // move the forged stack to esp + pop %ebp // the top of the forged stack contains ebp + ret //the top of the forged stack contains eip to go to - ;Save previous task's state + //Save previous task's state + //Notes: + // For cdecl// EAX, ECX, and EDX are already saved by the caller and don't need to be saved again + // EIP is already saved on the stack by the caller's "CALL" instruction + // The task isn't able to change CR3 so it doesn't need to be saved + // Segment registers are constants (while running kernel code) so they don't need to be saved - ;Notes: - ; For cdecl; EAX, ECX, and EDX are already saved by the caller and don't need to be saved again - ; EIP is already saved on the stack by the caller's "CALL" instruction - ; The task isn't able to change CR3 so it doesn't need to be saved - ; Segment registers are constants (while running kernel code) so they don't need to be saved + // push %ebx + // push %esi + // push %edi + // push %ebp - push ebx - push esi - push edi - push ebp + // mov %edi,[current_task] //edi = address of the previous task's "thread control block" + // mov [edi+TCB.ESP], %esp //Save ESP for previous task's kernel stack in the thread's TCB - mov edi,[current_task_TCB] ;edi = address of the previous task's "thread control block" - mov [edi+TCB.ESP],esp ;Save ESP for previous task's kernel stack in the thread's TCB + // Load next task's state - ;Load next task's state + // mov %esi,[esp+(4+1)*4] //esi = address of the next task's "thread control block" (parameter passed on stack) + // mov [current_task], %esi //Current task's TCB is the next task TCB - mov esi,[esp+(4+1)*4] ;esi = address of the next task's "thread control block" (parameter passed on stack) - mov [current_task_TCB],esi ;Current task's TCB is the next task TCB + // mov %esp,[esi+TCB.ESP] //Load ESP for next task's kernel stack from the thread's TCB + // mov eax,[esi+TCB.CR3] //eax = address of page directory for next task + // mov %ebx,[esi+TCB.ESP0] //ebx = address for the top of the next task's kernel stack + // mov [TSS.ESP0],ebx //Adjust the ESP0 field in the TSS (used by CPU for for CPL=3 -> CPL=0 privilege level changes) + // mov ecx,cr3 //ecx = previous task's virtual address space - mov esp,[esi+TCB.ESP] ;Load ESP for next task's kernel stack from the thread's TCB - mov eax,[esi+TCB.CR3] ;eax = address of page directory for next task - mov ebx,[esi+TCB.ESP0] ;ebx = address for the top of the next task's kernel stack - mov [TSS.ESP0],ebx ;Adjust the ESP0 field in the TSS (used by CPU for for CPL=3 -> CPL=0 privilege level changes) - mov ecx,cr3 ;ecx = previous task's virtual address space + // cmp eax,ecx //Does the virtual address space need to being changed? + // je .doneVAS // no, virtual address space is the same, so don't reload it and cause TLB flushes + // mov cr3,eax // yes, load the next task's virtual address space +// .doneVAS: - cmp eax,ecx ;Does the virtual address space need to being changed? - je .doneVAS ; no, virtual address space is the same, so don't reload it and cause TLB flushes - mov cr3,eax ; yes, load the next task's virtual address space -.doneVAS: - - pop ebp - pop edi - pop esi - pop ebx - - ret ;Load next task's EIP from its kernel stack) + // pop %ebp + // pop %edi + // pop %esi + // pop %ebx diff --git a/src/base.zig b/src/base.zig deleted file mode 100644 index 7e89379..0000000 --- a/src/base.zig +++ /dev/null @@ -1,4 +0,0 @@ -pub const assert = @import("std").debug.assert; -pub const std = @import("std"); - -pub usingnamespace @import("vga.zig"); diff --git a/src/console.zig b/src/console.zig index 5b21de7..5bf36fe 100644 --- a/src/console.zig +++ b/src/console.zig @@ -3,6 +3,13 @@ usingnamespace @import("index.zig"); var command: [10]u8 = undefined; var command_len: usize = 0; +fn test_a() void { + var a: u32 = 2; + a += 1; + a = 4; + a -= 1; +} + fn execute(input: []u8) void { const eql = std.mem.eql; if (eql(u8, input, "x86paging")) return x86.paging.introspect(); @@ -11,6 +18,10 @@ fn execute(input: []u8) void { if (eql(u8, input, "lspci")) return pci.lspci(); if (eql(u8, input, "uptime")) return time.uptime(); if (eql(u8, input, "topbar")) return topbar(); + if (eql(u8, input, "test")) { + const tbar = task.Task.new(@ptrToInt(topbar)) catch unreachable; + while (true) tbar.switch_to(); + } println("{}: command not found", input); } diff --git a/src/main.zig b/src/main.zig index a341dd1..dede56b 100644 --- a/src/main.zig +++ b/src/main.zig @@ -15,8 +15,6 @@ export const multiboot_header align(4) linksection(".multiboot") = multiboot: { }; }; -extern fn switch_tasks(stack: u32) void; - // arch independant initialization export fn kmain(magic: u32, info: *const multiboot.MultibootInfo) noreturn { assert(magic == multiboot.MULTIBOOT_BOOTLOADER_MAGIC); @@ -24,10 +22,11 @@ export fn kmain(magic: u32, info: *const multiboot.MultibootInfo) noreturn { println("--- x86 initialization ---"); x86.x86_main(info); println("--- core initialization ---"); - // pci.scan(); vmem.initialize(); - task.initialize() catch unreachable; - console.initialize(); + pci.scan(); - while (true) asm volatile ("hlt"); + console.initialize(); + // while (true) asm volatile ("hlt"); + const tbar = task.Task.new(@ptrToInt(topbar)) catch unreachable; + while (true) tbar.switch_to(); } diff --git a/src/task.zig b/src/task.zig index c0dc740..75c5cee 100644 --- a/src/task.zig +++ b/src/task.zig @@ -1,28 +1,39 @@ pub usingnamespace @import("index.zig"); const TASK_MAX = 1024; -var tasks = [1]?*Task{null} ** TASK_MAX; +var boot_task = Task{ .tid = 0, .esp = 0x47 }; +var current_task: *Task = &boot_task; +pub var tasks = [1]?*Task{&boot_task} ++ ([1]?*Task{null} ** TASK_MAX); const STACK_SIZE = x86.PAGE_SIZE; // Size of thread stacks. var tid_counter: u16 = 1; -pub const Task = struct { +///ASM +extern fn switch_tasks(new_esp: u32, old_esp_addr: u32) void; + +pub const Task = packed struct { + esp: usize, tid: u16, - stack_top: usize, - entrypoint: usize, - // context: isr.Context, + //context: isr.Context, //cr3: usize, pub fn new(entrypoint: usize) !*Task { // Allocate and initialize the thread structure. var t = try vmem.allocate(Task); - t.entrypoint = entrypoint; t.tid = tid_counter; tid_counter +%= 1; assert(tid_counter != 0); //overflow - t.stack_top = try vmem.malloc(STACK_SIZE); - assert(t.stack_top < layout.USER_STACKS_END); + // allocate a new stack + t.esp = (try vmem.malloc(STACK_SIZE)) + STACK_SIZE; + // top of stack is the address that ret will pop + t.esp -= 4; + @intToPtr(*usize, t.esp).* = entrypoint; + // top of stack is ebp that we will pop + t.esp -= 4; + @intToPtr(*usize, t.esp).* = t.esp + 8; + + println("new task esp=0x{x}, eip=0x{x}", t.esp, entrypoint); tasks[t.tid] = t; return t; @@ -30,20 +41,38 @@ pub const Task = struct { pub fn destroy(self: *Task) void { tasks[self.tid] = null; - vmem.free(self.stack_top); + vmem.free(self.esp); vmem.free(@ptrToInt(self)); } -}; -pub fn initialize() !void { - const t = try Task.new(0x0); - println("task=0x{x}", t.stack_top); -} + pub fn switch_to(self: *Task) void { + assert(self != current_task); + // save old stack + const old_task_esp_addr = ¤t_task.esp; + current_task = self; + // x86.cli(); + // don't inline the asm function, it needs to ret + @noInlineCall(switch_tasks, self.esp, @ptrToInt(old_task_esp_addr)); + // comptime { + // asm ( + // \\mov +8(%esp), %eax + // \\mov %esp, (%eax) + // \\mov +4(%esp), %eax + // \\mov %eax, %esp + // \\pop %ebp + // \\ret + // ); + // } + // x86.sti(); + println("after switch"); + } +}; pub fn introspect() void { for (tasks) |t| { if (t == null) continue; - println("{}", t); + if (t != current_task) println("{}", t); + if (t == current_task) println("*{}", t); } } diff --git a/src/vga.zig b/src/vga.zig index e2ed17a..d67c025 100644 --- a/src/vga.zig +++ b/src/vga.zig @@ -1,7 +1,7 @@ -// usingnamespace @import("index.zig"); -const time = @import("time.zig"); -const x86 = @import("arch/x86/index.zig"); -const std = @import("std"); +usingnamespace @import("index.zig"); +// const time = @import("time.zig"); +// const x86 = @import("arch/x86/index.zig"); +// const std = @import("std"); // Screen size. pub const VGA_WIDTH = 80; pub const VGA_HEIGHT = 25; @@ -56,9 +56,8 @@ const Errors = error{}; pub fn print(comptime format: []const u8, args: ...) void { var a = std.fmt.format({}, Errors, printCallback, format, args); } - pub fn println(comptime format: []const u8, args: ...) void { - var a = std.fmt.format({}, Errors, printCallback, format ++ "\n", args); + var a = print(format ++ "\n", args); } pub fn clear() void { vga.clear(); @@ -66,13 +65,16 @@ pub fn clear() void { pub fn topbar() void { const cursor = vga.cursor; const bg = vga.background; - vga.cursor = 0; - vga.background = Color.Red; + while (true) { + vga.cursor = 0; + vga.background = Color.Red; - time.uptime(); + time.uptime(); - vga.cursor = cursor; - vga.background = bg; + vga.cursor = cursor; + vga.background = bg; + task.tasks[0].?.switch_to(); + } } fn printCallback(context: void, string: []const u8) Errors!void {