elektito programming & stuff

LRFS Part 3: Init

This is part of a series of articles. You can find the first part here.

In this first part of this series we built a kernel and ran it with a very minimal (and useless) init program. We then built bash and used that as init. Let’s go back to our init program and see how we can make a more proper init system.

What is init?

The first process to be started by the kernel is called init. This process always has the Process ID (PID) of 1 and has a number of special properties:

  • It should keep running up until the system shuts down. If init is terminated, the kernel will panic.

  • All orphan processes are re-parented to init*. These are the processes whose parents has been terminated before them. The orphans, when terminated, become zombies. Init is tasked “reaping” these processes, so that their resources is allowed to be freed.

  • Signals without a signal handler do not have any default behavior for init. As an example, a process that does not handle SIGTERM, will shutdown by default if it receives that signal. If init, however, receives SIGTERM and has no signal handler for it, the signal is just ignored.

Init is the process that starts the Linux userland. Everything from the login prompt, your shell or your desktop environment is directly or indirectly started by init.

Note however, that technically speaking, the only thing that init “has to” do is reaping zombie processes. Today’s init systems though do a lot more. Starting and managing services is among the most important of those.

Our init system, called hello, does little though. For now, it is going to:

  • Run a startup script.
  • Start a shell.
  • Keep running and reap zombie processes.

Let’s do it then.

The code

Here is our expanded init system, in all its glory:

#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <stdio.h>

static void
handle_sigchld(int sig) {
  int saved_errno = errno;

  /* reap orphaned children, passed away. rip. */
  while (waitpid((pid_t)(-1), 0, WNOHANG) > 0) {}

  errno = saved_errno;
}

static int
run_program(const char *path)
{
  pid_t child;
  int ret;
  siginfo_t info;

  child = fork();
  if (child) {
    waitid(P_PID, child, &info, WEXITED);
    return info.si_status;
  } else {
    execl(path, path, NULL);
    printf("Could not run: %s\n", path);
    printf("    %s\n", strerror(errno));
    exit(255);
  }
}

static void
launch_login(void)
{
  if (!fork()) {
    execl("/bin/bash", "/bin/bash", NULL);
  }
}

int
main(int argc, char *argv[])
{
  struct sigaction sa;

  /* register sigchld handler */
  sa.sa_handler = &handle_sigchld;
  sigemptyset(&sa.sa_mask);
  sa.sa_flags = SA_RESTART | SA_NOCLDSTOP;
  if (sigaction(SIGCHLD, &sa, 0) == -1) {
    printf("Could not install signal handler. Aborting.\n");
    return 1;
  }

  printf("Hello, World!\n");
  printf("This is hello, your friendly init system!\n");

  printf("Attempting to run your rc.local...\n");
  run_program("/etc/rc.local");

  printf("Launching your shell...\n");
  launch_login();

  for (;;) {
    usleep(600 * 1000000);
  }

  return 0;
}

As you can see, we start by adding a SIGCHLD handler. SIGCHLD can be sent to a process whenever something interesting happens to its children. Here we explicitly ask to only be informed when one of the children has exited (SA_NOCLDSTOP).

We then display a friendly startup message and then run the script located at /etc/rc.local. We then launch bash as the shell and go to sleep. From here are, the only thing init does is to handle SIGCHLD and wait on the child processes so that their resources can be freed by the kernel.

Try it

As before, rebuild the package and add the contents to the image file and then run the result in qemu:

qemu-system-x86_64 -kernel /path/to/bzImage \
              -append "root=/dev/sda init=/bin/bash console=ttyS0" \
              -hda /path/to/image.img \
              -enable-kvm \
              -nographic \
              -serial mon:stdio

You can use the tools in the /tools directory for building the package and creating the rootfs.

The source

You can find the source code for hello and all the other tools and packages talked about in this series here on Github.

* Technically that is not always correct. In more recent versions, there can be “sub-reapers” that an orphan might be re-parented to. In the absence of sub-reapers though, orphans are re-parented to init.