Changing Your Roots or How to Switch to a New Root-fs in a Live Linux System
Author
This document was written by Detlev Zundel (dzu {at} denx {dot} de).
Context
A common problem in embedded devices is to update the firmware on the
device itself. In a GNU/Linux system this also includes updating the
root filesystem used. There are several possibilities to do this
depending on the type and storage location of it. As updating a "hot"
filesystem is rather problematic, many solutions use two separate
storage areas together with a technique resembling the well known
"pointer flip" in programming languages. Usually toggling between the
file systems involves rebooting the Linux system, rendering the device
non-functional for some time.
This application note shows one Linux specific approach to minimize
this interval. As always, it is very likely that other approaches
will have comparable results.
Theory of operation
In order to do the "root flip" multiple times (including unmounting
the old root)_all_ references to the old root filesystem must be
freed. Minimally this means that all applications need to be stopped
(they will have at least a
cwd
on the old root) and restarted later,
which is rather easily accomplished. The hard part will be to get
init
, the ancestor of all Unix processes, to free all references to
the old root filesystem, including its
cwd
and its connections to
/dev/console
on
stdin
,
stdout
and
stderr
.
The neccessary help from the Linux kernel is offered by the
pivot_root(2)
system call:
pivot_root() moves the root file system of the calling process to the
directory put_old and makes new_root the new root file system of the
calling process.
Usually this system call is used in the following way (taken from
pivot_root(8)
man page):
cd new_root
pivot_root . put_old
exec chroot . command
Note, that this of course only works if the commands are really
executed from the init process. This now poses the question on how to
use it in the (embedded) common case of
busybox
[1] being the init
process. Studying the source code, we find that this case was
properly anticipated and can be handled elegantly with a small
addition to
/etc/inittab
, namely
~ # grep restart /etc/inittab
/dev/console::restart:/sbin/init
~ #
This action instructs
init
from
busybox
to reopen
/dev/console
and do an
exec(2)
of
/sbin/init
on reception of
SIGHUP
or
SIGQUIT
. This is exactly what we need, so let's see how this works
in practice.
For the following demo, we have two (jffs2) root filesystems on
/dev/mtdblock4 and /dev/mtdblock5. Currently the former is the active
root. The respective
/etc/inittab
files include the entry above and
respawn
/bin/application
printing a short message what root
filesystem is currently active. Furthermore we have
/new-root
and
/new-root/old-root
directories in both file systems.
~ # mount -t jffs2 /dev/mtdblock5 /new-root
~ # cd /new-root/
/new-root # pivot_root . old-root
~ # umount /old-root/proc
~ # chroot . kill -QUIT 1
The system is going down NOW!
Sending SIGTERM to all processes
init started: BusyBox v1.7.1 (2008-04-01 21:48:01 MEST)
starting pid 97, tty '': '/etc/rc.sh'
starting pid 102, tty '': '/bin/application'
### Application running ... (mtd5)
starting pid 108, tty '': '/bin/sh'
~ # ls -l /proc/1/fd
lrwx------ 1 root root 64 Sep 15 23:30 0 -> /dev/console
lrwx------ 1 root root 64 Sep 15 23:30 1 -> /dev/console
lrwx------ 1 root root 64 Sep 15 23:30 2 -> /dev/console
~ # mount
rootfs on / type rootfs (rw)
/dev/mtdblock4 on /old-root type jffs2 (rw)
/dev/mtdblock5 on / type jffs2 (rw)
proc on /proc type proc (rw)
~ # umount /old-root
~ # process '/bin/application' (pid 102) exited. Scheduling it for restart.
starting pid 112, tty '': '/bin/application'
### Application running ... (mtd5)
As we can see, after sending the signal, init properly shuts down and
restarts from the new root filesystem. Note that we manually
unmounted /proc from the old root-filesystem.
[1] For this demo, busybox version 1.7.1 from
ELDK 4.2 was used.