根据前文所述,恢复出厂设置在Settings界面点击后,最终可以视为是通过如下两条命令来执行的最终操作:
adb shell 'echo "--wipe_data\n--locale=en_US" > /cache/recovery/command'
adb shell setprop sys.powerctl reboot,recovery
接下来就从这两条命令开始描述。
属性处理
上边设置的属性实际上是在/init.rc
中进行触发的:
on property:sys.powerctl=*
powerctl ${sys.powerctl}
在源码中对应的文件为device/rockchip/rksdk/recovery/etc/init.rc
。可以看到,该属性设置后, 就相当于调用了命令powerctl ${sys.powerctl}
,对应于参数reboot,recovery
,则最终命令为powerctl reboot,recover
。
处理流程
do_powerctl
这里不再描述Android的系统属性处理流程,直接从处理代码开始。可以看到,命令行powerctl reboot,recover
对应的处理函数位于文件system/core/init/builtins.c
,对应的函数为do_powerctl
:
int do_powerctl(int nargs, char **args)
{
char command[PROP_VALUE_MAX];
int res;
int len = 0;
int cmd = 0;
char *reboot_target;
res = expand_props(command, args[1], sizeof(command));
if (res) {
ERROR("powerctl: cannot expand '%s'\n", args[1]);
return -EINVAL;
}
if (strncmp(command, "shutdown", 8) == 0) {
cmd = ANDROID_RB_POWEROFF;
len = 8;
} else if (strncmp(command, "reboot", 6) == 0) {
cmd = ANDROID_RB_RESTART2;
len = 6;
} else {
ERROR("powerctl: unrecognized command '%s'\n", command);
return -EINVAL;
}
if (command[len] == ',') {
reboot_target = &command[len + 1];
} else if (command[len] == '\0') {
reboot_target = "";
} else {
ERROR("powerctl: unrecognized reboot target '%s'\n", &command[len]);
return -EINVAL;
}
return android_reboot(cmd, 0, reboot_target);
}
这段代码的核心思想就是对传入的命令行参数进行拆分处理,根据前边描述的命令行,则最终调用到的函数为android_reboot(ANDROID_RB_RESTART2, 0, "recovery")
。
android_reboot
函数android_reboot
实现在文件system/core/libcutils/android_reboot.c
中:
int android_reboot(int cmd, int flags, char *arg)
{
int ret;
sync();
remount_ro();
switch (cmd) {
case ANDROID_RB_RESTART:
ret = reboot(RB_AUTOBOOT);
break;
case ANDROID_RB_POWEROFF:
ret = reboot(RB_POWER_OFF);
break;
case ANDROID_RB_RESTART2:
ret = __reboot(LINUX_REBOOT_MAGIC1, LINUX_REBOOT_MAGIC2,
LINUX_REBOOT_CMD_RESTART2, arg);
break;
default:
ret = -1;
}
return ret;
}
根据传入的参数,这里直接调用到函数__reboot(LINUX_REBOOT_MAGIC1, LINUX_REBOOT_MAGIC2, LINUX_REBOOT_CMD_RESTART2, "recovery")
。
__reboot
函数__reboot
定义在文件bionic/libc/include/sys/reboot.h
内:
extern int __reboot(int, int, int, void *);
具体实现实现在文件bionic/libc/arch-arm/syscalls/__reboot.S
中:
ENTRY(__reboot)
mov ip, r7
ldr r7, =__NR_reboot
swi #0
mov r7, ip
cmn r0, #(MAX_ERRNO + 1)
bxls lr
neg r0, r0
b __set_errno
END(__reboot)
根据定义在文件kernel/include/uapi/asm-generic/unistd.h
中的如下内容可知:
#define __NR_reboot 142
__SYSCALL(__NR_reboot, sys_reboot)
这里对__NR_reboot
的调用,事实上是在调用文件kernel/kernel/sys.c
中的函数sys_reboot()
。
sys_reboot
考虑到函数sys_reboot
定义在文件include/linux/syscalls.h
:
asmlinkage long sys_reboot(int magic1, int magic2, unsigned int cmd, void __user * arg);
则最终的调用为sys_reboot(LINUX_REBOOT_MAGIC1, LINUX_REBOOT_MAGIC2, LINUX_REBOOT_CMD_RESTART2, "recovery")
,又根据前文可知,该函数实现在文件kernel/kernel/sys.c
中:
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;
}
这里的主要操作如下:
- 因为只有超级用户有权限执行重启操作,因此首先判断的是进程的所有者是否为超级用户,是超级用户则执行重启操作,否则不作为而直接退出;
- 当然因为重启操作是个大工程,因此还要校验魔数,不符的话,还是不能执行重启操作;
- 检查是否由该接口处理重启操作;
- 根据输入命令判断具体执行的操作,当在recovery模式时,最终调用的函数为
kernel_restart('recovery')
;
kernel_restart
函数kernel_restart
实现在文件kernel/kernel/sys.c
中:
void kernel_restart(char *cmd)
{
kernel_restart_prepare(cmd);
migrate_to_reboot_cpu();
syscore_shutdown();
if (!cmd)
printk(KERN_EMERG "Restarting system.\n");
else
printk(KERN_EMERG "Restarting system with command '%s'.\n", cmd);
kmsg_dump(KMSG_DUMP_RESTART);
machine_restart(cmd);
}
这里不再向下描述,而只在当前层做个简单描述,这里做了如下几件事:
- 调用
kernel_restart_prepare
完成如下任务:- 调用
blocking_notifier_call_chain
接口向关心reboot事件的进程,发送SYS_RESTART
事件,包括其中的参数recovery; - 将系统状态设置为
SYSTEM_RESTART
; - 调用
usermodehelper_disable
接口,禁止User mode helper; - 调用
device_shutdown
,关闭所有设备;
- 调用
- 调用
migrate_to_reboot_cpu
将当前进程移植到一个CPU上,在多CPU存在的情况下,无论哪个CPU触发了当前的系统调用,代码都可以运行在任意的CPU上,这个接口将代码分派到一个特定的CPU上,也就是说,这个接口被执行后,只有一个CPU在运行,泳衣完成后续的reboot动作; - 调用
syscore_shutdown
关闭系统核心器件; - 然后调用
printk
打印日志; - 最后调用到
machine_restart
执行重启操作;
machine_restart
machine_restart
实现在文件kernel/arch/arm/kernel/process.c
中:
void machine_restart(char *cmd)
{
local_irq_disable();
smp_send_stop();
/* Flush the console to make sure all the relevant messages make it
* out to the console drivers */
arm_machine_flush_console();
arm_pm_restart(reboot_mode, cmd);
/* Give a grace period for failure to restart of 1s */
mdelay(1000);
/* Whoops - the platform was unable to reboot. Tell the user! */
printk("Reboot failed -- System halted\n");
local_irq_disable();
while (1);
}
对于多CPU的机器而言,在重启之前需要保证其他的CPU都处于非活动状态,由其中的一个CPU来负责重启动作即可。另外必须实现一个基于硬件的重启操作,以保证所有的CPU同步重启。该函数中具体执行了如下这些任务:
- 调用
local_irq_disable
屏蔽当前CPU上的所有中断,通过操作arm核心中的寄存器来屏蔽到达CPU上的中断,此时中断控制器上所有送往该CPU的中断信号都将被忽略; - 调用
smp_send_stop
确保其他CPU处于非活动状态; - 调用
arm_machine_flush_console
将相关信息输出到终端设备; - 调用
arm_pm_restart
执行重启操作; - 调用
mdelay
等待一段时间以进行优雅的重启; - 调用
printk
重启失败则输出信息; - 调用
local_irq_disable
屏蔽当前CPU上的所有中断; - 调用
while(1)
重启失败,就死在这里咯;
rk3288_restart
根据上边的描述,真正的重启是在函数arm_pm_restart
中进行的,而该函数实际上是个函数指针,在系统初始化时赋值的,其对应的函数为rk3288_restart
,在文件kernel/arch/arm/mach-rockchip/rk3288.c
中实现:
static void rk3288_restart(char mode, const char *cmd)
{
u32 boot_flag, boot_mode;
rockchip_restart_get_boot_mode(cmd, &boot_flag, &boot_mode);
writel_relaxed(boot_flag, RK_PMU_VIRT + RK3288_PMU_SYS_REG0); // for loader
writel_relaxed(boot_mode, RK_PMU_VIRT + RK3288_PMU_SYS_REG1); // for linux
dsb();
/* pll enter slow mode */
writel_relaxed(0xf3030000, RK_CRU_VIRT + RK3288_CRU_MODE_CON);
dsb();
writel_relaxed(0xeca8, RK_CRU_VIRT + RK3288_CRU_GLB_SRST_SND_VALUE);
dsb();
}
首先调用rockchip_restart_get_boot_mode
,根据输入的recovery
设置boot_flag
的值为SYS_LOADER_REBOOT_FLAG + BOOT_FASTBOOT
,boot_mode
的值为BOOT_MODE_REBOOT
;再调用writel_relaxed
将boot_flag
写入地址RK3288_PMU_SYS_REG0
,将boot_mode
写入地址RK3288_PMU_SYS_REG1
;
到此就完成了重启的上半部分,也就是关机操作的内容,接下来就是系统启动了。