Writing an init with Go (part 1)
MrViK
Posted on December 30, 2020
Welcome to the first article of the series about making an init process with Go.
So recently I was making some systems programming stuff with Go (a Podman service manager in case you want to check it out).
Then thought, let's write an init, how hard can it be?
Hotting up
So, what is the PID 1 supposed to be on a Linux system?
- Wait zombie processes
- Start other needed services (not really attached to PID 1)
- Mount filesystems (also not really attached to PID 1)
- Do the teardown, sync filesystems and good night.
For sure you have been thinking how to do those things on your favourite language, at first sight it isn't so hard.
Tracing the first lines
From points before:
- PID 1 does point 1 and 4 (partly). Then it launches a thing called "service launcher".
- Service Launcher (name is so original!). It
- mounts filesystems (calls
mount -av
) (BUG here, / is not remounted according to/etc/fstab
settings) - listens on a unix socket (control socket)
- launches all services, watches them (only the main process, see last links for further info).
- watches signals
- does teardown (stop services and unmount fs)
- mounts filesystems (calls
Heavy load was put under service-manager.
Hands on PID 1
Check out the code at cmd/init
Ok, this one is simple. If you had a glance at the code, you may notice it's heavily based on sinit
, the suckless init
- First, we start the service daemon. This call may fail, but who's afraid of an early panic on the morning?
- Then we make sure ctrl+alt+del sends SIGINT to PID 1 instead of calling reboot routine in kernel.
- Catch signals. Which signals?
- SIGUSR1 - poweroff
- SIGINT, SIGTERM - reboot
- SIGCHLD - Wait zombie processes.
- After signal catcher exits (or
service-launcher
exits) it killsservice-launcher
, then waits for it, doessyscall.Sync()
andsyscall.Reboot()
with selected option (from signal watcher).
Only 3 explicit syscalls here (others are done from stdlib code).
-
syscall.Sync
will block until disks are synchronized. -
syscall.Reboot
requests changes in reboot logic from kernel. E.g. it changes ctrl+alt+del behavior, asks for a reboot, poweroff, halt, and so on. -
syscall.Wait4
. Go allows to wait for processes from stdlib, so why do we do this fromsyscall
package?. That call allows us to pass WNOHANG avoiding a blocking call, we only check if is anybody dead and ready to be removed from process table.
Nothing too complicated here, overall experience was pretty straight-forward and didn't hit any limitation in the stdlib nor the language.
Also, this can be compiled as an 1.6 MiB static binary (as it has no cgo calls).
Binary size can be reduced by removing the "log" package but I didn't see any problem with that size.
So, what about the service launcher? It looks promising
Yes, it was hard and has a lot of concepts, so we'll get around it on the next article (part 2). Stay tuned.
Third (and last) article we'll be running this on qemu and contain some final thoughts about the future of this thing.
See you next time!
Posted on December 30, 2020
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.