initramfs启动

本文最后更新于:2025年11月19日 下午

下面代码对应的内核版本:6.2

为何需要initrd,initramfs?

早期的文件系统一般只保存在一两种存储设备上,内核只需要把这些存储设备的驱动编译进内核即可。后面存储设备越来越多,把所有存储设备的驱动都编译进内核不划算。编译为模块后,这些模块怎么加载呢?linux使用udev来实现模块的自动加载。但是udev是一个应用程序,根文件系统挂载前,无法执行应用程序。所以需要一个中间过度用的文件系统。于是出现了initrd,一个小型的文件系统。

initrd和initramfs区别?

initrd是init ram disk,initramfs是init ram file system,前者把内存模拟成磁盘,后者直接把内存模拟成文件系统。
initrd是linux2.4及以前的用法,早已过时。现在用的话都是initramfs,但是很多时候还在说initrd。实际上大多数情况它说的还是initramfs。

kernel启动时,处理initramfs是直接解压。处理initrd是把文件写到/initrd.image。

rootfs挂载

这里rootfs指文件系统的名字就叫rootfs,不是root filesystem的简写。
start_kernel–>vfs_caches_init–>mnt_init –> init_mount_tree
init_mount_tree会mount rootfs

1
mnt = vfs_kern_mount(&rootfs_fs_type, 0, "rootfs", NULL);

rootfs_fs_type的定义为

1
2
3
4
5
struct file_system_type rootfs_fs_type = {
.name = "rootfs",
.init_fs_context = rootfs_init_fs_context,
.kill_sb = kill_litter_super,
};

它只挂载了一个空的根目录’/‘。

initramfs解压

start_kernel->arch_call_rest_init–>rest_init–>kernel_init–>kernel_init_freeable–>do_basic_setup–>do_initcalls–>”initcall” –> populate_rootfs –> do_populate_rootfs–>unpack_to_rootfs
initcall在kernel_init_freeable里面,有点迷惑

unpack_to_rootfs解压initramfs到前面挂载的根目录。
解压时会先判断解压算法,然后使用对应解压算法解压,解压的内容传给flush回调函数。该回调负责把内容写到rootfs文件系统。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
static void __init do_populate_rootfs(void *unused, async_cookie_t cookie)
{
/* Load the built in initramfs */ //先尝试initramfs文件是否直接编译进了内核,CONFIG_INITRAMFS_SOURCE
char *err = unpack_to_rootfs(__initramfs_start, __initramfs_size);
if (err)
panic_show_mem("%s", err); /* Failed to decompress INTERNAL initramfs */

if (!initrd_start || IS_ENABLED(CONFIG_INITRAMFS_FORCE))
goto done;

if (IS_ENABLED(CONFIG_BLK_DEV_RAM))
printk(KERN_INFO "Trying to unpack rootfs image as initramfs...\n");
else
printk(KERN_INFO "Unpacking initramfs...\n");

// 虽然入参传递的initrd,实际上内容还是处理initramfs
err = unpack_to_rootfs((char *)initrd_start, initrd_end - initrd_start);
if (err) {
#ifdef CONFIG_BLK_DEV_RAM
// 这个才是真正的initrd处理
populate_initrd_image(err);
#else
printk(KERN_EMERG "Initramfs unpacking failed: %s\n", err);
#endif
}

done:
/*
* If the initrd region is overlapped with crashkernel reserved region,
* free only memory that is not part of crashkernel region.
*/
if (!do_retain_initrd && initrd_start && !kexec_free_initrd())
free_initrd_mem(initrd_start, initrd_end); //释放这部分保留内存
initrd_start = 0;
initrd_end = 0;

flush_delayed_fput();
task_work_run();
}

initramfs的起始地址哪儿的

(1) 如果是把initramfs(配置CONFIG_INITRAMFS_SOURCE)编译进了内核,那么__initramfs_start,这种地址通过链接脚本可知。
(2) 如果initramfs是编译的单独的文件,那么会通过启动参数知道。有两种:一种是cmdline里面有,一种是引导程序把initramfs加载到内存,然后通过boot_params.hdr.ramdisk_image参数告诉内核地址和长度。

保存initramfs这块内存,内核会用吗?
比如boot阶段,传递initramfs放在0x100,长度1000。担心内核在使用内存时,去写了这片内存。
应该不会。boot应该给内核传递了,哪些内存已经被使用了。如我x86的打印:

1
2
3
4
5
6
7
8
9
10
11
12
13
BIOS-provided physical RAM map:
BIOS-e820: [mem 0x0000000000000000-0x000000000009fbff] usable
BIOS-e820: [mem 0x000000000009fc00-0x000000000009ffff] reserved
BIOS-e820: [mem 0x00000000000f0000-0x00000000000fffff] reserved
BIOS-e820: [mem 0x0000000000100000-0x0000000007fdffff] usable
BIOS-e820: [mem 0x0000000007fe0000-0x0000000007ffffff] reserved
BIOS-e820: [mem 0x00000000fffc0000-0x00000000ffffffff] reserved
.....
.....
RAMDISK: [mem 0x07c7c000-0x07fdffff]
.....
.....
Freeing initrd memory: 3472K

bios标记了[mem 0x0000000000100000-0x0000000007fdffff] 为usable,表示已经映射了物理地址,已使用,而ramdisk就处在这块内存上。
在initrd初始化完成后(已经解压到rootfs),就会调用free_initrd_mem,标记这块内存内核可以使用了。


人生苦短,远离bug Leon, 2024-10-23

initramfs启动
https://leon0625.github.io/2024/10/23/709e891d88d8/
作者
leon.liu
发布于
2024年10月23日
许可协议