What is BusyBox Init System Explained-Free Linux Device Drivers Training

PREV_LEC NEXT_LEC
Free Linux Device Drivers Course

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.

Topics Covered
BusyBox inittab init actions rcS rcK scripts Buildroot init embedded boot scripts

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

ActionWhen it runsTypical use
sysinitOnce, before any other action type, in file orderMounting proc/sysfs, setting up device nodes
respawnRuns the program and restarts it if it exitsLong-running daemons that must stay alive
askfirstSame as respawn, but waits for Enter before launchingInteractive login shell on a serial console
onceRuns a single time, never restartedOne-shot setup that shouldn’t repeat
waitRuns and blocks init until it completesSequential setup steps that must finish in order
restartRuns when init receives SIGHUPReloading configuration on demand
ctrlaltdelRuns when init receives SIGINTHandling a physical reset request
shutdownRuns when init is shutting the system downUnmounting 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.

BusyBox init Startup Order
/sbin/init reads /etc/inittab   1. run all “sysinit” lines, top to bottom   2. run all “wait” lines, blocking on each   3. run all “once” lines   4. launch “respawn” / “askfirst” lines (kept alive)   5. idle, waiting for signals (SIGHUP, SIGINT) or shutdown

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

Watch out for these:
  • Forgetting the execute bit on scripts in /etc/init.d/ — BusyBox’s rcS loop silently skips non-executable files.
  • Using respawn for 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_network before mounting /dev).
  • Editing inittab live 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 inittab itself 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 simple id::action:program format.
  • 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 Lectures

Frequently 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.

PREV_LEC NEXT_LEC

1 Comment

Leave a Reply

Your email address will not be published. Required fields are marked *