
在Openwrt系统中执行ps命令可以看到 1号进程就是procd.
root@OpenWrt:/# ps -w
PID USER VSZ STAT COMMAND
1 root 1856 S /sbin/procd但实际上内核启动完成后,运行的第一个用户进程并不是procd,在运行procd之前还执行了其他准备工作,换句话说,procd并不一开始就是“老大”,它只是最终接替了老大的位置。
下面就从内核即将启动用户空间第一个进程开始介绍Openwrt系统的启动流程。
在kernel启动的尾声,内核会去查找并调用 用户空间的init进程,从而进行内核态到用户态的切换,init进程就是用户空间的第一个进程,它的进程号为1 。
init进程路径可以通过如下方式指定:
如下2种方式是通过cmdline或者设备树获取init进程路径
如下方式是通过内核配置指定init进程路径
如下4种方式是直接运行指定的程序作为init进程(按顺序查找,如果同时存在也只会运行第一个):
static int __ref kernel_init(void *unused)
{
/*
省略部分初始化
*/
if (ramdisk_execute_command) {
ret = run_init_process(ramdisk_execute_command);
if (!ret)
return 0;
pr_err("Failed to execute %s (error %d)\n",
ramdisk_execute_command, ret);
}
if (execute_command) {
ret = run_init_process(execute_command);
if (!ret)
return 0;
panic("Requested init %s failed (error %d).",
execute_command, ret);
}
if (CONFIG_DEFAULT_INIT[0] != '\0') {
ret = run_init_process(CONFIG_DEFAULT_INIT);
if (ret)
pr_err("Default init %s failed (error %d)\n",
CONFIG_DEFAULT_INIT, ret);
else
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;
}
static int run_init_process(const char *init_filename) {
argv_init[0] = init_filename;
pr_info("Run %s as init process\n", init_filename);
return do_execve(getname_kernel(init_filename),
(const char __user *const __user *)argv_init,
(const char __user *const __user *)envp_init);
}
static int try_to_run_init_process(const char *init_filename) {
ret = run_init_process(init_filename);
}我目前使用的平台就是使用execute_command方式指定init进程的。
$ cat /proc/cmdline
console=ttyserial0,115200,n8 init=/etc/preinit启动log中也有相关打印:
[1:swapper/0][name:main&]Run /etc/preinit as init process接下来就开始分析/etc/preinit
etc/preinit是openwrt openwrt/package/base-files/files目录下的一个shell 脚本,其内容如下:
#!/bin/sh
[ -z "$PREINIT" ] && exec /sbin/init
export PATH="%PATH%"
. /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当前我们只需要关注第一行
[ -z "$PREINIT" ] && exec /sbin/init这行意思很明显,如果"
那么这个/sbin/init又是从哪里来的呢?
查看procd的packge makefile(openwrt/package/system/procd/Makefile)
define Package/procd/install
$(INSTALL_DIR) $(1)/sbin $(1)/etc $(1)/lib/functions
# /sbin/init 来自下面这一句
$(INSTALL_BIN) $(PKG_INSTALL_DIR)/usr/sbin/{init,procd,askfirst,udevtrigger,upgraded} $(1)/sbin/
$(INSTALL_DATA) $(PKG_INSTALL_DIR)/usr/lib/libsetlbf.so $(1)/lib
$(INSTALL_BIN) ./files/reload_config $(1)/sbin/
$(INSTALL_CONF) ./files/hotplug*.json $(1)/etc/
$(INSTALL_DATA) ./files/procd.sh $(1)/lib/functions/
endefinit,procd,askfirst,udevtrigger,upgraded这些命令都属于procd组件,最终都会被安装到/sbin.
/sbin/init执行流程如下:

init(debug log中带有init tag的log都是由init服务打印的)/bin,/sbin、/usr/bin等目录下面的命令init_debug=xxx字段获取fork一个子进程执行/sbin/kmodloader,即加载内核模块,并且会一直等待所有模块加载完成(即子进程结束)preinit()uloop_run到目前为止,我们的主角procd还没有出场,上述过程仍然是在进行预初始化。下面再接着分析preinit()过程:

uloop_process功能,这是libubox提供的一个组件,用于管理子进程。fork了2个子进程,一个运行/sbin/procd -h /etc/hotplug-preinit.json ,另一个执行/etc/preinit脚本,这些子进程在执行完毕后,都会调用对应的uloop_process.cb函数。/sbin/procd -h /etc/hotplug-preinit.json 的作用是监听内核uevent事件,并根据不同事件做出相应的处理(例如创建/dev/null设备节点)plugd_proc.cb函数执行的内容很简单,就是设置plugd_proc.pid=0/etc/preinit,这里又一次运行了这个脚本,只不过目前PREINIT=1,所以并不会再次执行/sbin/init,这里会执行/etc/preinit脚本后面的内容。/etc/preinit执行完成后,会调用preinit_proc.cb函数,这个函数里非常重要的一个步骤就是execvp(/sbin/procd),execvp函数会将当前进程的可执行文件替换成/sbin/procd并执行/sbin/procd,这里实际上实现了init到procd的转换,procd在这里就成为了pid=1的进程。/sbin/procd的执行流程如下:

procd执行过程中,最为关键的就是procd_state_next(),它会进行状态的流转,初始状态是STATE_EARLY STATE_EARLY阶段调用hotplug("/etc/hotplug.json")监听内核uevent事件,然后调用procd_coldplug()函数执行udevtrigger命令触发uevent事件,这一步完成后创建/dev/xxx设备节点,然后进入STATE_UBUSSTATE_UBUS阶段主要做2件事:1.启动ubusd 2.连接ubusd,最后进入状态STATE_INITSTATE_INIT阶段首先解析/etc/inittab脚本,然后依次运行respawn,askconsole,askfirst,sysinit对应的handler,最后进入STATE_RUNNING.STATE_RUNNING阶段会依次运行respawnlate,askconsolelate对应的handleruloop_run