Skip to content

Commit

Permalink
Add (optional) support for loading modules
Browse files Browse the repository at this point in the history
Add support for loading modules in tiny-initramfs. The support is
extremely simple, it just looks for a file /modules in the initramfs
image, where each line should be the absolute path (within the
initramfs image) to the module file name, followed optionally by a
space and the options.

No dependency resolution is performed, the modules are loaded in the
order they are specified in the /modules file. The construction of the
initramfs image should take care of the proper order of the modules and
adding all required dependencies.

If a module cannot be loaded, an error message will be shown, but
tiny-initramfs will still try to mount the root file system.
  • Loading branch information
chris-se committed Jan 23, 2016
1 parent d1d07f5 commit 8e2a2fa
Show file tree
Hide file tree
Showing 4 changed files with 233 additions and 28 deletions.
111 changes: 83 additions & 28 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,42 +54,38 @@ Features
(as documented in the kernel documentation). The network
configuration needs to be specified via the `ip=` kernel command
line parameter.
* Very trivial module loading support (**no** automatic dependency
resolution).

On a x86_64 system with the default `-Og` compiler flags, statically
linked with the binary stripped, and the resulting initramfs compressed
with `gzip -9`, the images produced have the following size, depending
on the compiled-in options:

| UUID | NFS4 | DEBUG | `initrd.img` size (bytes) |
| ---- | ---- | ----- | ------------------------- |
| N | N | N | 8996 |
| N | N | Y | 9432 |
| Y | N | N | 10347 |
| Y | N | Y | 10820 |
| N | Y | N | 12267 |
| N | Y | Y | 12767 |
| Y | Y | N | 13542 |
| Y | Y | Y | 14033 |

In all cases here, dietlibc 0.33~cvs20120325-6 was used.

The size of an initramfs using `tiny-initramfs` is thus about 16 KiB if
one doesn't use glibc.
When compiled on a x86_64 system with the default `-Og` compiler flags,
statically linked against dietlibc 0.33~cvs20120325-6, the binary
stripped and the resulting initramfs (without any modules added)
compressed with `gzip -9`, the images produced are between 9 kiB and
14 kiB, depending on the feature set selected.

Using musl instead of dietlibc adds between 1.8 and 2.4 kiB to the
resulting `initrd.img` size (depending on the feature set).

Using glibc instead of dietlibc adds around 310 kiB to the resulting
initrd image.
initrd image and is not recommended (although it will work).

Adding modules to the initramfs will increase the size, and many block
device and file system drivers are 100s of kiB in size. On the other
hand, the kernel would be larger if they were compiled in, so the
actual amount of space lost due to using modules is quite a bit
smaller.

Requirements
------------

* The kernel must have the necessary block device drivers built-in
that are required to access the root and `/usr` file systems.
**Warning:** this is not true for most default kernels of mainstream
distributions, as they require a full initramfs to load the modules
required to mount the root file system.
* The kernel should have the necessary block device and file system
drivers built-in that are required to access the root and `/usr`
file systems. **Warning:** this is not true for most default kernels
of mainstream distributions, as they require a full initramfs to
load the modules required to mount the root file system.
* If the necessary drivers are not built into the kernel, there is
limited support for loading modules from within the initramfs, see
below for details.
* The kernel must have `CONFIG_DEVTMPFS` built-in, because this
implementation assumes the kernel will just create the devices by
itself. (This is true for most distribution kernels.)
Expand All @@ -107,8 +103,6 @@ When not to use
udev (such as `/dev/disk/by-label/...`). Only the kernel names
themselves, such as `/dev/sda1`, as well as `UUID=` and hexadecimal
device numbers (`0xMAJMIN`, e.g. `0x801`) are supported.
* No modules can be loaded in the initramfs, everything that's
required needs to be compiled in.
* NFSv2/NFSv3 are currently not supported.
* When booting from USB storage you should always use `UUID=`, because
device names are not necessarily stable.
Expand Down Expand Up @@ -239,6 +233,67 @@ be used to boot the system. Note that as of now there is no integration
with distributions, so configuring the boot loader etc. has to be done
manually.

Support for loading modules
---------------------------

There is limited support for loading modules if `--enable-modules` is
specified during the `configure` invocation. To use this feature, one
needs to create a file `/modules` in the initramfs image that is of the
following format:

/file.ko options

The modules should not be in a sub-directory, because the directory
containing them will not be cleaned-up by tiny-initramfs after mounting
the root file system. (Loading the modules will work though.)

For example, the virtio block device driver `virtio_blk` requires some
additional modules to work. Using `modprobe` one may find out which:

$ /sbin/modprobe --all --ignore-install --quiet --show-depends virtio_blk
insmod /lib/modules/[...]/kernel/drivers/virtio/virtio.ko
insmod /lib/modules/[...]/kernel/drivers/virtio/virtio_ring.ko
insmod /lib/modules/[...]/kernel/drivers/block/virtio_blk.ko

It turns out that this is not quite sufficient, because the
`virtio_pci` driver is also required for `virtio_blk` to work (the
driver loads without `virtio_pci`, but doesn't work), so one may use:

$ /sbin/modprobe --all --ignore-install --quiet --show-depends virtio_blk virtio_pci
insmod /lib/modules/3.16.0-4-amd64/kernel/drivers/virtio/virtio.ko
insmod /lib/modules/3.16.0-4-amd64/kernel/drivers/virtio/virtio_ring.ko
insmod /lib/modules/3.16.0-4-amd64/kernel/drivers/block/virtio_blk.ko
insmod /lib/modules/3.16.0-4-amd64/kernel/drivers/virtio/virtio.ko
insmod /lib/modules/3.16.0-4-amd64/kernel/drivers/virtio/virtio_ring.ko
insmod /lib/modules/3.16.0-4-amd64/kernel/drivers/virtio/virtio_pci.ko

(Note that in case soft dependencies are treated via `install` lines,
these have to be resolved manually. This is typically not the case for
drivers needed within initramfs, because other implementations also
suffer from the same issue. `install` is deprecated anyway according to
the manual page of `modprobe.d`.)

One may then copy these drivers to the initramfs image, and then add
a module file with the following contents:

/virtio.ko
/virtio_ring.ko
/virtio_blk.ko
/virtio.ko
/virtio_ring.ko
/virtio_pci.ko

The order is important, because dependency resolution is **not**
performed by tiny-initramfs, it has to be done while creating the
initramfs image. Duplicate entries are not a problem, because they will
silently be ignored (but you may remove duplicate entries if you don't
change the order otherwise).

Options may be specified when followed by a space (tab characters not
supported), for example:

/libata.ko noacpi

Debugging
---------

Expand Down
21 changes: 21 additions & 0 deletions configure.ac
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,27 @@ AS_IF([test x"$enable_nfs4" = x"yes"], [AC_DEFINE([ENABLE_NFS4], [1], [Define if
AM_CONDITIONAL([ENABLE_NFS4], [test x"$enable_nfs4" = x"yes"])
AC_ARG_ENABLE([debug], [AS_HELP_STRING([--enable-debug], [Enable support for printing debug messages])])
AS_IF([test x"$enable_debug" = x"yes"], [AC_DEFINE([ENABLE_DEBUG], [1], [Define if debug messages are enabled])])
AC_ARG_ENABLE([modules], [AS_HELP_STRING([--enable-modules], [Enable support for loading modules (default is no)])])
AS_IF([test x"$enable_modules" = x"yes"], [
AC_CHECK_FUNC([finit_module], [AC_DEFINE([HAVE_FINIT_MODULE], [1], [Defined if finit_module is available.])], [])
AC_MSG_CHECKING([for SYS_finit_module])
AC_COMPILE_IFELSE([AC_LANG_SOURCE([[#include <unistd.h>
#include <sys/syscall.h>
int a =
#ifdef SYS_finit_module
42
#endif
;]])],[
ac_cv_have_sys_finit_module=yes
AC_DEFINE([HAVE_SYS_FINIT_MODULE], [1], [Defined if SYS_finit_module is available.])
AC_MSG_RESULT([yes])
], [AC_MSG_RESULT([no])])
AC_CHECK_FUNC([init_module])
AS_IF([test x"$ac_cv_func_init_module" != x"yes" && test x"$ac_cv_have_sys_finit_module" != x"yes" && test x"$ac_cv_func_finit_module" != x"yes"],
[AC_MSG_ERROR([Neither finit_module nor SYS_finit_module nor init_module syscalls available.])
])
AC_DEFINE([ENABLE_MODULES], [1], [Define if support for loading modules is enabled])
])
AC_ARG_WITH([variant-name], [AS_HELP_STRING([--with-variant-name=NAME], [The variant name of the installed binary (default: none)])])
AS_IF([test x"$with_variant_name" != x""], [
AC_DEFINE([VARIANT], ["$with_variant_name"], [The installed binary variant name])
Expand Down
125 changes: 125 additions & 0 deletions tiny_initramfs.c
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,14 @@
#include <errno.h>
#include <string.h>
#include <limits.h>
#if defined(ENABLE_MODULES)
#if !defined(HAVE_FINIT_MODULE) && !defined(HAVE_SYS_FINIT_MODULE)
#include <sys/stat.h>
#include <sys/mman.h>
#elif defined(HAVE_SYS_FINIT_MODULE)
#include <sys/syscall.h>
#endif
#endif

static void parse_cmdline();
static int parse_cmdline_helper(void *data, const char *line, int line_is_incomplete);
Expand All @@ -41,6 +49,13 @@ static void debug_dump_file(const char *fn);
static int debug_dump_file_helper(void *data, const char *line, int line_is_incomplete);
#endif

#ifdef ENABLE_MODULES
static void load_modules();
static int load_module_helper(void *data, const char *line, int line_is_incomplete);
static int cleanup_module_helper(void *data, const char *line, int line_is_incomplete);
extern int init_module(void *module_image, unsigned long len, const char *param_values);
#endif

static char root_device[MAX_PATH_LEN];
static char root_options[MAX_LINE_LEN];
#ifdef ENABLE_NFS4
Expand Down Expand Up @@ -68,6 +83,13 @@ int main(int argc, char **argv)
warn("Begun execution", NULL);
#endif

#ifdef ENABLE_MODULES
load_modules();
#ifdef ENABLE_DEBUG
warn("Loaded all kernel moduels", NULL);
#endif
#endif /* defined(ENABLE_MODULES) */

r = mount("proc", "/proc", "proc", MS_NODEV | MS_NOEXEC | MS_NOSUID, NULL);
if (r < 0)
panic(errno, "Could not mount /proc", NULL);
Expand Down Expand Up @@ -446,4 +468,107 @@ void cleanup_initramfs()
(void) rmdir("/dev");
(void) rmdir("/proc");
(void) unlink("/init");
#ifdef ENABLE_MODULES
(void) traverse_file_by_line(MODULES_FILE, (traverse_line_t) cleanup_module_helper, NULL);
(void) unlink(MODULES_FILE);
#endif
}

#ifdef ENABLE_MODULES
void load_modules()
{
(void) traverse_file_by_line(MODULES_FILE, (traverse_line_t) load_module_helper, NULL);
}

int load_module_helper(void *data, const char *line, int line_is_incomplete)
{
(void)data;
int r, fd;
char *ptr;
const char *opts;

if (line_is_incomplete)
return 0;

if (!*line)
return 0;

ptr = strchr(line, ' ');
if (ptr) {
*ptr = '\0';
opts = ptr + 1;
} else {
opts = "";
}

#ifdef ENABLE_DEBUG
if (*opts)
warn("Loading kernel module ", line, " (with options: ", opts, ")", NULL);
else
warn("Loading kernel module ", line, NULL);
#endif

fd = open(line, O_RDONLY | O_CLOEXEC);
if (fd < 0) {
warn("Couldn't load ", line, ": ", strerror(errno), NULL);
return 0;
}
#if defined(HAVE_FINIT_MODULE)
r = finit_module(fd, opts, 0);
if (r < 0)
r = -errno;
#elif defined(HAVE_SYS_FINIT_MODULE)
r = syscall(SYS_finit_module, fd, opts, 0);
if (r < 0)
r = -errno;
#else
{
void *contents;
struct stat st;

r = fstat(fd, &st);
if (r < 0) {
warn("Couldn't stat ", line, ": ", strerror(errno), NULL);
close(fd);
return 0;
}

contents = mmap(NULL, (size_t) st.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
if (!contents) {
warn("Couldn't mmap ", line, ": ", strerror(errno), NULL);
close(fd);
return 0;
}
r = init_module(contents, (unsigned long) st.st_size, opts);
if (r < 0)
r = -errno;
munmap(contents, (size_t) st.st_size);
}
#endif

/* Ignore duplicate modules, this simplifies initramfs creation logic
* a bit. */
if (r < 0 && r != -EEXIST)
warn("Couldn't load ", line, ": ", strerror(-r), NULL);

close(fd);

return 0;
}

int cleanup_module_helper(void *data, const char *line, int line_is_incomplete)
{
(void)data;
char *ptr;

if (line_is_incomplete)
return 0;

ptr = strchr(line, ' ');
if (ptr)
*ptr = '\0';

(void) unlink(line);
return 0;
}
#endif
4 changes: 4 additions & 0 deletions tiny_initramfs.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@
#define MAX_PATH_LEN 1024
#endif

#ifndef MODULES_FILE
#define MODULES_FILE "/modules"
#endif

#ifndef TARGET_DIRECTORY
#define TARGET_DIRECTORY "/target"
#endif
Expand Down

0 comments on commit 8e2a2fa

Please sign in to comment.