openwrt procd启动流程代码分析

1、内核调用

内核启动以后调用init_post函数


/* This is a non __init function. Force it to be noinline otherwise gcc
 * makes it inline to init() and it becomes part of init.text section
 */
static noinline int init_post(void)
	__releases(kernel_lock)
{
	/* need to finish all async __init code before freeing the memory */
	async_synchronize_full();
	free_initmem();
	mark_rodata_ro();
	system_state = SYSTEM_RUNNING;
	numa_default_policy();


	current->signal->flags |= SIGNAL_UNKILLABLE;

	if (ramdisk_execute_command) {
		run_init_process(ramdisk_execute_command);
		printk(KERN_WARNING "Failed to execute %s\n",
				ramdisk_execute_command);
	}

	/*
	 * We try each of these until one succeeds.
	 *
	 * The Bourne shell can be used instead of init if we are
	 * trying to recover a really broken machine.
	 */
	if (execute_command) {
		run_init_process(execute_command);
		printk(KERN_WARNING "Failed to execute %s.  Attempting "
					"defaults...\n", execute_command);
	}
	printk("init.main L%d\n", __LINE__);
	run_init_process("/etc/preinit");
	printk("init.main L%d\n", __LINE__);
	run_init_process("/sbin/init");
	printk("init.main L%d\n", __LINE__);
	run_init_process("/etc/init");
	printk("init.main L%d\n", __LINE__);
	run_init_process("/bin/init");
	printk("init.main L%d\n", __LINE__);
//	run_init_process("/bin/ash");

	panic("No init found.  Try passing init= option to kernel. "
	      "See Linux Documentation/init.txt for guidance.");
}

execute_command是通过command line参数传递过来的。因为我们command line中并没有指定init参数,所以会依次尝试调用/etc/preinit、/sbin/init、/etc/init、/bin/init。因为启动脚本/etc/preinit是存在的,所以实际上就是调用/etc/preinit启动了pid为1的1号进程。后面的/sbin/init、/etc/init、/bin/init实际上调用不到。

2、/etc/preinit调用

#!/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

系统刚启动,"$PREINIT"为空,所以会执行/sbin/init,/sbin/init实际上就是/sbin/procd的一个链接

root@OpenWrt:~# ls -l /sbin/init 
lrwxrwxrwx    1 root     root            11 Dec  1 08:12 /sbin/init -> /sbin/procd

3、procd进程

/*
 * Copyright (C) 2013 Felix Fietkau <nbd@openwrt.org>
 * Copyright (C) 2013 John Crispin <blogic@openwrt.org>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License version 2.1
 * as published by the Free Software Foundation
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 */

#include <sys/wait.h>
#include <sys/types.h>
#include <sys/stat.h>

#include <unistd.h>
#include <getopt.h>
#include <libgen.h>

#include "procd.h"
#include "hotplug.h"
#include "watchdog.h"

static int usage(const char *prog)
{
	ERROR("Usage: %s [options]\n"
		"Options:\n"
		"    -s <path>:		Path to ubus socket\n"
		"    -d:		Enable debug messages\n"
		"\n", prog);
	return 1;
}


static int main_procd_init(int argc, char **argv)
{
	procd_signal_preinit();
	procd_early();
	debug_init();
	watchdog_init(1);
	system("/sbin/kmodloader /etc/modules-boot.d/");
	uloop_init();
	hotplug("/etc/hotplug-preinit.json");
	procd_preinit();
	uloop_run();
	return 0;
}

int main(int argc, char **argv)
{
	int ch;

	if (!strcmp(basename(*argv), "init"))
		return main_procd_init(argc, argv);

	while ((ch = getopt(argc, argv, "ds:")) != -1) {
		switch (ch) {
		case 's':
			ubus_socket = optarg;
			break;
		case 'd':
			debug++;
			break;
		default:
			return usage(argv[0]);
		}
	}
	uloop_init();
	procd_signal();
	trigger_init();
	if (getpid() != 1)
		procd_connect_ubus();
	else
		procd_state_next();
	uloop_run();

	return 0;
}

从代码可以看出,调用/sbin/init,最后实际上是调用main_procd_init函数,main_procd_init又调用procd_preinit。

4、procd_preinit函数

void procd_preinit(void)
{
	char *argv[] = { "/bin/sh", "/etc/preinit", NULL };

	LOG("- preinit -\n");

	setenv("PREINIT", "1", 1);
	preinit.cb = spawn_procd;
	preinit.pid = fork();
	if (!preinit.pid) {
		execvp(argv[0], argv);
		ERROR("Failed to start preinit\n");
		exit(-1);
	}

	if (preinit.pid <= 0) {
		ERROR("Failed to start new preinit instance\n");
		return;
	}

	uloop_process_add(&preinit);
	DEBUG(2, "Launched preinit instance, pid=%d\n", (int) preinit.pid);
}

可以看到procd_preinit函数中fork了一个子进程,子进程中再次调用/etc/preinit脚本,不同的是在再次执行该脚本之前设置了环境变量PREINIT,所以再次执行preinit脚本的时候,不会再调用/sbin/init,而是会执行一系列boot_hook_init。执行完以后,子进程就退出了。由于子进程通过uloop_process_add被加入父进程/sbin/init的监控当中,所以当子进程退出时,父进程会收到信号,从而执行挂载的回调函数spawn_procd。

5、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;

	if (!stat("/tmp/sysupgrade", &s))
		while (true)
			sleep(1);

	unsetenv("INITRAMFS");
	unsetenv("PREINIT");
	DEBUG(1, "Exec to real procd now\n");
	if (wdt_fd)
		setenv("WDTFD", wdt_fd, 1);
	execvp(argv[0], argv);
}

父进程/sbin/init执行了spawn_procd函数,通过execvp调用/sbin/procd,execvp调用后不会返回,1号进程/sbin/init也变成了/sbin/procd。1号进程运行的/sbin/procd,不会再进入main_procd_init,最终是会调用procd_state_next函数。至此procd进程才正式运行起来,接管了后续其他进程的启动。

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值