kenrel_init()(init/main.c)函数,在kernel_init函数中,该函数首先会调用kernel_init_freeable,该函数主要完成以下工作:
1.打开/dev/console,而且该打开句柄的文件描述符是0(标准输出),接着调动sys_dup复制两个文件描述符,分别是1和2,用于标准输入和标准出错。因为它是第一个打开的文件,所以文件描述符是0,如果打开的是其他文件,标准输出就在是0了。
2.第二件事是看以下uboot有没有传启动ramdisk的命令过来,如果没有,就判断/init文件是否存在,如果存在则调用prepare_namespace函数,这个函数会完成根文件系统的挂载工作。
因为从开机的log可以看到uboot传来的启动命令[ 0.000000] Kernel command line: rootwait rootfsname=rootfs rootwait clk_ignore_unused,
所以saved_root_name=rootfs, 那么prepare_namespace()会调用name_to_dev_t()得到主次设备号并存放在ROOT_DEV(31:12),
得到主次设备号后会调用 mount_root, 该函数会调用 mount_block_root("/dev/root", root_mountflags);
mount_block_root 首先调用 get_fs_names 得到根文件系统的类型(通常由rootfstype=来指定), 然后调用 do_mount_root, 该函数会调用 sys_mount 完成任务,将根文件系统 mount 到 /root 后以后,会调用 chroot 将根目录切换到 /root 目录, 使其根文件系统变成真正的根。而原来的根只是一个虚拟的内存根。
成功log:[ 1.681344] VFS: Mounted root (squashfs filesystem) readonly on device 31:12.
31:12是mtd12 的主次设备号,我们可以用下面的命令来确认:
root@test:/dev# file /dev/mtdblock12
/dev/mtdblock12: block special (31/12)
而从flash分区情况可以知道该分区存放的是rootfs,分区表如下:
[ 1.453252] Creating 14 MTD partitions on "spi0.0":
[ 1.458100] 0x000000000000-0x000000040000 : "0:SBL1" //0号分区
[ 1.464274] 0x000000040000-0x000000060000 : "0:MIBIB"
[ 1.469425] 0x000000060000-0x0000000c0000 : "0:QSEE"
[ 1.474479] 0x0000000c0000-0x0000000d0000 : "0:CDT"
[ 1.479346] 0x0000000d0000-0x0000000e0000 : "0:DDRPARAMS"
[ 1.484785] 0x0000000e0000-0x0000000f0000 : "0:APPSBLENV"
[ 1.490212] 0x0000000f0000-0x000000170000 : "0:APPSBL"
[ 1.495430] 0x000000170000-0x000000180000 : "0:ART"
[ 1.500384] 0x000000180000-0x000000190000 : "config"
[ 1.505436] 0x000000190000-0x0000001a0000 : "pot"
[ 1.510249] 0x0000001a0000-0x0000001b0000 : "data"
[ 1.515434] 0x0000001b0000-0x000001fc0000 : "0:HLOS"
[ 1.520486] 0x000000540000-0x000001fc0000 : "rootfs" //12号分区
[ 1.525471] mtd: device 12 (rootfs) set to be root filesystem
[ 1.530832] 1 squashfs-split partitions found on MTD device rootfs
[ 1.536393] 0x000001130000-0x000001fc0000 : "rootfs_data"
执行完上面的代码后,会返回kernel_init函数,接着执行下面的代码,它首先会检查内核的启动参数中是否有设置init参数,如果有,则会使用该参数指定的程序作为init程序,否则会按照如下代码中所示的顺序依次尝试启动,如果都无法启动就会kernel panic。
如果没有给init传递参数,那么系统就会从“/etc/preinit” 开始执行,启动文件系统。
(openwrt/package/base-files/files/etc)
#!/bin/sh
# Copyright (C) 2006 OpenWrt.org
# Copyright (C) 2010 Vertical Communications
[ -z "$PREINIT" ] && exec /sbin/init
export PATH=/bin:/sbin:/usr/bin:/usr/sbin
pi_ifname=
pi_ip=192.168.1.1
pi_broadcast=192.168.1.255
pi_netmask=255.255.255.0
fs_failsafe_ifname=
fs_failsafe_ip=192.168.1.1
fs_failsafe_broadcast=192.168.1.255
fs_failsafe_netmask=255.255.255.0
fs_failsafe_wait_timeout=2
pi_suppress_stderr="y"
pi_init_suppress_stderr="y"
pi_init_path="/bin:/sbin:/usr/bin:/usr/sbin"
pi_init_cmd="/sbin/init"
. /lib/functions.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
这个初始化过程遵循如下主线:
下面我们一步一步分析这个过程。
done
这些脚本包括:
80_mount_root //这里会对overlay目录进行挂载
99_10_failsafe_login到此,/etc/preinit执行完毕并退出。如果需要跟踪调试这些脚本,可以 在/etc/preinit的最开始添加一条命令set -x,这样就会打印出执行命令的过程, 当并不会真正执行。
#####################################
因此开始运行busybox的init命令
##########################################
上面这些是在旧的openwrt下面的实现,在新的openwrt中没有pi_init_cmd这样的命令了,它在procd中实现。因为/sbin/init进程的最后一个函数preinit()函数会创建两个新的进程,一个是procd,一个是/etc/preinit,下面来仔细分析一下:
/sbin/init进程是来自procd这个package里面的,不再使用busybox了,而且它是从内核调用过来的,所以它的pid是1,pid 0是内核本身。fork创建父子进程,子进程做一些procd的配置后退出,注意这时procd并不算真正起来,它的pid不是1;父进程继续创建父子进程,子进程调用/etc/preinit后退出。在这过程中/sbin/init的pid为1,始终没有让位。
创建子进程执行/etc/preinit脚本时,此时PREINIT环境变量被设置为1,主进程(pid=1)同时使用uloop_process_add()把/etc/preinit子进程加入uloop进行监控,当/etc/preinit执行结束时回调plugd_proc_cb()函数把监控/etc/preinit进程对应对象中pid属性设置为0,表示/etc/preinit已执行完成
创建子进程执行/sbin/procd -h/etc/hotplug-preinit.json,主进程同时使用uloop_process_add()把/sbin/procd子进程加入uloop进行监控,当/sbin/procd进程结束时回调spawn_procd()函数,spawn_procd()函数繁衍后继真正使用的/sbin/procd进程,这时procd的进程号将是1。
下面这个函数会用procd将/sbin/init进程替换,从而procd的进程号为1:
从/tmp/debuglevel读出debug级别并设置到环境变量DBGLVL中,把watchdog fd设置到环境变量WDTFD中,最后调用execvp()繁衍/sbin/procd进程
这个进程以前是由busy box实现,但是现在由procd来实现了,找代码时不要找错位置。
int main(int argc, char **argv)
{
pid_t pid;
sigaction(SIGTERM, &sa_shutdown, NULL);
sigaction(SIGUSR1, &sa_shutdown, NULL);
sigaction(SIGUSR2, &sa_shutdown, NULL);
early();//-------->early.c
cmdline();
watchdog_init(1); //------->../watchdog.c
pid = fork();
if (!pid) {
char *kmod[] = { "/sbin/kmodloader", "/etc/modules-boot.d/", NULL };
if (debug < 3) {
int fd = open("/dev/null", O_RDWR);
if (fd > -1) {
dup2(fd, STDIN_FILENO);
dup2(fd, STDOUT_FILENO);
dup2(fd, STDERR_FILENO);
if (fd > STDERR_FILENO)
close(fd);
}
}
execvp(kmod[0], kmod);
ERROR("Failed to start kmodloader\n");
exit(-1);
}
if (pid <= 0)
ERROR("Failed to start kmodloader instance\n");
uloop_init();
preinit(); //-------------->watchdog.c
uloop_run();
return 0;
}
/proc
/sys
/tmp
/dev
/dev/pts
目录(early_mount)创建子进程执行/etc/preinit脚本,此时PREINIT环境变量被设置为1,主进程同时使用uloop_process_add()把/etc/preinit子进程加入uloop进行监控,当/etc/preinit执行结束时回调plugd_proc_cb()函数把监控/etc/preinit进程对应对象中pid属性设置为0,表示/etc/preinit已执行完成
创建子进程执行/sbin/procd -h/etc/hotplug-preinit.json,主进程同时使用uloop_process_add()把/sbin/procd子进程加入uloop进行监控,当/sbin/procd进程结束时回调spawn_procd()函数
spawn_procd()函数繁衍后继真正使用的/sbin/procd进程,从/tmp/debuglevel读出debug级别并设置到环境变量DBGLVL中,把watchdog fd设置到环境变量WDTFD中,最后调用execvp()繁衍/sbin/procd进程
如果存在/dev/watchdog设备,设置watchdog timeout等于30秒,如果内核在30秒内没有收到任何数据将重启系统。用户状进程使用uloop定时器设置5秒周期向/dev/wathdog设备写一些数据通知内核,表示此用户进程在正常工作
/**
* 初始化watchdog
*/
void watchdog_init(int preinit)
/**
* 设备通知内核/dev/watchdog频率(缺省为5秒)
* 返回老频率值
*/
int watchdog_frequency(int frequency)
/**
* 设备内核/dev/watchdog超时时间
* 当参数timeout<=0时,表示从返回值获取当前超时时间
*/
int watchdog_timeout(int timeout)
/**
* val为true时停止用户状通知定时器,意味着30秒内系统将重启
*/
void watchdog_set_stopped(bool val)
信息处理,下面为procd对不同信息的处理方法
procd有5个状态,分别为STATE_EARLY
、STATE_INIT
、STATE_RUNNING
、STATE_SHUTDOWN
、STATE_HALT
,这5个状态将按顺序变化,当前状态保存在全局变量state
中,可通过procd_state_next()
函数使用状态发生变化
STATE_EARLY
转变为STATE_INIT
main_object
对象,system_object
对象、watch_event
对象(procd_connect_ubus()函数),respawn
、askconsole
、askfirst
、sysinit
命令STATE_INITl
转变为STATE_RUNNING
STATE_RUNNING
状态后procd运行uloop_run()
主循环struct trigger {
struct list_head list;
char *type;
int pending;
int remove;
int timeout;
void *id;
struct blob_attr *rule;
struct blob_attr *data;
struct uloop_timeout delay;
struct json_script_ctx jctx;
};
struct cmd {
char *name;
void (*handler)(struct job *job, struct blob_attr *exec, struct blob_attr *env);
};
struct job {
struct runqueue_process proc;
struct cmd *cmd;
struct trigger *trigger;
struct blob_attr *exec;
struct blob_attr *env;
};
/**
* 初始化trigger任务队列
*/
void trigger_init(void)
/**
* 把服务和服务对应的规则加入trigger任务队列
*/
void trigger_add(struct blob_attr *rule, void *id)
/**
* 把服务从trigger任务队列中删除
*/
void trigger_del(void *id)
/**
*
*/
void trigger_event(const char *type, struct blob_attr *data)
Name | Handler | Blob_msg policy |
---|---|---|
set | service_handle_set | service_set_attrs |
add | service_handle_set | service_set_attrs |
list | service_handle_list | service_attrs |
delete | service_handle_delete | service_del_attrs |
update_start | service_handle_update | service_attrs |
update_complete | service_handle_update | service_attrs |
event | service_handle_event | event_policy |
validate | service_handle_validate | validate_policy |
Name | Handler | Blob_msg policy |
---|---|---|
board | system_board | |
info | system_info | |
upgrade | system_upgrade | |
watchdog | watchdog_set | watchdog_policy |
signal | proc_signal | signal_policy |
nandupgrade | nand_set | nand_policy |
代码库路径: package/system/procd/files/procd.sh 设备上路径: /lib/functions/procd.sh
/etc/init.d/daemon
#!/bin/sh /etc/rc.common
START=80
STOP=20
USE_PROCD=1
start_service()
{
procd_open_instance
procd_set_param command /sbin/daemon
procd_set_param respawn
procd_close_instance
}