03 openwrt的启动过程
引用博客:https://clockworkbird9.wordpress.com/2016/09/
[ 2.824545] VFS: Mounted root (ext4 filesystem) readonly on device 179:1.
[ 2.833446] Freeing unused kernel memory: 244K (84733000 - 84770000)
[ 3.006884] init: Console is alive
[ 3.011436] init: - watchdog -
[ 3.329383] init: - preinit -
[ 6.570976] mount_root: mounting /dev/root
[ 6.579281] EXT4-fs (mmcblk0p1): re-mounted. Opts: (null)
[ 6.596450] procd: - early -
[ 6.599817] procd: - watchdog -
[ 7.301153] procd: - ubus -
[ 7.362047] procd: - init -
可以明显的看到procd
进程接管了init
进程
1 启动流程
u-boot
它配置低级硬件,加载Linux
内核image
和设备树blob
,最后使用内核cmdline
跳转到RAM
中的Linux
内核映像;Kernel -> Hareware
Linux Kernel
初始化Hareware
Kernel -> filesystem
将挂载根文件系统Kernel -> Init Process (PID 1)
内核初始化进程Openwrt -> Preinit
openwrt
初始化进程,注意这里是Preinit
函数,不是脚本Openwrt -> Procd、perinit(脚本)
procd
回去调用/etc/rc.d
preinit
初始化完成之后。初始化过程就结束了
2 Preinit
2.1 /etc/preinit
OpenWRT
会将OpenWRT
初始化进程preinit
注入到内核初始化进程列表中(kernel_init
)。
此时设备会去执行/etc/preinit
位于package/base-files/etc/
#!/bin/sh
# Copyright (C) 2006-2016 OpenWrt.org
# Copyright (C) 2010 Vertical Communications
### 设备第一次执行到这里PREINIT参数未定义,因此/sbin/init会被执行
### 因此/sbin/init为设备的第一次初始化过程
[ -z "$PREINIT" ] && exec /sbin/init
export PATH="%PATH%"
. /lib/functions.sh
. /lib/functions/preinit.sh
. /lib/functions/system.sh
### boot_hook_init在 /lib/functions/preinit.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
### 执行/lib/preinit/下所有脚本
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
/sbin/init
是procd/init.d/init.c
编译的可执行文件。
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
* |->early_mounts
* | |-> mount
* | |->early_dev 设置环境变量
* |->LOG("Console is alive")
*/
early();
/* cmdline
* |-> get init_debug 获取init_debug等级
*/
cmdline();
/* watchdog_init
* |->LOG("- watchdog -")
*/
watchdog_init(1);
pid = fork();
if (!pid) {
/* /sbin/kmodloader
* |-> /etc/modules-boot.d 加载驱动
*/
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
* |-> LOG("- preinit -")
* |-> fork->procd
* |-> setenv("PREINIT", "1", 1)
* |-> fork->sh /etc/preinit
*/
preinit();
uloop_run();
return 0;
}
initd/preinit.c
void
preinit(void)
{
// perinit脚本
char *init[] = { "/bin/sh", "/etc/preinit", NULL };
// procd
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) {
/* plug "/sbin/procd", "-h", "/etc/hotplug-preinit.json"
* 先执行procd 入参为 -h /etc/hotplug-preinit.json
*/
execvp(plug[0], plug);
ERROR("Failed to start plugd: %m\n");
exit(EXIT_FAILURE);
}
if (plugd_proc.pid <= 0) {
ERROR("Failed to start new plugd instance: %m\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: %m\n");
else
close(fd);
preinit_proc.cb = spawn_procd;
preinit_proc.pid = fork();
if (!preinit_proc.pid) {
/* init "/bin/sh", "/etc/preinit
*/
execvp(init[0], init);
ERROR("Failed to start preinit: %m\n");
exit(EXIT_FAILURE);
}
if (preinit_proc.pid <= 0) {
ERROR("Failed to start new preinit instance: %m\n");
return;
}
uloop_process_add(&preinit_proc);
DEBUG(4, "Launched preinit instance, pid=%d\n", (int) preinit_proc.pid);
}
回调函数:回调函数与普通函数的区别在于在回调函数中主程序会把回调函数像参数一样传入库函数
fork procd
进程,指定了hotplug-preinit.json
,所以会执行hotplug_run
int main(int argc, char **argv)
{
int ch;
char *dbglvl = getenv("DBGLVL");
int ulog_channels = ULOG_KMSG;
if (dbglvl) {
debug = atoi(dbglvl);
unsetenv("DBGLVL");
}
while ((ch = getopt(argc, argv, "d:s:h:S")) != -1) {
switch (ch) {
case 'h':
/* 建立netlink通讯机制,完成内核的交互,监听uevent事件
*/
return hotplug_run(optarg);
case 's':
ubus_socket = optarg;
break;
case 'd':
debug = atoi(optarg);
break;
case 'S':
ulog_channels = ULOG_STDIO;
break;
default:
return usage(argv[0]);
}
}
ulog_open(ulog_channels, LOG_DAEMON, "procd");
setsid();
uloop_init();
procd_signal();
if (getpid() != 1)
procd_connect_ubus();
else
/* 状态机处理,实际效果如下
* [ 6.596450] procd: - early -
* [ 6.599817] procd: - watchdog -
* [ 7.301153] procd: - ubus -
* [ 7.362047] procd: - init -
*/
procd_state_next();
uloop_run();
uloop_done();
return 0;
}
procd_state_next
处理完状态之后设备设备会执行到rcS.c
int rcS(char *pattern, char *param, void (*q_empty)(struct runqueue *))
{
runqueue_init(&q);
q.empty_cb = q_empty;
q.max_running_tasks = 1;
// 这里便是我们常见的自启动脚本的地方
// 需要知道的是K开头的文件为stop,S开头的文件为start
return _rc(&q, "/etc/rc.d", pattern, "*", param);
}
这里引用别人的图解
1、early()
是init
中的第一个函数。它有四个主要任务:
early_mounts(): mount /proc, /sysfs, /dev, /tmp
;early_env()
: 使用/usr/sbin:/sbin:/usr/bin:/bin
设置PATH
参数;- 初始化
/dev/console
; - 从
init
打印第一条消息:"控制台是一个活的",如上所示;
2、cmdline()
是第二个函数,它从/proc/cmdline
读取内核引导命令行并解析init_debug
参数;
3、watchdog_init()
初始化监视程序 /dev/watchdog
并打印第二条消息 "- 监视程序 -" 如上所示;
4、fork
一个新线程,让/sbin/kmodloader
加载有关/etc/modules-boot.d/
的设备驱动程序;
5、uloop_init()
初始化uloop
,这是一个事件循环实现。后来的procd
和sh /etc/preinit
将由uloop
管理;
6、preinit()
有四个主要任务:
- 打印第三条消息:
"- preinit -"
,如上所示; fork()
一个新的线程来执行sh /etc/preinit
。这将是第二次执行此初始化脚本。一个名为spawn_procd()
的回调函数将在sh /etc/preinit
完成后执行。
注意:spawn_procd()
将从/tmp/debuglevel
读取系统调试级别,并将其设置为env DBGLVL
。它还将看门狗fd
设置为env WDTFD
。最后,它将分叉真正的/sbin/procd
作为deamon
。set env
变量PREINIT with setenv("PREINIT", "1", 1)
;fork()
一个新的线程,用于执行/sbin/procd
程序,参数为-h /etc/hotplug-preinit.json
。- 注意:这个新线程将通过
uloop_process_add()
以及一个callbakc
函数添加到uloop
中,作为当/sbin/procd – h
完成时,回调函数plugd_proc_cb()
3 启动脚本示例
#!/bin/sh /etc/rc.common ### 这里/etc/common表明使用了此文件中的提供的一些基本函数
START=15 ### 执行顺序,改变之后需要执行/etc/init.d/hello enable才能生效
STOP=85
start() {
}
stop() {
}
restart() {
}
3.1 rc.common
start
:启动服务stop
:关闭服务restart
:重启服务reload
:重新读取配置enable
:打开服务自启动,即将文件软链接到/etc/rc.d
disable
:关闭服务自启动。enabled
:查询当前自启动状态boot
:调用start
shutdown
:调用stop
help
: