Linux reboot全过程

一、版本说明

嵌入式Linux 下面的reboot命令看似简单,但出问题时定位起来发现别有洞天。

下面就按在shell下执行reboot命令之后程序的执行过程进行解析。

Busybox:1.23.2                        ——制作跟文件系统,/sbin/reboot程序的由来

Libc:2.6.1                                  ——标准C库

Linux kernel:2.6.35                 ——内核版本

 

二、流程简介

         如图所示是reboot的简要流程图。

 


 普通的reboot是通过busybox为入口,进入halt_main函数,然后给init进程发送SIGTERM信号,init进程接收到信号后给其他进程发送终止信号,最后调用C库函数reboot,reboot通过系统调用sys_reboot进入内核,内核将整个系统重启。其中在shell中执行reboot –f则通过halt_main直接调用C函数reboot,不经过init进程。

三、代码详解

1.reboot命令端

执行reboot命令,busybox检查当前命令为reboot,进入函数halt_main,

reboot,halt和poweroff都会进入这个函数,不同的命令发送的信号和执行的操作不同。

现只分析reboot的情况。

代码如下

int halt_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
int halt_main(int argc UNUSED_PARAM, char **argv)
{
	static const int magic[] = {
		RB_HALT_SYSTEM,
		RB_POWER_OFF,
		RB_AUTOBOOT
	};
	static const smallint signals[] = { SIGUSR1, SIGUSR2, SIGTERM };

	int delay = 0;
	int which, flags, rc;

	/* Figure out which applet we're running */
	for (which = 0; "hpr"[which] != applet_name[0]; which++)
		continue;

	/* Parse and handle arguments */
	opt_complementary = "d+"; /* -d N */
	/* We support -w even if !ENABLE_FEATURE_WTMP,
	 * in order to not break scripts.
	 * -i (shut down network interfaces) is ignored.
	 */
	flags = getopt32(argv, "d:nfwi", &delay);

	sleep(delay);

	write_wtmp();

	if (flags & 8) /* -w */
		return EXIT_SUCCESS;

	if (!(flags & 2)) /* no -n */
		sync();

	/* Perform action. */
	rc = 1;
	if (!(flags & 4)) { /* no -f */
//TODO: I tend to think that signalling linuxrc is wrong
// pity original author didn't comment on it...
		if (ENABLE_FEATURE_INITRD) {
			/* talk to linuxrc */
			/* bbox init/linuxrc assumed */
			pid_t *pidlist = find_pid_by_name("linuxrc");
			if (pidlist[0] > 0)
				rc = kill(pidlist[0], signals[which]);
			if (ENABLE_FEATURE_CLEAN_UP)
				free(pidlist);
		}
		if (rc) {
			/* talk to init */
			if (!ENABLE_FEATURE_CALL_TELINIT) {
				/* bbox init assumed */
				rc = kill(1, signals[which]);
			} else {
				/* SysV style init assumed */
				/* runlevels:
				 * 0 == shutdown
				 * 6 == reboot */
				execlp(CONFIG_TELINIT_PATH,
						CONFIG_TELINIT_PATH,
						which == 2 ? "6" : "0",
						(char *)NULL
				);
				bb_perror_msg_and_die("can't execute '%s'",
						CONFIG_TELINIT_PATH);
			}
		}
	} else {
		rc = reboot(magic[which]);
	}

	if (rc)
		bb_perror_nomsg_and_die();
	return rc;
}

该函数判断reboot是否带了 -f 参数,如果带了,直接调用reboot调用C函数库

如果没带,则通过

kill(1, signals[which]);

给init进程发送SIGTERM信号。


2.init进程端

init进程初始化函数init_main将部分信号进行重定义

		bb_signals_recursive_norestart(0
			+ (1 << SIGINT)  /* Ctrl-Alt-Del */
			+ (1 << SIGQUIT) /* re-exec another init */
#ifdef SIGPWR
			+ (1 << SIGPWR)  /* halt */
#endif
			+ (1 << SIGUSR1) /* halt */
			+ (1 << SIGTERM) /* reboot */
			+ (1 << SIGUSR2) /* poweroff */
#if ENABLE_FEATURE_USE_INITTAB
			+ (1 << SIGHUP)  /* reread /etc/inittab */
#endif
			, record_signo);


void record_signo(int signo)
{
	bb_got_signal = signo;
}

将SIGUSR1(halt) SIGUSR2(poweroff) SIGTERM(reboot)信号存入全局变量bb_got_signal中。 
在init_main的最后进入一个while(1)循环,不断检查信号和等待子进程的退出

其中check_delayed_sigs就是用来检查这个全局变量的,如下:

	while (1) {
		int maybe_WNOHANG;

		maybe_WNOHANG = check_delayed_sigs();

		/* (Re)run the respawn/askfirst stuff */
		run_actions(RESPAWN | ASKFIRST);
		maybe_WNOHANG |= check_delayed_sigs();

		/* Don't consume all CPU time - sleep a bit */
		sleep(1);
		maybe_WNOHANG |= check_delayed_sigs();

		/* Wait for any child process(es) to exit.
		 *
		 * If check_delayed_sigs above reported that a signal
		 * was caught, wait will be nonblocking. This ensures
		 * that if SIGHUP has reloaded inittab, respawn and askfirst
		 * actions will not be delayed until next child death.
		 */
		if (maybe_WNOHANG)
			maybe_WNOHANG = WNOHANG;
		while (1) {
			pid_t wpid;
			struct init_action *a;

			/* If signals happen _in_ the wait, they interrupt it,
			 * bb_signals_recursive_norestart set them up that way
			 */
			wpid = waitpid(-1, NULL, maybe_WNOHANG);
			if (wpid <= 0)
				break;

			a = mark_terminated(wpid);
			if (a) {
				message(L_LOG, "process '%s' (pid %d) exited. "
						"Scheduling for restart.",
						a->command, wpid);
			}
			/* See if anyone else is waiting to be reaped */
			maybe_WNOHANG = WNOHANG;
		}
	} /* while (1) */

而里面的while(1)一般会阻塞在waitpid中,那么信号检查是不是会有问题?

  • WNOHANG        如果没有可用的子进程退出状态,立即返回而不是阻塞

但maybe_WNOHANG的值应该是0,不是WNOHANG(=1)感觉还是会阻塞。我这样理解的,因为所有的用户进程都是init进程的子进程,我判断前面执行reboot时也是一个子进程,halt_main发送完信号后就会退出,init接收到信号而且waitpid成功,然后跳出循环检查信号。

下面看一下信号的处理部分

static int check_delayed_sigs(void)
{
	int sigs_seen = 0;

	while (1) {
		smallint sig = bb_got_signal;

		if (!sig)
			return sigs_seen;
		bb_got_signal = 0;
		sigs_seen = 1;
#if ENABLE_FEATURE_USE_INITTAB
		if (sig == SIGHUP)
			reload_inittab();
#endif
		if (sig == SIGINT)
			run_actions(CTRLALTDEL);
		if (sig == SIGQUIT) {
			exec_restart_action();
			/* returns only if no restart action defined */
		}
		if ((1 << sig) & (0
#ifdef SIGPWR
		    + (1 << SIGPWR)
#endif
		    + (1 << SIGUSR1)
		    + (1 << SIGUSR2)
		    + (1 << SIGTERM)
		)) {
			halt_reboot_pwoff(sig);
		}
	}
}
判断为SIGTERM进入halt_reboot_pwoff函数

static void halt_reboot_pwoff(int sig)
{
	const char *m;
	unsigned rb;

	/* We may call run() and it unmasks signals,
	 * including the one masked inside this signal handler.
	 * Testcase which would start multiple reboot scripts:
	 *  while true; do reboot; done
	 * Preventing it:
	 */
	reset_sighandlers_and_unblock_sigs();

	run_shutdown_and_kill_processes();

	m = "halt";
	rb = RB_HALT_SYSTEM;
	if (sig == SIGTERM) {
		m = "reboot";
		rb = RB_AUTOBOOT;
	} else if (sig == SIGUSR2) {
		m = "poweroff";
		rb = RB_POWER_OFF;
	}
	message(L_CONSOLE, "Requesting system %s", m);
	pause_and_low_level_reboot(rb);
	/* not reached */
}


reset_sighandlers_and_unblock_sigs函数将信号重置回默认处理。


static void reset_sighandlers_and_unblock_sigs(void)
{
	bb_signals(0
		+ (1 << SIGUSR1)
		+ (1 << SIGUSR2)
		+ (1 << SIGTERM)
		+ (1 << SIGQUIT)
		+ (1 << SIGINT)
		+ (1 << SIGHUP)
		+ (1 << SIGTSTP)
		+ (1 << SIGSTOP)
		, SIG_DFL);
	sigprocmask_allsigs(SIG_UNBLOCK);
}
run_shutdown_and_kill_processes函数给所有进程发送SIGTERM信号并执行sync(保存数据)

延时后再次发送SIGKILL信号,这里说明一下为什么要发送SIGKILL信号,一般的SIGINT和SIGTERM信号都可以屏蔽或转作他用,SIGKILL信号是不可被屏蔽的,

这样告诉其他进程必须终止。

static void run_shutdown_and_kill_processes(void)
{
	/* Run everything to be run at "shutdown".  This is done _prior_
	 * to killing everything, in case people wish to use scripts to
	 * shut things down gracefully... */
	run_actions(SHUTDOWN);

	message(L_CONSOLE | L_LOG, "The system is going down NOW!");

	/* Send signals to every process _except_ pid 1 */
	kill(-1, SIGTERM);
	message(L_CONSOLE | L_LOG, "Sent SIG%s to all processes", "TERM");
	sync();
	sleep(1);

	kill(-1, SIGKILL);
	message(L_CONSOLE, "Sent SIG%s to all processes", "KILL");
	sync();
	/*sleep(1); - callers take care about making a pause */
}
最终进入函数pause_and_low_level_reboot,起一个轻量级进程执行reboot标准C函数

static void pause_and_low_level_reboot(unsigned magic)
{
	pid_t pid;

	/* Allow time for last message to reach serial console, etc */
	sleep(1);

	/* We have to fork here, since the kernel calls do_exit(EXIT_SUCCESS)
	 * in linux/kernel/sys.c, which can cause the machine to panic when
	 * the init process exits... */
	pid = vfork();
	if (pid == 0) { /* child */
		reboot(magic);
		_exit(EXIT_SUCCESS);
	}
	while (1)
		sleep(1);
}
到这里busybox里面的内容全部处理完。


3.标准C函数reboot

前面执行reboot -f 就是直接执行的这个函数

reboot函数比较简单,直接进行系统调用进入内核。(0xffe1dead  feeldead这个魔术还是比较有意思的)

其中参数howto为RB_AUTOBOOT=0x01234567

sysdeps/unix/sysv/linux/reboot.c

int
reboot (int howto)
{
  return INLINE_SYSCALL (reboot, 3, (int) 0xfee1dead, 672274793, howto);
}

4.内核系统调用

kernel/sys.c

SYSCALL_DEFINE4(reboot, int, magic1, int, magic2, unsigned int, cmd,
		void __user *, arg)
{
。。。

	mutex_lock(&reboot_mutex);
	switch (cmd) {
	case LINUX_REBOOT_CMD_RESTART:
		kernel_restart(NULL);
		break;

	case LINUX_REBOOT_CMD_CAD_ON:
		C_A_D = 1;
		break;

	case LINUX_REBOOT_CMD_CAD_OFF:
		C_A_D = 0;
		break;

	case LINUX_REBOOT_CMD_HALT:
		kernel_halt();
		do_exit(0);
		panic("cannot halt");

	case LINUX_REBOOT_CMD_POWER_OFF:
		kernel_power_off();
		do_exit(0);
		break;

。。。

	default:
		ret = -EINVAL;
		break;
	}
	mutex_unlock(&reboot_mutex);
	return ret;
}
进入

case LINUX_REBOOT_CMD_RESTART:
kernel_restart(NULL);
break;
调用kernel_restart函数

——>machine_restart

void machine_restart(char *cmd)
{
	machine_shutdown();
	if (ppc_md.restart)
		ppc_md.restart(cmd);
#ifdef CONFIG_SMP
	smp_send_stop();
#endif
	printk(KERN_EMERG "System Halted, OK to turn off power\n");
	local_irq_disable();
	while (1) ;
}
这个函数之后就与具体的架构有关系了。

下面是powerpc P1020芯片的复位

ppc_md.restart(cmd);的函数原型在/arch/powerpc/platforms/85xx中定义

define_machine(p2020_rdb_pc) {
	.name			= "P2020RDB-PC",
	.probe			= p2020_rdb_pc_probe,
	.setup_arch		= mpc85xx_rdb_setup_arch,
	.init_IRQ		= mpc85xx_rdb_pic_init,
#ifdef CONFIG_PCI
	.pcibios_fixup_bus	= fsl_pcibios_fixup_bus,
#endif
	.get_irq		= mpic_get_irq,
	.restart		= fsl_rstcr_restart,
	.calibrate_decr		= generic_calibrate_decr,
	.progress		= udbg_progress,
};

void fsl_rstcr_restart(char *cmd)
{
	local_irq_disable();
	if (rstcr)
		/* set reset control register */
		out_be32(rstcr, 0x2);	/* HRESET_REQ */

	while (1) ;
}
最终cpu往寄存器Reset control register(0x000E_00B0)中写2

也就是往管脚HRESET_REQ发出了一个信号,该信号应该与HRESET硬复位管脚相连

这样就实现了CPU的复位


  • 16
    点赞
  • 90
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值