Can the init process be a shell script in Linux?

init is not "spawned" (as a child process), but rather exec'd like this:

# Boot the real thing.
exec switch_root /mnt/root /sbin/init

exec replaces the entire process in place. The final init is still the first process (pid 1), even though it was preceded with those in the Initramfs.

The Initramfs /init, which is a Busybox shell script with pid 1, execs to Busybox switch_root (so now switch_root is pid 1); this program changes your mount points so /mnt/root will be the new /.

switch_root then again execs to /sbin/init of your real root filesystem; thereby it makes your real init system the first process with pid 1, which in turn may spawn any number of child processes.

Certainly it could just as well be done with a Python script, if you somehow managed to bake Python into your Initramfs. Although if you don't plan to include busybox anyway, you would have to painstakingly reimplement some of its functionality (like switch_root, and everything else you would usually do with a simple command).

However, it does not work on kernels that do not allow script binaries (CONFIG_BINFMT_SCRIPT=y), or rather in such a case you'd have to start the interpreter directly and make it load your script somehow.


The exec syscall of the Linux kernel underestands shebangs natively

When the executed file starts with the magic bytes #!, they tell the kernel to use #!/bin/sh as:

  • do and exec system call
  • with executable /bin/sh
  • and with CLI argument: path to current script

This is exactly the same that happens when you run a regular userland shell script with:

./myscript.sh

If the file had started with the magic bytes .ELF instead of #!, the kernel would pick the ELF loader instead to run it.

More details at: Why do people write the #!/usr/bin/env python shebang on the first line of a Python script? | Stack Overflow

Once you have this in mind, it becomes easy to accept that /init can be anything that the kernel can execute, including a shell script, and also why /bin/sh will be the first executable in that case.

Here is a minimal runnable example for those that want to try it out: https://github.com/cirosantilli/linux-kernel-module-cheat/tree/cbea7cc02c868711109ae1a261d01fd0473eea0b#custom-init