什么会导致将 init=/path/to/program 传递给内核而不是像 init 一样启动程序?

什么会导致将 init=/path/to/program 传递给内核而不是像 init 一样启动程序?

我正在尝试在 Linux 系统上调试 init 脚本;我试图传递init=/bin/sh给内核以使其sh在不启动的情况下启动init,这样我就可以手动运行初始化序列。

我发现内核init无论如何都会启动。在启动过程中,printk 消息之一是命令行,这表明该行已正确设置;此外,我可以使用内核命令行影响其他事情。我已检查以确保该路径存在;确实如此。

这是一个busybox系统,init是busybox的符号链接;因此,为了确保 busybox 在 PID 为 1 时不会执行奇怪的魔法,我还尝试运行一个非 busybox 程序作为 init ;那也没用。看来无论我做什么,init都会运行。

什么可能导致这种行为?

答案1

初始恶作剧

如果您使用 initrd 或 initramfs,请记住以下几点:

  • rdinit=被用来代替init=

  • 如果rdinit=未给出,则尝试的默认路径为:/sbin/init/etc/init/bin/init/bin/sh但不是/init

    不使用 initrd 时,/init会尝试第一个路径,然后再尝试其他路径。

v4.15 RTFS:所有内容都包含在https://github.com/torvalds/linux/blob/v4.15/init/main.c文件。

首先我们了解到:

  • execute_comand是传递给:init=
  • ramdisk_execute_command是传递给:rdinit=

可以看出:

static int __init init_setup(char *str)
{
    unsigned int i;

    execute_command = str;
    /*
    * In case LILO is going to boot us with default command line,
    * it prepends "auto" before the whole cmdline which makes
    * the shell think it should execute a script with such name.
    * So we ignore all arguments entered _before_ init=... [MJ]
    */
    for (i = 1; i < MAX_INIT_ARGS; i++)
        argv_init[i] = NULL;
    return 1;
}
__setup("init=", init_setup);

static int __init rdinit_setup(char *str)
{
    unsigned int i;

    ramdisk_execute_command = str;
    /* See "auto" comment in init_setup */
    for (i = 1; i < MAX_INIT_ARGS; i++)
        argv_init[i] = NULL;
    return 1;
}
__setup("rdinit=", rdinit_setup);

where__setup是处理命令行参数的神奇方法。

start_kernel,内核“入口点”,调用,它在线程上rest_init“调用” :kernel_init

pid = kernel_thread(kernel_init, NULL, CLONE_FS);

那么,kernel_init是否:

static int __ref kernel_init(void *unused)
{
    int ret;

    kernel_init_freeable();

    [...]

    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);
    }

    [...]

    if (execute_command) {
        ret = run_init_process(execute_command);
        if (!ret)
            return 0;
        panic("Requested init %s failed (error %d).",
            execute_command, ret);
    }
    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.");
}

kernel_init_freeable执行以下操作:

static noinline void __init kernel_init_freeable(void)
{

    [...]

    if (!ramdisk_execute_command)
        ramdisk_execute_command = "/init";

    if (sys_access((const char __user *) ramdisk_execute_command, 0) != 0) {
        ramdisk_execute_command = NULL;
        prepare_namespace();
    }

TODO:明白sys_access

另请注意,ram 初始化和非 ram 初始化之间还有更多区别,例如控制台处理:嵌入式 initramfs 与外部 initramfs 执行 init 的区别?

答案2

https://www.kernel.org/doc/Documentation/filesystems/ramfs-rootfs-initramfs.txt

我发现:

当调试普通的根文件系统时,能够使用“init=/bin/sh”启动是件好事。 initramfs 的等效项是“rdinit=/bin/sh”,它同样有用。

所以可能尝试ridinit=/bin/sh

答案3

查看 Linux 内核源代码,我发现如果文件 /init 存在,内核将始终尝试运行它,假设它正在进行 ramdisk 引导。检查您的系统,看看 /init 是否存在,如果存在,那么这可能是您的问题。

答案4

您可以自定义 Linux 内核并重新编译它。对于 4.9 内核,编辑 init/main.c 中的函数“kernel_init”并尝试首先运行以下行:

try_to_run_init_process("/bin/sh")

另外,也可能是BootLoader传递的内核参数导致的。

相关内容