android系統reboot

  這裏所說的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模式


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章