user/init.c
About this file
/init is the first user program, process 1. The kernel creates its process in
userinit and loads this program into it in forkret
(kernel/proc.c:532); every other process descends from it.
It has two jobs:
- Give the system a console. It opens the console device as
descriptors 0, 1 and 2 (standard input, output and error).
Every later process inherits these through
fork, which is why programs can print without opening anything. - Keep a shell running and reap orphans. It starts
/sh, then waits forever. When the shell exits it starts a new one. Meanwhile itswaitalso collects every process whose parent died first, which the kernel hands toinit(reparent). Without this, such processes would stay zombies forever.
init must never exit: kexit panics the whole kernel if it tries.
Read before: kernel/proc.c (userinit, kwait,
reparent). Read next: user/sh.c.
Headers
The only kernel definition this program needs is CONSOLE, the console’s device
number, from kernel/file.h. That header also defines struct inode, which uses
struct sleeplock and NDIRECT, so kernel/spinlock.h,
kernel/sleeplock.h and kernel/fs.h must be included before it to make it
compile. kernel/fcntl.h supplies O_RDWR, and user/user.h the system calls.
kernel/stat.h is not used by this file’s own code.
Arguments for the shell
The argc and argv (program arguments) array passed to exec: one argument, the program name sh, then
the null pointer that ends every argv.
Open the console as descriptors 0, 1 and 2
The first process starts with no open files at all. The console is reached through
a device file named console in the root directory: an inode of type
T_DEVICE whose major number CONSOLE tells the kernel to send read and
write to consoleread and consolewrite (devsw).
mkfs does not create that file, so on the first boot of a fresh fs.img the
open fails. mknod then creates it (sys_mknod), and the second
open succeeds. The file stays in fs.img, so later boots find it.
Since no descriptor is open yet, open returns 0, the lowest free one. Each dup
then takes the next lowest: 1, then 2. All three now refer to the same
open file (struct file), readable and writable. Every process forked from here on inherits
them (kfork), and exec keeps them, which is how every program in xv6
gets a console for standard input, output and error.
Try to open the console device for reading and writing. With no descriptors open, success returns 0.
The device file is missing: create /console as a device with major number
CONSOLE (1) and minor number 0.
Open it again; this time it exists. The result, descriptor 0, is not checked.
Make descriptor 1 (standard output) refer to the console too.
And descriptor 2 (standard error).
Start a shell
The outer loop runs once per shell. printf writes to descriptor 1, the console
just set up; this is the init: starting sh line you see after boot.
Then the usual fork and exec pattern. The child replaces itself with the shell.
The path sh has no leading /, so it is looked up in the current directory, which
for this process is / (userinit set it, and fork copies it).
If fork or exec fails, init (or its child) gives up with exit(1). For the
child that is harmless: init will see it exit and start another. For init itself
it is fatal: kexit calls panic with init exiting
(kernel/proc.c:331), because there would be no process left to adopt orphans.
Create the child that will become the shell (fork → sys_fork →
kfork).
Fork failed. init exiting makes the kernel panic, so this ends the system.
In the child: become the shell. On success exec never returns.
exec failed (for example, no sh in the file system): this child exits, and init
tries again.
Wait forever, reaping the shell and orphans
wait (kwait) sleeps until any child of init exits, frees that
child’s process-table slot, and returns its PID (process ID). init’s children are not only
the shells it started. When any process exits while its own children are still
around, reparent makes init their parent and wakes init
(kernel/proc.c:317). A background job (&) started by the shell is the
everyday example: the shell’s helper process exits at once, leaving the job to
init.
So this loop sees two kinds of exits:
- The shell. For example after Ctrl-D at the prompt. The inner loop breaks, and the outer loop starts a new shell.
- An adopted orphan. Nothing to do;
waithas already freed its slot, which is the whole point. The loop waits again.
wait returns -1 only if init has no children (impossible while a shell runs) or
has been killed. The error branch calls exit, which panics the kernel. Typing
kill 1 at the shell prompt does crash xv6 with panic: init exiting (tested in
QEMU); the kernel notices the kill on its way back to user space, before init
gets to print the error message.
Sleep until any child exits; collect it and get its PID. The (int *)0 says the exit
status is not wanted.
The shell itself exited: leave the inner loop so the outer loop starts a new one.
No children, or init was killed. Exiting here panics the kernel.
An orphan adopted from an exited parent has been reaped. Nothing more to do.