Compare commits
2 Commits
01dcb954e5
...
907049c405
Author | SHA1 | Date | |
---|---|---|---|
907049c405 | |||
450ef2ce27 |
221
docs/boot_process.md
Normal file
221
docs/boot_process.md
Normal file
@ -0,0 +1,221 @@
|
|||||||
|
# The Luna boot process
|
||||||
|
|
||||||
|
## Stage 0: The Bootloader
|
||||||
|
Luna uses the [BOOTBOOT](https://gitlab.com/bztsrc/bootboot) bootloader. _(For more information, read the [bootloader specification](https://gitlab.com/bztsrc/bootboot/-/blob/master/bootboot_spec_1st_ed.pdf).)_
|
||||||
|
|
||||||
|
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/main.cpp), [kernel/src/arch/x86_64/CPU.cpp](../kernel/src/arch/x86_64/CPU.cpp#L285)_
|
||||||
|
|
||||||
|
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/main.cpp#L23), [kernel/src/thread/Scheduler.cpp](../kernel/src/thread/Scheduler.cpp#L205), [kernel/src/thread/Thread.cpp](../kernel/src/thread/Thread.cpp#L105), [kernel/src/sys/waitpid.cpp](../kernel/src/sys/waitpid.cpp#L84))_
|
||||||
|
|
||||||
|
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 blocked `waitpid()` 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 calling `kernel_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](../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](../apps/init.cpp#L406)_
|
||||||
|
|
||||||
|
`/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](../apps/startui.cpp), [wind/main.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 permissions `root:root`, and later drops privileges to `wind: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 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.
|
||||||
|
- 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 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
|
@ -109,7 +109,10 @@ Result<SharedPtr<VFS::Inode>> Thread::resolve_atfile(int dirfd, const String& pa
|
|||||||
#else
|
#else
|
||||||
if (this->id == 1) CPU::magic_exit(_status);
|
if (this->id == 1) CPU::magic_exit(_status);
|
||||||
#endif
|
#endif
|
||||||
if (is_kernel) state = ThreadState::Dying;
|
if (is_kernel) {
|
||||||
|
state = ThreadState::Dying;
|
||||||
|
Scheduler::signal_reap_thread();
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Scheduler::for_each_child(this, [](Thread* child) {
|
Scheduler::for_each_child(this, [](Thread* child) {
|
||||||
|
Loading…
Reference in New Issue
Block a user