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:

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:

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.



This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License.