引用博客: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
: