4.4.1. Auto-start Configuration

4.4.1.1. Overview and Introduction

In embedded systems or lightweight Linux environments, auto-start service management typically uses Busybox init. By creating auto-start shell scripts in the /etc/init.d directory, specific tasks or services can be automatically executed during system boot. This document provides a detailed explanation of the system boot process, the underlying principles of auto-start mechanisms, and how to customize auto-start scripts.

4.4.1.2. Detailed Functionality

System Boot Process

From Kernel to init

start_bring_up.png

In the final stage of Linux kernel startup, the start_kernel() function calls reset_init() to create the first process—the idle process with pid=0. This process runs in kernel mode and is the only one not created via fork() or kernel_thread(). Through a series of calls, it eventually enters cpu_idle_loop(), where two critical processes are spawned:

  • init process: The ancestor of all user-space processes, responsible for managing and spawning user-space processes.

  • kthreadd process: The ancestor of all kernel threads, responsible for kernel thread management.

The code analysis below clearly shows that pid=0 is the ancestor of all processes and threads, while the init and kthreadd processes handle different responsibilities.

static noinline void __ref rest_init(void)
{
...
    pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);
...
	cpu_startup_entry(CPUHP_ONLINE);
}

static int __ref kernel_init(void *unused)
{
...
	/* Specified via bootargs "rdinit=/sbin/init"; if set, ramdisk will be launched. */
    if (ramdisk_execute_command) {
        ret = run_init_process(ramdisk_execute_command);
...
    }

	/* Specified via bootargs "init=/sbin/init", including startup arguments argv_init[]. */
    if (execute_command) {
        ret = run_init_process(execute_command);
...
}
	/* If neither rdinit nor init is specified, try the following fixed paths in order. */
    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;
...
}

int kthreadd(void *unused)
{
...
    /* Setup a clean context for our children to inherit. */
    /* Rename kernel thread to kthreadd. */
    set_task_comm(tsk, "kthreadd");
...
    cgroup_init_kthreadd();

    for (;;) {
...
        spin_lock(&kthread_create_lock);
        /* Kernel threads are created by kthreadd traversing the kthread_create_list */
        /* and then extracting entries to create kernel threads via create_kthread(). */
        while (!list_empty(&kthread_create_list)) {
            struct kthread_create_info *create;

            create = list_entry(kthread_create_list.next,
                        struct kthread_create_info, list);
            list_del_init(&create->list);
            spin_unlock(&kthread_create_lock);

            create_kthread(create);

            spin_lock(&kthread_create_lock);
        }
        spin_unlock(&kthread_create_lock);
    }

    return 0;
}

Analysis of the init Process

init_main() is the entry point of the init process in Busybox. This process is responsible for setting up the user-space environment and determining the boot sequence based on the configuration in the /etc/inittab file.

int init_main(int argc UNUSED_PARAM, char **argv)
{
... ...
	/* Make sure environs is set to something sane */
	/* Set environment variables; SHELL points to /bin/sh */
	putenv((char *) "HOME=/");
	putenv((char *) bb_PATH_root_path);
	putenv((char *) "SHELL=/bin/sh");
	putenv((char *) "USER=root"); /* needed? why? */
... ...

	/* Check if we are supposed to be in single user mode */
	if (argv[1]
	 && (strcmp(argv[1], "single") == 0 || strcmp(argv[1], "-s") == 0 || LONE_CHAR(argv[1], '1'))
	) {
... ...
	} else {
		/* Not in single user mode - see what inittab says */

		/* NOTE that if CONFIG_FEATURE_USE_INITTAB is NOT defined,
		 * then parse_inittab() simply adds in some default
		 * actions (i.e., INIT_SCRIPT and a pair
		 * of "askfirst" shells) */
		/* Parse /etc/inittab file; inittab entries are executed in order: SYSINIT->WAIT->ONCE->RESPAWN|ASKFIRST. */
		parse_inittab();
	}
... ...
	/* Now run the looping stuff for the rest of forever */
	while (1) {
... ...
		/* Wait for any child process(es) to exit */
		while (1) {
			/* -1 means wait for any child process. Returns PID of terminated child on success, -1 on error,
			 * or 0 if WNOHANG is specified and no status change occurred for the target child.
			 */
			wpid = waitpid(-1, NULL, WNOHANG);
			if (wpid <= 0)
				break;

			/* Set init_action->pid to 0. */
			a = mark_terminated(wpid);
			if (a) {
				message(L_LOG, "process '%s' (pid %u) exited. "
						"Scheduling for restart.",
						a->command, (unsigned)wpid);
			}
		}
... ...
	} /* while (1) */
}

Within init_main(), the function parse_inittab(void) is called. If /etc/inittab is not configured, parse_inittab(void) applies some default configurations.

/* NOTE that if CONFIG_FEATURE_USE_INITTAB is NOT defined,
 * then parse_inittab() simply adds in some default
 * actions (i.e., runs INIT_SCRIPT and then starts a pair
 * of "askfirst" shells).  If CONFIG_FEATURE_USE_INITTAB
 * _is_ defined, but /etc/inittab is missing, this
 * results in the same set of default behaviors.
 */
static void parse_inittab(void)
{
#if ENABLE_FEATURE_USE_INITTAB
	char *token[4];
	parser_t *parser = config_open2("/etc/inittab", fopen_for_read);

	if (parser == NULL)
... ...
		/* No inittab file - set up some default behavior */
		/* Sysinit */
		new_init_action(SYSINIT, INIT_SCRIPT, "");
... ...
	}
... ...
}

Inside parse_inittab, new_init_action(SYSINIT, INIT_SCRIPT, "") is called, which determines that the initialization script to be executed next is the one defined by INIT_SCRIPT. The default SYSINIT script is /etc/init.d/rcS.

/* Default sysinit script. */
#ifndef INIT_SCRIPT
# define INIT_SCRIPT  "/etc/init.d/rcS"
#endif

Checking the content of /etc/inittab in the system confirms that /etc/init.d/rcS will be executed at boot:

rcS:12345:wait:/etc/init.d/rcS

/etc/init.d/rcS

The /etc/init.d/rcS script sequentially executes files matching /etc/init.d/S??* (where ?? represents numbers), launching necessary system services. In implementation, startup scripts are divided into two types: those ending with .sh and others. The execution differs as follows:

  • Scripts ending in .sh: Files ending with .sh are executed using the source command (. filename). Commands run within the current shell process rather than in a subshell. This allows faster execution and preserves variables and environment settings in the current shell.

  • Non .sh scripts: Files without the .sh extension are executed directly via $i start, launching them in a separate process.

#!/bin/sh


# Start all init scripts in /etc/init.d
# executing them in numerical order.
#
for i in /etc/init.d/S??* ;do

     # Ignore dangling symlinks (if any).
     [ ! -f "$i" ] && continue

     case "$i" in
        *.sh)
            # Source shell script for speed.
            (
                trap - INT QUIT TSTP
                set start
                . $i
            )
            ;;
        *)
            # No sh extension, so fork subprocess.
            $i start
            ;;
    esac
done

4.4.1.3. Porting and Development

During development and porting, users can easily add or modify auto-start scripts without rebuilding the system image. This is particularly useful during debugging and development, allowing quick validation of auto-start functionality. Based on the above analysis, users can add executable programs under /etc/init.d with names starting with S followed by two digits (e.g., S99auto_startup or S99auto_startup.sh). These programs will automatically run at system startup.

Example: Creating an Initialization Script

Step 1: Create the Script File

vi /etc/init.d/S99my_custom_service

Step 2: Edit the Script

Paste the following content into the editor and save the file.

#!/bin/sh

# Path to your program
PROG="/path/to/your/program"

# Optional arguments for your program
ARGS=""

start() {
    echo "Starting my_custom_service"
    $PROG $ARGS &
}

stop() {
    echo "Stopping my_custom_service"
    # Add commands to stop your service gracefully
}

restart() {
    stop
    sleep 1
    start
}

case "$1" in
  start)
    start
    ;;
  stop)
    stop
    ;;
  restart)
    restart
    ;;
  *)
    echo "Usage: $0 {start|stop|restart}"
    exit 1
    ;;
esac

exit 0

Step 3: Set Permissions

Ensure the script has execute permissions:

chmod +x /etc/init.d/S99my_custom_service

Usage Tips

  • Start service: sudo /etc/init.d/S99my_custom_service start

  • Stop service: sudo /etc/init.d/S99my_custom_service stop

  • Restart service: sudo /etc/init.d/S99my_custom_service restart

Notes

  • Replace /path/to/your/program with the actual path to your program.

  • Edit the ARGS variable as needed to pass required parameters to your program.

  • Add appropriate stop and restart commands in the stop and restart sections.

  • Auto-start programs are prioritized by the number in their prefix (S??); smaller numbers indicate higher priority, ranging from 0 to 99.

In the current system, a pre-configured auto-start script /etc/init.d/S99auto_startup exists. During boot, this script automatically searches for startup.sh in the /app and /userdata/ directories. If startup.sh exists and has execute permissions, it will be automatically executed.

#!/bin/bash
... ...
start()
{
        APP_STARTUP="/app/startup.sh"
        USERDATA_STARTUP="/userdata/startup.sh"

        if [ -x "$APP_STARTUP" ]; then
                echo -n "<$LOG_INFO>Starting custom script: $APP_STARTUP" > /dev/kmsg
                $APP_STARTUP &
        fi

        if [ -x "$USERDATA_STARTUP" ]; then
                echo -n "<$LOG_INFO>Starting custom script: $USERDATA_STARTUP" > /dev/kmsg
                $USERDATA_STARTUP &
        fi
}
... ...

During application debugging and development, users can leverage this auto-start mechanism to add startup programs without regenerating the system image.

4.4.1.4. Common Issues

  • What could cause an auto-start script to fail?
    Common causes include missing execute permissions, incorrect paths, or dependencies on services that have not yet started.

  • How can you verify whether an auto-start script has executed?
    You can check system logs or add logging output within the script to confirm successful execution.