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/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);

30
qemu.sh
View file

@ -1,5 +1,5 @@
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
@ -11,11 +11,11 @@ start() {
-enable-kvm \
-m 1337M \
-curses \
-append "Hello" \
-drive file=disk.img,if=virtio\
-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
@ -23,26 +23,16 @@ start() {
# -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-exec 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 system_reset
monitor-exec cont
}
"$@"

View file

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

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 {
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])"
:

View file

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

View file

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

View file

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

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

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

View file

@ -1,14 +1,18 @@
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,
//cr3: usize,
@ -16,13 +20,20 @@ pub const Task = struct {
// 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 = &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 {
for (tasks) |t| {
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");
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,6 +65,7 @@ pub fn clear() void {
pub fn topbar() void {
const cursor = vga.cursor;
const bg = vga.background;
while (true) {
vga.cursor = 0;
vga.background = Color.Red;
@ -73,6 +73,8 @@ pub fn topbar() void {
vga.cursor = cursor;
vga.background = bg;
task.tasks[0].?.switch_to();
}
}
fn printCallback(context: void, string: []const u8) Errors!void {