接前一篇文章Linux initramfs深度解析: 从内核启动到根文件系统的桥梁1实现机制深度剖析1. 内核中的initramfs处理流程让我们从内核源码的角度来看initramfs是如何被处理的。Linux内核在启动过程中有一个特殊的阶段来处理initramfs, 这个过程主要发生在init/main.c的 start_kernel()和后续的启动函数中。/* kernel/init/main.c - 简化版示意 */ static int __init do_populate_rootfs(void) { /* 初始化 rootfs, 解压 initramfs 内容 */ unpack_to_rootfs(__initramfs_start, __initramfs_size); /* 执行 init 进程 */ if (execute_command) { /* 如果提供了自定义 init 程序 */ ret run_init_process(execute_command); } else { /* 尝试多个标准 init 路径 */ ret run_init_process(/sbin/init); if (ret) ret run_init_process(/etc/init); if (ret) ret run_init_process(/bin/init); if (ret) ret run_init_process(/bin/sh); } return ret; }这段代码展示了几个关键步骤首先unpack_to_rootfs函数将内核中编译进来的initramfs通过符号__initramfs_start和__initramfs_size标记解压到rootfs中。然后内核尝试执行一个init程序这个程序将接管系统的初始化。2. initramfs文件格式cpio与压缩initramfs使用cpio格式进行打包。cpio是一个古老但仍然广泛使用的归档格式其优点是简单、易于解析对于内核这样的低级代码来说特别适合。让我们看看cpio的基本结构------------------------------ | Header | Padding | Data | Padding | ------------------------------现代Linux通常使用newc格式也叫SVR4 cpio格式这是一个ASCII十六进制格式便于调试和验证。一个典型的cpio header如下/* cpio header 结构 - newc 格式 */ struct cpio_newc_header { char c_magic[6]; /* 070701 for newc format */ char c_ino[8]; /* inode number */ char c_mode[8]; /* file mode */ char c_uid[8]; /* user id */ char c_gid[8]; /* group id */ char c_nlink[8]; /* number of hard links */ char c_mtime[8]; /* modification time */ char c_filesize[8]; /* file size */ char c_devmajor[8]; /* device major number */ char c_devminor[8]; /* device minor number */ char c_rdevmajor[8]; /* rdev major number */ char c_rdevminor[8]; /* rdev minor number */ char c_namesize[8]; /* length of filename */ char c_check[8]; /* checksum */ };每个文件在cpio归档中都有这样的header后跟文件名、padding、文件数据和更多padding。整个归档以一个名为TRAILER!!! 的特殊条目结尾标志着归档的完成。# 使用 cpio 命令创建 initramfs find . -print0 | cpio -0 -o -H newc -F ../initramfs.cpio # 或者使用 cpio 直接从文件列表创建 # 然后通过 gzip 压缩 gzip initramfs.cpio3. initramfs的加载流程当GRUB或其它引导加载程序加载Linux内核时如果指定了initramfs文件引导加载程序会将其加载到内存中并通过multiboot协议或其它引导协议将其位置和大小信息传递给内核。内核在启动时会识别这个initramfs并进行解压。流程如下[引导加载程序] | v [加载内核镜像到内存] | v [加载 initramfs 到内存] | v [内核启动并识别 initramfs] | v [解压 initramfs 到 rootfs] | v [执行 initramfs 中的 init] | v [init 挂载真实根文件系统] | v [执行 switch_root 切换到真实根]内核中处理initramfs的核心函数在init/initramfs.c中/* init/initramfs.c - initramfs 解压核心逻辑 */ static int __init unpack_to_rootfs(char *buf, unsigned long len) { long written; dry_run 1; written unpack_to_rootfs(buf, len); /* 第一遍: 检查完整性 */ if (written ! len) { printk(KERN_WARNING initramfs unpacking failed); return -1; } dry_run 0; written unpack_to_rootfs(buf, len); /* 第二遍: 真正解压 */ if (written ! len) { panic(initramfs unpacking failed); } return 0; } static int __init unpack_to_rootfs(char *buf, unsigned long len) { long written 0; while (len) { /* 处理 cpio header */ struct cpio_newc_header *hdr (void *)buf; /* 验证魔数 */ if (memcmp(hdr-c_magic, 070701, 6) ! 0) { break; /* 可能是压缩格式, 或者归档结束 */ } /* 解析 header, 提取文件名大小、文件大小等 */ unsigned long namesize hex2bin(hdr-c_namesize); unsigned long filesize hex2bin(hdr-c_filesize); /* 如果是 TRAILER, 表示 cpio 归档结束 */ if (!strcmp(filename, TRAILER!!!)) { break; } /* 创建文件或目录 */ if (S_ISDIR(mode)) { sys_mkdir(filename, mode); } else if (S_ISLNK(mode)) { sys_symlink(symlink_target, filename); } else { /* 创建普通文件 */ sys_open(filename, O_CREAT | O_WRONLY, mode); sys_write(fd, file_data, filesize); sys_close(fd); } /* 移动到下一个 cpio 条目 */ buf header_size name_size file_size; len - header_size name_size file_size; } return written;4. initramfs内部结构示意一个典型的initramfs包含以下目录结构initramfs/ ├── bin/ # 必要的二进制工具 │ ├── busybox # 提供 sh, ls, cat, mount 等基础命令 │ └── init # 初始化脚本或二进制 ├── sbin/ │ ├── insmod # 加载内核模块 │ ├── modprobe # 高级模块加载 │ └── ...其他工具 ├── etc/ # 配置文件 │ ├── modprobe.d/ # 模块配置 │ └── fstab # 文件系统配置 ├── lib/ # 库文件和内核模块 │ ├── modules/ # 编译的内核模块 │ └── ld-linux.so # C 库 ├── dev/ # 设备文件由内核自动创建 ├── proc/ # procfs 挂载点 ├── sys/ # sysfs 挂载点 ├── root/ # 临时 root 用户目录 └── newroot/ # 用于 switch_root 的挂载点这个结构虽然看起来复杂但本质上是一个最小的Linux根文件系统。initramfs的大小通常在几MB 到几十MB之间取决于需要包含的驱动和工具的数量。5. switch_root从initramfs切换到真实根当initramfs中的init程序完成了必要的初始化任务加载驱动、挂载根文件系统等后它需要”切换根“到真实的根文件系统。这是通过switch_root命令或系统调用来完成的。/* 核心思路: switch_root 的实现 */ int switch_root(const char *new_root) { /* 1. 改变根目录 */ if (chroot(new_root) 0) { perror(chroot failed); return -1; } /* 2. 改变当前工作目录到新根的根目录 */ if (chdir(/) 0) { perror(chdir failed); return -1; } /* 3. 执行新根中的 init 程序 * 使用 execve 替换当前进程的镜像, 这样就不需要启动新进程 */ if (execve(/sbin/init, argv, envp) 0) { if (execve(/etc/init, argv, envp) 0) { if (execve(/bin/init, argv, envp) 0) { execve(/bin/sh, argv, envp); } } } /* 如果 execve 失败, 才会执行到这里 */ return -1; }从流程图来看更多内容请看下回。
Linux initramfs深度解析: 从内核启动到根文件系统的桥梁(2)
发布时间:2026/6/16 3:35:06
接前一篇文章Linux initramfs深度解析: 从内核启动到根文件系统的桥梁1实现机制深度剖析1. 内核中的initramfs处理流程让我们从内核源码的角度来看initramfs是如何被处理的。Linux内核在启动过程中有一个特殊的阶段来处理initramfs, 这个过程主要发生在init/main.c的 start_kernel()和后续的启动函数中。/* kernel/init/main.c - 简化版示意 */ static int __init do_populate_rootfs(void) { /* 初始化 rootfs, 解压 initramfs 内容 */ unpack_to_rootfs(__initramfs_start, __initramfs_size); /* 执行 init 进程 */ if (execute_command) { /* 如果提供了自定义 init 程序 */ ret run_init_process(execute_command); } else { /* 尝试多个标准 init 路径 */ ret run_init_process(/sbin/init); if (ret) ret run_init_process(/etc/init); if (ret) ret run_init_process(/bin/init); if (ret) ret run_init_process(/bin/sh); } return ret; }这段代码展示了几个关键步骤首先unpack_to_rootfs函数将内核中编译进来的initramfs通过符号__initramfs_start和__initramfs_size标记解压到rootfs中。然后内核尝试执行一个init程序这个程序将接管系统的初始化。2. initramfs文件格式cpio与压缩initramfs使用cpio格式进行打包。cpio是一个古老但仍然广泛使用的归档格式其优点是简单、易于解析对于内核这样的低级代码来说特别适合。让我们看看cpio的基本结构------------------------------ | Header | Padding | Data | Padding | ------------------------------现代Linux通常使用newc格式也叫SVR4 cpio格式这是一个ASCII十六进制格式便于调试和验证。一个典型的cpio header如下/* cpio header 结构 - newc 格式 */ struct cpio_newc_header { char c_magic[6]; /* 070701 for newc format */ char c_ino[8]; /* inode number */ char c_mode[8]; /* file mode */ char c_uid[8]; /* user id */ char c_gid[8]; /* group id */ char c_nlink[8]; /* number of hard links */ char c_mtime[8]; /* modification time */ char c_filesize[8]; /* file size */ char c_devmajor[8]; /* device major number */ char c_devminor[8]; /* device minor number */ char c_rdevmajor[8]; /* rdev major number */ char c_rdevminor[8]; /* rdev minor number */ char c_namesize[8]; /* length of filename */ char c_check[8]; /* checksum */ };每个文件在cpio归档中都有这样的header后跟文件名、padding、文件数据和更多padding。整个归档以一个名为TRAILER!!! 的特殊条目结尾标志着归档的完成。# 使用 cpio 命令创建 initramfs find . -print0 | cpio -0 -o -H newc -F ../initramfs.cpio # 或者使用 cpio 直接从文件列表创建 # 然后通过 gzip 压缩 gzip initramfs.cpio3. initramfs的加载流程当GRUB或其它引导加载程序加载Linux内核时如果指定了initramfs文件引导加载程序会将其加载到内存中并通过multiboot协议或其它引导协议将其位置和大小信息传递给内核。内核在启动时会识别这个initramfs并进行解压。流程如下[引导加载程序] | v [加载内核镜像到内存] | v [加载 initramfs 到内存] | v [内核启动并识别 initramfs] | v [解压 initramfs 到 rootfs] | v [执行 initramfs 中的 init] | v [init 挂载真实根文件系统] | v [执行 switch_root 切换到真实根]内核中处理initramfs的核心函数在init/initramfs.c中/* init/initramfs.c - initramfs 解压核心逻辑 */ static int __init unpack_to_rootfs(char *buf, unsigned long len) { long written; dry_run 1; written unpack_to_rootfs(buf, len); /* 第一遍: 检查完整性 */ if (written ! len) { printk(KERN_WARNING initramfs unpacking failed); return -1; } dry_run 0; written unpack_to_rootfs(buf, len); /* 第二遍: 真正解压 */ if (written ! len) { panic(initramfs unpacking failed); } return 0; } static int __init unpack_to_rootfs(char *buf, unsigned long len) { long written 0; while (len) { /* 处理 cpio header */ struct cpio_newc_header *hdr (void *)buf; /* 验证魔数 */ if (memcmp(hdr-c_magic, 070701, 6) ! 0) { break; /* 可能是压缩格式, 或者归档结束 */ } /* 解析 header, 提取文件名大小、文件大小等 */ unsigned long namesize hex2bin(hdr-c_namesize); unsigned long filesize hex2bin(hdr-c_filesize); /* 如果是 TRAILER, 表示 cpio 归档结束 */ if (!strcmp(filename, TRAILER!!!)) { break; } /* 创建文件或目录 */ if (S_ISDIR(mode)) { sys_mkdir(filename, mode); } else if (S_ISLNK(mode)) { sys_symlink(symlink_target, filename); } else { /* 创建普通文件 */ sys_open(filename, O_CREAT | O_WRONLY, mode); sys_write(fd, file_data, filesize); sys_close(fd); } /* 移动到下一个 cpio 条目 */ buf header_size name_size file_size; len - header_size name_size file_size; } return written;4. initramfs内部结构示意一个典型的initramfs包含以下目录结构initramfs/ ├── bin/ # 必要的二进制工具 │ ├── busybox # 提供 sh, ls, cat, mount 等基础命令 │ └── init # 初始化脚本或二进制 ├── sbin/ │ ├── insmod # 加载内核模块 │ ├── modprobe # 高级模块加载 │ └── ...其他工具 ├── etc/ # 配置文件 │ ├── modprobe.d/ # 模块配置 │ └── fstab # 文件系统配置 ├── lib/ # 库文件和内核模块 │ ├── modules/ # 编译的内核模块 │ └── ld-linux.so # C 库 ├── dev/ # 设备文件由内核自动创建 ├── proc/ # procfs 挂载点 ├── sys/ # sysfs 挂载点 ├── root/ # 临时 root 用户目录 └── newroot/ # 用于 switch_root 的挂载点这个结构虽然看起来复杂但本质上是一个最小的Linux根文件系统。initramfs的大小通常在几MB 到几十MB之间取决于需要包含的驱动和工具的数量。5. switch_root从initramfs切换到真实根当initramfs中的init程序完成了必要的初始化任务加载驱动、挂载根文件系统等后它需要”切换根“到真实的根文件系统。这是通过switch_root命令或系统调用来完成的。/* 核心思路: switch_root 的实现 */ int switch_root(const char *new_root) { /* 1. 改变根目录 */ if (chroot(new_root) 0) { perror(chroot failed); return -1; } /* 2. 改变当前工作目录到新根的根目录 */ if (chdir(/) 0) { perror(chdir failed); return -1; } /* 3. 执行新根中的 init 程序 * 使用 execve 替换当前进程的镜像, 这样就不需要启动新进程 */ if (execve(/sbin/init, argv, envp) 0) { if (execve(/etc/init, argv, envp) 0) { if (execve(/bin/init, argv, envp) 0) { execve(/bin/sh, argv, envp); } } } /* 如果 execve 失败, 才会执行到这里 */ return -1; }从流程图来看更多内容请看下回。