openwrt 启动流程及配置文件生成解析

启动流程

这里主要梳理 kernel 引导,加载 /sbin/init, 然后引导执行 /etc/preinit 接着是 /sbin/procd . 然后整个过程中涉及到的 主板识别, 初始化配置生成 及 系统配置文件生成 这个流程。

其实已经在[[ openwrt led机制.md ]]文档中有过部分记录,这里做一下详细梳理。

1. kernel引导启动

kernel引导启动后,在kernel代码 init/main.c文件中:

static int __ref kernel_init(void *unused)
{
    int ret;
    // ...
    /* init= 方式传递启动项参数到 execute_command */
    if (execute_command) {
        ret = run_init_process(execute_command);
        if (!ret)
            return 0;
    }
    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;
}

老版本的openwrt系统,会有patch替换到启动脚本为 /etc/preinit ,但是我当前分析的系统 openwrt 21已经是使用默认的 /sbin/init 了。

2. /sbin/init

/sbin/init 进程分析
OpenWrt 启动顺序_preinit

/sbin/init从哪里来呢,答案就是 procd。
看一下 /sbin/init 的源码:

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

	ulog_open(ULOG_KMSG, LOG_DAEMON, "init");
    // 挂载了 /proc 、/sys 、/dev 、/tmp …等目录,并设置 PATH 环境变量
	early();
    // 从cmd line中获取debug log等级
	cmdline();
    // 开启看门狗
	watchdog_init(1);

	pid = fork();
	if (!pid) {
        ///etc/modules-boot.d/ 目录下保存了需要开机自动加载的kernel模块信息。
		char *kmod[] = { "/sbin/kmodloader", "/etc/modules-boot.d/", NULL };

		if (debug < 3)
			patch_stdio("/dev/null");
        //加载kernel模块
		execvp(kmod[0], kmod);
		ERROR("Failed to start kmodloader: %m\n");
		exit(EXIT_FAILURE);
	}
	if (pid <= 0) {
		ERROR("Failed to start kmodloader instance: %m\n");
	} else {
		const struct timespec req = {0, 10 * 1000 * 1000};
		int i;

		for (i = 0; i < 1200; i++) {
			if (waitpid(pid, NULL, WNOHANG) > 0)
				break;
			nanosleep(&req, NULL);
			watchdog_ping();
		}
	}
	uloop_init();
    // 执行/sbin/procd -h /etc/hotplug-preinit.json, 然后是 /bin/sh /etc/preinit ,并在/etc/preinit 执行结束的回调函数中调用 /sbin/procd
	preinit();
	uloop_run();

	return 0;
}

大概看一下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");
    //执行 /sbin/procd -h /etc/hotplug-preinit.json
	plugd_proc.cb = plugd_proc_cb;  // 回调函数中将pid设置为0
	plugd_proc.pid = fork();
	if (!plugd_proc.pid) {
		execvp(plug[0], plug);
		ERROR("Failed to start plugd: %m\n");
		exit(EXIT_FAILURE);
	}
	uloop_process_add(&plugd_proc);
    // 设置环境变量
	setenv("PREINIT", "1", 1);

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

    //执行 /etc/preinit
	preinit_proc.cb = spawn_procd;     //回调函数中调用 /sbin/procd
	preinit_proc.pid = fork();
	if (!preinit_proc.pid) {
		execvp(init[0], init);
		ERROR("Failed to start preinit: %m\n");
		exit(EXIT_FAILURE);
	}
	uloop_process_add(&preinit_proc);
}

用流程图梳理一下上面 /sbin/init 的流程如下:

kernel引导
/sbin/init
/sbin/procd -h /etc/hotplug-preinit.json
PREINIT=1
/bin/sh /etc/preinit
preinit 结束后,回调 /sbin/procd

3. /etc/preinit

OpenWRT 启动流程(二) /etc/preinit 脚本分析

细化一下 /etc/preinit 执行的动作

/bin/sh /etc/preinit
hook节点初始化 preinit_essential preinit_main failsafe initramfs preinit_mount_root
遍历 /lib/preinit/* 文件进行 boot_hook_add
run preinit_essential 内函数
run preinit_main 内函数
/lib/preinit/
# 配置参数
- 00_preinit.conf
# led灯相关函数
- 02_default_set_state
# 主板名称识别,从dts中解析
- 02_sysinfo
- 02_sysinfo_fixup
# eth 接口重新排序,相当于是重命名
- 05_layerscape_reorder_eth
# 设置接口的mac地址
- 10_fix_eth_mac.sh
# failsafe 状态的一些信息效果
- 10_indicate_failsafe
# preinit 状态的一些状态信息效果
- 10_indicate_preinit
- 30_failsafe_wait
- 40_run_failsafe_hook
# regular_preinit
- 50_indicate_regular_preinit
- 70_initramfs_test
# 证书分区挂载
- 75_certificates
# 挂载对应的boot分区
- 79_move_config
# 文件系统挂载,然后检查并恢复备份文件
- 80_mount_root
- 81_urandom_seed
- 99_10_failsafe_dropbear
- 99_10_failsafe_login
- 99_10_run_init

主要看一下 80_mount_root 内部细节,这个和文件系统挂载有关:

root@MUXI:/lib/preinit# cat 80_mount_root
# Copyright (C) 2006 OpenWrt.org
# Copyright (C) 2010 Vertical Communications

missing_lines() {
        local file1 file2 line
        file1="$1"
        file2="$2"
        oIFS="$IFS"
        IFS=":"
        while read line; do
                set -- $line
                grep -q "^$1:" "$file2" || echo "$*"
        done < "$file1"
        IFS="$oIFS"
}

do_mount_root() {
        mount_root  # 这个文件是按照 "/usr/sbin:/usr/bin:/sbin:/bin" 顺序查找 可执行文件位置,参考 3.1 章节
        boot_run_hook preinit_mount_root    # 其实是 79_move_config 内的 move_config 函数
        [ -f /sysupgrade.tgz ] && {
                echo "- config restore -"
                cp /etc/passwd /etc/group /etc/shadow /tmp
                cd /
                tar xzf /sysupgrade.tgz
                missing_lines /tmp/passwd /etc/passwd >> /etc/passwd
                missing_lines /tmp/group /etc/group >> /etc/group
                missing_lines /tmp/shadow /etc/shadow >> /etc/shadow
                rm /tmp/passwd /tmp/group /tmp/shadow
                # Prevent configuration corruption on a power loss
                sync
        }
}

[ "$INITRAMFS" = "1" ] || boot_hook_add preinit_main do_mount_root

而 /sbin/mount_root 这个文件是 fstools 工具编译生成的。

3.1 openwrt 运行环境变量

这里单独记录一点信息,在分析这个启动脚本的时候发现的两点信息,暂时先记录在这儿,后面整理启动流程的时候再挪过去:
openwer config文件中定义了两个参数:

CONFIG_TARGET_INIT_PATH="/usr/sbin:/usr/bin:/sbin:/bin"
CONFIG_TARGET_INIT_CMD="/sbin/init"

其中在编译过程中,package/base-files/Makefile文件中定义了配置文件写入及PATH替换操作:

    mkdir -p $(1)/lib/preinit
    echo 'pi_init_path="$(TARGET_INIT_PATH)"' >>$(1)/lib/preinit/00_preinit.conf
    echo 'pi_init_cmd=$(if $(CONFIG_TARGET_INIT_CMD),$(CONFIG_TARGET_INIT_CMD),"/sbin/init")' >>$(1)/lib/preinit/00_preinit.conf

同时还有参数替换,这个是指定了PATH路径,如果需要添加自己的路径信息,可以修改config文件来实现。

    $(SED) "s#%PATH%#$(TARGET_INIT_PATH)#g" \
        $(1)/sbin/hotplug-call \
        $(1)/etc/preinit \
        $(1)/etc/profile

4.配置文件生成

三个配置文件,一个是 /tmp/board.json, 一个是 /etc/board.json, 另一个是 /etc/config/system

设备配置文件的生成有三种途径:
1./etc/board.d/目录下脚本
2./bin/config_generate 脚本 (/etc/init.d/boot 调用)
3.uci-defaults目录下脚本 (/etc/init.d/boot 调用)

4.1 board.json

针对/etc/board.d/目录下脚本,会有2个调用处。
第一个是
/lib/preinit/10_indicate_preinit -> preinit_ip -> preinit_config_board -> /bin/board_detect /tmp/board.json -> 调用执行 /etc/board.d/ 目录下的可执行文件,生成配置信息。 -> /etc/board.d/***
第二个是
/etc/rc.d/S10boot -> /bin/config_generate -> /bin/board_detect /etc/board.json -> 调用执行 /etc/board.d/ 目录下的可执行文件,生成配置信息。 -> /etc/board.d/***

我看了下/etc/board.d/*** 目录下脚本中实现,其中 board_config_update、ucidef_set_led_xxx 、 board_config_flush 等函数实现主要是生成json 格式内容。
具体的函数实现可以看一下 /lib/functions/uci-defaults.sh 脚本内实现方法。

4.2 /bin/config_generate 生成配置文件

前面生成了 /etc/board.json文件之后,在 /bin/config_generate 文件中生成 /etc/config/ 目录下配置文件

4.3 uci-defaults 配置文件生成

参考资料

OpenWRT 启动流程(一) /sbin/init 进程分析
https://blog.csdn.net/agave7/article/details/86686145
openwrt overlayfs挂载过程
https://blog.csdn.net/ccwzhu/article/details/106059451
openwrt文件系统挂载
https://blog.csdn.net/caoshunxin01/article/details/79355428
Preinit and Root Mount and Firstboot Scripts
https://openwrt.org/docs/techref/preinit_mount
Openwrt启动流程及启动脚本分析
https://blog.csdn.net/wwx0715/article/details/41725917
https://www.cnblogs.com/rohens-hbg/articles/4775094.html
https://www.cnblogs.com/tinylaker/p/10573390.html
https://blog.bruceou.cn/2020/08/13-openwrt-startup-process/223/
https://blog.csdn.net/hzlarm/article/details/103409878
https://www.jianshu.com/p/18e14109c941
https://www.cnblogs.com/openwrt/p/7454809.html
https://blog.csdn.net/maclinuxye/article/details/52958717
https://blog.csdn.net/u013283985/article/details/86221846
https://blog.csdn.net/lee244868149/article/details/57396776

你可能感兴趣的:(Opwnert,openwrt)