Writing Linux Daemon Init Scripts-Free Linux Device Drivers Training

PREV_LECNEXT_LEC

Writing Linux Daemon Init Scripts
A free Linux kernel development course lecture on System V init.d scripts, start-stop-daemon, and embedded service management

In the previous lecture of this free linux kernel development course, you learned how System V init walks through runlevels using the /etc/rcN.d directories. In this lecture, we go one level deeper: how an individual init.d script is structured, how a real background daemon gets wired into the boot and shutdown sequence, and how you can start and stop services by hand on a running embedded board. This is exactly the kind of practical skill that separates a student who has read about Linux from an engineer who can bring up a custom board.

init.d scripts start-stop-daemon System V init embedded Linux services free embedded systems course runlevel symlinks

What You Will Learn

By the end of this lecture, part of our free linux device drivers course track on boot infrastructure, you will be able to:

  • Write a correct init.d script for your own background daemon
  • Explain what start-stop-daemon actually does and why embedded systems prefer it over raw fork()/backgrounding tricks
  • Wire a daemon into the correct runlevels using S and K symlinks
  • Start, stop, and query a service manually while debugging on target hardware
  • Recognize the two-digit ordering convention and avoid common startup-order bugs

Prerequisites

This lecture assumes you have already gone through the System V runlevels lecture in this series, and that you are comfortable with basic shell scripting and reading a board’s boot log. A Buildroot or Yocto based root filesystem (or any BusyBox based system) is enough to follow along — no physical hardware is required, a QEMU target works fine.

Anatomy of an init.d Script

Every script placed under /etc/init.d is a small command-line program that understands, at minimum, two arguments: start and stop. The runlevel switcher, /etc/init.d/rc, never talks to your daemon directly — it always talks to this script, and the script is responsible for translating a simple verb into whatever actions your particular program needs.

How rc Drives an init.d Script
rc script (runlevel change) | |–> for each K-prefixed symlink in rcN.d –> calls script “stop” | |–> for each S-prefixed symlink in rcN.d –> calls script “start” | +–> /etc/init.d/<service> (“start” | “stop”) | +–> start-stop-daemon –start / –stop

Two things matter here: order and idempotency. The rc script processes the kill (K) links first, then the start (S) links, and within each group it walks them in lexical order of the two-digit prefix. That prefix is not decoration — it is the entire ordering mechanism System V init has. If your daemon depends on the network being up, its script’s prefix must sort after the networking script’s prefix, or it will fail intermittently depending on boot timing.

Building a Real Daemon Script

Let’s build an init.d script for an original example daemon we’ll call ep_datalogger — imagine a small background process on an embedded board that periodically samples a sensor and appends readings to a log file. The script below is a clean, modern take on the classic pattern.

#!/bin/sh
# /etc/init.d/ep_datalogger

DAEMON=/usr/bin/ep_datalogger
NAME=ep_datalogger
PIDFILE=/var/run/ep_datalogger.pid

case "$1" in
  start)
    echo "Starting $NAME"
    start-stop-daemon -S -n "$NAME" -a "$DAEMON" -b -m -p "$PIDFILE"
    ;;
  stop)
    echo "Stopping $NAME"
    start-stop-daemon -K -n "$NAME" -p "$PIDFILE"
    rm -f "$PIDFILE"
    ;;
  restart)
    "$0" stop
    "$0" start
    ;;
  status)
    if [ -f "$PIDFILE" ] && kill -0 "$(cat "$PIDFILE")" 2>/dev/null; then
      echo "$NAME is running"
    else
      echo "$NAME is not running"
    fi
    ;;
  *)
    echo "Usage: $0 {start|stop|restart|status}"
    exit 1
    ;;
esac

exit 0

Save this as /etc/init.d/ep_datalogger and mark it executable with chmod +x. Notice the small improvements over a minimal script: a PIDFILE so we can reliably find the process later, a status verb (which the SEO-target System V convention encourages but does not require), and a restart verb built by simply re-invoking the script with different arguments — no duplicated logic.

Why start-stop-daemon Instead of Plain fork()

start-stop-daemon is a small helper utility, available in both the Debian-derived dpkg tooling and — more relevantly for embedded work — in BusyBox. Its job is to solve a handful of problems that a naive shell script would otherwise get wrong:

ProblemHow start-stop-daemon Solves It
Preventing duplicate instances-S checks for an already-running instance by name/pidfile before launching a new one
Finding the right process to stop-K locates the daemon by name or pidfile instead of relying on fragile ps parsing
Backgrounding correctly-b detaches the process from the controlling terminal so it survives the calling shell exiting
Sending the right signalDefaults to SIGTERM on stop, giving your daemon a chance to shut down cleanly, with -s to override

On a full desktop distribution you might reach for daemon() helper functions from an init framework instead, but on an embedded BusyBox-based root filesystem, start-stop-daemon is almost always the tool available, so it’s worth understanding well.

Wiring the Daemon into Runlevels

An init.d script sitting alone in /etc/init.d does nothing by itself — System V init only runs what is symlinked into a rcN.d directory. To have ep_datalogger start automatically in the default runlevel (typically 5 on desktop-style systems, though many embedded images use runlevel 3 or a custom default), create a start symlink:

# cd /etc/init.d/rc5.d
# ln -s ../init.d/ep_datalogger S95ep_datalogger

The prefix 95 here is a deliberate choice: high enough to run after networking (commonly prefixed in the 20s) and after the filesystem is mounted, but before the very last housekeeping scripts in the high 90s and 99. If you need the daemon to also be cleanly signalled on shutdown or reboot, add matching kill symlinks to runlevels 0 and 6:

# cd /etc/init.d/rc0.d
# ln -s ../init.d/ep_datalogger K05ep_datalogger
# cd /etc/init.d/rc6.d
# ln -s ../init.d/ep_datalogger K05ep_datalogger

Many embedded products skip shutdown handling entirely, since the device is simply power-cycled rather than gracefully halted — but if your daemon writes to a file or holds a lock, adding the kill links avoids data corruption on reboot.

Starting and Stopping Services by Hand

While bringing up a new board you will constantly want to run these scripts directly, without going through a full runlevel change. Any well-written init.d script supports this out of the box, since it is just a shell script you can invoke:

# /etc/init.d/ep_datalogger start
Starting ep_datalogger

# /etc/init.d/ep_datalogger status
ep_datalogger is running

# /etc/init.d/ep_datalogger stop
Stopping ep_datalogger

# /etc/init.d/ep_datalogger status
ep_datalogger is not running

This is the same mechanism mainstream distributions expose through a service command — service ep_datalogger restart is nothing more than a friendlier wrapper that finds and calls the right script under /etc/init.d. On a minimal embedded root filesystem, you will usually call the script directly instead.

Real-World Use Cases

  • Sensor logging daemons on industrial IoT gateways that must survive reboots but shouldn’t block boot if the sensor bus isn’t ready yet
  • Watchdog kick daemons that need to start very early, right after the filesystem is writable
  • Network-dependent services such as an MQTT client, which must be ordered after the networking script
  • Custom hardware bring-up helpers that configure GPIOs or load firmware blobs before the main application starts

Common Mistakes and Troubleshooting

Forgetting the executable bit. An init.d script that isn’t executable is silently skipped by the rc script on some BusyBox configurations, with no error printed anywhere. Always double-check with ls -l.
Wrong ordering prefix. A daemon that needs the network but starts as S05myapp will race the network bring-up script. Check what’s already using nearby prefixes with ls /etc/rc5.d before picking your own.
No pidfile, unreliable stop. Without -p PIDFILE, start-stop-daemon -K falls back to matching by executable name, which can accidentally kill the wrong process if two binaries share a name pattern.

Best Practices

  • Always implement start, stop, and ideally status — some embedded init frameworks and health-check scripts expect status to exist
  • Use a pidfile and pass it consistently to both the start and stop branches
  • Keep the script’s exit code accurate — return non-zero on failure so higher-level tooling can detect it
  • Prefer SIGTERM for a clean shutdown and only escalate to SIGKILL after a timeout, which start-stop-daemon -K --retry supports directly

Security Considerations

Init scripts typically run as root during boot. If your daemon does not need root privileges once it has opened any privileged resources (a low-numbered port, a device node), have the init.d script or the daemon itself drop privileges — start-stop-daemon supports a -c option to switch to a different user before exec’ing the daemon.

Summary and Key Takeaways

  • An init.d script is a plain shell script that answers to start and stop, called by the rc script during a runlevel change
  • start-stop-daemon handles the fiddly parts of backgrounding, duplicate-instance checks, and signalling
  • Symlinks under rcN.d, with their two-digit prefixes, are the entire ordering and enable/disable mechanism in System V init
  • You can and should call these scripts directly while bringing up a board, without waiting for a full runlevel transition

This is the core pattern behind most embedded boot infrastructure built on BusyBox and Buildroot, and it’s a skill worth practicing on real hardware. In the next lecture of this free linux development course, we move on to systemd — the modern alternative that many embedded products are now adopting.

Frequently Asked Questions

What is the difference between init.d and rc.d?

/etc/init.d holds the actual scripts, one per service. /etc/rcN.d directories hold symlinks back into init.d, and it is those symlinks — not the scripts themselves — that determine what actually runs in a given runlevel.

Do I always need start-stop-daemon, or can I just background with &?

You can background a process with &, but you lose reliable duplicate-instance detection and a clean way to find and stop it later. start-stop-daemon exists specifically to remove that guesswork.

Why does my daemon start before the network is up?

Its symlink’s two-digit prefix sorts earlier than the networking script’s prefix. Check existing prefixes in the target rcN.d directory and choose a higher number.

Is a pidfile mandatory?

Not mandatory, but strongly recommended. Without one, stopping the daemon relies on matching by executable name, which is less precise and can be unreliable if multiple processes share a name.

Can the same init.d script be used across multiple runlevels?

Yes — the script itself is runlevel-agnostic. You control which runlevels use it purely by which rcN.d directories contain a symlink to it.

What signal does start-stop-daemon send by default when stopping?

SIGTERM, which gives a well-behaved daemon the chance to catch the signal and shut down cleanly before a forced kill.

Does BusyBox’s start-stop-daemon behave exactly like the Debian one?

Mostly, but BusyBox’s version supports a smaller subset of options. Always check start-stop-daemon --help on your target’s BusyBox build before relying on a less common flag.

Continue the Free Linux Kernel Development Course

Explore more free lectures on embedded Linux boot infrastructure, device drivers, and kernel internals at EmbeddedPathashala.

Browse the Course Index Next Lecture: systemd Basics

PREV_LECNEXT_LEC

2 Comments

Leave a Reply

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