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.
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:
Let’s do it then.
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.
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.
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.