10 KiB
The Luna boot process
Stage 0: The Bootloader
Luna uses the BOOTBOOT bootloader. (For more information, read the bootloader specification.)
This bootloader reads the initial ramdisk, which contains the following files:
/sys/config - copy of the configuration file for the bootloader
/boot/moon - the kernel itself
/bin/preinit - the first user program run in the boot process, before the root filesystem is mounted
The bootloader loads the kernel in 64-bit mode into the higher half at address 0xffffffffffe02000
, with an appropriate stack already set up.
The first 16Gb of memory are identity-mapped at page 0.
It places a few other things into known addresses:
0xfffffffffc000000 - initial framebuffer
0xffffffffffe00000 - bootloader information passed to the kernel
0xffffffffffe01000 - kernel command line
From here, the kernel takes over.
Stage 1: The Kernel
Relevant files: kernel/src/main.cpp, kernel/src/arch/x86_64/CPU.cpp
The kernel begins execution in the _start()
function. This function initializes basic kernel functionality, such as time-keeping, memory management, graphics, and finally threading.
Once threading is set up and the scheduler is started, the kernel starts up a new kernel thread titled [kinit]
to finish starting up other subsystems that assume they're running in a thread.
Before switching to [kinit]
, _start
does one more thing, it calls the CPU::platform_finish_init()
function which is platform-specific. On x86_64, this function does the following things:
- Creates a new kernel thread:
[x86_64-io]
, which handles keyboard and mouse interrupts asynchronously - Starts receiving external interrupts
- Initializes the mouse
As soon as the scheduler switches to the [kinit]
thread, it will never return to _start
(since it has no thread associated to it).
IMPORTANT: Although the [kinit]
thread is the first thread to be started in the system, it has PID 2, not 1. The reason for this is that PID 1 is reserved for the userspace init process.
[kinit]
does the following things, in order:
- Loads kernel debug symbols from the initial ramdisk
- Creates the virtual file system and mounts the initial ramdisk on /
- Initializes virtual device files such as
/dev/null
(the internal kernel representation of them,/dev
is not mounted yet) - Loads
/bin/preinit
from the initial ramdisk as PID 1 - Creates two more kernel threads,
[reap]
and[oom]
- Scans for ATA hard disks and reads their partition tables
- Finally, it sets PID 1's state to "Running" so that the scheduler can switch to it, and exits
Kernel threads
[kinit]
spawns two more kernel threads, [reap]
and [oom]
. While [kinit]
exits before PID 1 is started, [reap]
and [oom]
are present throughout the lifetime of a Luna system, and can be seen in the output of ps
. Let's take a look at what they do.
-
[reap]
: To understand what this thread does, we must take a look at what happens when threads exit on Luna.(Relevant files: kernel/src/main.cpp, kernel/src/thread/Scheduler.cpp, kernel/src/thread/Thread.cpp, kernel/src/sys/waitpid.cpp)
When a thread calls the
_exit()
syscall, its state is set to "Exited". This tells the scheduler to avoid switching to it, and the thread's parent is notified, either by sending SIGCHLD or unblocking a blockedwaitpid()
call. The thread remains visible to the rest of the system, and if its parent does not wait for it, it will stay there as a "zombie thread".When the thread's parent waits for it, its state is instead set to "Dying", and the
[reap]
thread runs. (Kernel threads skip all the "parent notifying" shenanigans and go straight to the "Dying" state when callingkernel_exit()
).The
[reap]
thread then "reaps" all the "Dying" threads' resources. It frees up the threads' memory, file descriptors, and unmaps the memory used for the kernel stack corresponding to that thread. After reaping, the thread is deleted, and no trace of it is left. -
[oom]
: This thread handles Out-Of-Memory (OOM) situations. Whenever the kernel has 1/4 or 1/8 of the available physical memory left (thresholds may be tweaked in the future), or it has run out, it runs this thread.The OOM thread then goes through all the disk caches and purges them all, hoping to reclaim as much memory as possible.
File system and process layout
After the kernel stage of the boot process, the system looks like this:
File system
/ - initial ramdisk
/sys/config - copy of the configuration file for the bootloader
/boot/moon - the kernel itself
/bin/preinit - the first user program run in the boot process
Processes
/bin/preinit - PID 1
[kinit] - PID 2 (Exited, soon to be reaped)
[x86_64-io] - PID 3
[reap] - PID 4
[oom] - PID 5
Stage 2: preinit
Relevant files: apps/preinit.cpp
Luna's userspace init process is split into two programs: /bin/preinit
, which resides on the initial ramdisk, and /usr/bin/init
, which resides on the root partition.
/bin/preinit
's job is to set up the file system in a "minimal known good" state for the actual init
to run.
The "minimal known good" state includes:
- The ext2 root partition, which includes all the binaries in /usr
- The /dev file system
preinit
does the following things, in order:
- Mounts
/dev
to get access to disk device files - Mounts the root partition (
/dev/cd0p2
) on/osroot
- Unmounts
/dev
- Uses the
pivot_root
system call to change the root file system to the one that was in/osroot
, and mounts the old one on/mnt
(previously/osroot/mnt
) - Unmounts the initial ramdisk on
/mnt
- Mounts the
/dev
file system again on the new root partition - Executes
/usr/bin/init
For now, much of preinit
's functionality is hard-coded, but as Luna supports more devices, it will become responsible for loading device drivers, discovering the root partition, and more...
File system and process layout
After the preinit stage of the boot process, the system looks like this:
File system
/ - ext2 root partition
/dev - device file system
/usr, /etc, /home... - other directories contained in the root partition
Processes
/usr/bin/init - PID 1
[x86_64-io] - PID 3
[reap] - PID 4
[oom] - PID 5
Stage 3: init
Relevant files: apps/init.cpp
/usr/bin/init
is the actual init system. It is in charge of starting user-defined services.
It does the following things:
- Mounts
/tmp
,/dev/shm
and/dev/pts
- Sets the system hostname by reading
/etc/hostname
- Reads configuration files from
/etc/init
- Starts services defined in
/etc/init
- Enters the init loop, waiting for child processes and restarting them if needed
Currently, there are two service files defined by default in /etc/init
:
00-home
: This service sets up a tmpfs
on /home/selene
, so that the home directory is writable.
99-login
: This service starts a graphical session, by calling /usr/bin/startui
. This service will be restarted if necessary.
File system and process layout
After the init stage of the boot process, the system looks like this:
File system
/ - ext2 root partition
/dev - device file system
/dev/shm - POSIX shared memory file system
/dev/pts - POSIX pseudoterminal file system
/tmp - system temporary file directory
/usr, /etc, /home... - other directories contained in the root partition
/home/selene - temporary home directory
Processes
/usr/bin/init - PID 1
[x86_64-io] - PID 3
[reap] - PID 4
[oom] - PID 5
/usr/bin/startui - PID 13
Note: startui is PID 13 because the 00-home
service is a shell script, which starts a few subprocesses. Since Luna does not allow for PID reuse right now, startui ends up with PID 13.
Stage 4: startui
Relevant files: apps/startui.cpp, wind/main.cpp
/usr/bin/startui
starts a graphical user session.
A Luna graphical user session includes the following components:
- The display server itself,
/usr/bin/wind
. It is started with permissionsroot:root
, and later drops privileges towind:wind
. - The launch server (
/usr/bih/launch
), which starts processes and keeps them alive on behalf of other processes. It is started with the standard permissionsselene:selene
. - The taskbar,
/usr/bin/taskbar
. It is started with the standard permissionsselene:selene
, plus an extra groupwsys
to be able to connect to a special display server socket. - The init process corresponding to that session (
/usr/bin/init --user
). This process does the same thing asinit
above (manages services), but runs with user privileges and reads configuration files from/etc/user
instead (in the future this will be changed to a user-specific directory).
Currently, init --user
only does one thing: it opens up a terminal window on startup. It can be configured to do whatever the user desires to do on startup, by placing the appropriate configuration files in /etc/user
.
File system and process layout
After the startui stage of the boot process, the system is fully started up and looks like this:
File system
/ - ext2 root partition
/dev - device file system
/dev/shm - POSIX shared memory file system
/dev/pts - POSIX pseudoterminal file system
/tmp - system temporary file directory
/usr, /etc, /home... - other directories contained in the root partition
/home/selene - temporary home directory
Processes
/usr/bin/init - PID 1
[x86_64-io] - PID 3
[reap] - PID 4
[oom] - PID 5
/usr/bin/startui - PID 13
/usr/bin/wind - PID 14
/usr/bin/launch - PID 15
/usr/bin/taskbar - PID 16
/usr/bin/init --user - PID 17
/usr/bin/terminal - PID 18
/bin/sh - PID 19