Brendan's Tutorial Step 1: done
This commit is contained in:
parent
8d7e7591e9
commit
6af31b5b89
13 changed files with 163 additions and 127 deletions
5
.gdbinit
Normal file
5
.gdbinit
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
file build/kernel
|
||||||
|
target remote localhost:4242
|
||||||
|
set arch i386
|
||||||
|
set step-mode on
|
||||||
|
directory src
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
30
qemu.sh
30
qemu.sh
|
|
@ -1,5 +1,5 @@
|
||||||
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
|
||||||
|
|
||||||
|
|
@ -11,11 +11,11 @@ start() {
|
||||||
-enable-kvm \
|
-enable-kvm \
|
||||||
-m 1337M \
|
-m 1337M \
|
||||||
-curses \
|
-curses \
|
||||||
-append "Hello" \
|
|
||||||
-drive file=disk.img,if=virtio\
|
|
||||||
-kernel ${KERNEL}
|
-kernel ${KERNEL}
|
||||||
|
# -drive file=disk.img,if=virtio\
|
||||||
# -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
|
||||||
|
|
@ -23,26 +23,16 @@ start() {
|
||||||
# -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"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
"$@"
|
"$@"
|
||||||
|
|
|
||||||
|
|
@ -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!");
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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])"
|
||||||
:
|
:
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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)
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +0,0 @@
|
||||||
pub const assert = @import("std").debug.assert;
|
|
||||||
pub const std = @import("std");
|
|
||||||
|
|
||||||
pub usingnamespace @import("vga.zig");
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
11
src/main.zig
11
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
|
// 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();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
59
src/task.zig
59
src/task.zig
|
|
@ -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 = ¤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 {
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
14
src/vga.zig
14
src/vga.zig
|
|
@ -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,6 +65,7 @@ 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;
|
||||||
|
while (true) {
|
||||||
vga.cursor = 0;
|
vga.cursor = 0;
|
||||||
vga.background = Color.Red;
|
vga.background = Color.Red;
|
||||||
|
|
||||||
|
|
@ -73,6 +73,8 @@ pub fn topbar() void {
|
||||||
|
|
||||||
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 {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue