Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Ruby on Bare Metal

Sponsored · Your Podcast. Everywhere. Effortlessly. Share. Educate. Inspire. Entertain. You do you. We'll handle the rest.
Avatar for S.H. S.H.
April 22, 2026

Ruby on Bare Metal

Avatar for S.H.

S.H.

April 22, 2026

More Decks by S.H.

Other Decks in Technology

Transcript

  1. Motivation Had an interest in building custom OS and kernels

    on QEMU Thought “Can we build an OS in Ruby?” but it seemed impractical Instead: “What if we run Ruby as the userland?”
  2. Why CRuby? Using embedded Ruby implementations would be relatively easy

    and less interesting Running CRuby itself in a new domain opens up more possibilities CRuby assumes it runs on top of an OS — what if we remove that assumption?
  3. Implementation plan Build a bootloader and kernel for QEMU Provide

    libc and syscall dependencies for CRuby Bundle CRuby into the kernel build Seemed feasible
  4. Replacing libc internals Replaced malloc with a custom bump allocator

    Stubbed pthread (single-threaded) static unsigned char heap[128 * 1024 * 1024]; static size_t heap_used = 0; void *malloc(size_t size) { size_t start = ALIGN(heap_used, 16); heap_used = start + size; return (void *)(heap + start); // no free }
  5. Replacing syscalls Need read, write, mmap, clock_gettime, etc. Call kernel

    C functions directly instead of syscall Implemented 83 syscalls // Before (musl): syscall instruction __asm__ volatile ("syscall" : "=a"(ret) ...); // After: direct function call return ruby_on_bare_metal_syscall(n, a1, ...);
  6. Setting up Ruby from C Initialize CRuby from C and

    start the shell void kernel_main(void) { serial_init(); timer_init(); init_musl_tls(); memory_init(); ruby_init_stack((void *)&stack_anchor); ruby_setup(); ruby_script("ruby_on_bare_metal"); rb_eval_string(init_rb); // start shell }
  7. Build and boot traps SSE/FPU not enabled -> CRuby’s GC

    crashes instantly jmp_buf size mismatch (musl: 200 bytes vs CRuby: 40 bytes) -> stack corruption
  8. Builtin method problem 3.times { |i| puts i } ->

    NoMethodError In CRuby 4.0, Integer#times etc. are implemented in Ruby ruby_setup() alone does not load them In early development, had to write loops with while
  9. Normal loading path Normally loaded through this call chain This

    path is not taken when embedding in a kernel ruby_process_options() -> ruby_opt_init() -> rb_call_builtin_inits()
  10. Want to run something CRuby runs on bare metal, but

    that alone is not enough Want to run a real Ruby program on it How about a text editor?
  11. Candidates Textbringer (Emacs-like) Directly depends on curses, hard to replace

    -> gave up Mui (Vim-like editor, my own project) Uses curses, but through an adapter pattern Can write a custom adapter
  12. VFS (Virtual File System) Need VFS to load the editor’s

    Ruby files No OS, so implemented VFS with Ruby’s Hash
  13. Embedding the editor Converted 97 Ruby files to C byte

    arrays Statically embedded into the kernel // Auto-generated: embed_mui.rb -> generated_mui.c const unsigned char mui_lib_mui_rb[] = { 0x6d, 0x6f, 0x64, 0x75, 0x6c, ... };
  14. Terminal adapter Draw directly with ANSI escape sequences instead of

    curses Read key input byte-by-byte with sysread(1) def write(str) $stdout.syswrite(str) # direct output to serial port end
  15. Summary CRuby depends on OS, libc, and initialization sequence Hidden

    mechanisms like rb_call_builtin_inits() exist By revealing and replacing these assumptions, CRuby runs on bare metal Bare metal is just the start — new domains await CRuby