Skip to main content

Julia Evans

Day 36: On programming without malloc

So right now I’m working on writing a kernel in Rust. My current goal is to press keys on the keyboard and have them echoed to the screen. This is going okay! I anticipate being able to type by the end of the week.

One thing that’s interesting is that my expectations around what programs should be able to do is really different right now. Normally I write Python or other high-level languages, so my programs don’t run too quickly, but have tons of resources available to them (the Internet, a standard library, memory allocation, garbage collection, …).

Writing operating systems is totally different. This is kind of obvious, but actually doing it is really fascinating. My OS literally can’t allocate memory, and there’s no standard way to print (I have to write to the VGA buffer manually). I can still write loops, though, and in general writing Rust doesn’t feel too unfamiliar. But I expect my code to run super fast, because it has no excuse not to :). Right now I definitely don’t have timers or anything, so I’m looping 80,000,000 times to sleep.

A few things that I can’t do that I’m used to being able to do:

  • allocate memory
  • print (I can sort of do this)
  • sleep
  • run other processes (there are no other programs)
  • read from stdin (I don’t have a keyboard driver yet. There is no stdin.)
  • open files (there are no files)
  • list files (there are no files)

(thanks to Lea for “there are no files” =D)

The only real problem with not having malloc is that all the memory I use has to either be

  • in the program at compile time, or
  • allocated on the stack

This is less difficult than I expected it to be! We’ll see how it continues. It does mean that I use a lot of global variables, and it’s given me an appreciation for why there is so much use of global variables in the Linux kernel – if just need 1 struct, it makes so much more sense to just have 1 global struct than to keep mallocing and freeing it all the time.

Here’s an example of some code I have in the kernel! main() prints all the ASCII characters in a loop.

pub unsafe fn putchar(x: u16, y: u16, c: u8) {
    let idx : uint =  (y * VGA_WIDTH * 2 + x * 2) as uint;
    // 0xb8000 is the VGA buffer
    *((0xb8000 + idx) as *mut u16) = make_vgaentry(c, Black, Yellow);
}

fn make_vgaentry(c: u8, fg: Color, bg: Color) -> u16 {
    // VGA entries are 2 bytes. The first byte is the character, the
    second is the colour
    let color = fg as u16 | (bg as u16 << 4);
    return c as u16 | (color << 8);
}

pub unsafe fn main() {
    let mut i: u32 = 0;
    let mut c: u8 = 65; // 'A'
    let N: u32 = 80000000; // big enough number so that it goes slowly
    loop {
        i += 1;
        if (i % N == 0) {
            c += 1;
            putchar(2, 4, c);
        }
    }
}

Note for pedants: I actually do have a malloc function because my Rust standard library needs to link against it, but it’s defined like this:

malloc:
    jmp $

That’s assembly-speak for “loop forever”. If I get around to implementing malloc it will be the Most Exciting Thing

Day 35: Types in Rust, for beginners Day 37: After 5 days, my OS doesn't crash when I press a key