Openwrt内核启动流程及相关脚本简易分析

版权声明:转载请声明~ https://blog.csdn.net/weixin_38890593/article/details/83384377

本人在学openwrt时,鉴于网上资料太过繁杂,故自己结合资料研究源代码重新整理一下,供学习交流!
1.简介

关于 OpenWrt
openwrt是嵌入式设备上运行的linux系统。
OpenWrt 的文件系统是可写的,开发者无需在每一次修改后重新编译,
令它更像一个小型的 Linux 电脑系统,也加快了开发速度。
你会发现无论是 ARM, PowerPC 或 MIPS 的处理器,都有很好的支持。
并且附带3000左右的软件包,用户可以方便的自定义功能来制作固件。
也可以方便的移植各类功能到openwrt下。

在openwrt中用PS查看进程会发现,进程号为1的程序是procd!
2.系统启动流程分析
如图所示(此图引用于网络图片):
openwrt启动流程图
内核启动过程:【可在…/init/mian.c中查看详细过程】
uboot–>start_kernel()–>rest_init()—>kernel_thread(kernel_init)—> kernel_init_freeable()

初始化过程:
Linux 内核(kernel_init)—>/etc/preinit —>/sbin/init —>/etc/preinit、/sbin/procd—>/sbin/procd。

/etc/preinit脚本是系统其它脚本的入口,在/etc/preinit脚本中,第一条命令为:
[ -z “$PREINIT” ] && exec /sbin/init
由于一开始PREINIT没有被设置,所以为空,执行/init脚本

其中/sbin/init 由procd/init.c编译产生的(它先执行一些ulog_open、early、cmdline等函数。最后执行preinit()函数。)
init.c部分代码如下:

	ulog_open(ULOG_KMSG, LOG_DAEMON, "init");//进行日志相关工作

	sigaction(SIGTERM, &sa_shutdown, NULL);
	sigaction(SIGUSR1, &sa_shutdown, NULL);
	sigaction(SIGUSR2, &sa_shutdown, NULL);

	early();//完成系统挂载以及设置环境变量等工作
	cmdline();//设置日志级别
	watchdog_init(1);//初始化watchdog

	pid = fork();
	if (!pid) {
		char *kmod[] = { "/sbin/kmodloader", "/etc/modules-boot.d/", NULL };

	...............................
		}
		execvp(kmod[0], kmod);//kmodloader,加载部分KO模块
		ERROR("Failed to start kmodloader\n");
		exit(-1);
	}
...........................
	}
	uloop_init();
	preinit();//调用preinit函数,下面再做分析
	uloop_run();

看启动流程,当procd退出后会调用execvp函数执行/sbin/proc,替换当前的init进程,这就是系统启动完成后,进程号为1最终为/sbin/procd的由来,中间改变了几次。

preinit()函数代码如下(有删减,具体请查看源码):

void
preinit(void)
{
	char *init[] = { "/bin/sh", "/etc/preinit", NULL };
	char *plug[] = { "/sbin/procd", "-h", "/etc/hotplug-preinit.json", NULL };

	LOG("- preinit -\n");

	plugd_proc.cb = plugd_proc_cb;
	plugd_proc.pid = fork();
	if (!plugd_proc.pid) {
		execvp(plug[0], plug);//创建进程执行procd
	}
.................................
	uloop_process_add(&plugd_proc);

	setenv("PREINIT", "1", 1);//这里配置了环境变量,第二次执行/preinit的时候就不再运行/init

	preinit_proc.cb = spawn_procd;//回调函数
	preinit_proc.pid = fork();
	if (!preinit_proc.pid) {
		execvp(init[0], init);//创建进程执行preinit
	}
..................................................
	uloop_process_add(&preinit_proc);
...............................................
}

preinit函数配置了环境变量PREINIT,然后再去fork进程来执行/preinit,执行完毕后,再调用回调函数spawn_procd,在回调函数spawn_procd中调用了execvp函数来启动/sbin/procd这个脚本,/procd最后执行/etc/init.d/目录下的文件,从而启动系统各个服务。

spawn_procd函数代码如下:

static void
spawn_procd(struct uloop_process *proc, int ret)
{
	char *wdt_fd = watchdog_fd();
	char *argv[] = { "/sbin/procd", NULL};
	struct stat s;
	char dbg[2];
.............................................
	unsetenv("INITRAMFS");
	unsetenv("PREINIT");
	DEBUG(2, "Exec to real procd now\n");
	if (wdt_fd)
		setenv("WDTFD", wdt_fd, 1);
	check_dbglvl();
...............................................
	}

	execvp(argv[0], argv);
}

下面开始介绍第二次开始执行 /etc/preinit的过程
在这里插入图片描述
上图截取自/preinit脚本,当PREINIT被设置好了,往下运行就会执行上面的代码。
可以看到三个脚本被启动:
. /lib/functions.sh
. /lib/functions/preinit.sh
. /lib/functions/system.sh
这几个脚本主要定义了shell函数,在preinit.sh中,定义了一些函数挂到hook上,当运行时,这些hook们会按函数加入的顺序来启动函数。如boot_hook_init()等函数,之后使用boot_hook_init定义了五个hook节点:
boot_hook_init preinit_essential
boot_hook_init preinit_main
boot_hook_init failsafe
boot_hook_init initramfs
boot_hook_init preinit_mount_root
后面就是当前shell下依次在执行/lib/preinit/目录下的脚本:

                for pi_source_file in   /lib/preinit/*; do
                . $pi_source_file
                done

定义那些要添加到hook结点的函数,然后通过boot_hook_add将该函数添加到对应的hook结点。
最后,/etc/preinit就会执行boot_run_hook函数执行对应hook结点上的函数。在当前环境下只执行了preinit_essential和preinit_main结点上的函数,如下:
boot_run_hook preinit_essential
boot_run_hook preinit_main
到此,/etc/preinit执行完毕并退出。

强调!
当/etc/preinit执行完毕并退出,进程消失了,但此时已经调用了回调函数spawn_procd(),而回调函数spawn_procd()里面execvp(“procd”),所以最终procd会重新被执行,从而启动系统其它各个配置!

preinit/目录脚本如下:

root@:/# ls /lib/preinit/
02_default_set_state         50_indicate_regular_preinit
03_preinit_do_ipq806x.sh     70_initramfs_test
10_indicate_failsafe         80_mount_root
10_indicate_preinit          81_load_wifi_board_bin
10_sysinfo                   99_10_failsafe_login
30_failsafe_wait             99_10_run_init
40_run_failsafe_hook

随便查看一个脚本:

#!/bin/sh
indicate_regular_preinit() {
        preinit_net_echo "Continuing with Regular Preinit\n"
        set_state preinit_regular
}

boot_hook_add preinit_main indicate_regular_preinit
这里的脚本实现的功能就是把函数添加到对应的hook点内!


在/etc/preinit脚本中有如下代码:
. /lib/functions.sh
. /lib/functions/preinit.sh
. /lib/functions/system.sh
查看. /lib/functions/preinit.sh脚本

boot_hook_add() {                                                           
        local hook="${1}_hook${PI_HOOK_SPLICE:+_splice}"                    
        local func="${2}"                  
                                           
        [ -n "$func" ] && {      
                local v; eval "v=\$$hook"                               
                export -n "$hook=${v:+$v }$func"                        
        }                                                               
} 
.......
boot_run_hook() {                                                           
        local hook="$1"                                                     
        local func                                                          
                                                                            
        while boot_hook_shift "$hook" func; do                              
                local ran; eval "ran=\$PI_RAN_$func"                        
                [ -n "$ran" ] || {                    
                        export -n "PI_RAN_$func=1"  
                        $func "$1" "$2"             //执行传进来的函数
                }                                                       
        done                                                            
}                        
这个脚本都是实现hook函数相关的!     

猜你喜欢

转载自blog.csdn.net/weixin_38890593/article/details/83384377