Zig Bare Bones

From OSDev.wiki
Revision as of 11:20, 1 December 2021 by osdev>Leapofazzam (Created page with "{{Stub}} In this tutorial, we'll make a simple hello world kernel in Zig. == Prerequisites == First off, you'll need: * The Zig compiler, at least version 0.7.0 * [[GRUB]...")
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to navigation Jump to search
This page is a stub.
You can help the wiki by accurately adding more contents to it.

In this tutorial, we'll make a simple hello world kernel in Zig.

Prerequisites

First off, you'll need:

  • The Zig compiler, at least version 0.7.0
  • GRUB as our bootloader to boot the kernel

Code

If you done setting up all of the prerequisites above, we can now write some code for our kernel

build.zig

const std = @import("std");
const Builder = @import("std").build.Builder;
const Target = @import("std").Target;
const CrossTarget = @import("std").zig.CrossTarget;
const Feature = @import("std").Target.Cpu.Feature;

pub fn build(b: *Builder) void {
    const features = Target.x86.Feature;

    var disabled_features = Feature.Set.empty;
    var enabled_features = Feature.Set.empty;

    disabled_features.addFeature(@enumToInt(features.mmx));
    disabled_features.addFeature(@enumToInt(features.sse));
    disabled_features.addFeature(@enumToInt(features.sse2));
    disabled_features.addFeature(@enumToInt(features.avx));
    disabled_features.addFeature(@enumToInt(features.avx2));
    enabled_features.addFeature(@enumToInt(features.soft_float));

    const target = CrossTarget{
        .cpu_arch = Target.Cpu.Arch.i386,
        .os_tag = Target.Os.Tag.freestanding,
        .abi = Target.Abi.none,
        .cpu_features_sub = disabled_features,
        .cpu_features_add = enabled_features
    };

    const mode = b.standardReleaseOptions();

    const kernel = b.addExecutable("kernel.elf", "src/main.zig");
    kernel.setTarget(target);
    kernel.setBuildMode(mode);
    kernel.setLinkerScriptPath(.{ .path = "src/linker.ld" });
    kernel.code_model = .kernel;
    kernel.install();

    const kernel_step = b.step("kernel", "Build the kernel");
    kernel_step.dependOn(&kernel.install_step.?.step);

    const iso_dir = b.fmt("{s}/iso_root", .{b.cache_root});
    const kernel_path = b.getInstallPath(kernel.install_step.?.dest_dir, kernel.out_filename);
    const iso_path = b.fmt("{s}/disk.iso", .{b.exe_dir});

    const iso_cmd_str = &[_][]const u8{ 
        "/bin/sh", "-c",
        std.mem.concat(b.allocator, u8, &[_][]const u8{
            "mkdir -p ", iso_dir, " && ",
            "cp ", kernel_path, " ", iso_dir, " && ",
            "cp src/grub.cfg ", iso_dir, " && ",
            "grub-mkrescue -o ", iso_path, " ", iso_dir
        }) catch unreachable
    };

    const iso_cmd = b.addSystemCommand(iso_cmd_str);
    iso_cmd.step.dependOn(kernel_step);

    const iso_step = b.step("iso", "Build an ISO image");
    iso_step.dependOn(&iso_cmd.step);
    b.default_step.dependOn(iso_step);

    const run_cmd_str = &[_][]const u8{
        "qemu-system-x86_64",
        "-cdrom", iso_path,
        "-debugcon", "stdio",
        "-vga", "virtio",
        "-m", "4G",
        "-machine", "q35,accel=kvm:whpx:tcg",
        "-no-reboot", "-no-shutdown"
    };

    const run_cmd = b.addSystemCommand(run_cmd_str);
    run_cmd.step.dependOn(b.getInstallStep());

    const run_step = b.step("run", "Run the kernel");
    run_step.dependOn(&run_cmd.step);
}

src/main.zig

const console = @import("console.zig");

const ALIGN = 1 << 0;
const MEMINFO = 1 << 1;
const MAGIC = 0x1BADB002;
const FLAGS = ALIGN | MEMINFO;

const MultibootHeader = packed struct {
    magic: i32 = MAGIC,
    flags: i32,
    checksum: i32,
};

export var multiboot align(4) linksection(".multiboot") = MultibootHeader{
    .flags = FLAGS,
    .checksum = -(MAGIC + FLAGS),
};

export var stack_bytes: [16 * 1024]u8 align(16) linksection(".bss") = undefined;
const stack_bytes_slice = stack_bytes[0..];

export fn _start() callconv(.Naked) noreturn {
    @call(.{ .stack = stack_bytes_slice }, kmain, .{});

    while (true) {}
}

fn kmain() void {
    console.initialize();
    console.write("Hello world!");
}

src/console.zig

const fmt = @import("std").fmt;
const mem = @import("std").mem;

const VGA_WIDTH = 80;
const VGA_HEIGHT = 25;
const VGA_SIZE = VGA_WIDTH * VGA_HEIGHT;

pub const ConsoleColors = enum(u8) {
    Black = 0,
    Blue = 1,
    Green = 2,
    Cyan = 3,
    Red = 4,
    Magenta = 5,
    Brown = 6,
    LightGray = 7,
    DarkGray = 8,
    LightBlue = 9,
    LightGreen = 10,
    LightCyan = 11,
    LightRed = 12,
    LightMagenta = 13,
    LightBrown = 14,
    White = 15
};

var row: usize = 0;
var column: usize = 0;
var color = vgaEntryColor(ConsoleColors.LightGray, ConsoleColors.Black);
var buffer = @intToPtr([*]volatile u16, 0xB8000);

fn vgaEntryColor(fg: ConsoleColors, bg: ConsoleColors) u8 {
    return @enumToInt(fg) | (@enumToInt(bg) << 4);
}
 
fn vgaEntry(uc: u8, new_color: u8) u16 {
    var c: u16 = new_color;
    return uc | (c << 8);
}

pub fn initialize() void {
    clear();
}

pub fn setColor(new_color: u8) void {
    color = new_color;
}

pub fn clear() void {
    mem.set(u16, buffer[0..VGA_SIZE], vgaEntry(' ', color));
}

pub fn putCharAt(c: u8, new_color: u8, x: usize, y: usize) void {
    const index: usize = y * VGA_WIDTH + x;
    buffer[index] = vgaEntry(c, new_color);
}

pub fn putChar(c: u8) void {
    putCharAt(c, color, column, row);
    if (column == VGA_WIDTH) {
        column = 0;
        if (row == VGA_HEIGHT)
            row = 0;
    }
}

pub fn puts(data: []const u8) void {
    for (data) |i|
        putChar(data[i]);
}

pub fn printf(comptime format: []const u8, args: anytype) void {
    var buf: [100]u8 = undefined;
    puts(fmt.bufPrint(&buf, format, args) catch unreachable);
}

src/linker.ld

ENTRY(_start)
 
SECTIONS {
    . = 1M;
 
    .text BLOCK(4K) : ALIGN(4K) {
        *(.multiboot)
        *(.text)
    }
 
    .rodata BLOCK(4K) : ALIGN(4K) {
        *(.rodata)
    }
 
    .data BLOCK(4K) : ALIGN(4K) {
        *(.data)
    }
 
    .bss BLOCK(4K) : ALIGN(4K) {
        *(COMMON)
        *(.bss)
    }
}

src/grub.cfg

menuentry "Zig Bare Bones" {
	multiboot /boot/kernel.elf
}

Build

Now that our kernel is done, we need build our kernel with the command below:

$ zig build

To boot our kernel, simple run this command:

$ zig build run