Zig Bare Bones
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:
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 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 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