BusyBox Init System Explained
Reading and writing an /etc/inittab file, understanding every action keyword, and wiring up Buildroot-style start/kill scripts.
In the last lecture we covered why every embedded Linux board needs a PID 1 and compared BusyBox init, System V init, and systemd. This lecture is the hands-on half of that free linux kernel development course topic: we build a real /etc/inittab, walk through every action keyword BusyBox init understands, and show how Buildroot’s rcS/rcK scripts turn a flat init config into an extensible startup system — all part of this free embedded linux course.
What You Will Learn
- The exact syntax BusyBox init expects in /etc/inittab
- Every action keyword BusyBox init supports, with practical examples
- Building an original inittab from scratch and booting it
- How Buildroot’s rcS/rcK scripts make init extensible without editing inittab
- Common mistakes that produce a board that boots but never reaches a shell
Prerequisites
This lecture assumes you’ve read Linux Init Program Explained (the previous lecture in this free embedded systems course) and have a Buildroot or similar embedded root filesystem you can rebuild and boot, ideally in QEMU for fast iteration.
The inittab File Format
BusyBox init is deliberately small — it has no daemon dependency graph, no unit files, just one configuration file it reads once at startup: /etc/inittab. Each non-comment line follows a fixed four-field format:
<id>::<action>:<program>
id— the controlling terminal for the command (often left blank for non-terminal actions).action— the condition under which BusyBox init runs this line (see table below).program— the actual command or script to execute.
Every Action Keyword, Explained
| Action | When it runs | Typical use |
|---|---|---|
sysinit | Once, before any other action type, in file order | Mounting proc/sysfs, setting up device nodes |
respawn | Runs the program and restarts it if it exits | Long-running daemons that must stay alive |
askfirst | Same as respawn, but waits for Enter before launching | Interactive login shell on a serial console |
once | Runs a single time, never restarted | One-shot setup that shouldn’t repeat |
wait | Runs and blocks init until it completes | Sequential setup steps that must finish in order |
restart | Runs when init receives SIGHUP | Reloading configuration on demand |
ctrlaltdel | Runs when init receives SIGINT | Handling a physical reset request |
shutdown | Runs when init is shutting the system down | Unmounting filesystems cleanly |
Building an Original inittab
Here’s a minimal but complete example for a board that mounts the essentials, brings up a device manager, and gives you a login shell — none of it copied from any reference book, just a straightforward setup you’d actually use:
# /etc/inittab
::sysinit:/bin/mount -t proc proc /proc
::sysinit:/bin/mount -t sysfs sysfs /sys
::sysinit:/bin/mount -t devtmpfs devtmpfs /dev
::sysinit:/etc/init.d/rcS
ttyS0::askfirst:-/bin/sh
::ctrlaltdel:/sbin/reboot
::shutdown:/bin/umount -a -r
The sysinit lines run first, in order, so the mounts happen before rcS tries to use /dev. The askfirst line waits for you to press Enter on the serial console before dropping into a shell — handy so you’re not fighting boot log spam for control of the terminal.
Making It Extensible: rcS and rcK
A single flat inittab gets unwieldy fast once you have more than a couple of daemons. That’s the problem Buildroot’s rcS/rcK pattern solves. Instead of listing every startup step directly in inittab, you point sysinit at one script, /etc/init.d/rcS, which then iterates over every executable in /etc/init.d/ whose name starts with a capital S followed by two digits — running them in numerical order. A matching rcK script does the same for files starting with K at shutdown.
# /etc/init.d/S01ep_mountfs
#!/bin/sh
mount -t proc proc /proc
mount -t sysfs sysfs /sys
# /etc/init.d/S02ep_network
#!/bin/sh
ip link set eth0 up
udhcpc -i eth0 -b
# /etc/init.d/S10ep_myapp
#!/bin/sh
/usr/bin/ep_sensor_daemon &
# /etc/init.d/K10ep_myapp
#!/bin/sh
killall ep_sensor_daemon
Because the two-digit prefix controls ordering, adding a new package’s startup step is just a matter of dropping in a new S/K file with an appropriate number — no editing of a growing monolithic script required.
Build and Boot Walkthrough
To see this in action, rebuild your root filesystem with the inittab above and an S10ep_myapp script that just echoes a message, then boot in QEMU:
$ make menuconfig # ensure BusyBox init is selected, not systemd
$ make
$ qemu-system-arm -M vexpress-a9 -kernel output/images/zImage \
-dtb output/images/*.dtb -drive file=output/images/rootfs.ext4,if=sd \
-append "console=ttyAMA0 root=/dev/mmcblk0" -nographic
Expected output on the console:
[ 1.812340] Run /sbin/init as init process
Starting rcS...
[ep_mountfs] proc and sysfs mounted
[ep_network] eth0 up, requesting DHCP lease
[ep_myapp] sensor daemon started, pid 118
Please press Enter to activate this console
If instead the board hangs right after “Run /sbin/init as init process” with no further output, the most common cause is a syntax error in inittab or a missing #!/bin/sh shebang on one of the rcS scripts.
Common Mistakes
- Forgetting the execute bit on scripts in
/etc/init.d/— BusyBox’s rcS loop silently skips non-executable files. - Using
respawnfor a script that’s supposed to run once — this causes it to loop forever, often flooding the console. - Mismatched S/K numbering between related scripts, so a service starts before a dependency it needs (like starting
ep_networkbefore mounting/dev). - Editing
inittablive and forgetting that BusyBox init only re-reads it on SIGHUP — a raw edit with no reload leaves the old rules active.
Best Practices
Recommendations
- Keep
inittabitself minimal — mounts, the rcS/rcK hooks, and your console shell — and push everything else into numbered init.d scripts. - Leave gaps between S-numbers (S01, S05, S10 rather than S01, S02, S03) so you can insert new steps later without renumbering everything.
- Always test a new init.d script’s exit behavior — a script that hangs on a
wait-style dependency will stall the entire boot sequence behind it.
Summary and Key Takeaways
- BusyBox init reads one file,
/etc/inittab, with a simpleid::action:programformat. - Eight action keywords cover everything from one-shot setup to respawned daemons to signal-triggered handlers.
- Buildroot’s rcS/rcK convention turns a flat inittab into an extensible, numerically-ordered startup system used by every installed package.
Next: Learning About Processes and Threads
With init and boot-time startup covered, the course continues into how Linux manages the processes init supervises.
Continue the Free Linux Kernel Course Browse All LecturesFrequently Asked Questions
Does BusyBox init re-read inittab automatically after I edit it?
No. You need to send it SIGHUP (typically via init q or kill -HUP 1) to force a reload, or reboot the board.
What’s the difference between “once” and “wait” in inittab?
Both run a command exactly once, but “wait” blocks init from proceeding to the next line until the command finishes, while “once” starts it and immediately moves on.
Why use rcS/rcK instead of listing every startup step directly in inittab?
A flat inittab becomes hard to maintain as more daemons are added, and every package would need to edit a shared file. The rcS/rcK pattern lets each package drop in its own numbered script independently.
What happens if a respawn program keeps crashing immediately?
BusyBox init will keep restarting it as fast as it exits, which can spin the CPU. Production inittabs often wrap fragile daemons in a small supervisor script with a backoff delay rather than respawning the raw binary.
Can I mix BusyBox init actions with a Buildroot rcS setup?
Yes — this is in fact the standard Buildroot pattern: inittab’s sysinit line calls rcS, and everything else (respawn shells, ctrlaltdel, shutdown) stays directly in inittab.

1 Comment