On a traditional SysV-init system, how did init decide which services to run when it entered runlevel 3, and what determined the order they ran in?
answer
- init itself knew nothing about services
- a directory of symlinks per runlevel
- K with stop, then S with start
- two digits decide the order
- LSB headers computed the numbers later
basics
~20 sInit consulted /etc/inittab, then ran the rc script for that runlevel, which walked /etc/rc3.d in lexical order: K-links were invoked with stop first, then S-links with start. Each link pointed at a shell script in /etc/init.d, and the two-digit number in the link name fixed the order.
solid answer
~50 sEntering a runlevel was a directory walk. `/etc/inittab` mapped each runlevel to an `rc` script, and that script processed `/etc/rc3.d/`, a directory full of symlinks pointing back at shell scripts in `/etc/init.d/`. Links beginning with `K` were run first with the argument `stop`, then links beginning with `S` with `start`. Order came purely from the two-digit number in each link name — `S20` ran before `S80` — so ordering was a human-assigned convention, not a declared dependency. Each script was an ordinary shell script accepting `start`, `stop`, `restart` and usually `status`. The consequences were significant: execution was strictly sequential, so one slow or hanging script stalled the whole boot; and once a script returned, init knew nothing more about the daemon it had launched. Later LSB headers let tools like `insserv` or `chkconfig` compute the numbers from declared dependencies.
go deeper
Recognise the layout when you see it: /etc/init.d holds the scripts, /etc/rcN.d holds numbered symlinks to them, and the scripts take start, stop, restart and status.
Explain the two-pass walk — K links with stop, then S links with start, ordered by the two-digit prefix — and why numbers are a convention rather than a declared dependency.
Draw out the operational consequences you have lived with: sequential boot with no timeouts, no supervision after the script exits, and PID-file state that goes stale, which is why a legacy script on a modern host deserves conversion.
Speak to migration strategy — deciding whether to convert vendor init scripts, rely on the compatibility generator, or replace the component, and what that choice costs in boot reliability and support burden.
## The dispatch: inittab `/etc/inittab` was init's only configuration file. Beyond the `initdefault` line naming the default runlevel, it contained lines associating each runlevel with a command to run — conventionally an `rc` script invoked with the runlevel number, such as `/etc/rc.d/rc 3` on Red Hat systems or `/etc/init.d/rc 3` on Debian. Init itself contained no knowledge whatsoever about services; it merely ran that one script and let shell take over. ## The rc directories For each runlevel N there was a directory `/etc/rcN.d/` (or `/etc/rc.d/rcN.d/`). It held nothing but symlinks, each pointing back at a real script in `/etc/init.d/`. A typical entry looked like: ``` /etc/rc3.d/S20nginx -> ../init.d/nginx /etc/rc3.d/K80nginx -> ../init.d/nginx ``` The first character encoded the verb and the two digits encoded the order. ## Kill first, then start The rc script processed the directory in two passes. First it took every link whose name began with `K`, in ascending numeric order, and invoked the target script with the argument `stop`. Then it took every `S` link, again in ascending order, and invoked the script with `start`. The two passes are why the same daemon appears twice in the tree with different prefixes and different numbers: it must be stopped early when leaving a level and started at a particular point when entering one. ## The scripts themselves An init script was a plain shell script implementing a small command-line contract: ```bash case "$1" in start) start_daemon ;; stop) stop_daemon ;; restart) stop_daemon; start_daemon ;; status) report_status ;; esac ``` Because it was shell, it could do absolutely anything, and in practice it did: creating directories, setting ulimits, sourcing distribution-specific helper libraries, writing PID files, and finally launching a daemon that detached itself. Every distribution had its own helper functions, which is a large part of why init scripts were not portable. ## Ordering by number, and its limits The numbers were the ordering mechanism, and they were assigned by convention. If your database needed to be up before your application, you gave the database a lower `S` number. Nothing in the system verified or even understood that relationship, so a package installed with the wrong number produced a boot-order bug that appeared only intermittently. The Linux Standard Base later addressed this with a comment block at the top of the script: ``` ### BEGIN INIT INFO # Provides: myapp # Required-Start: $network $remote_fs # Default-Start: 2 3 4 5 ### END INIT INFO ``` Tools such as `insserv` on Debian and `chkconfig` on Red Hat parsed those headers and computed the symlink numbers from them. This was a genuine improvement, but it was still a compilation step producing numbers, not a runtime dependency graph. ## Enabling a service meant managing links There was no separate enablement database. `chkconfig nginx on` or `update-rc.d nginx defaults` created the `S`/`K` links; the corresponding `off`/`remove` deleted them. `service nginx start` invoked the script directly for the running system. That is precisely the enable-versus-start split that systemd later expressed with install symlinks under a target. ## What the model could not do Three limitations drove the move away from it: - **Strictly sequential.** Scripts ran one at a time. On a server with fifty services, boot took as long as the sum of them, and one script that blocked on an unreachable NFS mount or a DNS lookup stalled the entire boot indefinitely. - **No supervision.** Once a script returned, init had no relationship with the daemon at all. The daemon had detached and been reparented to init as an anonymous child. If it crashed a minute later, nothing noticed and nothing restarted it. - **No reliable process tracking.** State lived in a PID file the daemon wrote itself, with all the staleness and race problems that implies, and any children the daemon forked were invisible to the stop path. ## What survives today systemd still runs legacy init scripts: a generator reads `/etc/init.d/` at boot and synthesises service units from what it finds, honouring LSB headers where present. The `service` command is usually redirected to `systemctl`. So the model persists as a compatibility layer, and encountering a genuine init script on a modern host usually means a vendor package that was never converted.
- Why does the same daemon appear twice in the rc directories, once with an S prefix and once with a K prefix?The two prefixes serve the two passes of the rc script. When entering a runlevel it first invokes every K link with `stop`, then every S link with `start`. A daemon that should run at this level needs an S link here and K links in the levels where it must be shut down, and the numbers usually mirror each other so shutdown order reverses startup order.
- What did LSB init-script headers change, and what did they not change?They let a script declare what it provides and what it requires, so tools like insserv or chkconfig could compute symlink numbers from real relationships instead of a human guessing. What they did not change is the runtime model: the output is still fixed numbers in filenames, execution is still sequential, and init still gains no supervision over what the script launched.
- Why could a single init script stall an entire boot?The rc script invoked each link synchronously and waited for it to return before moving on. A script that blocked — mounting an unreachable NFS export, waiting on a DNS lookup, or prompting for input — held the whole sequence indefinitely with no timeout and nothing to run in parallel meanwhile. Timeouts and parallelism were exactly what later init systems added.
saying these in an interview costs you the question
- Thinks init itself knew which daemons to start
- Says the rc scripts ran in parallel
- Believes S and K links are alternatives rather than two passes
- Claims the numbers expressed real dependencies
- Assumes init supervised the daemon after the script returned