Mounting /proc, /sys, and /dev in an Init Process

When the Linux kernel finishes booting, it needs to know what to run next. What process will be the “manager” of all of the other userland processes? This process, called the “init process” or “PID 1” is commonly part of a larger “init system”. There are several. Mostly only SysV Init and systemd are worth mentioning, though.

The first order of business is to do something that I can’t believe the kernel doesn’t do for you: Mounting /proc, /sys, and /dev. (The kernel does most of the work, in fact, you just get to push the button that says “go”).

Here’s a minimal example of an init process which mounts these three directories/pseudo-directories:

#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mount.h>
#include <unistd.h>

void mount_proc();
void mount_sys();
void mount_dev();
void run_terminal();

void mount_proc() {
    printf("Mounting /proc...");
    if (0 != mount("none", "/proc", "proc", 0, "")) {
        printf("\nFailed to mount /proc!\n");
        exit(1);
    }
    printf("Done!\n");
}

void mount_sys() {
    printf("Mounting /sys...");
    if (0 != mount("none", "/sys", "sysfs", 0, "")) {
        printf("\nFailed to mount /proc!\n");
        exit(1);
    }
    printf("Done!\n");
}

void mount_dev() {
    printf("Mounting /dev...");
    if (0 != mount("none", "/dev", "devtmpfs", 0, "")) {
        int errno2 = errno;
        // Error code 16 ("device or resource busy") is spurrious and doesn't really matter. Ignore it.
        if (errno2 != 16) {
            printf("\nFailed to mount /dev! errno=%d strerror=%s\n", errno, strerror(errno));
            exit(1);
        }
    }
    printf("Done!\n");
}

void run_terminal() {
    execl("busybox-x86_64", "busybox-x86_64", "ash", NULL);
}

int main(int argc, char *argv[]) {
    printf("Hello from the init system!\n");

    mount_proc();
    mount_sys();
    mount_dev();

    run_terminal();

	return 0;
}

To compile it, be sure to use -static so gcc won’t try to use any shared libraries:

gcc -static main.c -o ./boot_disk/init

You can put this file on a drive image and load it into QEMU, and specify it as the init= part of the Linux kernel parameters (in GRUB2 or directly through QEMU). It’s also handy to have busybox on the drive, and I left an example of how to invoke it in the code above. That way you can drop into the busybox shell and see the mounted filesystem:

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.