Inferno Emulator Initialization Flow

Inferno, the evolution of the Plan9 OS, can run on top of another operating system. When we fire up the hosted version, we are not running it directly on hardware. Instead, it runs on a host OS like Linux. In this case, we are going to host it on a remote Linux server.

The Inferno source tree reflects this setup. There are two major directories that matter here: os and emu. The os directory holds code for the native version of Inferno and is organized by hardware platforms. The emu directory holds the hosted version and is organized by host operating systems.

Inside emu, most of our work will happen in the port directory. This contains portable code that works across all hosts and is where the majority of the kernel logic lives. The port code is responsible for setting up the kernel, but it relies on host specific details to do so. Early in the process, it reaches out to the appropriate host directory to gather that information.

In our case, we are using Linux as the host, so it pulls that information from the /linux/ directory. If we were using a different host operating system, it would go to the corresponding directory instead, such as /nt/ or /osx/. This setup allows the system to initialize consistently while adapting to whatever host it is running on.

The initialization process begins in port/main.c inside a function called main(). This is the very first code that runs when the system is launched. To set up our kernel, we need to know information about our host, so we switch over to our Linux specific code and run libinit() in linux/os.c. That function sets up things like the user ID, group ID, and signal environment for Linux. Once that Linux specific setup is complete, we return to portable code and start building out the core parts of the Inferno kernel. This includes creating kernel processes, setting up device drivers, and eventually launching the Dis virtual machine that runs user programs written in Limbo.

High-Level Initialization Flow

This simplified diagram captures the high-level structure of the Inferno initialization process when hosted on a platform like Linux. It starts at the root, enters the emulator, moves through portable and host specific kernel setup, and finishes by launching the Dis virtual machine.

Inferno Emulator Initialization Flow Diagram

Detailed Initialization Process

The following flow chart takes you through the full setup process for starting the Inferno OS on a hosted environment - in this case Linux.

1. Start in portable code
Begin in /port/main.c at main(). This is the entry point for all platforms. It sets up the eve user, parses arguments, and prepares to load the first module.
/port/main.c
main()
2. Switch to host specific
The main() calls the libinit(imod) in /Linux/os.c. This gathers user ID, group ID, hostname, and configures signal handling appropriate to Linux.
/Linux/os.c
libinit(imod)
3. Create first kproc
Still in host specific code, we use kproc to launch emuinit(), starting the bridge to virtual machine logic.
kproc("main", emuinit, ...)
4. Back to portable init
Back in portable code the emuinit(imod) function in /port/main.c sets up device interfaces and standard file descriptors.
/port/main.c
emuinit(imod)
5. Launch disinit kproc
A second kernel process is spawned in from main() to run disinit(), which begins setup of the user level Dis virtual machine.
kproc("main", disinit, ...)
6. Enter disinit setup
In /port/dis.c, we initialize the floating point unit, load the module from imod, and schedule it.
/port/dis.c
disinit()
7. Run vmachine()
Finally, we enter the infinite vmachine() loop which schedules and runs all Dis processes. The system is now fully running.
vmachine()

Key Takeaways

The Inferno initialization process demonstrates a clean separation between portable and host-specific code. The system starts with portable initialization, switches to host-specific setup when needed, and then returns to portable code for the core kernel functionality. This architecture allows Inferno to run consistently across different host operating systems while leveraging platform-specific features when necessary.

The process culminates in the vmachine() loop, which is the heart of the Dis virtual machine that runs user programs written in Limbo. This virtual machine provides the runtime environment for all Inferno applications, making the system truly portable across different hardware and host operating systems.