Skip site navigation (1)Skip section navigation (2)
Date:      Thu, 5 Feb 1998 08:41:00 +0000 (GMT)
From:      Terry Lambert <tlambert@primenet.com>
To:        abial@nask.pl (Andrzej Bialecki)
Cc:        freebsd-current@FreeBSD.ORG
Subject:   Re: Custom init(8)
Message-ID:  <199802050841.BAA13172@usr08.primenet.com>
In-Reply-To: <Pine.NEB.3.95.980204110152.12994A-100000@korin.warman.org.pl> from "Andrzej Bialecki" at Feb 4, 98 11:11:01 am

next in thread | previous in thread | raw e-mail | index | archive | help
------------------------------------------------------------------------
NOTE: The following would probably be a useful addition to the handbook
or someplace else.  *BUT* I typed this thing at near full-speed, off
the top of my head, and without reference to man pages or code, so
there may be factual errors arising from changes since the last time
I looked at the code.  Use the following information with  caution and
with man pages in hand.  It is also bound to contain a lot of typos of
the type seen caused by dyslexics typing at full speed.  8-).
------------------------------------------------------------------------

THE POOR MAN'S GUID TO HACKING INIT ON UNIX SYSTEMS

> I'd like to write my own custom init. To be frank, I'm not quite aware of
> what is absolutely necessary for it to do, and what is not.

I think you are maybe barking up the wrong tree (see way below 8-)).

The init's process is hand-crafted by the kernel as the first user
process on the system, and is forked and exec'ed from the kernel by
/sys/kern/init_main.c after a user space stack is hand built for the
exec.

Effectively, it is the first process on the system from which all
other processes are descendents.

An init will typically:

1)	Be statically linked.

2)	Not use a lot of libc, other than system calls.

3)	Control "mode".  Single user mode is defined by init
	running a user shell, generally /bin/sh, also expected
	to be statically linked.  Multiuser mode is defined by
	init running a user shell, generally /bin/sh, to process
	commands in a /etc/rc file.

4)	If running a shell, init fork()'s, and the shell is run in
	the child (init is alsways PID 1).  In the case of single
	user mode, init will set the console canonical processing
	mode and exec the shell with "-" as the argument, after
	opening the console and making the child the process group
	leader and controlling process for the tty.  In the case of
	multiuser mode, init will will fork(), exec the /etc/rc file
	after setting the console canonical processing modes, and
	will wait for the child to exit (ie: the rc file to complete)
	before continuing.  It is the job of the rc file to do all
	the work of bringing thesystem to multiuser mode.  This includes
	starting daemons in the correct order, clearing log files and
	lock files and tmp directories, mounting require file systems
	(the most probable explanation for your failure is that you
	linked your init program shared, the default, and it could not
	find /usr/libexe/ld.so or /usr/lib/libc.so.* because /usr was
	not yet mounted), and similar tasks.

5)	When init is done fork()ing, and potentially waitng for the
	child process, it generally reads a configuration file to tell
	it the programs it should respawn (re-fork and exec should they
	exit).  In SVR4, this is the /etc/inittab, the same file that
	told it which rc files to start (SVR4's init is smarter than 
	SD's, and knows how to manage multiple run levels, not just
	"single user" and "multiuser").  In BSD, this file is /etc/ttys.

6)	Init starts the processes in the file it read.

7)	Init establishes a sigchld handler, a sighup handler, and any
	other handlers it choolses.  Generally a sighup handler is used
	to tell init to reread its configuration file, above, so it
	can know that the file has been changed.

8)	Init goes into a loop, where it calls sigpause(2) to wait for
	a signal.  Since SIGCLD is a persistent condition, rather
	than an event, a good init will set a flag in the handler
	instead of trying to reap children there (a volatile one if
	you are using an ANSI compiler and don't want it to overly
	ambitiously optimize the flag variable away).  This is
	needed because you can only know that "one or more SIGCLD's
	have been recieved".  Signals are not events.  A good SVR4
	"port monitor" (basically an init for a single class of
	daemons) will use this same technique, as will a good state
	automaton implementing logins for many X terminals via a
	single "xdm" type program.

9)	The main loop wakes up on the signal.  If the SIGHUP flag is
	set, it checks the configuration file (usually by timestamp
	via "stat") to see if it has changed -- or just assumes it
	has, if the programmer is lazy -- and "does the right thing".
	If the SIGCHLD flag is set, it calls a system call in the
	wait(2) family; generally, wait4().  It uses one of the
	interfaces which can be called in non-blocking mode (WNOHANG)
	so that it can call the function iteratively, since all it
	knows is that "one or more child processes have exited", and
	can not know the exact number (signals are persistent conditions,
	not events; I harp on this because too many programmers try to
	write code instead of setting flags in signal handlers).

10)	When all signals have been processed, and all processes no
	longer in the config file killed, and all new processes now
	in the config file started, then init goes back to #8.  It
	does this for as long as the system is up.

11)	Depending on implementation, the init may or may not run
	a shutdown script.  Typically, if it does, then a user space
	shutdown command will, instead of calling a system call, send
	init a SIGTERM.  This means catching SIGTERM (see #7, above).

12)	The init may use a shutdown script to make an /etc/nologin file,
	which will be printed to the user by the login (or getty) program
	after they type their name and after/before (respectively) the
	login program has been exec'ed by getty.  In BSD, the shutdown
	program does this.  It is not a system call, and expects init
	to shutdown the system safely from SIGTERM.  You can choose to
	talk to your init program however you like, with or without
	running the standard shutdown program.  The BSD shutdown will
	set nologin kill SIGTERM all processes, then kill -9 them
	after waiting for them to dies.  Then it exec's the "halt"
	or "reboot" programs.  If you do this yourself, you should
	look at their source code to see the system calls involved.

13)	The SVR4 init signals the initd, which then shuts things
	down.  Effectively, "shutting down" is just another "run level".
	The SVR4 initd can do this gracefully because they implement
	tty revocation and process group propagation of SIGHUP as a
	result of the controlling tty being revoked from the process
	greoup leader.  BSD doesn't do this, so its shutdown process
	has to be vastly more complicated (BSD misinterprets POSIX here,
	IMO).


> Basically, I want it to mount local disks and start a few shells on
> vty's.

Most likely you really want to modify the program you run in /etc/ttys,
or if you want getty to configure the terminals for you, then you need
to modify /etc/gettytab to set the "lo" program.  You can change the
login prompt to something other than "login:" in /etc/gettytab as
well.  If you do not want to do this, you will need to manage setting
up the tty's yourself (for example, if you wanted to automatically
start an accounting program on the accountant desk, or a video rental
POS program at the video counter, etc.).  If you can live with a
little delay, it's best to get getty to do this for you, you can change
the login prompt to "hit return to start" or something similar, and
still use getty.  This will be generally acceptable, unless what you
want to run takes a long time to start.

This will leave the mount and other code in the /etc/rc file intact.
If you want to stop network services, etc., you can njust not configure
them.  Otherwise, if yyou want to delete programs for a smaller footprint,
you can just rewrite the /etc/rc file to contain just the mount commands
and other commands you want.


> Thus far I managed to get "panic: init died" from my first (and probably
> too naive) tests...

See above; this is probably because you liked the thing against a shared
libc.  8-).

> Can you give me some advice (except 'man init')? Perhaps some pointers to
> a code examples...

The above should be enough to let a reasonably industrious person do the
job in a relatively short period of time.  As I pointed out, though, I
think you may be barking up the tree of the wrong program.  8-).


					Terry Lambert
					terry@lambert.org
---
Any opinions in this posting are my own and not those of my present
or previous employers.



Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?199802050841.BAA13172>