这里所说的reboot指的是软件重启,并非断电重启。我们知道android系统的几个功能,比如:回复出厂设置、OTA升级等都需要重启系统,而且重启后要进入recovery模式,有的手机还带有重启进入fastboot或者其他模式。这些在软重启中式怎么做到的呢?
经过一段查找找到了这个文件:\frameworks\base\core\java\android\os\RecoverySystem.java
我们来看这个文件里面有一个类public class RecoverySystem 我们来看这个类的说明
/** * RecoverySystem contains methods for interacting with the Android * recovery system (the separate partition that can be used to install * system updates, wipe user data, etc.) */
这个类里面完成了android系统的回复包括安装系统更新,删除用户数据等。
下面我们来看这个类里面的几个 函数
/** * Reboots the device in order to install the given update * package. * 重启系统来安装升级包 * Requires the {@link android.Manifest.permission#REBOOT} permission. * * @param context the Context to use * @param packageFile the update package to install. Must be on * a partition mountable by recovery. (The set of partitions * known to recovery may vary from device to device. Generally, * /cache and /data are safe.) * * @throws IOException if writing the recovery command file * fails, or if the reboot itself fails. */ public static void installPackage(Context context, File packageFile) throws IOException { String filename = packageFile.getCanonicalPath(); Log.w(TAG, "!!! REBOOTING TO INSTALL " + filename + " !!!"); String arg = "--update_package=" + filename; bootCommand(context, arg); } /** * Reboots the device and wipes the user data partition. This is * sometimes called a "factory reset", which is something of a * misnomer because the system partition is not restored to its * factory state. * 重启系统删除用户数据,这个通常被回复出厂设置调用 * Requires the {@link android.Manifest.permission#REBOOT} permission. * * @param context the Context to use * * @throws IOException if writing the recovery command file * fails, or if the reboot itself fails. */ public static void rebootWipeUserData(Context context) throws IOException { final ConditionVariable condition = new ConditionVariable(); Intent intent = new Intent("android.intent.action.MASTER_CLEAR_NOTIFICATION"); context.sendOrderedBroadcast(intent, android.Manifest.permission.MASTER_CLEAR, new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { condition.open(); } }, null, 0, null, null); // Block until the ordered broadcast has completed. condition.block(); bootCommand(context, "--wipe_data"); } /** * Reboot into the recovery system to wipe the /cache partition. * 重启系统来删除 /cache目录文件 * @throws IOException if something goes wrong. */ public static void rebootWipeCache(Context context) throws IOException { bootCommand(context, "--wipe_cache"); }
这几个函数功能的注释写的很清楚,android系统做 wipe_data、wipe_cache、OTA升级就是调用的这三个函数。具体在哪调用的我们不一一列举了,简单说一下,比如rebootWipeUserData是在回复出厂设置时候调用的,代码在\frameworks\base\services\java\com\android\server\MasterClearReceiver.java中。
我们仔细研究着几个 函数发现,要实现不同的重启模式就是要bootCommand()这个函数传送不同的参数,android重启就是由这个函数来实现的,
我们就来看
/** * Reboot into the recovery system with the supplied argument. * @param arg to pass to the recovery utility. * @throws IOException if something goes wrong. */ private static void bootCommand(Context context, String arg) throws IOException { RECOVERY_DIR.mkdirs(); // 创建recovery的目录 COMMAND_FILE.delete(); // 清空recovery命令文件 /* *这两个文件都在/cache目录中,他俩的定义是 * private static File RECOVERY_DIR = new File("/cache/recovery"); * private static File COMMAND_FILE = new File(RECOVERY_DIR, "command"); */ LOG_FILE.delete(); FileWriter command = new FileWriter(COMMAND_FILE);//穿件新的命令文件 try { command.write(arg); //将命令写入命令文件,给recovery模式使用 command.write("\n"); } finally { command.close(); } // Having written the command file, go ahead and reboot PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE); pm.reboot("recovery"); //重启系统 throw new IOException("Reboot failed (no permissions?)"); }
这里首先要说明/cache目录和命令文件这两个东西,他们是主系统和recovery模式之间交流的桥梁,主系统把要做的事情写入到这两个中,然后recovery会读取这两个文件,再做相应的处理,这一点在我的另一篇文章中做了更详细的说明 http://blog.csdn.net/dkleikesa/article/details/9706137
从这个函数就可以看出了,到了这里上面的三个功能最终合并成了一个--进入recovery模式,于是我们就来看 pm.reboot("recovery");
在\frameworks\base\core\java\android\os\PowerManager.java
public void reboot(String reason) { try { mService.reboot(reason); } catch (RemoteException e) { } }
这里mService的定义
public PowerManager(IPowerManager service, Handler handler) { mService = service; mHandler = handler; }
是在构造函数里传进来的,我们继续来看这个参数的传送在\frameworks\base\core\java\android\app\ContextImpl.java中有这么一段
registerService(POWER_SERVICE, new ServiceFetcher() { public Object createService(ContextImpl ctx) { IBinder b = ServiceManager.getService(POWER_SERVICE); IPowerManager service = IPowerManager.Stub.asInterface(b); return new PowerManager(service, ctx.mMainThread.getHandler()); }});
可以知道 b = ServiceManager.getService(POWER_SERVICE);得到了PowerManagerService这个服务,service = IPowerManager.Stub.asInterface(b);然后将这个sevice的指针传给PowerManager()的构造函数。也就是说 这里的mService就是 PowerManagerService这个服务,因此这里的reboot也就是PowerManagerService的reboot 我们来看它的代码在:\frameworks\base\services\java\com\android\server\PowerManagerService.java中
/** * Reboot the device immediately, passing 'reason' (may be null) * to the underlying __reboot system call. Should not return. */ public void reboot(String reason) { mContext.enforceCallingOrSelfPermission(android.Manifest.permission.REBOOT, null); if (mHandler == null || !ActivityManagerNative.isSystemReady()) { throw new IllegalStateException("Too early to call reboot()"); } //建立一个 shutdown线程来执行整个关机过程 final String finalReason = reason; Runnable runnable = new Runnable() { public void run() { synchronized (this) { ShutdownThread.reboot(mContext, finalReason, false); } } }; // ShutdownThread must run on a looper capable of displaying the UI. //关机时的UI显示 mHandler.post(runnable); // PowerManager.reboot() is documented not to return so just wait for the inevitable. synchronized (runnable) { while (true) { try { runnable.wait(); } catch (InterruptedException e) { } } } }
下面我们就来看shutdown线程的 reboot函数:\frameworks\base\core\java\com\android\internal\app\ShutdownThread.java
/** * Request a clean shutdown, waiting for subsystems to clean up their * state etc. Must be called from a Looper thread in which its UI * is shown. * * @param context Context used to display the shutdown progress dialog. * @param reason code to pass to the kernel (e.g. "recovery"), or null. * @param confirm true if user confirmation is needed before shutting down. */ public static void reboot(final Context context, String reason, boolean confirm) { mReboot = true; //吧reboot设置为true mRebootReason = reason; shutdown(context, confirm);//关机之前的 系统清理,保存好所有的数据 }
这里要进一步执行的话靠的是mReboot=true;这一句,具体的执行过程在ShutdownThread:run()函数中
/** * Makes sure we handle the shutdown gracefully. * Shuts off power regardless of radio and bluetooth state if the alloted time has passed. */ public void run() { 。。。。。。。。。。。。。 。。。。。。。。。。。。。 rebootOrShutdown(mReboot, mRebootReason); }
继续看:\frameworks\base\core\java\com\android\internal\app\ShutdownThread.java
/** * Do not call this directly. Use {@link #reboot(Context, String, boolean)} * or {@link #shutdown(Context, boolean)} instead. * * @param reboot true to reboot or false to shutdown * @param reason reason for reboot */ public static void rebootOrShutdown(boolean reboot, String reason) { if (reboot) { Log.i(TAG, "Rebooting, reason: " + reason); try { Power.reboot(reason); } catch (Exception e) { Log.e(TAG, "Reboot failed, will attempt shutdown instead", e); } } else if (SHUTDOWN_VIBRATE_MS > 0) { // vibrate before shutting down Vibrator vibrator = new Vibrator(); try { vibrator.vibrate(SHUTDOWN_VIBRATE_MS);//关机震动一下,如果是reboot就不震动 } catch (Exception e) { // Failure to vibrate shouldn't interrupt shutdown. Just log it. Log.w(TAG, "Failed to vibrate during shutdown.", e); } // vibrator is asynchronous so we need to wait to avoid shutting down too soon. try { Thread.sleep(SHUTDOWN_VIBRATE_MS); } catch (InterruptedException unused) { } } // Shutdown power Log.i(TAG, "Performing low-level shutdown..."); Power.shutdown(); }
这里最终调用的是Power.reboot(reason);代码在\frameworks\base\core\java\android\os\Power.java
/** * Reboot the device. * @param reason code to pass to the kernel (e.g. "recovery"), or null. * * @throws IOException if reboot fails for some reason (eg, lack of * permission) */ public static void reboot(String reason) throws IOException { rebootNative(reason); }
这里到了jni层 代码在\frameworks\base\core\jni\android_os_Power.cpp
static void android_os_Power_reboot(JNIEnv *env, jobject clazz, jstring reason) { if (reason == NULL) { android_reboot(ANDROID_RB_RESTART, 0, 0); } else { const char *chars = env->GetStringUTFChars(reason, NULL); android_reboot(ANDROID_RB_RESTART2, 0, (char *) chars); env->ReleaseStringUTFChars(reason, chars); // In case it fails. } jniThrowIOException(env, errno); }
继续看这里调用的android_reboot()在:\system\core\libcutils\android_reboot.c
int android_reboot(int cmd, int flags, char *arg) { int ret; if (!(flags & ANDROID_RB_FLAG_NO_SYNC)) sync(); if (!(flags & ANDROID_RB_FLAG_NO_REMOUNT_RO)) 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函数它的实现在\android40\bionic\libc\arch-arm\syscalls\__reboot.S
ENTRY(__reboot) .save {r4, r7} stmfd sp!, {r4, r7} ldr r7, =__NR_reboot swi #0 ldmfd sp!, {r4, r7} movs r0, r0 bxpl lr b __set_syscall_errno END(__reboot)
这里__NR_reboot 定义为88 也就是说它是linux系统调用列表里的第88个函数,这样我们就可以去kernel里找这个函数的定义了
系统调用的原理这里就不多讲了,可以百度一下很容易找到,最终得到的reboot函数在\kernel_imx\kernel\sys.c中
SYSCALL_DEFINE4(reboot, int, magic1, int, magic2, unsigned int, cmd, void __user *, arg) { char buffer[256]; int ret = 0; /* We only trust the superuser with rebooting the system. */ if (!capable(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; /* 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_restart - reboot the system * @cmd: pointer to buffer containing command to execute for restart * or %NULL * * Shutdown everything and perform a clean reboot. * This is not safe to call in interrupt context. */ void kernel_restart(char *cmd) { kernel_restart_prepare(cmd); 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); }
下一步调用machine_restart()代码在\kernel_imx\arch\arm\kernel\process.c中
void machine_restart(char *cmd) { machine_shutdown(); arm_pm_restart(reboot_mode, cmd); }
下一步看arm_pm_restart(reboot_mode, cmd);,在本文件中有这个的定义void (*arm_pm_restart)(char str, const char *cmd) = arm_machine_restart;
也就是这个函数指针指向了arm_machine_restart,它的代码在本文件中
void arm_machine_restart(char mode, const char *cmd) { //ar_mode(mode,cmd); //lijianzhang /* Flush the console to make sure all the relevant messages make it * out to the console drivers */ arm_machine_flush_console(); /* Disable interrupts first */ local_irq_disable(); local_fiq_disable(); /* * Tell the mm system that we are going to reboot - * we may need it to insert some 1:1 mappings so that * soft boot works. */ setup_mm_for_reboot(mode); /* Clean and invalidate caches */ flush_cache_all(); /* Turn off caching */ cpu_proc_fin(); /* Push out any further dirty data, and ensure cache is empty */ flush_cache_all(); /* * Now call the architecture specific reboot code. */ arch_reset(mode, cmd); /* * Whoops - the architecture was unable to reboot. * Tell the user! */ mdelay(1000); printk("Reboot failed -- System halted\n"); while (1); }
这里执行reboot的函数是arch_reset(mode, cmd),在执行arch_reset的代码都是关闭系统和cpu一些东西,其中包括了mmu,关闭了mmu以后,访问寄存器必须直接使用寄存器地址,ioremap这个函数就不能再使用了,同样的,由于mmu关闭了 ,这个函数下面就是printk是打印不出来的,字符过多还会报出内核错误,因此这里虽然写了printk 可以打印出reboot错误,其实也是打不出来的,这点gun官网上也有说明,网上有人修改了内核,在关闭mmu以后将printk缓存替换成物理地址,这样才能正常使用。
好了下面来看arch_reset(mode, cmd); \kernel_imx\arch\arm\plat-mxc\system.c
/* * Reset the system. It is called by machine_restart(). */ void arch_reset(char mode, const char *cmd) { unsigned int wcr_enable; arch_reset_special_mode(mode, cmd); #ifdef CONFIG_ARCH_MX6 /* wait for reset to assert... */ #ifdef CONFIG_MX6_INTER_LDO_BYPASS wcr_enable = 0x14; /*reset system by extern pmic*/ #else wcr_enable = (1 << 2); #endif __raw_writew(wcr_enable, wdog_base); /*errata TKT039676, SRS bit may be missed when SRC sample it, need to write the wdog controller twice to avoid it */ __raw_writew(wcr_enable, wdog_base/*MX6Q_WDOG1_BASE_ADDR*/); /* wait for reset to assert... */ mdelay(500); printk(KERN_ERR "Watchdog reset failed to assert reset\n"); return; #endif #ifdef CONFIG_MACH_MX51_EFIKAMX if (machine_is_mx51_efikamx()) { mx51_efikamx_reset(); return; } #endif if (cpu_is_mx1()) { wcr_enable = (1 << 0); } else { struct clk *clk; clk = clk_get_sys("imx2-wdt.0", NULL); if (!IS_ERR(clk)) clk_enable(clk); wcr_enable = (1 << 2); } /* Assert SRS signal */ __raw_writew(wcr_enable, wdog_base); /* wait for reset to assert... */ mdelay(500); printk(KERN_ERR "Watchdog reset failed to assert reset\n"); /* delay to allow the serial port to show the message */ mdelay(50); /* we'll take a jump through zero as a poor second */ }
好了这里看arch_reset_special_mode(mode, cmd); 这个函数就是进入各个模式,与uboot通信的地方,来看代码
static void arch_reset_special_mode(char mode, const char *cmd) { if (strcmp(cmd, "download") == 0) do_switch_mfgmode(); else if (strcmp(cmd, "recovery") == 0) do_switch_recovery(); else if (strcmp(cmd, "fastboot") == 0) do_switch_fastboot(); else if (strcmp(cmd, "bootloader") == 0) do_switch_bootloader(); }
这里可以看到内核支持4个模式,每个模式都有相应的函数,我们来贴一个recovery的,在\kernel_imx\arch\arm\mach-mx6\system.c
void do_switch_recovery(void) { u32 reg; reg = __raw_readl((SRC_BASE_ADDR + SRC_GPR10)); reg |= ANDROID_RECOVERY_BOOT; __raw_writel(reg, (SRC_BASE_ADDR + SRC_GPR10)); }
原理是把一个寄存器的值的一位置高,这个寄存器在复位过程中是不会被清零的,重启以后,uboot可以检测这一位来确定启动的模式,
过了这个函数,下面执行__raw_writew(wcr_enable, wdog_base); 这个是打开看门狗,可以知道系统重启最后就是利用看门狗,产生复位信号,来重启的。
到了这里reboot的流程就走完了,下一步就是正常启动了,详细的启动过程参见我的另一篇文章 http://blog.csdn.net/dkleikesa/article/details/9792747
下面来说明一个小技巧,如果要重启进入各个模式,是可以由命令来实现的,也就是"reboot recovery"进入recovery模式 “reboot fastboot”是进入fastboot模式