Share
Embed
Transcript
26 slides
-
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?” -
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? -
Implementation plan Build a bootloader and kernel for QEMU Provide
libc and syscall dependencies for CRuby Bundle CRuby into the kernel build Seemed feasible -
libc dependency CRuby depends on 291 external symbols Adopted musl
libc as the base -
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 } -
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, ...); -
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 } -
Some problems Low-level traps: SSE, jmp_buf A Ruby mystery: Integer#times
raises NoMethodError -
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 -
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 -
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() -
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? -
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 -
VFS (Virtual File System) Need VFS to load the editor’s
Ruby files No OS, so implemented VFS with Ruby’s Hash -
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, ... }; -
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 -
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