init will tell you only a small part of the story.
There's a sort of myopia that affects the Linux world. People think that they use a thing called "System 5
init", and that is both what is traditional and the best place to start. Neither is in fact the case.
Tradition isn't in fact what such people say it to be, for starters. System 5
init and System 5
rc date to AT&T UNIX System 5, which was almost as far after the first UNIX as we are now (say) after the first version of Linux-Mandrake.
1st Edition UNIX only had
init. It did not have
rc. The 1st Edition assembly language
init (whose code has been restored and made available by Warren Toomey et al.) directly spawned and respawned 12
getty processes, mounted 3 hardwired filesystems from a built-in table, and directly ran a program from the home directory of a user named
getty table was also directly in the program image.
It was another decade after UNIX System 5 that the so-called "traditional" Linux init system came along. In 1992, Miquel van Smoorenburg (re-)wrote a Linux
rc, and their associated tools, which people now refer to as "System 5
init", even though it isn't actually the software from UNIX System 5 (and isn't just
rc isn't the best place to start, and even if one adds on knowledge of systemd that doesn't cover half of what there is to know. There's been a lot of work in the area of init system design (for Linux and the BSDs) that has happened in the past two decades alone. All sorts of engineering decisions have been discussed, made, designed, implemented, and practised. The commercial Unices did a lot, too.
Existing systems to study and and learn from
Here is an incomplete list of some of the major init systems other than those two, and one or two of their (several) salient points:
- Joachim Nilsson's finit went the route of using a more human-readable configuration file.
- Felix von Leitner's minit went for a filesystem-is-the-database configuration system, small memory footprints, and start/stop dependencies amongst things that
- Gerrit Pape's runit went for what I have previously described as the just spawn four shell scripts approach.
- InitNG aimed to have dependencies, named targets, multiple configuration files, and a more flexible configuration syntax with a whole load more settings for child processes.
- upstart went for a complete redesign, modelling the system not as services and interdependencies at all, but as events and jobs triggered by them.
- The design of nosh includes pushing all of the service management out (including even the
getty spawning and zombie reaping) into a separate service manager, and just handling operating-system-specific "API" devices/symlinks/directories and system events.
- sinit is a very simple init. It executes
/bin/rc.init whose job it is to start programs, mount filesystem, etc. For this you can use something like minirc.
Moreover, about 10 years ago, there was discussion amongst daemontools users and others of using
svscan as process #1, which led to projects like Paul Jarc's svscan as process 1 study, Gerrit Pape's ideas, and Laurent Bercot's svscan as process 1.
Which brings us to what process #1 programs do.
What process #1 programs do
Notions of what process #1 is "supposed" to do are by their natures subjective. A meaningful objective design criterion is what process #1 at minimum must do. The kernel imposes several requirements on it. And there are always some operating-system-specific things of various kinds that it has to do. When it comes to what process #1 has traditionally done, then we are not at that minimum and never really have been.
There are several things that various operating system kernels and other programs demand of process #1 that one simply cannot escape.
People will tell you that
fork()ing things and acting as the parent of orphaned processes is the prime function of process #1. Ironically, this is untrue. Dealing with orphaned processes is (with recent Linux kernels, as explained at https://unix.stackexchange.com/a/177361/5132) a part the system that one can largely factor out of process #1 into other processes, such as a dedicated service manager. All of these are service managers, that run outwith process #1:
- the IBM AIX
srcmstr program, the System Resource Controller
- Gerrit Pape's
runsvdir from runit
- Daniel J. Bernstein's
svscan from daemontools, Adam Sampson's
svscan from freedt, Bruce Guenter's
svscan from daemontools-encore, and Laurent Bercot's
s6-svscan from s6
- Wayne Marshall's
perpd from perp
- the Service Management Facility in Solaris 10
service-manager from nosh
Similarly, as explained at https://superuser.com/a/888936/38062, the whole
/dev/initctl idea doesn't need to be anywhere near process #1. Ironically, it is the highly centralized systemd that demonstrates that it can be moved out of process #1.
Conversely, the mandatory things for
init, that people usually forget in their off-the-top-of-the-head designs, are things such as handling
SIGWINCH, and so forth sent from the kernel and enacting the various system state change requests sent from programs that "know" that certain signals to process #1 mean certain things. (For example: As explained at https://unix.stackexchange.com/a/196471/5132, BSD toolsets "know" that
SIGUSR1 has a specific meaning.)
There are also once-off initialization and finalization tasks that one cannot escape, or will suffer greatly from not doing, such as mounting "API" filesystems or flushing the filesystem cache.
The basics of dealing with "API" filesystems are little different to the operation of
init rom 1st Edition UNIX: One has a list of information hardwired into the program, and one simply
mount()s all of the entries in the list. You'll find this mechanism in systems as diverse as BSD (sic!)
init, through the nosh
system-manager, to systemd.
"set the system up for a simple shell"
As you have observed,
init=/bin/sh doesn't get "API" fileystems mounted, crashes in an ungainly fashion with no cache flush when one types
exit (https://unix.stackexchange.com/a/195978/5132), and in general leaves it to the (super)user to manually do the actions that make the system minimally usable.
To see what one actually has no choice but to do in process #1 programs, and thus set you on a good course for your stated design goal, your best option is to look at the overlaps in the operation of Gerrit Pape's runit, Felix von Leitner's minit, and the
system-manager program from the nosh package. The former two show two attempts to be minimalist, yet still handle the stuff that it is impossible to avoid.
The latter is useful, I suggest, for its extensive manual entry for the
system-manager program, which details exactly what "API" filesystems are mounted, what initialization tasks are run, and what signals are handled; in a system that by design has the system manager just spawn three other things (the service manager, an accompanying logger, and the program to run the state changes) and only do the unavoidable in process #1.