Technology10 min read

Moxnox 1.0: NT-Like, POSIX-Compatible OS Running Atop Btrfs

By · Published by Everything Blog

In short

Moxnox 1.0 is an NT-like, POSIX-compatible operating system running atop the Btrfs filesystem, designed for Unix-like software compatibility. It supports both amd64 and aarch64 architectures and runs on btrfs. The project is mature with a 130,000-line C++ codebase and self-hosting capabilities, allowing for easy building on Moxnox itself. It supports modern hardware, including the Raspberry Pi 5 and newer.

Key points

  • Moxnox 1.0 is an NT-like, POSIX-compatible operating system.: Moxnox 1.0 is an NT-like, POSIX-compatible operating system.
  • The project is mature with a 130,000-line C++ codebase.: The project is mature with a 130,000-line C++ codebase.
  • It is self-hosting, allowing building on Moxnox itself.: It is self-hosting, allowing building on Moxnox itself.

Sources checked by our analysis pipeline: 1

Moxnox is an NT-like, POSIX-compatible hobby OS which runs on btrfs and is capable of self-hosting. That means:

- Its design, such as the choice of syscalls, is modelled after that of Windows NT...

- ...yet at the same time, it should be capable of running software written for Unix-like systems, provided that they have been recompiled for it, and comes with a full GNU base system

- It is mature enough that you can build Moxnox on Moxnox

- And it runs on everybody's favourite Linux filesystem

Version 1.0 is around 130,000 lines of C++, and runs on amd64 and aarch64.

The btrfs code works for basic purposes, but has not been battle-hardened. Please don't try to mount anything but throwaway filesystems.

To test Moxnox in qemu, download moxnox-amd64-1.0.img.gz and moxnox-kernel-amd64-1.0.efi. Run gunzip moxnox-amd64-1.0.img.gz

to decompress the image.

Then:

qemu-system-x86_64 -M q35 -enable-kvm -cpu host -m 1024 \

-smp cores=4,threads=1 \

-kernel moxnox-kernel-amd64-1.0.efi \

-append "root=/dev/sda" \

-device ich9-ahci,id=ahci -drive id=disk,file=moxnox-amd64-1.0.img,if=none \

-device ide-hd,drive=disk,bus=ahci.0 \

-nic user,model=virtio-net-pci \

-device qemu-xhci \

-device usb-kbd,id=kbd \

-device usb-tablet,id=tablet \

-serial stdio

You will need to increase the memory from 1024 MB if you are trying anything strenuous, like self-hosting.

For ARM, something like the below should work:

qemu-system-aarch64 -M virt -cpu cortex-a76 -m 1024 \

-smp cores=4,threads=1 \

-kernel moxnox-kernel-aarch64-1.0.efi \

-append "root=/dev/sda" \

-device ich9-ahci,id=ahci -drive id=disk,file=moxnox-aarch64-1.0.img,if=none \

-device ide-hd,drive=disk,bus=ahci.0 \

-nic user,model=virtio-net-pci \

-drive if=pflash,unit=0,file=/usr/share/qemu/edk2-aarch64-code.fd,readonly=on \

-device qemu-xhci \

-device usb-kbd,id=kbd \

-device usb-tablet,id=tablet \

-serial stdio

First, use gparted

or similar to create a new partition. 32 GB should be

plenty. I'm calling it /dev/nvme0n1p99

below - don't get this wrong.

Make sure you have a recent versions of btrfs-progs, as we don't support some of the default settings used by the older versions.

Prepare the root drive:

# mkfs.btrfs /dev/nvme0n1p99

# mkdir /mnt/moxnox

# mount /dev/nvme0n1p99 /mnt/moxnox

# cd /mnt/moxnox

# wget https://github.com/maharmstone/moxnox/releases/download/v1.0/moxnox-amd64-1.0.tar.gz

# tar -xvzf moxnox-amd64-1.0.tar.gz

# rm moxnox-amd64-1.0.tar.gz

# cd

# umount /mnt/moxnox

Copy the kernel:

# mount /boot/efi

# cd /boot/efi

# wget https://github.com/maharmstone/moxnox/releases/download/v1.0/moxnox-kernel-amd64-1.0.efi

To install the boot entry, do something like:

# efibootmgr -c -d /dev/nvme0n1 -p 1 -L "Moxnox" -l '\moxnox-kernel-amd64-1.0.efi' -u 'root=/dev/nvme0n1p99'

efibootmgr

likes to change the boot order afterwards, this probably is not

what you want. The names of block devices should be more or less the same on

Linux and Moxnox.

The "EFI" file is actually a polyglot file that also conforms to the Linux boot protocol, so it should also work on non-EFI GRUB. I have not tested this. How to set this up is left as an exercise for the reader.

For amd64, everything is compiled for x86-64-v3, which covers more or less

anything manufactured since 2015 or so. Moxnox automatically uses and enables

AVX512 if it detects it. For qemu, you need to specify the q35

machine type;

the default pc

machine is ancient, and doesn't have PCI Express. microvm

works too.

For aarch64, we're using LSE atomics, which require ARMv8.1. That means the

Raspberry Pi 5, but not any earlier models. Moxnox only supports ACPI, not

Device Tree, so for the Pi you will also need https://github.com/worproject/rpi5-uefi.

Unfortunately there is no raspi5

machine type in qemu, but I have had success

with virt

, and sbsa-ref

works too.

Device support:

- Block devices: NVMe, SATA, SDHCI (i.e. SD cards), virtio-block, USB mass storage

- Network cards: virtio-net, RTL8168, RTL8125

- Input devices: USB HID, PS/2 keyboard, PS/2 mouse, virtio-input

- Graphics: virtio-gpu, GOP framebuffer, VBE, VGA text console (pass

text=80x25

on non-UEFI boots) - Serial ports: virtio-console, x86 serial port, PL011 ARM serial port

- USB: XHCI

The easiest way to build is to pull the Docker containers maharmstone/moxnox-ci-amd64

or maharmstone/moxnox-ci-aarch64

, which contain the cross-compilers for

x86_64-pc-moxnox

or aarch64-pc-moxnox

.

Then in the container:

$ git clone https://github.com/maharmstone/moxnox

$ cd moxnox

CMake will compile whichever of the submodules are checked out. For a minimal system:

$ git submodule update --init --depth 1 ports/acpica/repo ports/freetype/repo ports/gnu-efi/repo ports/harfbuzz/repo ports/libpng/repo ports/mingw-w64/repo ports/zlib/repo ports/musl/repo ports/tre/repo

For all of the ports, which might take a while:

$ git submodule update --init --recursive

Then:

$ mkdir build

$ cd build

$ cmake -DCMAKE_TOOLCHAIN_FILE=amd64.cmake -GNinja ..

or

$ cmake -DCMAKE_TOOLCHAIN_FILE=clang-aarch64-native.cmake -GNinja ..

Make sure you specify Ninja - we need this as we're using C++ modules. CMake defaults to Unix Makefiles, which don't work.

Finally:

$ ninja

gcc

, llvm

, and rust

are not compiled unless explicitly requested due to

their size. hexyl

and ripgrep

also need to be specified explicitly due to

their dependency on rust

.

To build on Moxnox, as above, but you don't need a toolchain file: cmake -GNinja ..

.

gcc

, rust

, hexyl

, and ripgrep

can't yet be compiled in Moxnox, but

everything else works (eventually). You may need a lot of RAM and even more

patience. If CMake complains about rust-libc

, make sure that you don't have

the Rust submodules checked out.

The kernel and first-party programs are licensed under the GPL. The first-party

DLLs are licensed under the LGPL. The contents of the modules

directory are

MIT.

See the ports submodules for details of their particular licences.

Current ports:

- bash

- binutils

- bison

- bzip2

- cairo

- cmake

- coreutils

- curl

- diffutils

- expat

- file

- findutils

- flex

- freetype

- gawk

- gcc (amd64 only)

- git

- gmp

- gperf

- grep

- gzip

- harfbuzz

- hexyl

- jsoncpp

- less

- libarchive

- libiconv

- libpng

- librhash

- libuv

- llvm

- m4

- make

- mako

- markupsafe

- mesa

- mpc

- mpfr

- nano

- ncurses

- netcat

- ninja

- openssh

- openssl

- patch

- perl

- pixman

- python

- pyyaml

- readline

- ripgrep

- rust

- sed

- tar

- vim

- wget

- xz

- zlib

- zstd

Loads - but this is version 1.0. In particular:

- A GUI

- Support for different timezones

- Support for different keyboard layouts

- Locales and non-UTF-8 character sets

- Any sort of security

- Multiple users and groups

For performance issues and similar:

- The userspace heap allocator could be a lot quicker (this is the bottleneck when self-hosting)

- TCP is slow, because we don't negotiate an MSS and keep to the default 64KB window

- We ought to be doing early writeback on memory pressure

- There should be an LRU system to drop clean pages from the page cache...

- ...made worse by the fact that the dentry cache never gets pruned, so inodes stay open until deleted

And obviously there should be more ports and hardware support.

The contract is that the syscalls and low-level interfaces match those of NT, but not the daemons or how the kernel itself is structured. This is in contrast to what ReactOS aims to do, which is an exact recreation of Windows.

So:

- Binaries are PE format rather than ELF, and have a

.exe

or.dll

extension - C++ exceptions are done through SEH

- POSIX signals are done through Asynchronous Procedure Calls

- All the non-kernel kernel-like stuff gets done in

ntdll.dll

- The rcx, rdx, r8, r9 calling convention matches that of Windows

- And its C eccentricities match those of Windows:

sizeof(long) == sizeof(int32_t)

,sizeof(wchar_t) == sizeof(char16_t)

,sizeof(long double) == sizeof(double)

.

There's an awful lot of stuff in NT, but the interesting stuff that's missing is tokens, the Registry, and I/O completion ports.

NT has a complex system where there's a virtual filesystem but it's hidden

(unless you use winobj), with the true path of a file hanging off the name of

its block device (so C:\Windows\System32

might actually be \Device\HarddiskVolume1\Windows\System32

).

I've not replicated any of that. Paths are POSIX-like: slashes rather than

backslashes, /

as the root filesystem, and mounted directories. Unlike real

Windows, NtCreateFile

resolves . and .., to work better with the dentry cache.

Other things I've deliberately not copied are the various forms of transactions, which were a product of their time, and the idea of paged kernel memory, which causes more problems than it solves.

We have implemented 642 of the ~1,200 functions in POSIX 2024. This is the vast

majority of what's used in practice, with gnulib

papering over some of the

cracks. Unlike Windows, fork

is there. I've used musl

for its mathematical

and cryptographic functions, and mingw

for printf

and some other things,

and tre

for its regexes.

The POSIX functions are split over ucrtbase

, iphlpapi

, and ws2_32

, as on

Windows, with anything that Windows doesn't implement put in mnposix

.

I've borrowed liberally from Linux's best ideas: the dentry cache, large folios, mapping the whole of physical memory, evdev.

Other POSIX shortcomings:

- AF_UNIX is half-complete.

socketpair

works, including for SCM_RIGHTS (ssh

uses this), but the normal client-server code doesn't work mkfifo

creates an inode, but I/O to it does not get redirected to a buffer as it ought to do- APCs (i.e. signals) only get delivered when a thread returns from a syscall.

So a program doing

while (true) { }

is currently unkillable.

There's nothing that automatically starts daemons yet, so manually.

First generate the host key:

# ssh-keygen -A

There's no passwords yet, so you will need to add a /root/.ssh/authorized_keys

file so that you can connect. If you have trouble, double-check that /root is

owned by root, as sshd will ignore this file otherwise.

Then start sshd using its full path:

# /bin/sshd.exe

No. Or at least, not yet.

We deliberately do not implement any Win32 DLLs, bar a few functions in

kernelbase

that the mingw runtime wants. A future improvement would be to

strip these out entirely, then port across Wine's versions of the Win32 DLLs.

Some parts of Wine, like wineserver

and ntdll

, we would not need as we are

already taking care of it ourselves.

None of the substantive code was written by AI.

I used AI to help with the mechanical parts of porting (e.g. translating

autotools to CMake, replacing ioctl

calls with tcgetwinsize

, translating

the C structs into Rust), as well as to write some of the smoke tests (e.g.

cairo-test

, mesa-test

).

Where Claude really shines though is in reviewing your own commits. You give it a hash, and it tells you about the silly memory leak or deadlock that you left in, before you can make a fool of yourself.

For funsies. Plus the only way of really learning how something works is to have a go at writing it yourself.

Also, the NT kernel is criminally underrated, and pretty much unknown to

hobbyists. It had features that Linux took years and years to come up with its

own design for, such as eventfd

and pidfd

.

It's a shame that Windows keeps shying away from full POSIX compatibility. They had a half-complete version in the early days, then more recently had WSL1, only to then abandon it. And Nano Server was another cool idea, only again to be itself abandoned. So this is my take on what I wish Windows had been like.

Original source: github.com

Technology