Brendan's Tutorial Step 1: done

This commit is contained in:
Jack Halford 2019-12-14 22:46:48 +01:00
parent 8d7e7591e9
commit 6af31b5b89
13 changed files with 163 additions and 127 deletions

5
.gdbinit Normal file
View file

@ -0,0 +1,5 @@
file build/kernel
target remote localhost:4242
set arch i386
set step-mode on
directory src

View file

@ -11,7 +11,7 @@ pub fn build(b: *Builder) void {
kernel.addAssemblyFile("src/arch/x86/gdt.s"); kernel.addAssemblyFile("src/arch/x86/gdt.s");
kernel.addAssemblyFile("src/arch/x86/isr.s"); kernel.addAssemblyFile("src/arch/x86/isr.s");
kernel.addAssemblyFile("src/arch/x86/paging.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.setBuildMode(b.standardReleaseOptions());
kernel.setTarget(builtin.Arch.i386, builtin.Os.freestanding, builtin.Abi.none); kernel.setTarget(builtin.Arch.i386, builtin.Os.freestanding, builtin.Abi.none);

60
qemu.sh
View file

@ -1,48 +1,38 @@
QEMU_SOCKET=/tmp/qemu.sock QEMU_SOCKET=/tmp/qemu.sock
QEMU_MONITOR="socat - UNIX-CONNECT:${QEMU_SOCKET}" QEMU_MONITOR="sudo socat - UNIX-CONNECT:${QEMU_SOCKET}"
QEMU_GDB_PORT=4242 QEMU_GDB_PORT=4242
KERNEL=build/kernel KERNEL=build/kernel
start() { start() {
sudo pkill -9 qemu sudo pkill -9 qemu
sudo qemu-system-i386 \ sudo qemu-system-i386 \
-gdb tcp::${QEMU_GDB_PORT} \ -gdb tcp::${QEMU_GDB_PORT} \
-monitor unix:${QEMU_SOCKET},server,nowait \ -monitor unix:${QEMU_SOCKET},server,nowait \
-enable-kvm \ -enable-kvm \
-m 1337M \ -m 1337M \
-curses \ -curses \
-append "Hello" \ -kernel ${KERNEL}
-drive file=disk.img,if=virtio\ # -drive file=disk.img,if=virtio\
-kernel ${KERNEL} # -no-reboot \
# -no-reboot \ # -device virtio-net,netdev=network0 -netdev tap,id=network0,ifname=tap0,script=no,downscript=no \
# -device virtio-net,netdev=network0 -netdev tap,id=network0,ifname=tap0,script=no,downscript=no \ # -S \
# build/kernel.iso # build/kernel.iso
# this allows to monitor with ^a-c, but doesn't # this allows to monitor with ^a-c, but doesn't
# play nice with irqs apparently... # play nice with irqs apparently...
# -serial mon:stdio \ # -serial mon:stdio \
} }
monitor() { monitor() { sudo ${QEMU_MONITOR}; }
if [ "$1" == "" ]; then monitor-exec() { echo "$1" | sudo ${QEMU_MONITOR} >/dev/null; }
sudo ${QEMU_MONITOR}
else quit() { monitor-exec quit; }
echo "$1" | sudo ${QEMU_MONITOR} >/dev/null
fi
}
reload() { reload() {
monitor stop monitor-exec stop
# monitor "change ide1-cd0 ${KERNEL}" # monitor "change ide1-cd0 ${KERNEL}"
monitor system_reset monitor-exec system_reset
monitor cont monitor-exec cont
}
gdb() {
gdb -q \
-symbols "${KERNEL}" \
-ex "target remote :${QEMU_GDB_PORT}" \
-ex "set arch i386"
} }
"$@" "$@"

View file

@ -59,7 +59,12 @@ pub fn initialize() void {
isr.install_syscalls(); isr.install_syscalls();
interrupt.registerIRQ(0, interrupt.pit_handler); interrupt.registerIRQ(0, interrupt.pit_handler);
interrupt.registerIRQ(1, kernel.ps2.keyboard_handler); interrupt.registerIRQ(1, kernel.ps2.keyboard_handler);
interrupt.register(3, test_b);
// load IDT // load IDT
lidt(@ptrToInt(&idtr)); lidt(@ptrToInt(&idtr));
} }
pub fn test_b() void {
kernel.println("int3!");
}

View file

@ -1,9 +1,3 @@
////
// Load a new Task Register.
//
// Arguments:
// desc: Segment selector of the TSS.
//
pub inline fn ltr(desc: u16) void { pub inline fn ltr(desc: u16) void {
asm volatile ("ltr %[desc]" asm volatile ("ltr %[desc]"
: :
@ -11,27 +5,25 @@ pub inline fn ltr(desc: u16) void {
); );
} }
////
// Completely stop the computer.
//
pub inline fn hang() noreturn { pub inline fn hang() noreturn {
asm volatile ("cli"); cli();
while (true) asm volatile ("hlt"); 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"); asm volatile ("sti");
} }
pub inline fn int3() void { pub inline fn int3() void {
asm volatile ("int3"); asm volatile ("int3");
} }
////
// Load a new Interrupt Descriptor Table.
//
// Arguments:
// idtr: Address of the IDTR register.
//
pub inline fn lidt(idtr: usize) void { pub inline fn lidt(idtr: usize) void {
asm volatile ("lidt (%[idtr])" asm volatile ("lidt (%[idtr])"
: :

View file

@ -7,7 +7,6 @@ pub fn x86_main(info: *const kernel.multiboot.MultibootInfo) void {
idt.initialize(); idt.initialize();
pmem.initialize(info); pmem.initialize(info);
paging.initialize(); paging.initialize();
// enable interrupts // enable interrupts
sti(); sti();
} }

View file

@ -16,7 +16,7 @@ pub inline fn pageAlign(address: u32) u32 {
// Return the amount of variable elements (in bytes). // Return the amount of variable elements (in bytes).
// //
pub inline fn available() usize { pub fn available() usize {
return stack_index * PAGE_SIZE; return stack_index * PAGE_SIZE;
} }

View file

@ -1,47 +1,55 @@
;https://wiki.osdev.org/Multitasking_Systems //https://wiki.osdev.org/Multitasking_Systems
;C declaration: //C declaration:
; void switch_tasks(thread_control_block *next_thread); // void switch_tasks(thread_control_block *next_thread)//
; //
;WARNING: Caller is expected to disable IRQs before calling, and enable IRQs again after function returns //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: 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: // push %ebx
; For cdecl; EAX, ECX, and EDX are already saved by the caller and don't need to be saved again // push %esi
; EIP is already saved on the stack by the caller's "CALL" instruction // push %edi
; The task isn't able to change CR3 so it doesn't need to be saved // push %ebp
; Segment registers are constants (while running kernel code) so they don't need to be saved
push ebx // mov %edi,[current_task] //edi = address of the previous task's "thread control block"
push esi // mov [edi+TCB.ESP], %esp //Save ESP for previous task's kernel stack in the thread's TCB
push edi
push ebp
mov edi,[current_task_TCB] ;edi = address of the previous task's "thread control block" // Load next task's state
mov [edi+TCB.ESP],esp ;Save ESP for previous task's kernel stack in the thread's TCB
;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 %esp,[esi+TCB.ESP] //Load ESP for next task's kernel stack from the thread's TCB
mov [current_task_TCB],esi ;Current task's TCB is the next task 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 // cmp eax,ecx //Does the virtual address space need to being changed?
mov eax,[esi+TCB.CR3] ;eax = address of page directory for next task // je .doneVAS // no, virtual address space is the same, so don't reload it and cause TLB flushes
mov ebx,[esi+TCB.ESP0] ;ebx = address for the top of the next task's kernel stack // mov cr3,eax // yes, load the next task's virtual address space
mov [TSS.ESP0],ebx ;Adjust the ESP0 field in the TSS (used by CPU for for CPL=3 -> CPL=0 privilege level changes) // .doneVAS:
mov ecx,cr3 ;ecx = previous task's virtual address space
cmp eax,ecx ;Does the virtual address space need to being changed? // pop %ebp
je .doneVAS ; no, virtual address space is the same, so don't reload it and cause TLB flushes // pop %edi
mov cr3,eax ; yes, load the next task's virtual address space // pop %esi
.doneVAS: // pop %ebx
pop ebp
pop edi
pop esi
pop ebx
ret ;Load next task's EIP from its kernel stack)

View file

@ -1,4 +0,0 @@
pub const assert = @import("std").debug.assert;
pub const std = @import("std");
pub usingnamespace @import("vga.zig");

View file

@ -3,6 +3,13 @@ usingnamespace @import("index.zig");
var command: [10]u8 = undefined; var command: [10]u8 = undefined;
var command_len: usize = 0; var command_len: usize = 0;
fn test_a() void {
var a: u32 = 2;
a += 1;
a = 4;
a -= 1;
}
fn execute(input: []u8) void { fn execute(input: []u8) void {
const eql = std.mem.eql; const eql = std.mem.eql;
if (eql(u8, input, "x86paging")) return x86.paging.introspect(); 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, "lspci")) return pci.lspci();
if (eql(u8, input, "uptime")) return time.uptime(); if (eql(u8, input, "uptime")) return time.uptime();
if (eql(u8, input, "topbar")) return topbar(); 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); println("{}: command not found", input);
} }

View file

@ -15,8 +15,6 @@ export const multiboot_header align(4) linksection(".multiboot") = multiboot: {
}; };
}; };
extern fn switch_tasks(stack: u32) void;
// arch independant initialization // arch independant initialization
export fn kmain(magic: u32, info: *const multiboot.MultibootInfo) noreturn { export fn kmain(magic: u32, info: *const multiboot.MultibootInfo) noreturn {
assert(magic == multiboot.MULTIBOOT_BOOTLOADER_MAGIC); assert(magic == multiboot.MULTIBOOT_BOOTLOADER_MAGIC);
@ -24,10 +22,11 @@ export fn kmain(magic: u32, info: *const multiboot.MultibootInfo) noreturn {
println("--- x86 initialization ---"); println("--- x86 initialization ---");
x86.x86_main(info); x86.x86_main(info);
println("--- core initialization ---"); println("--- core initialization ---");
// pci.scan();
vmem.initialize(); vmem.initialize();
task.initialize() catch unreachable; pci.scan();
console.initialize();
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();
} }

View file

@ -1,28 +1,39 @@
pub usingnamespace @import("index.zig"); pub usingnamespace @import("index.zig");
const TASK_MAX = 1024; 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. const STACK_SIZE = x86.PAGE_SIZE; // Size of thread stacks.
var tid_counter: u16 = 1; 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, tid: u16,
stack_top: usize, //context: isr.Context,
entrypoint: usize,
// context: isr.Context,
//cr3: usize, //cr3: usize,
pub fn new(entrypoint: usize) !*Task { pub fn new(entrypoint: usize) !*Task {
// Allocate and initialize the thread structure. // Allocate and initialize the thread structure.
var t = try vmem.allocate(Task); var t = try vmem.allocate(Task);
t.entrypoint = entrypoint;
t.tid = tid_counter; t.tid = tid_counter;
tid_counter +%= 1; tid_counter +%= 1;
assert(tid_counter != 0); //overflow assert(tid_counter != 0); //overflow
t.stack_top = try vmem.malloc(STACK_SIZE); // allocate a new stack
assert(t.stack_top < layout.USER_STACKS_END); 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; tasks[t.tid] = t;
return t; return t;
@ -30,20 +41,38 @@ pub const Task = struct {
pub fn destroy(self: *Task) void { pub fn destroy(self: *Task) void {
tasks[self.tid] = null; tasks[self.tid] = null;
vmem.free(self.stack_top); vmem.free(self.esp);
vmem.free(@ptrToInt(self)); vmem.free(@ptrToInt(self));
} }
};
pub fn initialize() !void { pub fn switch_to(self: *Task) void {
const t = try Task.new(0x0); assert(self != current_task);
println("task=0x{x}", t.stack_top); // save old stack
} const old_task_esp_addr = &current_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 { pub fn introspect() void {
for (tasks) |t| { for (tasks) |t| {
if (t == null) continue; if (t == null) continue;
println("{}", t); if (t != current_task) println("{}", t);
if (t == current_task) println("*{}", t);
} }
} }

View file

@ -1,7 +1,7 @@
// usingnamespace @import("index.zig"); usingnamespace @import("index.zig");
const time = @import("time.zig"); // const time = @import("time.zig");
const x86 = @import("arch/x86/index.zig"); // const x86 = @import("arch/x86/index.zig");
const std = @import("std"); // const std = @import("std");
// Screen size. // Screen size.
pub const VGA_WIDTH = 80; pub const VGA_WIDTH = 80;
pub const VGA_HEIGHT = 25; pub const VGA_HEIGHT = 25;
@ -56,9 +56,8 @@ const Errors = error{};
pub fn print(comptime format: []const u8, args: ...) void { pub fn print(comptime format: []const u8, args: ...) void {
var a = std.fmt.format({}, Errors, printCallback, format, args); var a = std.fmt.format({}, Errors, printCallback, format, args);
} }
pub fn println(comptime format: []const u8, args: ...) void { 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 { pub fn clear() void {
vga.clear(); vga.clear();
@ -66,13 +65,16 @@ pub fn clear() void {
pub fn topbar() void { pub fn topbar() void {
const cursor = vga.cursor; const cursor = vga.cursor;
const bg = vga.background; const bg = vga.background;
vga.cursor = 0; while (true) {
vga.background = Color.Red; vga.cursor = 0;
vga.background = Color.Red;
time.uptime(); time.uptime();
vga.cursor = cursor; vga.cursor = cursor;
vga.background = bg; vga.background = bg;
task.tasks[0].?.switch_to();
}
} }
fn printCallback(context: void, string: []const u8) Errors!void { fn printCallback(context: void, string: []const u8) Errors!void {