Project: Write a Minimal Init System (PID 1)
A minimal init
system is a great way to understand Unix-like process management, signal handling, and what happens when a Linux system boots. In this project, you’ll write your own tiny init process and run it as PID 1.
What Is an Init System?
The init system is the first user-space process launched by the Linux kernel. It always has PID 1 and is responsible for:
- Starting other user-space programs (like shells or daemons)
- Reaping zombie processes (crucial)
- Handling shutdown or reboot requests
We’re not building
systemd
— just a minimal educational version.
Prerequisites
You’ll need:
- Intermediate knowledge of C programming
- Familiarity with Linux system calls
- Access to a VM, container, or custom boot environment
Goals
Your init
program will:
- Run as PID 1
- Spawn a shell (like
/bin/sh
) - Reap child processes (zombies)
- Handle basic signals like
SIGTERM
orSIGINT
Step-by-Step
1. Write the Minimal init.c
|
|
2. Compile the Program
Use static linking to ensure it works in a minimal root filesystem:
|
|
3. Run It in a Minimal Environment
Option A: Using Docker
|
|
This version won’t run as PID 1 (when you run ps
). If you run ps
in the new shell, it will look
something like:
|
|
What’s Going On?
- PID 1 is not your init — it’s Docker’s
/sbin/docker-init
- Docker injects its own init process (usually
tini
) to reap zombies and forward signals correctly. - Your custom
init
is actually running as PID 7 — so it’s not truly PID 1.
But Your Code Still Works
Even though it’s PID 7, your minimal init
:
- Spawned a
/bin/sh
shell (PID 8) - Stayed alive
- Can still handle SIGCHLD, etc.
So your code is fine — it’s just not running as PID 1 due to Docker’s isolation behavior.
Option B: Want to Truly Run as PID 1? QEMU + Custom Initramfs
Here we will:
- Build a minimal Linux kernel
- Bundle your init in an initramfs
- Boot it with qemu:
✅ What You Need
i. A Kernel Image: bzImage
Option A: Build It Yourself
|
|
After compilation, copy the result:
|
|
Option B: Download Prebuilt Kernel (Lazy option 🤣)
You can download a bzImage
from sources like TinyCore or GitHub projects, but compiling is best for full control
and for the exploratory stuff we are doing here.
|
|
Or search for “prebuilt bzImage”.
ii. A Minimal Initramfs: initramfs.cpio.gz
initramfs
(short for initial RAM filesystem) is a temporary root filesystem that is loaded into memory and used by the Linux kernel very early in the boot process, before the real root filesystem is mounted.
It must include:
/init
(your compiled static init program)/bin/sh
(e.g., via BusyBox)/dev/console
(required for proper boot output)
A. Download and Build BusyBox (If you don’t have it already)
|
|
This gives you a statically linked busybox
binary in ./busybox
.
B. Create Root Filesystem Layout
|
|
C. Create Symbolic Links for BusyBox Linux commands
|
|
D. Create /dev/console device node:
|
|
E. Install Your Custom init
Make sure your compiled init
binary (from earlier) is copied into the root filesystem as /init
.
|
|
This will now be PID 1 when QEMU boots.
Make it executable:
|
|
F. Build the Initramfs
|
|
iii. Boot It With QEMU (Once You Have the Files)
Make sure you’re in the directory with:
|
|
Then run:
|
|
You should see:
|
|
This is expected — job control is unavailable without a full controlling terminal, but your shell works perfectly without it.
iv. Confirm It Works
Run ps
in the shell. You should see this, plus many other kernel threads:
|
|
✔️ Your init is PID 1
✔️ Your shell launched
✔️ ps shows all processes
✔️ Kernel threads like [kworker/…] are visible
✅ You are now officially running your own custom Linux init system from scratch inside QEMU!
4. Stretch Goals
Add extra functionality to your init
:
- Handle
SIGTERM
,SIGINT
for clean shutdown - Spawn multiple child processes (like daemons)
- Log messages to a file or pseudo-terminal
- Implement shutdown: call
sync()
andreboot(RB_POWER_OFF)
- Parse a config file (like a mini
/etc/inittab
)
References
Next Steps
Coming soon:
- Test zombie reaping with background processes
- Send it signals and confirm it handles them
- Add shutdown/reboot signal support
- Launch other system services from your init