Supporting multiple architectures
Reading time: 1 minute
axle used to be a 32-bit-only OS. In late 2021, I had a hankering to fix this longstanding limitation, and plunge into a world that expanded not just our address spaces, but our hope.
Roughly, if you want to add support for x86_64 to an existing x86 OS, you’ll need to update these components:
- Global descriptor table
- Interrupt descriptor table
- Interrupt handlers
- Hand-coded assembly routines
- Multitasking
- Paging and virtual memory manager
- Linker scripts
- ELF loader
- Ported software
- Disable the red zone
- Build system
- Years of assumptions that a memory address will fit in a
u32
For a lot of these items, we’ll save ourselves headache and heartache if we have one interface to do a job, and swap out the details of the implementation based on the architecture we’re targeting. For example, the API for the physical memory manager will always look like this, regardless of target architecture:
uintptr_t pmm_alloc(void);
void pmm_free(uintptr_t frame_addr);
I created a small extension of axle’s build system to facilitate this without littering the code with #ifdefs
.
The virtual memory manager, which has separate implementations for x86 and x86_64, is now laid out in the source tree like so:
- vmm.h
- vmm.c
+ vmm.h.i686.arch_specific
+ vmm.c.i686.arch_specific
+ vmm.h.x86_64.arch_specific
+ vmm.c.x86_64.arch_specific
During the build, the right files for the current architecture are templated to a source file, and the rest of the system doesn’t need to worry about the nitty-gritty.
scripts/build_os_image.py
# Stage architecture-specific source files
kernel_root = Path(__file__).parents[1] / "kernel"
arch_specific_assembly = [
kernel_root / "boot" / "boot.s",
kernel_root / "kernel" / "util" / "walk_stack.s",
kernel_root / "kernel" / "multitasking" / "tasks" / "process_small.s",
kernel_root / "kernel" / "segmentation" / "gdt_activate.s",
kernel_root / "kernel" / "interrupts" / "idt_activate.s",
kernel_root / "kernel" / "interrupts" / "int_handler_stubs.s",
kernel_root / "kernel" / "pmm" / "pmm_int.h",
kernel_root / "kernel" / "vmm" / "vmm.h",
kernel_root / "kernel" / "vmm" / "vmm.c",
]
for file in arch_specific_assembly:
arch_specific_file = file.parent / f"{file.name}.{ARCH}.arch_specific"
if not arch_specific_file.exists():
raise ValueError(f"Missing arch-specific file {arch_specific_file}")
if not file.exists() or copied_file_is_outdated(arch_specific_file, file):
print(f"\tCopying arch-specific code {arch_specific_file} to {file}...")
shutil.copy(arch_specific_file.as_posix(), file.as_posix())