Luna/docs/boot_process.md
apio 22766a6724
All checks were successful
Build and test / build (push) Successful in 1m35s
docs: Correct a few details in boot_process.md
2024-12-21 13:40:53 +01:00

12 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 processes 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 process calls the _exit() syscall, all its threads' states are set to "Dying". This tells the scheduler to avoid switching to them, and the process's parent is notified, by sending SIGCHLD and (optionally) unblocking a blocked waitpid() call. The process remains visible to the rest of the system, and if its parent does not wait for it, it will stay there as a "zombie process". Meanwhile, the [reap] thread runs and collects all the resources from each thread. The process object is still alive (in a "zombie" state), but its threads have been cleaned up.

    When the process's parent waits for it, it is marked for reaping (by setting its thread count to -1 (PROCESS_SHOULD_REAP)), and the [reap] thread runs.

    The [reap] thread then "reaps" all the dead processes' resources. It frees up their memory, file descriptors, and other resources. After reaping, the process 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: system/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: system/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/loginui. 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/loginui    - PID 13

Note: loginui 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, loginui ends up with PID 13.

Stage 4: loginui

Relevant files: gui/loginui.cpp, gui/wind/main.cpp

/usr/bin/loginui's job is quite simple: it prompts the user to log in with their password, after which a graphical session is started.

Note: On development builds, Autologin=true is added to /etc/loginui.conf which disables password prompting and executes startui directly.

First, loginui starts the display server, /usr/bin/wind, so that it can use its capabilities to show a graphical login prompt. It is started with permissions root:root, and later drops privileges to wind:wind.

After that, loginui prompts for a username and password, checks it against the hashed password stored in /etc/shadow, and finally executes /usr/bin/startui which does the actual heavy work of starting all the services needed for a UI session.

File system and process layout

After the loginui 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
/usr/bin/wind           - PID 14

Stage 5: startui

Relevant files: system/startui.cpp, gui/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. If not already started by loginui, startui makes sure it's running.
  • The execution server (/usr/bin/execd), which starts processes and keeps them alive on behalf of other processes. It is started with the standard permissions selene:selene.
  • The taskbar, /usr/bin/taskbar. It is started with the standard permissions selene:selene, plus an extra group wsys to be able to connect to a special display server socket (/tmp/wsys.sock, as opposed to the standard /tmp/wind.sock). This grants it the ability to use advanced wind features, such as placing the taskbar window behind all other windows.
  • The init process corresponding to that session (/usr/bin/init --user). This process does the same thing as init 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 text editor with a welcome message 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/execd          - PID 15
/usr/bin/taskbar        - PID 16
/usr/bin/init --user    - PID 17
/usr/bin/editor welcome - PID 18