根據前文所述,恢復出廠設置在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
;
到此就完成了重啟的上半部分,也就是關機操作的內容,接下來就是系統啟動了。