🧪 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