内核启动后需要挂载根文件系统,找到并执行init程序。在调试适配阶段,我们甚至还没有实现各种存储设备的驱动,此时也没法从对应的存储设备挂载文件系统找到init执行。此时我们可以使用initramfs,直接将简单的根文件系统打包压缩嵌入到内核中,启动时解压解包文件到rootfs中,此时就可以找到对应的init执行,后续再加载存储设备驱动,挂载实际的根文件系统,并切换过去。
整个过程涉及如下几个主要过程
(1)挂载rootfs文件系统
(2)解压解包rootfs.cpio.gz中文件到rootfs
(3)找到rootfs中的/init执行
当然要使用initramfs还需要一些配置等,下面就分享下相关内容。
需要配置以下选项
BLK_DEV_INITRD 配置使能支持initramfs/initrd
INITRAMFS_SOURCE 配置rootfs.cpio.gz源文件的路径
INITRAMFS_COMPRESSION_GZIP 配置使用gzip压缩INITRAMFS,依赖RD_ZIP,当然也可以使用其他压缩方式这里以gzip为例,其他类似。
对应配置如下
init/Kconfig中
config BLK_DEV_INITRD
bool "Initial RAM filesystem and RAM disk (initramfs/initrd) support"
help
The initial RAM filesystem is a ramfs which is loaded by the
boot loader (loadlin or lilo) and that is mounted as root
before the normal boot procedure. It is typically used to
load modules needed to mount the "real" root file system,
etc. See <file:Documentation/admin-guide/initrd.rst> for details.
If RAM disk support (BLK_DEV_RAM) is also included, this
also enables initial RAM disk (initrd) support and adds
15 Kbytes (more on some other architectures) to the kernel size.
If unsure say Y.
usr/Kconfig中INITRAMFS_SOURCE配置
config INITRAMFS_SOURCE
string "Initramfs source file(s)"
default ""
help
This can be either a single cpio archive with a .cpio suffix or a
space-separated list of directories and files for building the
initramfs image. A cpio archive should contain a filesystem archive
to be used as an initramfs image. Directories should contain a
filesystem layout to be included in the initramfs image. Files
should contain entries according to the format described by the
"usr/gen_init_cpio" program in the kernel tree.
When multiple directories and files are specified then the
initramfs image will be the aggregate of all of them.
See <file:Documentation/driver-api/early-userspace/early_userspace_support.rst> for more details.
If you are not sure, leave it blank.
usr/Kconfig中INITRAMFS_COMPRESSION_GZIP配置
config INITRAMFS_COMPRESSION_GZIP
bool "Gzip"
depends on RD_GZIP
help
Use the old and well tested gzip compression algorithm. Gzip provides
a good balance between compression ratio and decompression speed and
has a reasonable compression speed. It is also more likely to be
supported by your build system as the gzip tool is present by default
on most distros.
config RD_GZIP
bool "Support initial ramdisk/ramfs compressed using gzip"
default y
select DECOMPRESS_GZIP
help
Support loading of a gzip encoded initial ramdisk or cpio buffer.
If unsure, say Y.
menuconfig配置
General setup --->
[*] Initial RAM filesystem and RAM disk (initramfs/initrd) support
(images/rootfs.cpio.gz)Initramfs source file(s)
[*] Support initial ramdisk/ramfs compressed using gzip
Built-in initramfs compression mode (Gzip) --->
usr/Makefile中
obj-$(CONFIG_BLK_DEV_INITRD) := initramfs_data.o
$(obj)/initramfs_data.o: $(obj)/initramfs_inc_data
ramfs-input := $(strip $(shell echo $(CONFIG_INITRAMFS_SOURCE)))
如果SOURCE指定的是压缩文件直接使用
# If CONFIG_INITRAMFS_SOURCE specifies a single file, and it is suffixed with
# .cpio.*, use it directly as an initramfs, and avoid double compression.
ifeq ($(words $(subst .cpio.,$(space),$(ramfs-input))),2)
cpio-data := $(ramfs-input)
compress-y := shipped
endif
initramfs_inc_data依赖
$(obj)/initramfs_inc_data: $(cpio-data) FORCE
$(call if_changed,$(compress-y))
targets += initramfs_data.cpio initramfs_inc_data
output/usr/.initramfs_inc_data.cmd中
将rootfs.cpio.gz复制为initramfs_inc_data
cmd_usr/initramfs_inc_data := cat /home/qinyunti/sdk/buildroot/enhanced_5.10_glibc_br_defconfig/images/rootfs.cpio.gz > usr/initramfs_inc_data
usr/initramfs_data.S中
将usr/initramfs_inc_data链接进Vmlinux.
放在段.init.ramfs中
__initramfs_size处放置大小,放在段.init.ramfs.info中
.section .init.ramfs,"a"
__irf_start:
.incbin "usr/initramfs_inc_data"
__irf_end:
.section .init.ramfs.info,"a"
.globl __initramfs_size
__initramfs_size:
#ifdef CONFIG_64BIT
.quad __irf_end - __irf_start
#else
.long __irf_end - __irf_start
#endif
include/asm-generic/vmlinux.lds.h中
. = ALIGN(4); \
__initramfs_start = .; \
KEEP(*(.init.ramfs)) \
. = ALIGN(8); \
KEEP(*(.init.ramfs.info))
arch/um/include/asm/common.lds.S中
放在了镜像最后
. = ALIGN(4096);
.init.ramfs : {
INIT_RAM_FS
}
可以通过__initramfs_start知道其位置,__initramfs_size知道其大小。
(gdb) p &__irf_end
$7 = (no debug info> *) 0xffffffe0012a0174
(gdb) p &__irf_start
$8 = (no debug info> *) 0xffffffe000890788
(gdb) p &__irf_end-&__irf_start
warning: Type size unknown, assuming 1. Try casting to a known type, or void *.
$9 = 10549740
(gdb)
(gdb) p &__initramfs_start
$10 = (char (*)[]) 0xffffffe000890788
(gdb)
(gdb) p &__initramfs_size
$11 = (unsigned long *) 0xffffffe0012a0178
(gdb)
(gdb) p __initramfs_size
$2 = 10549740
(gdb)
3.1rootfs挂载
Breakpoint 1, init_mount_tree () at ../fs/namespace.c:3791
␦␦/home/qinyunti/sdk/linux/linux-custom/fs/namespace.c:3791:93171:beg:0xffffffe0000130d0
(gdb) bt
#0 init_mount_tree () at ../fs/namespace.c:3791
#1 mnt_init () at ../fs/namespace.c:3846
#2 0xffffffe000012da0 in vfs_caches_init () at ../fs/dcache.c:3234
#3 0xffffffe000002bee in start_kernel () at ../init/main.c:1041
#4 0xffffffe000002092 in _start_kernel () at ../arch/riscv/kernel/head.S:281
Backtrace stopped: frame did not save the PC
(gdb)
linux/linux-custom/init/initramfs.c中
populate_rootfs
(gdb) x /64xb 0xffffffe000890788
0xffffffe000890788: 0x1f 0x8b 0x08 0x00 0x00 0x00 0x00 0x00
0xffffffe000890790: 0x02 0x03 0xbc 0x9a 0x79 0x9c 0xcd 0x65
0xffffffe000890798: 0xfb 0xc7 0xbf 0xe7 0xcc 0x99 0x31 0x26
0xffffffe0008907a0: 0x71 0xac 0xd9 0x1d 0x8c 0xa5 0x6c 0x67
0xffffffe0008907a8: 0x06 0x63 0x94 0x98 0x19 0x93 0x2d 0x24
0xffffffe0008907b0: 0x14 0x92 0xec 0x45 0x59 0xca 0x4e 0xdb
0xffffffe0008907b8: 0x58 0xaa 0x21 0x4a 0x85 0x94 0x16 0xa1
0xffffffe0008907c0: 0x85 0x52 0x48 0x7b 0x09 0x6d 0xb4 0x29
(gdb)
hexdump images/rootfs.cpio.gz -n 64
0000000 8b1f 0008 0000 0000 0302 9abc 9c79 65cd
0000010 c7fb e7bf 99cc 2631 ac71 1dd9 a58c 676c
0000020 6306 9894 9319 242d 9214 45ec ca59 db4e
0000030 aa58 4a21 9485 a116 5285 7b48 6d09 29b4
0000040
执行路径
(gdb) hb populate_rootfs
Hardware assisted breakpoint 1 at 0xffffffe000004c58: file ../init/initramfs.c, line 606.
(gdb) c
Continuing.
Breakpoint 1, populate_rootfs () at ../init/initramfs.c:606
␦␦/home/qinyunti/sdk/linux/linux-custom/init/initramfs.c:606:13203:beg:0xffffffe000004c58
(gdb) bt
#0 populate_rootfs () at ../init/initramfs.c:606
#1 0xffffffe000200762 in do_one_initcall (fn=0xffffffe000004c58
) at ../init/main.c:1217 #2 0xffffffe000002e18 in do_initcall_level (command_line=0xffffffe001e05800 "earlycon", level=5) at ../init/main.c:1290
#3 do_initcalls () at ../init/main.c:1306
#4 do_basic_setup () at ../init/main.c:1326
#5 kernel_init_freeable () at ../init/main.c:1526
#6 0xffffffe0008743d6 in kernel_init (unused=
) at ../init/main.c:1415 #7 0xffffffe000201b5e in handle_exception () at ../arch/riscv/kernel/entry.S:243
Backtrace stopped: frame did not save the PC
(gdb)
(gdb) hb rest_init
Hardware assisted breakpoint 1 at 0xffffffe0008742d2: file ../init/main.c, line 681.
(gdb) c
Continuing.
Breakpoint 1, rest_init () at ../init/main.c:681
␦␦/home/qinyunti/sdk/linux/linux-custom/init/main.c:681:16754:beg:0xffffffe0008742d2
(gdb) bt
#0 rest_init () at ../init/main.c:681
#1 0xffffffe0000026e4 in arch_call_rest_init () at ../init/main.c:845
#2 0xffffffe000002c12 in start_kernel () at ../init/main.c:1061
#3 0xffffffe000002092 in _start_kernel () at ../arch/riscv/kernel/head.S:281
Backtrace stopped: frame did not save the PC
(gdb)
下面分析整个执行过程
populate_rootfs
include/linux/init.h中
static initcall_t __initcall_
__attribute__((__section__(
rootfs_initcall(populate_rootfs);
展开为
static initcall_t __initcall_populate_rootfsrootfs __used \
__attribute__((__section__(".initcallrootfs.init"))) = populate_rootfs;
include/linux/init.h中
typedef int (*initcall_t)(void);
即定义了一个函数类型指针变量__initcall_populate_rootfsrootfs其值为populate_rootfs,
放在了段.initcallrootfs.init中
GDB可以直接查看该变量
(gdb) p __initcall_populate_rootfsrootfs
$1 = (initcall_t) 0xffffffe000004c58
(gdb)
init/main.c中
static void __init do_initcalls(void)
{
int level;
size_t len = strlen(saved_command_line) + 1;
char *command_line;
command_line = kzalloc(len, GFP_KERNEL);
if (!command_line)
panic("%s: Failed to allocate %zu bytes\n", __func__, len);
for (level = 0; level < ARRAY_SIZE(initcall_levels) - 1; level++) {
/* Parser modifies command_line, restore it each time */
strcpy(command_line, saved_command_line);
do_initcall_level(level, command_line);
}
kfree(command_line);
}
遍历initcall_levels
extern initcall_entry_t __initcall_start[];
extern initcall_entry_t __initcall0_start[];
extern initcall_entry_t __initcall1_start[];
extern initcall_entry_t __initcall2_start[];
extern initcall_entry_t __initcall3_start[];
extern initcall_entry_t __initcall4_start[];
extern initcall_entry_t __initcall5_start[];
extern initcall_entry_t __initcall6_start[];
extern initcall_entry_t __initcall7_start[];
extern initcall_entry_t __initcall_end[];
static initcall_entry_t *initcall_levels[] __initdata = {
__initcall0_start,
__initcall1_start,
__initcall2_start,
__initcall3_start,
__initcall4_start,
__initcall5_start,
__initcall6_start,
__initcall7_start,
__initcall_end,
};
调用do_initcall_level
static void __init do_initcall_level(int level, char *command_line)
{
initcall_entry_t *fn;
parse_args(initcall_level_names[level],
command_line, __start___param,
__stop___param - __start___param,
level, level,
NULL, ignore_unknown_bootoption);
trace_initcall_level(initcall_level_names[level]);
for (fn = initcall_levels[level]; fn < initcall_levels[level+1]; fn++)
do_one_initcall(initcall_from_entry(fn));
}
遍历initcall_levels[level]到initcall_levels[level+1]之间的函数指针,调用
initcall_levels[level+1]
int __init_or_module do_one_initcall(initcall_t fn)
{
int count = preempt_count();
char msgbuf[64];
int ret;
if (initcall_blacklisted(fn))
return -EPERM;
do_trace_initcall_start(fn);
ret = fn();
do_trace_initcall_finish(fn, ret);
msgbuf[0] = 0;
if (preempt_count() != count) {
sprintf(msgbuf, "preemption imbalance ");
preempt_count_set(count);
}
if (irqs_disabled()) {
strlcat(msgbuf, "disabled interrupts ", sizeof(msgbuf));
local_irq_enable();
}
WARN(msgbuf[0], "initcall %pS returned with %s\n", fn, msgbuf);
add_latent_entropy();
return ret;
}
中 ret = fn();调用
initcall_levels[level]到initcall_levels[level+1]之间的函数指针
linux/linux-custom/output/arch/riscv/kernel/vmlinux.lds中
.init.data : AT(ADDR(.init.data) - 0xffffffe000000000) { KEEP(*(SORT(___kentry+*))) *(.init.data init.data.*) *(.meminit.data*) *(.init.rodata .init.rodata.*) . = ALIGN(8); __start_ftrace_events = .; KEEP(*(_ftrace_events)) __stop_ftrace_events = .; __start_ftrace_eval_maps = .; KEEP(*(_ftrace_eval_map)) __stop_ftrace_eval_maps = .; *(.meminit.rodata) . = ALIGN(8); __clk_of_table = .; KEEP(*(__clk_of_table)) KEEP(*(__clk_of_table_end)) . = ALIGN(8); __reservedmem_of_table = .; KEEP(*(__reservedmem_of_table)) KEEP(*(__reservedmem_of_table_end)) . = ALIGN(8); __timer_of_table = .; KEEP(*(__timer_of_table)) KEEP(*(__timer_of_table_end)) . = ALIGN(8); __cpu_method_of_table = .; KEEP(*(__cpu_method_of_table)) KEEP(*(__cpu_method_of_table_end)) . = ALIGN(32); __dtb_start = .; KEEP(*(.dtb.init.rodata)) __dtb_end = .; . = ALIGN(8); __irqchip_of_table = .; KEEP(*(__irqchip_of_table)) KEEP(*(__irqchip_of_table_end)) . = ALIGN(8); __earlycon_table = .; KEEP(*(__earlycon_table)) __earlycon_table_end = .; . = ALIGN(8); __kunit_suites_start = .; KEEP(*(.kunit_test_suites)) __kunit_suites_end = .; . = ALIGN(16); __setup_start = .; KEEP(*(.init.setup)) __setup_end = .; __initcall_start = .; KEEP(*(.initcallearly.init)) __initcall0_start = .; KEEP(*(.initcall0.init)) KEEP(*(.initcall0s.init)) __initcall1_start = .; KEEP(*(.initcall1.init)) KEEP(*(.initcall1s.init)) __initcall2_start = .; KEEP(*(.initcall2.init)) KEEP(*(.initcall2s.init)) __initcall3_start = .; KEEP(*(.initcall3.init)) KEEP(*(.initcall3s.init)) __initcall4_start = .; KEEP(*(.initcall4.init)) KEEP(*(.initcall4s.init)) __initcall5_start = .; KEEP(*(.initcall5.init)) KEEP(*(.initcall5s.init)) __initcallrootfs_start = .; KEEP(*(.initcallrootfs.init)) KEEP(*(.initcallrootfss.init)) __initcall6_start = .; KEEP(*(.initcall6.init)) KEEP(*(.initcall6s.init)) __initcall7_start = .; KEEP(*(.initcall7.init)) KEEP(*(.initcall7s.init)) __initcall_end = .; __con_initcall_start = .; KEEP(*(.con_initcall.init)) __con_initcall_end = .; . = ALIGN(4); __initramfs_start = .; KEEP(*(.init.ramfs)) . = ALIGN(8); KEEP(*(.init.ramfs.info)) }
对应linux/linux-custom/include/asm-generic/vmlinux.lds.h中
#define INIT_DATA_SECTION(initsetup_align) \
.init.data : AT(ADDR(.init.data) - LOAD_OFFSET) { \
INIT_DATA \
INIT_SETUP(initsetup_align) \
INIT_CALLS \
CON_INITCALL \
INIT_RAM_FS \
}
#define INIT_CALLS \
__initcall_start = .; \
KEEP(*(.initcallearly.init)) \
INIT_CALLS_LEVEL(0) \
INIT_CALLS_LEVEL(1) \
INIT_CALLS_LEVEL(2) \
INIT_CALLS_LEVEL(3) \
INIT_CALLS_LEVEL(4) \
INIT_CALLS_LEVEL(5) \
INIT_CALLS_LEVEL(rootfs) \
INIT_CALLS_LEVEL(6) \
INIT_CALLS_LEVEL(7) \
__initcall_end = .;
arch/riscv/kernel/vmlinux.lds.S中
INIT_DATA_SECTION(16)
即.initcallrootfs.init放在了.initcall5.init和.initcall6.init之间
__initcall5_start = .; KEEP(*(.initcall5.init)) KEEP(*(.initcall5s.init)) __initcallrootfs_start = .; KEEP(*(.initcallrootfs.init)) KEEP(*(.initcallrootfss.init)) __initcall6_start = .; KEEP(*(.initcall6.init)) KEEP(*(.initcall6s.init))
和
static initcall_entry_t *initcall_levels[] __initdata = {
__initcall0_start,
__initcall1_start,
__initcall2_start,
__initcall3_start,
__initcall4_start,
__initcall5_start,
__initcall6_start,
__initcall7_start,
__initcall_end,
};
对应
for (fn = initcall_levels[level]; fn < initcall_levels[level+1]; fn++)
do_one_initcall(initcall_from_entry(fn));
在level=5时遍历到initcall_levels[5]~initcall_levels[6]之间,即__initcall5_start~ __initcall6_start之间,包括了.initcall5.init段和.initcallrootfs.init段之间,即可执行到populate_rootfs。
调用unpack_to_rootfs(__initramfs_start, __initramfs_size);解压
参数就是前面cpio嵌入内核的过程分析的段的开始和长度。
(gdb) p buf
$6 = 0xffffffe000890788 "\037\213\b"
(gdb) p len
$7 = 10549740
(gdb) p buf
$8 = 0xffffffe000890788 "\037\213\b"
(gdb)
decompress = decompress_method(buf, len, &compress_name); 获取压缩方式
此时
(gdb) p compress_name
$10 = 0xffffffe00158c1a8 "gzip"
(gdb) p decompress
$11 = (decompress_fn) 0xffffffe000018c7a
(gdb)
然后解压
int res = decompress(buf, len, NULL, flush_buffer, NULL,
&my_inptr, error);
(gdb) bt
#0 run_init_process (init_filename=0xffffffe001543f90 "/init") at ../init/main.c:1342
#1 0xffffffe000874438 in kernel_init (unused=
) at ../init/main.c:1437 #2 0xffffffe000201b5e in handle_exception () at ../arch/riscv/kernel/entry.S:243
Backtrace stopped: frame did not save the PC
(gdb)
kernel_init中
if (ramdisk_execute_command) {
ret = run_init_process(ramdisk_execute_command);
if (!ret)
return 0;
pr_err("Failed to execute %s (error %d)\n",
ramdisk_execute_command, ret);
}
/*
* We try each of these until one succeeds.
*
* The Bourne shell can be used instead of init if we are
* trying to recover a really broken machine.
*/
if (execute_command) {
ret = run_init_process(execute_command);
if (!ret)
return 0;
panic("Requested init %s failed (error %d).",
execute_command, ret);
}
if (CONFIG_DEFAULT_INIT[0] != '\0') {
ret = run_init_process(CONFIG_DEFAULT_INIT);
if (ret)
pr_err("Default init %s failed (error %d)\n",
CONFIG_DEFAULT_INIT, ret);
else
return 0;
}
if (!try_to_run_init_process("/sbin/init") ||
!try_to_run_init_process("/etc/init") ||
!try_to_run_init_process("/bin/init") ||
!try_to_run_init_process("/bin/sh"))
return 0;
panic("No working init found. Try passing init= option to kernel. "
"See Linux Documentation/admin-guide/init.rst for guidance.");
这里以替换原来roofs.cpio中的init为自己的init程序为例,来演示整个操作过程。
buildroot构建后images目录下有images/rootfs.cpio和rootfs.cpio.gz
解压rootfs.cpio.gz得到rootfs.cpio,已经有了rootfs.cpio则无需再解压:
解压命令:gunzip rootfs.cpio.gz
解包,假设rootfs.cpio在tmp文件夹下
解包命令:cpio -idmv < rootfs.cpio
解压后看到文件如下
tree -L 1
.
bin
dev
etc
init
lib
lib64 -> lib
lib64v_xthead
linuxrc -> bin/busybox
media
mnt
opt
proc
root
rootfs.cpio
run
sbin
sys
tmp
usr
var
17 directories, 3 files
这里以创建一个简单的init可执行程序,打印一条字符串进行测试。
Menuconfig,确认以下配置使能了elf支持
Executable file formats --->
[*] Kernel support for ELF binaries
buildroot/enhanced_5.10_glibc_br_defconfig/images/目录下
新建init.c文件内容如下
int main(int argc,char argv[])
{
printf("hello world, from initramfs.\n");
return 0;
}
编译生成init可执行文件
~/sdk/buildroot/enhanced_5.10_glibc_br_defconfig/host/bin/riscv64-unknown-linux-gnu-gcc -g -o init -static init.c
可查看对应的汇编
~/sdk/buildroot/enhanced_5.10_glibc_br_defconfig/host/bin/riscv64-unknown-linux-gnu-objdump -l -S init > a.s
添加到文件夹中
cp init tmp/
先删除原来的rootfs.cpio
当前位于buildroot/enhanced_5.10_glibc_br_defconfig/images/tmp
添加如下节点
sudo mknod -m 600 dev/console c 5 1
否则会打印如下信息Warning: unable to open an initial console.,init中标准输入输出无法使用
这个打印位于init/main.c
/* Open /dev/console, for stdin/stdout/stderr, this should never fail */
void __init console_on_rootfs(void)
{
struct file *file = filp_open("/dev/console", O_RDWR, 0);
if (IS_ERR(file)) {
pr_err("Warning: unable to open an initial console.\n");
return;
}
init_dup(file);
init_dup(file);
init_dup(file);
fput(file);
}
添加修改新的文件后打包。
1. 产生cpio文件
usr/gen_initramfs.sh -o rootfs.cpio ../../images/tmp/
2.压缩
gzip rootfs.cpio
3.替换
cp rootfs.cpio.gz ../../images/
本文分享了initramfs相关的内容,包括rootfs.cpio.gz嵌入到内核的相关配置和流程,rootfs.cpio.gz解压解包过程流程。以及一一个实力分享了如何操作cpio文件。