Skip to main content.

Changing Your Roots or How to Switch to a New Root-fs in a Live Linux System


This document was written by Detlev Zundel (dzu {at} denx {dot} de).


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
~ # 

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/'
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.