Page 1 of 1

Fun little retro os project

Posted: Tue Apr 28, 2026 10:47 am
by gsos
Like many of you I always had an itch to write a hobby os. My dream was one where I could play my childhood dos games like PoP, keen, doom etc. natively, along with running normal linux programs. With the advent of AI coding, one can now focus on core design and have AI implement dos/dpmi/linux api's which makes getting a project to a fun state where you can actually play old games in your OS doable within a reasonable time investment.

https://github.com/gerben-stavenga/RetroOS

For a video showing multitasking dos + busybox linux.
https://youtu.be/Clh45xUpZNY

Before you are bemoaning AI slop, I put some real thought into the architecture which became actually quite nice. It's a 32-bit kernel supporting 16, 32 and 64 bit user programs. The "shell" is DN (a open source 16-bit Norton commander clone) from which you can start your 16 bit dos games (or 32 bit dpmi games) and also 32/64 bit linux binaries (of course not close to supporting full linux syscall API surface,, but some minimal set of file / process syscalls, with some rust compiled test programs using musl).

It's a high mem kernel 0xC000_0000 with recursive paging. The paging code is written so that the same code works for legacy (32 bit paging), PAE and x64 (PML4). The page tables are setup so that the kernel can seamlessly toggle between PAE and PML4 (compat mode). This way it supports 16-bit 8086 (using vm86 in old-style 32 bit protected mode), and 64 bit programs using 32-bit compat mode. Just before returning to userspace it checks if it's vm86 or 64bit and toggles if necessary.

The 32, 64 entry points all quickly merge into a single entry point with a canonical Regs frame. I tried to deal with hardware warts as much as possible in one place. The regs struct has the 64bit registers rax-r15, segments and the 64 bit entry stack frame. 32 bit entry follows the same layout by pushing 0 for the high dwords. The 32 bit entry frame is converted into 64 bit frame at entry and vice versa at exit. The extra segments that vm86 pushes are copied into their regular es,ds fields in Regs if vm86 mode entry is detected and vice versa on exit.

So ring-0 has a single entry point (call from entry.asm) and single exit point (return to entry.asm). The bulk of the kernel runs in ring-1 so that it can run in privileged pages but can make normal syscalls to ring-0. This makes ring-1 very easy to understand and makes ring-0 basically trivial.

Ring-0 flow
If userspace is interrupted
1) if pagefaults service directly/return if possible (COW/demand paging) else goto 3
2) if irq handle it and push interrupt-event in queue, then goto 3
3) swap frame with kernel frame, set (intno, extra) as return values (only execute syscall goes to userspace) and return (to ring-1 kernel)

if kernel space is interrupted
1) if pagefault handle (often kernel panic as ring-1 mostly shouldnt pagefault)
2) irq => handle and push in queue return to ring-1
3) syscall execute => swap frame with frame as provided return (to userspace)
4) few other syscalls like map mem that just return to ring-1

If ring-0 is interrupted service irq and return

Ring-1 flow

Code: Select all

loop {
   let event = ring_0_syscall_execute(&mut regs);  // run process returns with some event (syscall, interrupt)
   let action = match current_thread_os_personality {
      Dos(dos_thread) => HandleDos(event, dos_thread, &mut regs),
      Linux(linux_thread) => HandleLinux(event, linux_thread, &mut regs),
      ...
  }
  match action {
    Switch(tid) => ring_0_syscall_switch_address_space(..)
    ...
  }
}
Just a simple event loop consisting of normal rust code. Because this design is free of hidden interrups/control flow, rust drops(RAII) just works. Hence from here on, AI is capable of producing ton of code implementing dos 21 functions, xms/ems/dpmi functions and some basic linux syscalls.

It's playing keen (it flickers with qemu but i suspect its qemu ega issue where wrapping 16bit segments is not implemented), original doom, original quake, prince of persia. You can multitask between them with F11 which background a DOS task (command.com does linux fork+exec).

I tried hard to minimize asm code. Only entry.asm contains actual logic, but it's only setting up a unified frame and upon exit a test to use 32 or 64 bit exit based on cpu-mode. The only other asm function is toggle between PAE and compat that needs to be identity mapped on a page (0xF000). One more trick is using segments on entry to make the kernel symbols lineup at the right high address at which they are linked. Often initial page tables are constructed in asm to map kernel high before calling into high level code. This allows building page tables using normal code (otherwise you have to be very careful that you don't use addresses that are at physical address because paging isnt active or use asm).

Re: Fun little retro os project

Posted: Sat Jul 11, 2026 11:42 pm
by gsos
This hobby project has added quite a bit of features and I think the architecture will be interesting to the OS aficionados on this forum. See

https://www.youtube.com/watch?v=_DG_4CkjV9I

The most interesting thing perhaps is that I was able to isolate the x86 core stuff into arch-metal lib that implements arch-abi trait. The way it's setup is such, that I also implemented the trait by wrapping QEMU TCG engine (unicorn) and also a full kvm variant. The actual kernel (vfs, dos personality, linux personality, threading, ...) is written in almost entirely safe rust calling into a handful of arch functions through the arch-abi trait. This means that the same kernel can be build in three modes,

1) metal. It's a full OS with both legacy bios and modern uefi compatibility. So it can run on old machines with vga compatible graphics and sound blaster and it will expose this. But it can also run on uefi with hda, in which case vga and sound blaster is emulated, so you can still play your retro-games directly on the machine.

2) interpreted. This is basically dosbox mode, the QEMU TCG emulates the userspace programs, while the kernel is compiled as a regular app running native on the host machine. This can be played on any architecture.

3) kvm. This is basically dosemu2 mode. The userspace apps run natively on the cpu under kvm hypervisor, the kernel still runs as a regular app on the host machine. Most of the hosted specific code is shared with interpreted mode. The only differnce is that TCG is replaced with native execution using kvm

All the three modes above run the exact same kernel, with only a different implementation of the arch-abi injected. This actually is quite convenient as one can just run and debug the kernel like any normal app. No need to hook gdb through qemu or that kind of tricks.

So far it allows me to play my favorite games from my youth again on my razer laptop which is very satisfying. You can use it by just copying the kernel into your ext4 /boot directory, setup grub entry and /home/retroos becomes c: . You have access to your linux tools by starting shell.elf in c:\boot , but this is still work in progress (ls/cat/echo work but I'm not close to full linux environment). Of course I recommend qemu and installing it on your computer as in the video above is at your own risk. Anyway the project is complete enough that it might appeal to some retro hobbyist.

Of course I used AI as a "compiler" to compile DOS syscall spec to rust, compile the DPMI spec to rust, linux syscall API to rust. But the design and architecture is mine and you can see that that is still very similar to my code written back in 2023 in this repo. The design of the OS seems to occupy a somewhat new/original spot in the OS design space, sharing features and ideas with many other concepts in kernel design but in a unique way.

It is a monolithic kernel, one elf linked together, but it shares many ideas with microkernels. Arch is a microkernel , but uses just function calls instead of messages. The arch design is more like a hypervisor then the standard osdev tutorial/linux flow, which allows to abstract the core as a library. I haven't seen the trait factorization, ie. providing different ways of running the same kernel, done this way, but it of course strongly reminds one of UM-linux.

What I certainly havent seen before is using recursive paging in a way that unifies 32bit PAE paging and x64 paging, allowing the kernel to seamlessly toggle between 32bit and 64bit mode, which allows vm86 and 64-bit code to execute under one kernel (without the virtualization instruction set).

Specs:
It should run on 386/bios all the way up to x64/uefi without CSM. If you have an old computer with an actual sound blaster, games can use it natively. Otherwise sound blaster/adlib (experimental gus) is supported over hda/ac97 systems. Also when vga is not available, vga is emulated over the regular framebuffer. The video is RetroOS running on a razer (2025) with realtek hda. Old 486/sb I only tested through qemu/bochs/86box emulators.