Writing an init with Go (part 1)
Tags: go, linux, systemsprogramming, showdev
Published: 2020-12-30 13:57:27

---
title: Writing an init with Go (part 1)
published: true
description: 
tags:
  - go
  - linux
  - systemsprogramming
  - showdev
series: Writing init with go
---

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](https://gitlab.com/mrvik/podman-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)

Heavy load was put under service-manager.

## Hands on PID 1

Check out the code at [cmd/init](https://gitlab.com/mrvik/go-pid1/-/tree/master/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](http://git.suckless.org/sinit/file/sinit.c.html)

* 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 kills `service-launcher`, then waits for it, does `syscall.Sync()` and `syscall.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 from `syscall` 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!