由于本人水平相当有限,不当之处还望大家多多指教。
涉及的内核源码,基于linux-3.10.102。
首先,对于用户态发起的这类操作请求,最终都是通过sys_reboot系统调用(源码在kernel/sys.c)实现的。
其代码如下。如其注释所言,他除了可以重启关机停机,还可以修改ctrl-alt-del组合键的含义。
另外,注释还说到此系统调用不会做sync。即sync需要用户在调用此系统调用之前自己完成。
/*
* Reboot system call: for obvious reasons only root may call it,
* and even root needs to set up some magic numbers in the registers
* so that some mistake won't make this reboot the whole machine.
* You can also set the meaning of the ctrl-alt-del-key here.
*
* reboot doesn't sync: do that yourself before calling this.
*/
SYSCALL_DEFINE4(reboot, int, magic1, int, magic2, unsigned int, cmd,
void __user *, arg)
{
struct pid_namespace *pid_ns = task_active_pid_ns(current);
char buffer[256];
int ret = 0;
/* We only trust the superuser with rebooting the system. */
if (!ns_capable(pid_ns->user_ns, CAP_SYS_BOOT))
return -EPERM;
/* For safety, we require "magic" arguments. */
if (magic1 != LINUX_REBOOT_MAGIC1 ||
(magic2 != LINUX_REBOOT_MAGIC2 &&
magic2 != LINUX_REBOOT_MAGIC2A &&
magic2 != LINUX_REBOOT_MAGIC2B &&
magic2 != LINUX_REBOOT_MAGIC2C))
return -EINVAL;
/*
* If pid namespaces are enabled and the current task is in a child
* pid_namespace, the command is handled by reboot_pid_ns() which will
* call do_exit().
*/
ret = reboot_pid_ns(pid_ns, cmd);
if (ret)
return ret;
/* Instead of trying to make the power_off code look like
* halt when pm_power_off is not set do it the easy way.
*/
if ((cmd == LINUX_REBOOT_CMD_POWER_OFF) && !pm_power_off)
cmd = LINUX_REBOOT_CMD_HALT;
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;
case LINUX_REBOOT_CMD_RESTART2:
if (strncpy_from_user(&buffer[0], arg, sizeof(buffer) - 1) < 0) {
ret = -EFAULT;
break;
}
buffer[sizeof(buffer) - 1] = '\0';
kernel_restart(buffer);
break;
#ifdef CONFIG_KEXEC
case LINUX_REBOOT_CMD_KEXEC:
ret = kernel_kexec();
break;
#endif
#ifdef CONFIG_HIBERNATION
case LINUX_REBOOT_CMD_SW_SUSPEND:
ret = hibernate();
break;
#endif
default:
ret = -EINVAL;
break;
}
mutex_unlock(&reboot_mutex);
return ret;
}
从上述代码来看,相关的操作分别是由函数kernel_restart, kernel_power_off, kernel_halt完成的。
另外,对于修改ctrl-alt-del组合键含义的操作(LINUX_REBOOT_CMD_CAD_ON和LINUX_REBOOT_CMD_CAD_OFF),
内核只是简单修改一下变量C_A_D的值,然后就返回了。
而kernel_restart, kernel_power_off, kernel_halt这几个函数的实现也很相似:
先是通知reboot_notifier_list中的模块,系统要重启了。
然后将调用此系统调用的线程迁移到CPU 0上面。
然后执行syscore_ops_list中的各种关机操作。
最后,将系统转入restart/halt/power_off流程。
再看看用户态触发sys_reboot系统调用的C库函数reboot(man 2 reboot)的说明,对于重启关机停机类的操作,其中全都提到If not preceded by a sync(2), data will be lost.。可见,如果在调用reboot库函数前,没有调用sync(2),会导致未保存的文件数据丢失。
同时,说明中还提到对于成功的关机重启停止操作,reboot函数将不会返回。
通常,比较完善的关机重启停机流程,是通过用户态工具reboot实现的。
如果查看此工具的帮助,会发现一共有3个工具,分别是reboot, halt, poweroff。
但他们实际上对应于同一个可执行程序/sbin/reboot。
[root@localhost ~]# ls -l /sbin/reboot
-rwxr-xr-x. 1 root root 13932 Jun 25 2013 /sbin/reboot
[root@localhost ~]# ls -l /sbin/halt
lrwxrwxrwx. 1 root root 6 Aug 7 2016 /sbin/halt -> reboot
[root@localhost ~]# ls -l /sbin/poweroff
lrwxrwxrwx. 1 root root 6 Aug 7 2016 /sbin/poweroff -> reboot
这三个工具,分别实现他们的名字对应的功能。
如果使用时,加了--force选项,则他们将直接调用C库函数reboot。这会导致未保存的文件数据丢失。
如果未加--force,则他们会执行用户态工具shutdown。由此可见,完善的操作流程是由shutdown程序完成的。
shutdown具体是如何组织流程的,可以参考其手册页 (man 8 shutdown)。
实际上,主要工作是shutdown向init进程发起了一个改变系统运行级别(runlevel)的请求,然后由init进程将系统降到相应的运行级别。
最后,感觉sys_reboot似乎并没有对可能存在着的大量的用户态进程进行妥善处理。而且他的注释也说明了,需要用户自己调用sync保存数据。
sys_reboot只管重启关机停机。
因此,完善的结束用户态的所有上下文的任务,就留给了用户态自己。而从前面的分析来看,正常的流程,最终就是init进程负责清空所有的用户态上下文。
这样一来,内核与用户态的关系就极其简单清晰。内核就是以系统调用为界面,为用户提供服务。用户空间的各种复杂关系,是用户空间自己的事。
系统启动时,内核在后期,拉起init进程。然后init进程通过各种系统调用,建立起用户态的一切。
最后系统正常关闭时,同样由init进程通过各种系统调用,清空用户态的一切,最后通过sys_reboot关闭系统。
当然,对于非正常关机,例如某个进程直接触发了sys_reboot,那么用户态的上下文就没有妥善结束,因此就可能丢失未保存的数据。
最后,再额外提一下,内核自身(例如,软件看门狗)在紧急情况下,想重启系统,可能会通过直接调用emergency_restart函数来实现。
下面是其代码
/**
* emergency_restart - reboot the system
*
* Without shutting down any hardware or taking any locks
* reboot the system. This is called when we know we are in
* trouble so this is our best effort to reboot. This is
* safe to call in interrupt context.
*/
void emergency_restart(void)
{
kmsg_dump(KMSG_DUMP_EMERG);
machine_emergency_restart();
}
EXPORT_SYMBOL_GPL(emergency_restart);
void machine_emergency_restart(void)
{
__machine_emergency_restart(1);
}
static void __machine_emergency_restart(int emergency)
{
reboot_emergency = emergency;
machine_ops.emergency_restart();
}