Skip to content

Commit

Permalink
init/initramfs.c: allow asynchronous unpacking
Browse files Browse the repository at this point in the history
This is primarily motivated by an embedded ppc target, where unpacking
even the rather modest sized initramfs takes 0.6 seconds, which is
long enough that the external watchdog becomes unhappy that it doesn't
get enough attention soon enough.

But normal desktops might benefit as well. On my mostly stock Ubuntu
kernel, my initramfs is a 26M xz-compressed blob, decompressing to
around 126M. That takes almost two seconds.

So add an initramfs_async= kernel parameter, allowing the main init
process to proceed to handling device_initcall()s without waiting for
populate_rootfs() to finish.

Should one of those initcalls need something from the initramfs (say,
a kernel module or a firmware blob), it will simply wait for the
initramfs unpacking to be done before proceeding, which should in
theory make this completely safe to always enable. But if some driver
pokes around in the filesystem directly and not via one of the
official kernel interfaces (i.e. request_firmware*(),
call_usermodehelper*) that theory may not hold - also, I certainly
might have missed a spot when sprinkling wait_for_initramfs().

Signed-off-by: Rasmus Villemoes <linux@rasmusvillemoes.dk>
  • Loading branch information
Villemoes authored and intel-lab-lkp committed Feb 24, 2021
1 parent c03c21b commit e7d2b5f
Show file tree
Hide file tree
Showing 7 changed files with 84 additions and 1 deletion.
12 changes: 12 additions & 0 deletions Documentation/admin-guide/kernel-parameters.txt
Expand Up @@ -1820,6 +1820,18 @@
initcall functions. Useful for debugging built-in
modules and initcalls.

initramfs_async= [KNL] Normally, the initramfs image is
unpacked synchronously, before most devices
are initialized. When the initramfs is huge,
or on slow CPUs, this can take a significant
amount of time. Setting this option means the
unpacking is instead done in a background
thread, allowing the main init process to
begin calling device_initcall()s while the
initramfs is being unpacked.
Format: <bool>
Default set by CONFIG_INITRAMFS_ASYNC.

initrd= [BOOT] Specify the location of the initial ramdisk

initrdmem= [KNL] Specify a physical address and size from which to
Expand Down
2 changes: 2 additions & 0 deletions drivers/base/firmware_loader/main.c
Expand Up @@ -15,6 +15,7 @@
#include <linux/kernel_read_file.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/initrd.h>
#include <linux/timer.h>
#include <linux/vmalloc.h>
#include <linux/interrupt.h>
Expand Down Expand Up @@ -504,6 +505,7 @@ fw_get_filesystem_firmware(struct device *device, struct fw_priv *fw_priv,
if (!path)
return -ENOMEM;

wait_for_initramfs();
for (i = 0; i < ARRAY_SIZE(fw_path); i++) {
size_t file_size = 0;
size_t *file_size_ptr = NULL;
Expand Down
7 changes: 7 additions & 0 deletions include/linux/initrd.h
Expand Up @@ -24,3 +24,10 @@ extern char __initramfs_start[];
extern unsigned long __initramfs_size;

void console_on_rootfs(void);

#ifdef CONFIG_BLK_DEV_INITRD
extern void _wait_for_initramfs(const char *caller);
#define wait_for_initramfs() _wait_for_initramfs(__func__)
#else
static inline void wait_for_initramfs(void) { }
#endif
51 changes: 50 additions & 1 deletion init/initramfs.c
Expand Up @@ -530,6 +530,43 @@ static int __init keepinitrd_setup(char *__unused)
__setup("keepinitrd", keepinitrd_setup);
#endif

static bool __initdata initramfs_async = CONFIG_INITRAMFS_ASYNC;
static int __init initramfs_async_setup(char *str)
{
strtobool(str, &initramfs_async);
return 1;
}
__setup("initramfs_async=", initramfs_async_setup);

static __initdata struct work_struct initramfs_wrk;
static DECLARE_COMPLETION(initramfs_done);
static bool initramfs_unpack_started;

void _wait_for_initramfs(const char *caller)
{
unsigned long start;

if (try_wait_for_completion(&initramfs_done))
return;
if (!initramfs_unpack_started) {
/*
* Something before rootfs_initcall wants to access
* the filesystem/initramfs. Probably a bug. Make a
* note, avoid deadlocking the machine, and let the
* caller's access fail as it used to.
*/
pr_warn_once("wait_for_initramfs() called by %s"
" before rootfs_initcalls\n", caller);
return;
}

start = jiffies;
wait_for_completion(&initramfs_done);
pr_info("%s() waited %lu jiffies for initramfs_done\n",
caller, jiffies - start);
}
EXPORT_SYMBOL_GPL(_wait_for_initramfs);

extern char __initramfs_start[];
extern unsigned long __initramfs_size;
#include <linux/initrd.h>
Expand Down Expand Up @@ -602,7 +639,7 @@ static void __init populate_initrd_image(char *err)
}
#endif /* CONFIG_BLK_DEV_RAM */

static int __init populate_rootfs(void)
static void __init do_populate_rootfs(struct work_struct *w)
{
/* Load the built in initramfs */
char *err = unpack_to_rootfs(__initramfs_start, __initramfs_size);
Expand Down Expand Up @@ -637,6 +674,18 @@ static int __init populate_rootfs(void)
initrd_end = 0;

flush_delayed_fput();
complete_all(&initramfs_done);
}

static int __init populate_rootfs(void)
{
initramfs_unpack_started = true;
if (initramfs_async) {
INIT_WORK(&initramfs_wrk, do_populate_rootfs);
schedule_work(&initramfs_wrk);
} else {
do_populate_rootfs(NULL);
}
return 0;
}
rootfs_initcall(populate_rootfs);
1 change: 1 addition & 0 deletions init/main.c
Expand Up @@ -1534,6 +1534,7 @@ static noinline void __init kernel_init_freeable(void)

kunit_run_all_tests();

wait_for_initramfs();
console_on_rootfs();

/*
Expand Down
2 changes: 2 additions & 0 deletions kernel/umh.c
Expand Up @@ -27,6 +27,7 @@
#include <linux/ptrace.h>
#include <linux/async.h>
#include <linux/uaccess.h>
#include <linux/initrd.h>

#include <trace/events/module.h>

Expand Down Expand Up @@ -107,6 +108,7 @@ static int call_usermodehelper_exec_async(void *data)

commit_creds(new);

wait_for_initramfs();
retval = kernel_execve(sub_info->path,
(const char *const *)sub_info->argv,
(const char *const *)sub_info->envp);
Expand Down
10 changes: 10 additions & 0 deletions usr/Kconfig
Expand Up @@ -32,6 +32,16 @@ config INITRAMFS_FORCE
and is useful if you cannot or don't want to change the image
your bootloader passes to the kernel.

config INITRAMFS_ASYNC
bool "Unpack initramfs asynchronously"
help
This option sets the default value of the initramfs_async=
command line parameter. If that parameter is set, unpacking
of initramfs (both the builtin and one passed from a
bootloader) is done asynchronously. See
<file:Documentation/admin-guide/kernel-parameters.txt> for
details.

config INITRAMFS_ROOT_UID
int "User ID to map to 0 (user root)"
depends on INITRAMFS_SOURCE!=""
Expand Down

0 comments on commit e7d2b5f

Please sign in to comment.