启动流程
这里主要梳理 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 的流程如下:
3. /etc/preinit
OpenWRT 启动流程(二) /etc/preinit 脚本分析
细化一下 /etc/preinit 执行的动作
/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替换操作:
```javascript
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
其中在编译过程中,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