OpenWrt 根文件系统启动过程分析

内核启动加载根文件系统后,执行的第一个脚本是init,具体参见内核源码kernel/init/main.c

static int __ref kernel_init(void *unused)
{
    ...
    ...
    ...
        
	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/init.txt for guidance.");
}

依次尝试执行/sbin/init,/etc/init,/bin/init,/bin/sh, openwrt 的根文件系统里面是使用的/sbin/init,它是一个可执行程序,来自procd源码。查看procd的CMakeLists,判断函数入口在init.c中,

ADD_EXECUTABLE(init initd/init.c initd/early.c initd/preinit.c initd/mkdev.c watchdog.c
	utils/utils.c ${SOURCES_ZRAM})

procd源码会编译出好几个执行文件,包括:prcod、init、udevtrigger、askfirst等,还有根据配置打开的ujail、utrace。

preinit阶段

init程序里面:

int
main(int argc, char **argv)
{
	pid_t pid;

	ulog_open(ULOG_KMSG, LOG_DAEMON, "init");

	sigaction(SIGTERM, &sa_shutdown, NULL);
	sigaction(SIGUSR1, &sa_shutdown, NULL);
	sigaction(SIGUSR2, &sa_shutdown, NULL);

	early();
	cmdline();
	watchdog_init(1);

	pid = fork();
	if (!pid) {
		char *kmod[] = { "/sbin/kmodloader", "/etc/modules-boot.d/", NULL };

		if (debug < 3)
			patch_stdio("/dev/null");

		execvp(kmod[0], kmod);
		ERROR("Failed to start kmodloader\n");
		exit(-1);
	}
	if (pid <= 0) {
		ERROR("Failed to start kmodloader instance\n");
	} else {
		int i;

		for (i = 0; i < 1200; i++) {
			if (waitpid(pid, NULL, WNOHANG) > 0)
				break;
			usleep(10 * 1000);
			watchdog_ping();
		}
	}
	uloop_init();
	preinit();
	uloop_run();

	return 0;
}

主要是:

early();
	--->early_mounts();	//挂载proc、sysfs、tmpfs等这些
	--->early_env();	//设置PATH环境变量

watchdog_init(1);		//设置看门狗

cmdline();				//处理/proc/cmdline的内容,主要读取bootloader设置的bootargs,看init_debug的值,设置调试等级。

调用/sbin/kmodloader,按照/etc/modules-boot.d/的配置加载模块

preinit();				//详见下面分析

init的最后阶段调用了preinit()函数,

void
preinit(void)
{
	char *init[] = { "/bin/sh", "/etc/preinit", NULL };
	char *plug[] = { "/sbin/procd", "-h", "/etc/hotplug-preinit.json", NULL };
	int fd;

	LOG("- preinit -\n");

	plugd_proc.cb = plugd_proc_cb;
	plugd_proc.pid = fork();
	if (!plugd_proc.pid) {
		execvp(plug[0], plug);
		ERROR("Failed to start plugd\n");
		exit(-1);
	}
	if (plugd_proc.pid <= 0) {
		ERROR("Failed to start new plugd instance\n");
		return;
	}
	uloop_process_add(&plugd_proc);

	setenv("PREINIT", "1", 1);

	fd = creat("/tmp/.preinit", 0600);

	if (fd < 0)
		ERROR("Failed to create sentinel file\n");
	else
		close(fd);

	preinit_proc.cb = spawn_procd;
	preinit_proc.pid = fork();
	if (!preinit_proc.pid) {
		execvp(init[0], init);
		ERROR("Failed to start preinit\n");
		exit(-1);
	}
	if (preinit_proc.pid <= 0) {
		ERROR("Failed to start new preinit instance\n");
		return;
	}
	uloop_process_add(&preinit_proc);

	DEBUG(4, "Launched preinit instance, pid=%d\n", (int) preinit_proc.pid);
}

在preinit里面主要干了这些事情:

execvp(plug[0], plug);				//执行`/sbin/procd -h /etc/hotplug-preinit.json`
setenv("PREINIT", "1", 1);			//设置环境变量PREINIT为1
fd = creat("/tmp/.preinit", 0600);	//创建/tmp/.preinit标识文件
execvp(init[0], init);				//子进程执行/etc/preinit
uloop_process_add(&preinit_proc);	//注册一个回调,等上面执行/etc/preinit的子进程退出,执行spawn_procd

/sbin/procd -h /etc/hotplug-preinit.json preinit阶段的hotplug规则。


脚本/etc/preinit:

#!/bin/sh
# Copyright (C) 2006-2016 OpenWrt.org
# Copyright (C) 2010 Vertical Communications

[ -z "$PREINIT" ] && exec /sbin/init

export PATH="/usr/sbin:/usr/bin:/sbin:/bin"

. /lib/functions.sh
. /lib/functions/preinit.sh
. /lib/functions/system.sh

boot_hook_init preinit_essential
boot_hook_init preinit_main
boot_hook_init failsafe
boot_hook_init initramfs
boot_hook_init preinit_mount_root

for pi_source_file in /lib/preinit/*; do
	. $pi_source_file
done

boot_run_hook preinit_essential

pi_mount_skip_next=false
pi_jffs2_mount_success=false
pi_failsafe_net_message=false

boot_run_hook preinit_main

执行过程中首先需要了解的是boot_hook_init、boot_run_hook、boot_add_hook这几个函数,他们定义在/lib/functions/preinit.sh中,不深入分析这几个函数的实现,可以认为是:

boot_hook_init list_name					//初始化一个名字为list_name的回调列表
boot_add_hook list_name cb_name				//向名字为list_name的回调列表中添加一个回调函数cb_name
boot_run_hook list_name						//调用回调列表list_name里面添加的回调函数

这样做是方便按照一定的功能分类和调用顺序去执行一系列的函数。具体到/etc/preinit里面就是:

//初始化一系列的回调列表
boot_hook_init XXX	

//从/lib/preinit/目录下按照名字顺序添加回调
for pi_source_file in /lib/preinit/*; do
	. $pi_source_file
done

//执行从/lib/preinit/下面脚本添加的回调函数
boot_run_hook preinit_essential		//<==我的lede17.01这个是空的,未添加任何cb
boot_run_hook preinit_main

spawn_procd主要是清理preinit阶段的一些产物,最后执行/sbin/procd

static void
spawn_procd(struct uloop_process *proc, int ret)
{
	char *wdt_fd = watchdog_fd();
	char *argv[] = { "/sbin/procd", NULL};
	struct stat s;
	char dbg[2];

	if (plugd_proc.pid > 0)
		kill(plugd_proc.pid, SIGKILL);

	if (!stat("/tmp/sysupgrade", &s))
		while (true)
			sleep(1);

	unsetenv("INITRAMFS");
	unsetenv("PREINIT");
	unlink("/tmp/.preinit");
	DEBUG(2, "Exec to real procd now\n");
	if (wdt_fd)
		setenv("WDTFD", wdt_fd, 1);
	check_dbglvl();
	if (debug > 0) {
		snprintf(dbg, 2, "%d", debug);
		setenv("DBGLVL", dbg, 1);
	}

	execvp(argv[0], argv);
}
上一篇:Shell编程之正则表达式


下一篇:GFPGAN源码分析—第八篇