• OpenWrt 根文件系统启动过程分析


    内核启动加载根文件系统后,执行的第一个脚本是init,具体参见内核源码kernel/init/main.c

    static int __ref kernel_init(void *unused)
    {
        ...
        ...
        ...
            
    	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;
    
    	panic("No working init found.  Try passing init= option to kernel. "
    	      "See Linux Documentation/init.txt for guidance.");
    }
    

    依次尝试执行/sbin/init,/etc/init,/bin/init,/bin/sh, openwrt 的根文件系统里面是使用的/sbin/init,它是一个可执行程序,来自procd源码。查看procd的CMakeLists,判断函数入口在init.c中,

    ADD_EXECUTABLE(init initd/init.c initd/early.c initd/preinit.c initd/mkdev.c watchdog.c
    	utils/utils.c ${SOURCES_ZRAM})
    

    procd源码会编译出好几个执行文件,包括:prcod、init、udevtrigger、askfirst等,还有根据配置打开的ujail、utrace。

    preinit阶段

    init程序里面:

    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();
    	cmdline();
    	watchdog_init(1);
    
    	pid = fork();
    	if (!pid) {
    		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
    ");
    		exit(-1);
    	}
    	if (pid <= 0) {
    		ERROR("Failed to start kmodloader instance
    ");
    	} else {
    		int i;
    
    		for (i = 0; i < 1200; i++) {
    			if (waitpid(pid, NULL, WNOHANG) > 0)
    				break;
    			usleep(10 * 1000);
    			watchdog_ping();
    		}
    	}
    	uloop_init();
    	preinit();
    	uloop_run();
    
    	return 0;
    }
    

    主要是:

    early();
    	--->early_mounts();	//挂载proc、sysfs、tmpfs等这些
    	--->early_env();	//设置PATH环境变量
    
    watchdog_init(1);		//设置看门狗
    
    cmdline();				//处理/proc/cmdline的内容,主要读取bootloader设置的bootargs,看init_debug的值,设置调试等级。
    
    调用/sbin/kmodloader,按照/etc/modules-boot.d/的配置加载模块
    
    preinit();				//详见下面分析
    

    init的最后阶段调用了preinit()函数,

    void
    preinit(void)
    {
    	char *init[] = { "/bin/sh", "/etc/preinit", NULL };
    	char *plug[] = { "/sbin/procd", "-h", "/etc/hotplug-preinit.json", NULL };
    	int fd;
    
    	LOG("- preinit -
    ");
    
    	plugd_proc.cb = plugd_proc_cb;
    	plugd_proc.pid = fork();
    	if (!plugd_proc.pid) {
    		execvp(plug[0], plug);
    		ERROR("Failed to start plugd
    ");
    		exit(-1);
    	}
    	if (plugd_proc.pid <= 0) {
    		ERROR("Failed to start new plugd instance
    ");
    		return;
    	}
    	uloop_process_add(&plugd_proc);
    
    	setenv("PREINIT", "1", 1);
    
    	fd = creat("/tmp/.preinit", 0600);
    
    	if (fd < 0)
    		ERROR("Failed to create sentinel file
    ");
    	else
    		close(fd);
    
    	preinit_proc.cb = spawn_procd;
    	preinit_proc.pid = fork();
    	if (!preinit_proc.pid) {
    		execvp(init[0], init);
    		ERROR("Failed to start preinit
    ");
    		exit(-1);
    	}
    	if (preinit_proc.pid <= 0) {
    		ERROR("Failed to start new preinit instance
    ");
    		return;
    	}
    	uloop_process_add(&preinit_proc);
    
    	DEBUG(4, "Launched preinit instance, pid=%d
    ", (int) preinit_proc.pid);
    }
    

    在preinit里面主要干了这些事情:

    execvp(plug[0], plug);				//执行`/sbin/procd -h /etc/hotplug-preinit.json`
    setenv("PREINIT", "1", 1);			//设置环境变量PREINIT为1
    fd = creat("/tmp/.preinit", 0600);	//创建/tmp/.preinit标识文件
    execvp(init[0], init);				//子进程执行/etc/preinit
    uloop_process_add(&preinit_proc);	//注册一个回调,等上面执行/etc/preinit的子进程退出,执行spawn_procd
    

    /sbin/procd -h /etc/hotplug-preinit.json preinit阶段的hotplug规则。


    脚本/etc/preinit:

    #!/bin/sh
    # Copyright (C) 2006-2016 OpenWrt.org
    # Copyright (C) 2010 Vertical Communications
    
    [ -z "$PREINIT" ] && exec /sbin/init
    
    export PATH="/usr/sbin:/usr/bin:/sbin:/bin"
    
    . /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
    

    执行过程中首先需要了解的是boot_hook_init、boot_run_hook、boot_add_hook这几个函数,他们定义在/lib/functions/preinit.sh中,不深入分析这几个函数的实现,可以认为是:

    boot_hook_init list_name					//初始化一个名字为list_name的回调列表
    boot_add_hook list_name cb_name				//向名字为list_name的回调列表中添加一个回调函数cb_name
    boot_run_hook list_name						//调用回调列表list_name里面添加的回调函数
    

    这样做是方便按照一定的功能分类和调用顺序去执行一系列的函数。具体到/etc/preinit里面就是:

    //初始化一系列的回调列表
    boot_hook_init XXX	
    
    //从/lib/preinit/目录下按照名字顺序添加回调
    for pi_source_file in /lib/preinit/*; do
    	. $pi_source_file
    done
    
    //执行从/lib/preinit/下面脚本添加的回调函数
    boot_run_hook preinit_essential		//<==我的lede17.01这个是空的,未添加任何cb
    boot_run_hook preinit_main
    

    spawn_procd主要是清理preinit阶段的一些产物,最后执行/sbin/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];
    
    	if (plugd_proc.pid > 0)
    		kill(plugd_proc.pid, SIGKILL);
    
    	if (!stat("/tmp/sysupgrade", &s))
    		while (true)
    			sleep(1);
    
    	unsetenv("INITRAMFS");
    	unsetenv("PREINIT");
    	unlink("/tmp/.preinit");
    	DEBUG(2, "Exec to real procd now
    ");
    	if (wdt_fd)
    		setenv("WDTFD", wdt_fd, 1);
    	check_dbglvl();
    	if (debug > 0) {
    		snprintf(dbg, 2, "%d", debug);
    		setenv("DBGLVL", dbg, 1);
    	}
    
    	execvp(argv[0], argv);
    }
    
  • 相关阅读:
    shell变量解析
    visual studio code(vscode)使用
    linux虚拟机安装
    算法总结系列之八:复读机的故事散列表及其在.NET中的应用浅析(上集)
    对改善Dictionary时间性能的思考及一个线程安全的Dictionary实现
    算法总结系列之八:复读机的故事 散列表.NET应用的研究(下集)
    使用WiX打包你的应用程序之二向WiX脚本传递信息(属性)的几种方式
    当心Dictionary带来的一种隐式内存泄漏
    从DWG到XAML (II) DWFx格式解析及其和XPS的关系
    从DWG到XAML (I) 浅谈DWG历史,现状及方向
  • 原文地址:https://www.cnblogs.com/thammer/p/14846700.html
Copyright © 2020-2023  润新知