背景
我需要组装自己的 initramfs,在编写其 init 脚本之前,我想手动(即从交互式 shell)执行所有必需的操作。我的选择是所有操作都静态链接busybox。
我做什么
我在 initramfs /bin 中有 busybox 可执行文件,在 /bin: 'sh' -> /bin/busybox 中有 shell 的符号链接,在 initramfs root 中,有到 /bin/sh 的 init 符号链接
我得到什么
busybox 现在认为它的名称是init
并给我一个提示Please press Enter to activate this console.
按 Enter 键后,我进入 shell,但它的 PID 不是 1。PID=1 的进程是init
。因此,我无法手动执行switch_root
并继续启动主系统。
我尝试在 initramfs 中将 bash 作为 shell 运行并成功:它以 PID=1 运行并允许执行switch_root
.
问题
从 initramfs 中,如何运行 PID=1 的交互式 busybox shell?
答案1
TLDR 答案
您可以通过简单地将init
程序符号链接到您选择的 shell(在 initramfs 中)来完成此操作,正如您已经发现的那样。
“忙碌盒”问题
您面临的问题是 busybox“功能”。 Busybox 将其在编译时选择包含的所有实用程序打包到单个通用可执行文件中。然后,它根据其符号链接名称(即argv[0]
向量)在执行时启动“子实用程序”。
这种技术允许它静态链接所有实用程序(不需要 dylib 又名 DLL - 使其非常强大 - 损坏的 libc (如 glibc)永远不会使你的 busybox 无法工作 - 但它允许它共享编译后的代码ebbeded 实用程序就好像它们被拆分为 exes 和 dylib 并由它们引用,因为所有实用程序都位于单个可执行文件中,因此所有实用程序都可以直接调用相同的共享函数(例如 等strlen
)printf
- 即可执行文件中的函数代码在所有实用程序之间共享子实用程序就好像它位于 DLL 中一样)并节省存储空间(您只需要一个 busybox 二进制文件,其中包含单个文件中的所有实用程序/命令,因此所有实用程序共享相同的 ELF 标头和其他 ELF 格式结构,并且只是该可执行文件的函数 - 使整体结果更小,就像数十个单独的可执行文件一样:符号链接在设备上占用的空间比多个实用程序可执行文件的多个 ELF 结构占用的空间更少。
ls
现在,busybox通过在执行时查看["busybox", "ls"...]
或查看["ls",..]
其 argv 向量的第 0 个位置来确定要运行的子实用程序。
如果前面的符号链接放置正确(即在 $PATH 中),则ieln -s busybox ls ; ./ls
相当于 running ,busybox ls
而 running 相当于 running 。ls
当你的符号链接被命名时init
,它被内核传递到 busysbox 的argv[0]
向量中,这就是 busybox “看到”它被调用的名称,从而作为实际的init
程序子实用程序启动。
解决方案
为了解决这个问题,这可能会起作用:
- 创建文本文件
/bin/init
- 将其标记为可执行
- 使其代码如下:
#!/bin/sh
exec /bin/sh # this should replace current init process which is the sh interpreter from shebang above but with arg[0] of `init` with sh interpreter with argv[0] of `sh` - and this should be enough, to make busybox believe it should run `sh` sub-utility, which will end as PID1 because of `exec` in front.
简而言之,您必须说服 busybox 它作为一个sh
为其命名的进程运行,以像 shell 一样运行。
根据我的经验,在开发临时 iniramfses 和 init 方案时,指向/bin/init
实际的 init 实现并定义 2 个引导加载程序菜单会更有效率:
- 菜单项 1:使用我的 init 启动
- menuitem 2:使用 shell 启动,通过将
init=/bin/sh
LAST 参数添加到内核命令行
这样,您可以通过从 booloader/qemu 等中选择所需的菜单项来简单地选择启动模式(测试运行、手动)。