Hos is an operating system for X86-64, written in mainly Haskell 98 for the JHC compiler. It consists of a microkernel and a few user-space drivers, including a simple ATA driver, but no filesystem.


Hos is an operating system consisting of a microkernel and a few user-space programs. It is written mostly in JHC-compatible Haskell 98, with fallbacks to C when necessary (garbage collection, for example).

For an overview of how to build and run Hos, see the project page on Github.

Continue reading for a quick tour around the source code.

To Long Mode and Beyond…

The main Hos kernel runs in long mode. We use ISOLINUX to start a small multiboot-compatible kernel written in X86-64 assembler that takes us from protected mode directly into long mode. This can be found at cbits/boot.S. This file also initializes the garbage collector (bootstrap_kernel), JHC run-time system (see the call to jhc_hs_init), and finally calls the main haskell function (see call to _amain).

This boot code also maps our kernel into the address space at 0xffffff7f00000000. This gives the kernel 4 GB of room, because the top 512 GB are used for a self-referential mapping of the page tables themselves.

Dealing with memory

Operating systems have to be familiar with the intimate details of memory, but Haskell is a garbage collected language. This means we must resort to C for the garbage collector.

The bootstrap_kernel function in cbits/multiboot.c deals with all the memory setup prior to running the main kernel. This function maps the multiboot modules into visible address space and initializes the buddy page allocator (initalize_regions).

The page allocator allows us to allocate and free physical pages. We tie this into the JHC run-time system by mapping the allocated physical pages into the next available address in the kernel memory region. Once a kernel page is allocated it is never freed, but the memory is re-used. This should eventually change if we were to make Hos into a “real” OS.

The code for the page allocator is in cbits/multiboot.c in the alloc_from_regions and free_from_regions. The allocator is initialized in the initialize_regions function in the same file, which uses the multiboot memory map to create the proper free regions.

The page allocation system used is a simple buddy based allocator, explained in depth here.

The run-time system

A modified version of the JHC run-time system is in the rts directory. The JHC garbage collector only needs three calls to integrate with a new memory system. These calls are ext_page_aligned_alloc which returns the virtual address of newly allocated pages, ext_free which frees an allocation made by ext_page_aligned_alloc, and ext_alloc_megablock which returns a new megablock. Essentially, these calls all delegate to the buddy physical page allocator, and then establish the correct virtual mapping.

This is all we need to run Haskell bare bones!

The main kernel

The main kernel is located in the src directory. The entry point is at src/main.hs. The hosMain function takes in an Arch record, corresponding to architecture-specific calls, and produces an IO action that will run the operating system.

It begins by parsing the init tasks ELF headers from the multiboot module using the C code to locate the module. It then creates a new init task, adds it to the task table, prepares the system to enter userspace, and switches to user space almost immediately.

The kernel is structured as a co-routine with user space. Essentially, we yield to userspace using the archSwitchToUserspace function, which runs the userspace continuation, until a system call is made or a trap is hit. We then handle the specific details, and switch back into userspace. This entire system is in the kernelize function in main.hs. Note the large case expression in this function, which handles all the reasons we may have come back from userspace.

I won’t deal with the architecture-specific code here, but it should look familiar to anyone with experience in operating systems.

User space

Just as we have a kernel-level run-time system, there is also a user-level one at progs/hos. This re-exports enough C standard library functions to let the vanilla JHC run-time system run in Hos userspace. Notably, it uses dlmalloc to provide memory management and a C-level Hos system call library to provide sbrk functionality (see progs/hos/hos.c).

Currently, I’ve written four user-level drivers:


Hos is still unfinished. I would like to make the ATA driver auto-start an ISO9660 filesystem driver which will expose the CD objects to storage as objects. Then, I would like to create a simple VGA text console driver, and load it from the CD using init.

This would allow us to make a much more impressive demo. Patches welcome. Ping me if you have any questions. Happy hacking!