早期用户空间(Early userspace)是一组库和程序,提供了在 Linux 内核启动过程中非常重要的功能,但不需要在内核本身内运行的功能。
它包括几个主要的基础组件:
- gen_init_cpio:一个构建包含根文件系统镜像的 cpio 格式存档的程序。这个存档是经过压缩的,压缩后的镜像会链接到内核镜像中。
- initramfs:一段代码,在内核引导过程中解压缩压缩的 cpio 镜像。
- klibc:一个用户空间的 C 库,目前是单独打包的,它针对正确性和小尺寸进行了优化。
initramfs 使用的 cpio 文件格式是 "newc"(又名 "cpio -H newc")格式,其文档在文件 "initramfs buffer format" 中有说明。有两种方法可以添加早期用户空间镜像:指定现有的 cpio 存档用作镜像,或者让内核构建过程根据规范构建镜像。
CPIO 存档方法:
你可以创建一个包含早期用户空间镜像的 cpio 存档。你的 cpio 存档应该在 CONFIG_INITRAMFS_SOURCE 中指定,并将直接使用。在 CONFIG_INITRAMFS_SOURCE 中只能指定一个单独的 cpio 文件,不允许同时使用目录和文件名。
镜像构建方法:
内核构建过程也可以根据源部件构建早期用户空间镜像,而不是提供一个 cpio 存档。这种方法提供了一种创建具有根文件所有者文件的镜像的方式,即使该镜像是由一个非特权用户构建的。
镜像在 CONFIG_INITRAMFS_SOURCE 中指定为一个或多个源。源可以是目录或文件 - 在构建源时不允许使用 cpio 存档。
源目录及其所有内容将被打包。指定的目录名称将映射到 '/'。在打包目录时,可以执行有限的用户和组 ID 转换。INITRAMFS_ROOT_UID 可以设置为需要映射到用户 root(0)的用户 ID。INITRAMFS_ROOT_GID 可以设置为需要映射到组 root(0)的组 ID。
源文件必须是 usr/gen_init_cpio 实用程序所需格式的指令(运行 'usr/gen_init_cpio -h' 以获取文件格式)。文件中的指令将直接传递给 usr/gen_init_cpio。
当指定目录和文件的组合时,initramfs 镜像将是它们所有内容的聚合。通过这种方式,用户可以创建一个 'root-image' 目录,并将所有文件安装到其中。因为设备特殊文件无法由非特权用户创建,特殊文件可以列在一个 'root-files' 文件中。'root-image' 和 'root-files' 都可以列在 CONFIG_INITRAMFS_SOURCE 中,非特权用户可以构建完整的早期用户空间镜像。
作为技术说明,当指定目录和文件时,整个 CONFIG_INITRAMFS_SOURCE 都会传递给 usr/gen_initramfs.sh。这意味着 CONFIG_INITRAMFS_SOURCE 实际上可以被解释为 gen_initramfs.sh 的任何合法参数。如果将目录指定为参数,则会扫描内容,执行 uid/gid 转换,并输出 usr/gen_init_cpio 文件指令。如果将目录指定为 usr/gen_initramfs.sh 的参数,则文件内容的所有指令将简单地复制到输出中。目录扫描和文件内容复制的所有输出指令都将由 usr/gen_init_cpio 处理。
另请参阅 'usr/gen_initramfs.sh -h'。
这一切的目的是什么?
klibc 发行版包含了使早期用户空间变得有用的一些必要软件。klibc 发行版目前是与内核分开维护的。
你可以从 https://www.kernel.org/pub/linux/libs/klibc/ 获取 klibc 的一些不太频繁的快照。
对于活跃的用户,最好使用 klibc 的 git 存储库,网址是 https://git.kernel.org/?p=libs/klibc/klibc.git
独立的 klibc 发行版目前除了 klibc 库之外还提供了三个组件:
- ipconfig:配置网络接口的程序。它可以静态配置它们,也可以使用 DHCP 动态获取信息(也称为 "IP 自动配置")。
- nfsmount:可以挂载 NFS 文件系统的程序。
- kinit:使用 ipconfig 和 nfsmount 来替换旧的 IP 自动配置支持,挂载 NFS 文件系统,并使用该文件系统作为根继续系统引导的 "粘合剂"。
kinit 被构建为一个单独的静态链接二进制文件,以节省空间。
最终,希望将更多的内核功能块移动到早期用户空间:
- 几乎所有的 init/do_mounts*(其开始部分已经就位)
- ACPI 表解析
- 插入不需要真正位于内核空间的笨重子系统
如果 kinit 不满足你当前的需求,并且你有多余的空间,klibc 发行版包括一个小型的 Bourne 兼容 shell(ash)和许多其他实用程序,因此你可以替换 kinit 并构建符合你需求的自定义 initramfs 镜像。
有关问题和帮助,你可以在 https://www.zytor.com/mailman/listinfo/klibc 注册早期用户空间邮件列表。
它是如何工作的?
内核目前有 3 种方式挂载根文件系统:
- 所有必需的设备和文件系统驱动程序都编译到内核中,没有 initrd。init/main.c:init() 将调用 prepare_namespace() 来挂载最终的根文件系统,基于 root= 选项和可选的 init= 来运行一些不在 init/main.c:init() 末尾列出的其他 init 二进制文件。
- 一些设备和文件系统驱动程序作为模块构建,并存储在一个 initrd 中。initrd 必须包含一个名为 '/linuxrc' 的二进制文件,它应该加载这些驱动模块。也可以通过 linuxrc 挂载最终的根文件系统,并使用 pivot_root 系统调用。通过 prepare_namespace() 挂载和执行 initrd。
- 使用 initramfs。必须跳过对 prepare_namespace() 的调用。这意味着一个二进制文件必须完成所有工作。所述二进制文件可以通过修改 usr/gen_init_cpio.c 或通过新的 initrd 格式,即 cpio 存档,存储到 initramfs 中。它必须被命名为 "/init"。这个二进制文件负责执行 prepare_namespace() 将要做的所有工作。
为了保持向后兼容性,只有当 /init 二进制文件通过 initramfs cpio 存档传递时,它才会运行。如果不是这种情况,init/main.c:init() 将运行 prepare_namespace() 来挂载最终的根,并执行预定义的 init 二进制文件之一。
Bryan O'Sullivan bos@serpentine.com