Humble hobby OS project

A cool project. I do like seeing OS research being done because it is very much becoming a lost skill.

After exploring alternatives like Zig and Rust, I concluded that C remains the most practical, predictable, and hardware-transparent language for low-level system development on 32-bit Intel machines.
Contrast to the hard time I give Rust when it is spammed ontop of layers of C, I am quite interested in 100% pure Rust operating systems. Interestingly the name of your R4R is quite unfortunate because it has nothing to do with Rust as a language!

  • Designed to run on real hardware (i386/i486) as well as emulators like Bochs, or VirtualBox.
  • Note: QEMU is currently unsuitable for testing this build due to a known issue with legacy i486 protected-mode task switching.See QEMU Bug 2024806 – “Protected mode LJMP via TSS/LDT fails with pc=nil.”
This is very interesting. I always assumed that Qemu provided more flexible x86 emulation than VirtualBox but perhaps not. Are there plans to support Qemu in future or does Qemu need to be changed?

R4R attempts to bring all four rings into play in a coordinated and observable way.
This is also interesting. So what is your ultimate goal for this? I.e what features of R4R do you plan to provide to demonstrate this?
 
Contrast to the hard time I give Rust when it is spammed ontop of layers of C, I am quite interested in 100% pure Rust operating systems. Interestingly the name of your R4R is quite unfortunate because it has nothing to do with Rust as a language!
The original desire and inspiration was to write this in Rust, so the inspiration for R4R originally came from there. My capabilities are not that great now, so I decided to go with C and I'm still not happy with LLVM support for x86_32 because it makes a lot of gaps in the code... I started the project with a burning desire to get Rust people interested if they find this interesting.

Even if someone from Rust is interested, my code will remain 100% in C. If someone else wants to rewrite it and improve it in Rust, good luck to them! but I will not mix code between them. In the end, another Rust code repo may be opened ...
 
This is also interesting. So what is your ultimate goal for this? I.e what features of R4R do you plan to provide to demonstrate this?
The ultimate goal of R4R is to demonstrate, in a tangible and observable way, how all four Intel x86 protection rings (0–3) can coexist and interact within a single operating system — each with its own stack, TSS, LDT, and clearly defined domain.

While most modern systems only use Ring 0 and Ring 3 for simplicity, R4R aims to revive Intel’s original privilege model and show how segmentation, paging, and call gates can be combined to create real hardware-enforced isolation — not just software-level separation.

The main features that demonstrate this are:

Independent initialization of all four rings, each running its own “mili-kernel” (Core, Devs, Libs, Users).

Hardware-managed transitions using call gates and task gates (no software traps).

Per-ring TSS and LDT, showing that every ring can maintain its own execution context and memory space.

System calls implemented purely through call gates, demonstrating cross-ring communication without stack argument passing.

A static paging layout combined with segment-based isolation, proving that segmentation still works perfectly when properly configured.

In short, R4R’s goal is educational and architectural:
to prove that the x86 protection model — often dismissed as obsolete — is in fact coherent, elegant, and fully functional when used as intended.

Future versions (0.01+) will extend this with a ring-aware scheduler, interrupt handling, and message-passing between rings to illustrate real multi-domain operation.
 
Nice.
Few years ago I started my own. But then RL (kids,..) took precedence..

Out of curiosity: why did you put an extra nop in the handler?
When I started my project I hated att syntax. Ended up rewritting everything with att. :). I still tend to disassemble with intel one (objdump/gdb/ida..) but less so.
 
Okay, I've read all the documentation, at the end I see your OS on the COMPAQ CONTURA it's way cool. I'm working on a project like yours and I have to thank GPT for helping me too.

Your code is clean and clear; there are still some magic numbers. Don't you use static code analysis (clang-tidy or cppchek) ? I'm disturbed by your typedef for regular types, why you do that ?


C:
// Signed types
typedef int8_t   i8;   // 8-bit  signed integer
typedef int16_t  i16;  // 16-bit signed integer
typedef int32_t  i32;  // 32-bit signed integer
typedef int64_t  i64;  // 64-bit signed integer

// Unsigned types
typedef uint8_t  u8;   // 8-bit  unsigned integer
typedef uint16_t u16;  // 16-bit unsigned integer
typedef uint32_t u32;  // 32-bit unsigned integer
typedef uint64_t u64;  // 64-bit unsigned integer

// Pointer-sized unsigned integer (32-bit architecture assumed)
typedef u32                uptr;
 
Nice.
Few years I started my own. But then RL (kids,..) took precedence..

Out of curiosity: why did you put an extra nop in the handler?
When I started my project I hated att syntax. Ended up rewritting everything with att. :). I still tend to disassemble with intel one (objdump/gdb/ida..) but less so.
You caught that well, it's a completely unnecessary nop here, I missed that when I was testing something in the BOCHS debugger...
Deleted!
 
Okay, I've read all the documentation, at the end I see your OS on the COMPAQ CONTURA it's way cool. I'm working on a project like yours and I have to thank GPT for helping me too.

Your code is clean and clear; there are still some magic numbers. Don't you use static code analysis (clang-tidy or cppchek) ? I'm disturbed by your typedef for regular types, why you do that ?


C:
// Signed types
typedef int8_t   i8;   // 8-bit  signed integer
typedef int16_t  i16;  // 16-bit signed integer
typedef int32_t  i32;  // 32-bit signed integer
typedef int64_t  i64;  // 64-bit signed integer

// Unsigned types
typedef uint8_t  u8;   // 8-bit  unsigned integer
typedef uint16_t u16;  // 16-bit unsigned integer
typedef uint32_t u32;  // 32-bit unsigned integer
typedef uint64_t u64;  // 64-bit unsigned integer

// Pointer-sized unsigned integer (32-bit architecture assumed)
typedef u32                uptr;
I completely understand your frustration... I just like it this way because this is a completely hardware-dependent system and I want shorter but more precise type descriptions because I liked it when I was programming embedded hardware... I answered you honestly. I haven't used the cppchek analyzer before.
 
Back
Top