Zig Bare Bones

From OSDev Wiki
Jump to: navigation, search

This article is a stub! This page or section is a stub. You can help the wiki by accurately contributing to it.

This tutorial needs to explain what the code does as tutorials are not just copy paste. You can help out by editing this page to include more context to what the code does.

This article was written like there is only one author. This is a wiki, not a personal site. You can help the wiki by editing this article to remove mentions of authors.

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

Contents

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.puts("Hello world!");
}

src/console.zig

const fmt = @import("std").fmt;
const Writer = @import("std").io.Writer;
 
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 = @as([*]volatile u16, @ptrFromInt(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 {
    @memset(u16, buffer[0..VGA_SIZE], vgaEntry(' ', color));
}
 
pub fn putCharAt(c: u8, new_color: u8, x: usize, y: usize) void {
    const index = y * VGA_WIDTH + x;
    buffer[index] = vgaEntry(c, new_color);
}
 
pub fn putChar(c: u8) void {
    putCharAt(c, color, column, row);
    column += 1;
    if (column == VGA_WIDTH) {
        column = 0;
        row += 1;
        if (row == VGA_HEIGHT)
            row = 0;
    }
}
 
pub fn puts(data: []const u8) void {
    for (data) |c|
        putChar(c);
}
 
pub const writer = Writer(void, error{}, callback){ .context = {} };
 
fn callback(_: void, string: []const u8) error{}!usize {
    puts(string);
    return string.len;
}
 
pub fn printf(comptime format: []const u8, args: anytype) void {
    fmt.format(writer, format, args) catch unreachable;
}

src/linker.ld

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

src/grub.cfg

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

Build

Now that our kernel code is done, we'll now build our kernel by running the command below:

$ zig build

To boot our kernel, simply run this command:

$ zig build run
Personal tools
Namespaces
Variants
Actions
Navigation
About
Toolbox