diff options
Diffstat (limited to 'hurd/bootstrap.mdwn')
-rw-r--r-- | hurd/bootstrap.mdwn | 311 |
1 files changed, 311 insertions, 0 deletions
diff --git a/hurd/bootstrap.mdwn b/hurd/bootstrap.mdwn new file mode 100644 index 00000000..fbce3bc1 --- /dev/null +++ b/hurd/bootstrap.mdwn @@ -0,0 +1,311 @@ +[[!meta copyright="Copyright © 2020 Free Software Foundation, Inc."]] + +[[!meta license="""[[!toggle id="license" text="GFDL 1.2+"]][[!toggleable +id="license" text="Permission is granted to copy, distribute and/or modify this +document under the terms of the GNU Free Documentation License, Version 1.2 or +any later version published by the Free Software Foundation; with no Invariant +Sections, no Front-Cover Texts, and no Back-Cover Texts. A copy of the license +is included in the section entitled [[GNU Free Documentation +License|/fdl]]."]]"""]] + +[[!meta title="System bootstrap"]] + +[[!tag open_issue_documentation]] <!-- Someone still needs to make a pass over +this text. --> + +[[!toc]] + +# State at the beginning of the bootstrap + +After initializing itself, GNU Mach sets up tasks for the various bootstrap +translators (which were loader by the GRUB bootloader). It notably makes +variables replacement on their command lines and boot script function calls (see +the details in `gnumach/kern/boot_script.c`). For instance, if the GRUB +bootloader has the following configuration: + + multiboot /boot/gnumach-1.8-486-dbg.gz root=device:hd1 console=com0 + module /hurd/ext2fs.static ext2fs --readonly \ + --multiboot-command-line='${kernel-command-line}' \ + --host-priv-port='${host-port}' \ + --device-master-port='${device-port}' \ + --exec-server-task='${exec-task}' -T typed '${root}' \ + '$(task-create)' '$(task-resume)' + module /lib/ld.so.1 exec /hurd/exec '$(exec-task=task-create)' + + +GNU Mach will first make the `$(task-create)` function calls, and thus create a +task for the ext2fs module and a task for the exec module (and store a port on +that task in the `exec-task` variable). + +It will then replace the variables (`${foo}`), i.e. + +* `${kernel-command-line}` with its own command line (`root=device:hd1 console=com0`), +* `${host-port}` with a reference to the GNU Mach host port, +* `${device-port}` with a reference to the GNU Mach device port, +* `${exec-task}` with a reference to the exec task port. +* `${root}` with `device:hd1` + +This typically results in: + + task loaded: ext2fs --readonly --multiboot-command-line=root="device:hd1 console=com0" --host-priv-port=1 --device-master-port=2 --exec-server-task=3 -T typed device:hd1 + task loaded: exec /hurd/exec + +(You will have noticed that `/hurd/exec` is not run directly, but through +`ld.so.1`: Mach only knows to run statically-linked ELF binaries, so we could +either load `/hurd/exec.static`, or load the dynamic loader `ld.so.1` and tell +it to load `/hurd/exec`) + +GNU Mach will eventually make the `$(task-resume)` function calls, and thus +resume the ext2fs task only. + +This starts a dance between the bootstrap processes: `ext2fs`, `exec`, `startup`, +`proc`, and `auth`. Indeed, there are a few dependencies between them: `exec` needs +`ext2fs` working to be able to start `startup`, `proc` and `auth`, and `ext2fs` needs to +register itself to `startup`, `proc` and `auth` so as to appear as a normal process, +running under uid 0. + +The base principle is that `ext2fs` has a nul bootstrap port set to while other +translators will have a non-nul bootstrap port, with which they will discuss. We +thus have a hierarchy between the bootstrap processes. `ext2fs` is at the root, +`exec` and `startup` are its direct children, and `auth` and `port` are direct +children of `startup`. + +Usually the bootstrap port is used when starting a translator, see +`fshelp_start_translator_long`: the parent translator starts the child and sets +its bootstrap port. The parent then waits for the child to call `fsys_startup` +on the bootstrap port, for the child to provide its control port, and for the +parent to provide the FS node that the child is translator for. + +For the bootstrap translators, the story is extended: + +* Between `ext2fs` and `startup`: `startup` additionally calls `fsys_init`, to +provide `ext2fs` with `proc` and `auth` ports. +* Between `startup` and `proc`: `proc` just calls `startup_procinit` to hand +over a `proc` port and get `auth` and `priv` ports. +* Between `startup` and `auth`: `auth` calls `startup_authinit` to hand over an +`auth` port and get a `proc` port, then calls `startup_essential_task` to notify +`startup` that the boot can proceed. +* For translators before `ext2fs`, the child calls `fsys_startup` to pass over +the control port of `ext2fs` (instead of its own control port, which is useless +for its parent). This is typically done in the `S_fsys_startup` stub, simply +forwarding it. The child also calls `fsys_init` to pass over the `proc` and +`auth` ports. Again, this is typically done in the `S_fsys_init` stub, simply +forwarding them. + +With that in mind, the dance between the bootstrap translators is happening as +described in the next sections. + +# Initialization of translators before ext2fs + +We plan to start pci-arbiter and rumpdisk translators before ext2fs. + +pci-arbiter's `main` function parses the arguments, and since it is given a disk +task port, it knows it is a bootstrap translator and thus initializes the +machdev library. `machdev_trivfs_init` resumes the disk task. + +rumpdisk's `main` function parses the arguments, and since it is given a FS task +port, it knows it is a bootstrap translator, and thus `machdev_trivfs_init` +resumes the FS task. + +# ext2fs initialization + +ext2fs's `main` function starts by calling `diskfs_init_main`. + +`diskfs_init_main` parses the ext2fs command line with `argp_parse`, to record +the parameters set up by the kernel. It makes sure to have a working stdout by +opening the Mach console. + +Since the multiboot command line is available, `diskfs_init_main` sets the +ext2fs bootstrap port to `MACH_PORT_NULL`: it is the bootstrap filesystem which +will be in charge of dancing with the exec and startup translator. + +`diskfs_init_main` then initializes the libdiskfs library and spawns a thread to +manage libdiskfs RPCs. + +ext2fs continues its initialization: creating a pager, opening the +hypermetadata, opening the root inode to be set as root by libdiskfs. + +ext2fs then calls `diskfs_startup_diskfs` to really run the startup, implemented +by the libdiskfs library. + +# libdiskfs bootstrap + +Since the bootstrap port is `MACH_PORT_NULL`, `diskfs_startup_diskfs` calls +`diskfs_start_bootstrap`. + +`diskfs_start_bootstrap` starts by creating a open port on itself for the +current and root directory, all other processes will inherit it. + +`diskfs_start_bootstrap` does have a port on the exec task, so it can dance with +it. It calls `start_execserver` that sets the bootstrap port of the exec task +to a port of the `diskfs_execboot_class`, and resumes the exec task. +`diskfs_start_bootstrap` then waits for `execstarted`. + +# exec bootstrap + +exec's `main` function starts and calls `task_get_bootstrap_port` to get +its bootstrap port and `getproc` to get a port on the proc translator (thus +`MACH_PORT_NULL` at this point since the proc translator is not started yet). + +exec initializes the trivfs library, and eventually calls `trivfs_startup` on +its bootstrap port. + +`trivfs_startup` creates a control port for the exec translator, and calls +`fsys_startup` on the bootstrap port to notify ext2fs that it is ready, give it +its exec control port, and get back a port on the underlying node for the exec +translator (we want to make it show up on `/servers/exec`). + +# libdiskfs taking back control + +`diskfs_execboot_fsys_startup` is thus called. It calls `dir_lookup` on +`/servers/exec` to return the underlying node for the exec translator, and +stores the `exec` control port in `diskfs_exec_ctl`. It can then signal `execstarted`. + +`diskfs_start_bootstrap` thus takes back control, It calls `fsys_getroot` on the +control port of exec, and uses `dir_lookup` and `file_set_translator` to attach +it to `/servers/exec`. + +`diskfs_start_bootstrap` then looks for which startup process to run. It may +be specified on the multiboot command line, but otherwise it will default to +`/hurd/startup`. + +Now that exec is up and running, the `startup` process can be created with +`exec_exec`. `diskfs_start_bootstrap` takes a lot of care in this: this is +the first unix-looking process, it notably inherits the root directory and +current working directory initialized above, it gets stdin/out/err on the mach +console. It is passed as bootstrap port a port from the `diskfs_control_class`. + +`diskfs_start_bootstrap` is complete, we are back to `diskfs_startup_diskfs`, +which checks whether ext2fs was given a bootstrap port, i.e. whether +the rumpdisk translator was started before ext2fs. If so, it +calls `diskfs_call_fsys_startup` which creates a new control port and passes +it do a call to `fsys_startup` on the bootstrap port, so rumpdisk gets access +to the ext2fs filesystem. Rumpdisk however does not return any `realnode` port, +since we are not exposing the ext2fs filesystem in rumpdisk, but rather the +converse. TODO: Rumpdisk forwards this `fsys_startup` call to pci-arbiter, so +the latter also gets access to the ext2fs filesystem. + +# startup + +startup's `main` function starts and calls `task_get_bootstrap_port` to get its +bootstrap port, i.e. the control port of ext2fs, and `fsys_getpriv` on it to get +the host privileged port and device master port. It +clears the bootstrap port so children do not inherit it. It sets itself up with +output on the Mach console, and wires itself against swapping. It requests +notification for ext2fs translator dying to detect it and print a warning in +case that happens during boot. It creates a `startup` port which it will get +RPCs on. + +startup can then complete the unixish initialization, and run `/hurd/proc` and +`/hurd/auth`, giving them as bootstrap port the `startup` port. + +# proc + +proc's `main` function starts. It initializes itself, and calls +`task_get_bootstrap_port` to get a port on startup. It can then call +`startup_procinit` on it to pass it the proc port that will represent the startup +task, and get ports on the auth server, the host privileged port, and device +master port. + +Eventually, proc calls `startup_essential_task` to tell startup that it is +ready. + +# auth + +auth's `main` function starts. It creates the initial root auth handle (all +permissions allowed). It calls `task_get_bootstrap_port` to get a port on +startup. It can then call `startup_authinit` on it to pass the initial root auth +handle, and get a port on the proc server. It can then register itself to proc. + +Eventually, auth calls `startup_essential_task` to tell startup that it is ready. + +# startup getting back control + +startup notices initialization of auth and proc from `S_startup_procinit` and +`S_startup_authinit`. Once both have been called, it calls `launch_core_servers`. + +`launch_core_servers` starts by calling `startup_procinit_reply` to actually +reply to the `startup_procinit` RPC with a port on auth. + +`launch_core_servers` then tells proc that the bootstrap processes are +important, and how they relate to each other. + +`launch_core_servers` then calls `install_as_translator` to show up in the +filesystem on `/servers/startup`. + +`launch_core_servers` calls `startup_authinit_reply` to actually reply to the +`startup_authinit` RPC with a port on proc. + +`launch_core_servers` eventually calls `fsys_init` on its bootstrap port, to +give ext2fs the proc and auth ports. + +diskfs' `diskfs_S_fsys_init` thus gets called. It first replies to startup, so +startup is not stuck in its `fsys_init` call and not able to reply to RPCs. From +then on, startup will be watching for `startup_essential_task` calls from the +various bootstrap processes. + +# libdiskfs taking back control + +In diskfs' `diskfs_S_fsys_init`, diskfs now knows that proc and auth are ready, +and can call `exec_init` on the exec port. + +# exec getting initialized + +exec's `S_exec_init` gets called from the `exec_init` call from ext2fs. Exec can +register itself with proc, and eventually call `startup_essential_task` to tell +startup that it is ready. + +# back to libdiskfs initialization + +It also calls `fsys_init` +on its bootstrap port, i.e. rumpdisk. + +# rumpdisk getting initialized + +rumpdisk's `trivfs_S_fsys_init` gets called from the `fsys_init` call from +ext2fs. It calls `fsys_init` on its bootstrap port. + +# pci-arbiter getting initialized + +pci-arbiter's `trivfs_S_fsys_init` gets called from the `fsys_init` call from +rumpdisk. + +It gets the root node of ext2fs, sets all common ports, and install +pci-arbiter in the ext2fs FS as translator for `/servers/bus/pci`. + +It eventually calls `startup_essential_task` to tell startup that it is ready, +and requests shutdown notifications. + +# back to rumpdisk initialization + +It gets the root node of ext2fs, sets all common ports, and install +rumpdisk in the ext2fs FS as translator for `/dev/disk`. + +It eventually calls `startup_essential_task` to tell startup that it is ready, +and requests shutdown notifications. + +# back to libdiskfs initialization + +It initializes the default proc and auth ports to be given to processes. + +It calls `startup_essential_task` on the startup port to tell startup that +it is ready. + +Eventually, it calls `_diskfs_init_completed` to finish its initialization, and +notably call `startup_request_notification` to get notified by startup when the +system is shutting down. + +# startup monitoring bootstrap progress + +As mentioned above, the different essential tasks (pci-arbiter, rumpdisk, ext2fs, proc, auth, exec) +call `startup_essential_task` when they are ready. startup's +`S_startup_essential_task` function thus gets called each time, and startup +records each of them as essential, monitoring their death to crash the whole +system. + +Once all of proc, auth, exec have called `startup_essential_task`, startup +replies to their respective RPCs, so they actually start working altogether. It +also calls `init_stdarrays` which sets the initial values of the standard exec data, and `frob_kernel_process` to plug the kernel task into the picture. +It eventually calls `launch_something`, which "launches +something", which by default is `/libexec/runsystem`, but if that can not be +found, launches a shell instead, so the user can fix it. |