天天看點

Android 關機流程 從kernel到frameworkKernel層事件處理Java層關機Native層關機Kernel層關機

Android6.0關機流程

Android系統關機有如下方式:1.定時關機、2.指令行輸入reboot重新開機、3.長按電源鍵出現關機對畫框等,本文以長按電源鍵為例來分析基于Android 6.0的高通源碼。

長按電源鍵會啟動Android系統的按鍵消息處理機制。每個activity具有一個phonewindow對象,每個phonewindow對象具有一個DecorView對象,每個DecorVier又被設定到一個ViewRoot對象中(如圖一).每個activity建立的時候會通過ViewRoot注冊一個inputmanager,然後建立一組對應的事件擷取和分發的線程:InputReader 和InputDispatch。當沒有事件發生的時候Reader處于輪訓狀态,Dispatch處于休眠狀态。當Reader擷取到event的時候,Reader将會喚醒Dispatch,讓其分發這個event到目前focus 的activity,然在activity的dispatchkeyEvent中進行處理。我們将關機流程分為PowerKey事件擷取、Java層的關機流程分析、Native層的關機流程分析和kernel層的關機流程分析四部分來分析。

Kernel層事件處理

kernel層的事件上傳,是指PowerKey按下的事件傳遞到Framework之前的流程,然後Framework層進行關機的相關操作。

1.pmic8xxx-pwrkey.c-input_report_key

static irqreturn_t pwrkey_press_irq(int irq, void*_pwrkey)

{

struct pmic8xxx_pwrkey*pwrkey = _pwrkey;

if (pwrkey->press== true) {

pwrkey->press = false;

return IRQ_HANDLED;

} else {

pwrkey->press = true;

}

input_report_key(pwrkey->pwr,KEY_POWER, 1);

//PowerKey按下事件,進入input.c中的inputEvent函數

input_sync(pwrkey->pwr);

return IRQ_HANDLED;

}

檢測powerkey的按下,開始處理

2.input.c-inputEvent

void input_event(struct input_dev *dev,

        unsigned int type, unsigned int code, intvalue)

{

    unsigned long flags;

    if (is_event_supported(type,dev->evbit, EV_MAX)) {

       spin_lock_irqsave(&dev->event_lock,flags);

       input_handle_event(dev, type, code, value);

//進入input_handle_event,上下文進行中斷鎖的操作

       spin_unlock_irqrestore(&dev->event_lock,flags);

    }

}

進入input_handle_event,然後注冊該事件到device,交給Android的消息處理服務進行處理,其中我們開始說的reader讀取到該事件。

static void input_handle_event(struct input_dev *dev,

                  unsigned int type, unsigned int code,int value)

{

    int disposition;

    disposition = input_get_disposition(dev,type, code, value);

    if ((disposition &INPUT_PASS_TO_DEVICE) && dev->event)

       dev->event(dev, type, code,value);

//event 處理

    if (!dev->vals)

       return;

    if (disposition &INPUT_PASS_TO_HANDLERS) {

       struct input_value *v;

       if (disposition &INPUT_SLOT) {

           v =&dev->vals[dev->num_vals++];

           v->type = EV_ABS;

           v->code = ABS_MT_SLOT;

           v->value =dev->mt->slot;

       }

       v = &dev->vals[dev->num_vals++];

       v->type = type;

       v->code = code;

       v->value = value;

    }

    if (disposition &INPUT_FLUSH) {

       if (dev->num_vals >= 2)

           input_pass_values(dev,dev->vals, dev->num_vals);

       dev->num_vals = 0;

    } else if (dev->num_vals >=dev->max_vals - 2) {

       dev->vals[dev->num_vals++]= input_value_sync;

       input_pass_values(dev,dev->vals, dev->num_vals);

       dev->num_vals = 0;

    }

}

inputread在讀取到有keyboard事件上報後,會調用到keydispatch的notifykey,緊接着調用interceptKeyBeforeQueueing,最終傳回到phonewindowmanager. interceptKeyBeforeQueueing(kernel相關的内容待更新、消息服務内容待更新)

kernel層工作 總結:

1.powerkey事件存儲

2.消息服務讀取powerkey事件

3.消息服務将keyevent進行傳遞

Java層關機

3.Phonewindowmanager.java-interceptKeyBeforeQueueing

public int interceptKeyBeforeQueueing(KeyEvent event, int policyFlags) {

        …

final boolean interactive = (policyFlags & FLAG_INTERACTIVE) != 0;

final boolean down = event.getAction() == KeyEvent.ACTION_DOWN;

final boolean canceled = event.isCanceled();

final int keyCode = event.getKeyCode();

// Handle special keys.

 switch (keyCode) {

  …

 case KeyEvent.KEYCODE_POWER: {

     result &=~ACTION_PASS_TO_USER;

     isWakeKey = false; // wake-upwill be handled separately

     if (down) {

         interceptPowerKeyDown(event, interactive);

//進入interceptPowerKeyDown,上文處理各種消息來源,以及特殊按鍵功能

     } else {

          interceptPowerKeyUp(event,interactive, canceled);

     }

     break;

}

if (useHapticFeedback) {

performHapticFeedbackLw(null, HapticFeedbackConstants.VIRTUAL_KEY, false);

   }

if (isWakeKey) {

wakeUp(event.getEventTime(),mAllowTheaterModeWakeFromKey,android.policy:KEY");

}

return result;

}

該方法主要完成各種按鍵消息的處理,包括按下電源鍵與音量下鍵進行抓屏、來電時按電源鍵啟動靜音模式等。在該方法根據條件處理按下電源鍵後的特殊任務,然後調用interceptPowerKeyDown進行進一步的處理。

4. interceptPowerKeyDown方法

 private voidinterceptPowerKeyDown(KeyEvent event, boolean interactive) {

    // 持有鎖

if(!mPowerKeyWakeLock.isHeld()) {

         mPowerKeyWakeLock.acquire();

}

     // 多次按下,隻處理一次

if(mPowerKeyPressCounter != 0) {

           mHandler.removeMessages(MSG_POWER_DELAYED_PRESS);

}

   // Detect userpressing the power button in panic when an application has

   // taken overthe whole screen.

boolean panic =mImmersiveModeConfirmation.onPowerKeyDown(interactive,

               SystemClock.elapsedRealtime(), isImmersiveMode(mLastSystemUiFlags));

if (panic) {

           mHandler.post(mHiddenNavPanic);

}

// Latch power key stateto detect screenshot chord.

if (interactive&& !mScreenshotChordPowerKeyTriggered

               && (event.getFlags() & KeyEvent.FLAG_FALLBACK) == 0) {

           mScreenshotChordPowerKeyTriggered = true;

           mScreenshotChordPowerKeyTime = event.getDownTime();

           interceptScreenshotChord();

}

//來電時powerKey,停止響鈴

TelecomManagertelecomManager = getTelecommService();

boolean hungUp = false;

if (telecomManager !=null) {

if (telecomManager.isRinging()) {

// Pressing Power while there's a ringing incoming

// call should silence the ringer.

telecomManager.silenceRinger();

} else if ((mIncallPowerBehavior

&Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR_HANGUP) != 0

&& telecomManager.isInCall() &&interactive) {

// Otherwise, if "Power button ends call" isenabled,

// the Power button will hang up any current active call.

hungUp = telecomManager.endCall();

}

}

mPowerKeyHandled =hungUp || mScreenshotChordVolumeDownKeyTriggered

                ||mScreenshotChordVolumeUpKeyTriggered;

if (!mPowerKeyHandled) {

if (interactive) {

// When interactive, we're already awake.

// Wait for a long press or for the buttonto be released to decide what to do.

            if(hasLongPressOnPowerBehavior()) {

Message msg =mHandler.obtainMessage(MSG_POWER_LONG_PRESS);

            msg.setAsynchronous(true);

           mHandler.sendMessageDelayed(msg,

                           ViewConfiguration.get(mContext).getDeviceGlobalActionKeyTimeout());

                }

            } else{

               wakeUpFromPowerKey(event.getDownTime());

if(mSupportLongPressPowerWhenNonInteractive &&hasLongPressOnPowerBehavior()) {

Message msg =mHandler.obtainMessage(MSG_POWER_LONG_PRESS);

//PowerKey 的事件傳遞到handler進行處理

                   msg.setAsynchronous(true);

                    mHandler.sendMessageDelayed(msg,

                           ViewConfiguration.get(mContext).getDeviceGlobalActionKeyTimeout());

                   mBeganFromNonInteractive = true;

                }else {

                   final int maxCount = getMaxMultiPressPowerCount();

                   if (maxCount <= 1) {

                       mPowerKeyHandled = true;

                   } else {

                       mBeganFromNonInteractive = true;

                   }

                }

            }

        }

}

該方法中主要處理電源鍵按下後的一下情況,比如電源鍵松開之前一直亮屏、取消多次按鍵時的逾時消息、當設定後按下電源鍵後結束響鈴,挂斷電話等。如果電源鍵按下的處理情況還沒有處理時,解會分長按、短按和多次按來進行處理,此處我們隻關心長按電源鍵,是以會進入mHandler.sendMessageDelayed的方法,而mHandler的類型是PolicyHandler,在初始化PhoneWindowManager時對其進行了指派。調用sendMessageDelayed方法發送消息,最終會在PolicyHandler的handleMessage進行處理該消息。

5.PolicyHandler 函數

    private class PolicyHandler extends Handler {

        @Override

        public voidhandleMessage(Message msg) {

            switch(msg.what) {

    …

               case MSG_POWER_LONG_PRESS:

                   powerLongPress();

//處理函數進入powerLongPress,上下文中處理不同按鍵的邏輯

                   break;

   …

            }

        }

}

該函數很簡單,就是處理各種消息,這裡大部分都是鍵盤按鍵消息,我們關心的長按電源鍵的消息處理。由源碼可知,當PolicyHandler接收到MSG_POWER_LONG_PRESS消息之後,将會進入powerLongPress方法做進一步的處理。

6.powerLongPress 函數

private void powerLongPress() {

        final intbehavior = getResolvedLongPressOnPowerBehavior();

        switch(behavior) {

        case LONG_PRESS_POWER_GLOBAL_ACTIONS:

           mPowerKeyHandled = true;

            if(!performHapticFeedbackLw(null, HapticFeedbackConstants.LONG_PRESS, false)) {

               performAuditoryFeedbackForAccessibilityIfNeed();

            }

            showGlobalActionsInternal();

//進入showGlobalActionsInternal,上下文針對不同的powerkey事件進行處理

            break;

        caseLONG_PRESS_POWER_SHUT_OFF:

        caseLONG_PRESS_POWER_SHUT_OFF_NO_CONFIRM:

           mPowerKeyHandled = true;

           performHapticFeedbackLw(null, HapticFeedbackConstants.LONG_PRESS,false);

           sendCloseSystemWindows(SYSTEM_DIALOG_REASON_GLOBAL_ACTIONS);

           mWindowManagerFuncs.shutdown(behavior == LONG_PRESS_POWER_SHUT_OFF);

            break;

        }

}

在getResolvedLongPressOnPowerBehavior方法中,如果是工廠模式測試時,傳回LONG_PRESS_POWER_SHUT_OFF_NO_CONFIRM,否則傳回mLongPressOnPowerBehavior成員變量,而該成員變量是在PhoneWindowManager初始化時進行設定

mLongPressOnPowerBehavior =mContext.getResources().getInteger(

                               com.android.internal.R.integer.config_longPressOnPowerBehavior);

由mLongPressOnPowerBehavior成員變量初始化可知,getResolvedLongPressOnPowerBehavior方法中最終傳回的是config_longPressOnPowerBehavior的值,而該值是在/framework/base/core/res/values/config.xml中進行配置的,在該配置檔案中将config_longPressOnPowerBehavior屬性值配為1代表是全局的動作;0代表不做任何動作;2表示确認後關機;3表示不确認,直接關機,這就是LONG_PRESS_POWER_SHUT_OFF_NO_CONFIRM的值,在工廠模式測試時,不需要确認直接關機。此處我們将會進入全局動作中,即會進入showGlobalActionsInternal方法,進行關機對話框的顯示。

7. showGlobalActionsInternal函數

void showGlobalActionsInternal() {

   sendCloseSystemWindows(SYSTEM_DIALOG_REASON_GLOBAL_ACTIONS);

    if(mGlobalActions == null) {

      mGlobalActions = new GlobalActions(mContext, mWindowManagerFuncs);

    }

    final booleankeyguardShowing = isKeyguardShowingAndNotOccluded();

   mGlobalActions.showDialog(keyguardShowing, isDeviceProvisioned());

//進入showdialog函數,顯示關機對話框

    if(keyguardShowing) {

        // since it took two seconds of long press tobring this up,

        // poke the wake lock so they have sometime to see the dialog.

        mPowerManager.userActivity(SystemClock.uptimeMillis(),false);

        }

    }

該函數中首先調用sendCloseSystemWindows函數,發送由于全局關機動作的原因,最終會調用ActivityManagerService類的closeSystemDialogs函數關閉其他的系統對話框。利用單例模式建立GlobalActions對象,并儲存到其成員變量mGlobalActions中,最終會調用GlobalActions的showDialog方法進行顯示關機對話框。

8.GlobalActions.java-showdialog 函數

public void showDialog(

boolean keyguardShowing, boolean isDeviceProvisioned) {

mKeyguardShowing =keyguardShowing;

mDeviceProvisioned =isDeviceProvisioned;

if (mDialog != null) {

mDialog.dismiss();

mDialog = null;

// Show delayed, so that the dismiss of the previousdialog completes

mHandler.sendEmptyMessage(MESSAGE_SHOW);

        } else {

           handleShow();

//進入handleshow,上文處理keyguard是否顯示,顯示則推遲處理。

        }

}

該方法中主要是判斷是否會顯示keyguard,如果之前已經有全局對話框顯示,則發生延遲消息,以便其顯示完後最終關閉,如果是第一次啟動全局對話框,則會進入handleShow方法中進行處理。

9.handleshow 函數

private void handleShow() {

awakenIfNecessary();

mDialog = createDialog();//建立對話框,并相應點選事件

prepareDialog();//更新各個模式如靜音、飛行

// If we only have 1 item and it's a simple press action,just do this action.

if (mAdapter.getCount() == 1

                && mAdapter.getItem(0)instanceof SinglePressAction

                &&!(mAdapter.getItem(0) instanceof LongPressAction)) {

            ((SinglePressAction)mAdapter.getItem(0)).onPress();

} else {

WindowManager.LayoutParams attrs =mDialog.getWindow().getAttributes();

attrs.setTitle("GlobalActions");

mDialog.getWindow().setAttributes(attrs);

mDialog.show();

          mDialog.getWindow().getDecorView().setSystemUiVisibility(View.STATUS_BAR_DISABLE_EXPAND);

}

}

在該方法中,首先調用awakenIfNecessary方法進行了螢幕喚醒,然後調用createDialog()建立全局關機對話框,當對話框建立完成後,調用prepareDialog方法進行keyguard視窗風格樣式的設定,最後會進行我們全局關機對匡樣式進行判斷,如果是隻有一個item,則會通過onPress方法進行處理,否則會進行系統UI顯示的設定。此處我們進入creatDialog方法進行建立全局關機對話框。

10.PowerAction 點選函數

private final class PowerAction extends SinglePressAction

implementsLongPressAction {

       …

public void onPress() {

// shutdownby making sure radio and power are handled accordingly.

mWindowManagerFuncs.shutdown(false );

//mWindowManagerFuncs實際上是windowmanagerservice的對象,進入shutdown

}

}

如果長按會進入過PowerAction的onLongPress函數,最後會進入到mWindowManagerFuncs.rebootSafeMode函數中;如果是短按關機Aciton會進入到PowerAction的onPress函數,最後進入到mWindowManagerFuncs.shutDown方法進行處理。在PhoneWindowManager的初始化過程可知,mWindowManagerFuncs被指派為WindowManagerService,是以會調用WindowManagerService的shutdown方法。

11.windowmanagerservice.java-shutdown 函數

public void shutdown(boolean confirm) {

ShutdownThread.shutdown(mContext,confirm);

//調用shutdownThread的shutdown方法

}

12.shutdownThread.java-shutdown 方法

public static void shutdown(final Context context,boolean confirm) {

        mReboot =false;

       mRebootSafeMode = false;

        Log.d(TAG,"!!! Request to shutdown !!!");

        if (mSpew){

           StackTraceElement[] stack = new Throwable().getStackTrace();

            for(StackTraceElement element : stack)

            {

               Log.d(TAG, "    |----" +element.toString());

            }

        }

        if(SystemProperties.getBoolean("ro.monkey", false)) {

           Log.d(TAG, "Cannot request to shutdown when Monkey is running,returning.");

            return;

        }

       shutdownInner(context, confirm);

//進一步調用shutdowninner,上文判斷,若在monkey,不進行關機操作

}

在該方法中隻是初始化了一些參數,最後由shutdownInner方法進行處理,參數confirm來設定是否要彈出關機對話框。

13.shutdowninner 方法

static void shutdownInner(final Context context, booleanconfirm) {

        …

        if(confirm) {

            …

        } else {

           beginShutdownSequence(context);

//開始進行關機準備,進入beginShutdownSequence

        }

}

在該方法中,會通過傳進來的confirm參數來判斷是否要顯示關機對話框,如果顯示會設定關鍵對話框,如果不顯示直接關機,無論顯示與否,最後會進入到beginShutdownSequence方法,做進一步的關機處理。

14.beginshutdownsequence 函數

private static void beginShutdownSequence(Contextcontext) {

       // Throw up a system dialog to indicate thedevice is rebooting / shutting down.

       ProgressDialog pd = new ProgressDialog(context);

        if(PowerManager.REBOOT_RECOVERY.equals(mRebootReason)) {

           mRebootUpdate = new File(UNCRYPT_PACKAGE_FILE).exists();

            if(mRebootUpdate) {

               pd.setTitle(context.getText(com.android.internal.R.string.reboot_to_update_title));

                pd.setMessage(context.getText(

                       com.android.internal.R.string.reboot_to_update_prepare));

               pd.setMax(100);

               pd.setProgressNumberFormat(null);

               pd.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);

               pd.setProgress(0);

               pd.setIndeterminate(false);

            } else{

                //Factory reset path. Set the dialog message accordingly.

               pd.setTitle(context.getText(com.android.internal.R.string.reboot_to_reset_title));

               pd.setMessage(context.getText(

                       com.android.internal.R.string.reboot_to_reset_message));

               pd.setIndeterminate(true);

            }

        } else {

           pd.setTitle(context.getText(com.android.internal.R.string.power_off));

           pd.setMessage(context.getText(com.android.internal.R.string.shutdown_progress));

           pd.setIndeterminate(true);

        }

       pd.setCancelable(false);

       pd.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);

        // startthe thread that initiates shutdown

       sInstance.mHandler = new Handler() {

        };

       beginAnimationTime = 0;

        booleanmShutOffAnimation = configShutdownAnimation(context);

        intscreenTurnOffTime = getScreenTurnOffTime(context);

       synchronized (mEnableAnimatingSync) {

            if(mEnableAnimating) {

                if(mShutOffAnimation) {

                   Log.d(TAG, "mIBootAnim.isCustBootAnim() is true");

                   bootanimCust();//播放動畫

                }else {

                   pd.show();

                   sInstance.mProgressDialog = pd;

                }

               sInstance.mHandler.postDelayed(mDelayDim, screenTurnOffTime);

            }

        }

        if(sInstance.getState() != Thread.State.NEW || sInstance.isAlive()) {

            …

        } else {

            sInstance.start();//進入線程run方法

        }

    }

該方法主要初始化一些關機操作,比如擷取audio,停止啟動應用程式播放music等,最後調用ShutdownThread的start函數來啟動關機線程,進入到ShutdownThread線程的run方法中。

15.run 函數

public void run() {

       BroadcastReceiver br = new BroadcastReceiver() {

        @Overridepublic void onReceive(Context context, Intent intent) {

        // We don'tallow apps to cancel this, so ignore the result.

            actionDone();

            }

        };

        {

            Stringreason = (mReboot ? "1" : "0") + (mRebootReason != null ?mRebootReason : "");

           SystemProperties.set(SHUTDOWN_ACTION_PROPERTY, reason);

//記錄關機原因

        }

        if(mRebootSafeMode) {

           SystemProperties.set(REBOOT_SAFEMODE_PROPERTY, "1");

        }

        Log.i(TAG,"Sending shutdown broadcast...");

        // Firstsend the high-level shut down broadcast.

        mActionDone= false;

        Intentintent = new Intent(Intent.ACTION_SHUTDOWN);

       intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);

       mContext.sendOrderedBroadcastAsUser(intent,

               UserHandle.ALL, null, br, mHandler, 0, null, null);

final long endTime = SystemClock.elapsedRealtime() +MAX_BROADCAST_TIME;

       synchronized (mActionDoneSync) {

            while(!mActionDone) {

               long delay = endTime - SystemClock.elapsedRealtime();

                if(delay <= 0) {

                    Log.w(TAG, "Shutdown broadcast timedout");

                   break;

                }

                try{

                   mActionDoneSync.wait(delay);

                }catch (InterruptedException e) {

                }

            }

        }       

        Log.i(TAG,"Shutting down activity manager...");

        //關閉activitymanager

        finalIActivityManager am =

           ActivityManagerNative.asInterface(ServiceManager.checkService("activity"));

        if (am !=null) {

            try {

               am.shutdown(MAX_BROADCAST_TIME);

            } catch(RemoteException e) {

            }

        }

        Log.i(TAG,"Shutting down package manager...");

//關閉packagemanager

        finalPackageManagerService pm = (PackageManagerService)

           ServiceManager.getService("package");

        if (pm !=null) {

           pm.shutdown();

        }

        String shutDownFile = null;

       //showShutdownAnimation() is called from here to sync

        //music andanimation properly

       if(checkAnimationFileExist()) {

           lockDevice();

            showShutdownAnimation();

            if(!isSilentMode()

                   && (shutDownFile = getShutdownMusicFilePath()) != null) {

                isShutdownMusicPlaying= true;

               shutdownMusicHandler.obtainMessage(0, shutDownFile).sendToTarget();

            }

        }

        Log.i(TAG,"wait for shutdown music");

        final longendTimeForMusic = SystemClock.elapsedRealtime() + MAX_BROADCAST_TIME;

       synchronized (mActionDoneSync) {

            while(isShutdownMusicPlaying) {

               long delay = endTimeForMusic - SystemClock.elapsedRealtime();

                if(delay <= 0) {

                   Log.w(TAG, "play shutdown music timeout!");

                   break;

                }

                try{

                   mActionDoneSync.wait(delay);

                }catch (InterruptedException e) {

                }

            }

            if(!isShutdownMusicPlaying) {

               Log.i(TAG, "play shutdown music complete.");

            }

        }

//關閉通信相關内容

        // Shutdownradios.

        shutdownRadios(MAX_RADIO_WAIT_TIME);

        // ShutdownMountService to ensure media is in a safe state

IMountShutdownObserver observer = newIMountShutdownObserver.Stub() {

public void onShutDownComplete(int statusCode) throwsRemoteException {

               Log.w(TAG, "Result code " + statusCode + " fromMountService.shutdown");

               actionDone();

            }

        };

        Log.i(TAG,"Shutting down MountService");

//關閉挂載服務

        // Setinitial variables and time out time.

        mActionDone= false;

        final longendShutTime = SystemClock.elapsedRealtime() + MAX_SHUTDOWN_WAIT_TIME;

       synchronized (mActionDoneSync) {

            try {

               final IMountService mount = IMountService.Stub.asInterface(

                       ServiceManager.checkService("mount"));

                if(mount != null) {

                    mount.shutdown(observer);

                }else {

                   Log.w(TAG, "MountService unavailable for shutdown");

                }

            } catch(Exception e) {

               Log.e(TAG, "Exception during MountService shutdown", e);

            }

            while(!mActionDone) {

               long delay = endShutTime - SystemClock.elapsedRealtime();

                if(delay <= 0) {

                   Log.w(TAG, "Shutdown wait timed out");

                   break;

                }

                try{

                   mActionDoneSync.wait(delay);

                }catch (InterruptedException e) {

                }

            }

        }

//進入rebootorshutdown函數

       rebootOrShutdown(mReboot, mRebootReason);

}

該函數中主要完成建立接收關機的廣播,設定關機原因,關閉ActivityManagerService、PackagerManagerService、MountServie等,最後進入rebootOrshutDown進行關機操作。

16.rebootorshutdown 函數

  public staticvoid rebootOrShutdown(boolean reboot, String reason) {

       deviceRebootOrShutdown(reboot, reason);

//檢查廠商的關機處理

        if (reboot){

           Log.i(TAG, "Rebooting, reason: " + reason);

           PowerManagerService.lowLevelReboot(reason);

           Log.e(TAG, "Reboot failed, will attempt shutdown instead");

        } else if(SHUTDOWN_VIBRATE_MS > 0) {

            //vibrate before shutting down

//關機震動

           Vibrator vibrator = new SystemVibrator();

            try {

               vibrator.vibrate(SHUTDOWN_VIBRATE_MS, VIBRATION_ATTRIBUTES);

            } 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) {

            }

        }

        // Shutdownpower

        Log.i(TAG,"Performing low-level shutdown...");

       PowerManagerService.lowLevelShutdown();

//進入lowlevelshutdown函數

}

在該方法中,首先調用deviceRebootOrShutdown方法來查找OEM關機相關類,然後判斷是否是重新開機,如果重新開機PowerManagerService.lowLevelReboot方法進行重新開機;否則如果是關機前有震動,則會建立Vibrator對象,并調用其vibrate方法執行震動操作,最後進入到PowerManagerService.lowLevelShutdown方法中執行關機操作。

17.powermanagerservice.java- lowLevelShutdown 函數

public static void lowLevelShutdown() {

       SystemProperties.set("sys.powerctl", "shutdown");

//設定關機屬性值,進入systemproperties.set函數

}

在該方法中調用,SystemProperties.set方法,修改sys.powerctl的屬性值為shutdown

18. SystemProperties.java-set 函數

public static void set(String key, String val) {

//判斷傳入值的合法性

        if(key.length() > PROP_NAME_MAX) {

            throw newIllegalArgumentException("key.length > " + PROP_NAME_MAX);

        }

        if (val !=null && val.length() > PROP_VALUE_MAX) {

            thrownew IllegalArgumentException("val.length > " +

               PROP_VALUE_MAX);

        }

       native_set(key, val);

//調用底層native_set函數

}

該方法比較簡單,隻是判斷了一下key和value的長度後,就進入本地方法native_set中。

Java層工作總結:

1.PhoneWindowManger負責接收由InputManagerService的發放的按鍵資訊,本例中将會擷取長

按Power鍵資訊。

2.PolicyHandler發送長按Power鍵資訊,并在其handleMessage方法中做相應的處理。

3.PhoneWindowManger的powerLongPress處理長按Power鍵的不同情況,1代表是全局的動作;

0代表不做任動作;2表示确認後關機;3表示不确認,直接關機。

4.GlobalActionsDialog調用createDialog建立關鍵對話框,将關機選擇、重新開機選擇、飛行模

式選擇以封裝的Action對象添加在擴充卡清單中。封裝完成Action後就是建立關機對話框,采用MyAdapter擴充卡儲存這些比對。

5.WindowManagerService的shutDown方法,最終會進入到shutDownThread線程的rebootOrs

hutDown方法成關機操作。

6.最後進入到PowerManagerServie的lowLevelShutdown方法,進入到SystemProperties.se

t方法,将“sys.powerctl”屬性設定為“shutdown”最後進入native層完成關機操作。

Native層關機

19.android_os_properties_set.cpp-native_set 函數

static void SystemProperties_set(JNIEnv *env, jobjectclazz,

                                      jstringkeyJ, jstring valJ)

{

    int err;

    const char*key;

    const char*val;

    if (keyJ ==NULL) {

       jniThrowNullPointerException(env, "key must not be null.");

        return ;

    }

    key = env->GetStringUTFChars(keyJ, NULL);

    if (valJ ==NULL) {

        val ="";      

    } else {

        val =env->GetStringUTFChars(valJ, NULL);

    }

err = property_set(key, val);

//調用perperty_set方法,進入native關機

   env->ReleaseStringUTFChars(keyJ, key);

    if (valJ !=NULL) {

       env->ReleaseStringUTFChars(valJ, val);

    }

    if (err < 0){

       jniThrowException(env, "java/lang/RuntimeException",

                         "failed to set system property");

    }

}

20.properties_set.cpp-property_set 函數

int property_set(const char *key, const char *value)

{

return__system_property_set(key, value);

//進一步調用 __system_property_set函數

}

21.system_properties.cpp-__system_property_set 函數

int __system_property_set(constchar *key, const char *value)

{

    if (key == 0) return -1;

    if (value == 0) value = "";

    if (strlen(key) >= PROP_NAME_MAX) return-1;

    if (strlen(value) >= PROP_VALUE_MAX)return -1;

prop_msg msg;

//将關機屬性的name,value,size 放入到msg中

    memset(&msg, 0, sizeof msg);

    msg.cmd = PROP_MSG_SETPROP;

    strlcpy(msg.name, key, sizeof msg.name);

    strlcpy(msg.value, value, sizeofmsg.value);

const int err =send_prop_msg(&msg);

//将msg發送到服務端,property的服務程序在init.c中運作

//觸發 關機屬性值的設定 調用dopwrctrl

    if (err < 0) {

        return err;

    }

    return 0;

}

22.init.rc

on property:sys.powerctl=*

//表示當sys.powerctl被設定成任意值時觸發下面的動作

   powerctl${sys.powerctl}

init程序是Android系統的第一個程序,是由Linux核心啟動。init程序主要作用是兩個:一個是解析init.rc以及init{hardware}.rc等rc檔案;

onproperty:sys.powerctl=*

powerctl${sys.powerctl}

在init.rc檔案主要由以on開頭和service開頭,分别代表動作和服務;init程序會調用init_parser.c的parse_config函數來解析這些rc檔案,最終會生成動作清單和服務清單,動作清單的定義在keywords.h中

int do_powerctl(int nargs, char**args);

#endif

 ……

   KEYWORD(powerctl,    COMMAND, 1,do_powerctl)

     ……

#ifdef __MAKE_KEYWORD_ENUM__

   KEYWORD_COUNT,

};

Init程序通過drain_action_queue函數來解析動作清單和服務清單;通過設定init.rc中動作參數進而執行動作清單中對應的函數。另一個是初始化系統屬性,init程序可以通過property_init函數初始化系統屬性,并通過property_set函數來設定系統屬性。并将設定的參數作為參數傳入動作清單對應函數的參數中。是以在關機過程Step17中,通過property_set函數,将sys.powerctl屬性值設定為“shutdown”,并交個動作清單中對應的函數do_powerctl來處理。

23.bulltins.c –do_powerctl 函數

int do_powerctl(int nargs, char **args)

{

    charcommand[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;

    }

returnandroid_reboot(cmd, 0, reboot_target);

//進入android_reboot 函數

}

該函數中首先讀取Java層設定的該屬性的值,我們設定為“shutdown”,是以會将cmd設定為ANDROID_RB_POWEROFF,長度為8;如果為重新開機,則cmd設定為ANDROID_RB_RESTART2。否則就是顯示指令錯誤。是以我們知道sys.powerctl屬性隻有兩個值,一個是shutdown(關機);一個是reboot(重新開機)。最後将cmd指令作為參數傳入android_reboot函數,做最後的關機操作。

24.Android_reboot.c-android_reboot 函數

int android_reboot(int cmd, int flags UNUSED, char *arg)

{

    int ret;

    sync();

    remount_ro();

    switch (cmd) {

        caseANDROID_RB_RESTART:

            ret =reboot(RB_AUTOBOOT);

            break;

        caseANDROID_RB_POWEROFF:

            ret =reboot(RB_POWER_OFF);

//進入reboot 函數

            break;

        case ANDROID_RB_RESTART2:

            ret =syscall(__NR_reboot, LINUX_REBOOT_MAGIC1, LINUX_REBOOT_MAGIC2,

                          LINUX_REBOOT_CMD_RESTART2, arg);

            break;

        default:

            ret =-1;

    }

    return ret;

}

該函數首先是做了一些關機重新開機前的預處理工作,sync()作用是将緩存中的資訊寫入磁盤,以免程式異常結束導緻檔案被損壞,linux系統關機前會做幾次這樣的動作;而remount_ro()作用是強制将檔案系統挂載為隻讀,不再允許任何寫入操作,同時會通過檢查/proc/mounts的裝置狀态來确認是否目前的所有寫入工作已經完成,這個檢查過程是阻塞操作。

cmd參數中ANDROID_RB_RESTART為普通關機,reason為RB_AUTOBOOT;ANDROID_RB_POWEROFF,無需reason,直接調用reboot進行關機;帶參數的特殊重新開機ANDROID_RB_RESTART2,reason 将為預設值-1。本例中是reboot “shutdown”,則在Step18中傳入的cmd參數為ANDROID_RB_POWEROFF,則會進入reboot(RB_POWER_OFF)函數進行處理。這些cmd值是在/bionic/libc/include/sys/Reboot.h中

#define RB_AUTOBOOT     LINUX_REBOOT_CMD_RESTART

#define RB_HALT_SYSTEM  LINUX_REBOOT_CMD_HALT

#define RB_ENABLE_CAD   LINUX_REBOOT_CMD_CAD_ON

#define RB_DISABLE_CAD  LINUX_REBOOT_CMD_CAD_OFF

#define RB_POWER_OFF    LINUX_REBOOT_CMD_POWER_OFF

而LINUX_REBOOT_CMD_XXXX是在/binoic/libc/kernel/uapi/linux/Reboot.h中:我們直接進入到reboot(RB_POWER_OFF)函數中做關機操作。

#define LINUX_REBOOT_CMD_RESTART0x01234567

#define LINUX_REBOOT_CMD_HALT0xCDEF0123

#define LINUX_REBOOT_CMD_CAD_ON0x89ABCDEF

#define LINUX_REBOOT_CMD_CAD_OFF0x00000000

#define LINUX_REBOOT_CMD_POWER_OFF0x4321FEDC

#define LINUX_REBOOT_CMD_RESTART20xA1B2C3D4

#defineLINUX_REBOOT_CMD_SW_SUSPEND 0xD000FCE2

#define LINUX_REBOOT_CMD_KEXEC0x45584543

#endif

25.reboot.cpp – reboot 函數

include <unistd.h>

#include <sys/reboot.h>

extern "C" int __reboot(int, int, int, void*);

int reboot(int mode) {

  return__reboot(LINUX_REBOOT_MAGIC1, LINUX_REBOOT_MAGIC2, mode, NULL);

//表明這是外部定義實作的一個C函數 進入kernel層關機

}

該函數中,調用Kernel層的_reboot函數做進一步分析。該函數在/bionic/libc/bionic/Reboot.cpp檔案中。另外,在該檔案中有extern"C" int __reboot,該表示在外部檔案定義的__reboot函數,并且是以C語言方式進行編譯連接配接,是以__reboot函數是c語言函數。

Native層工作總結 :

1.采用property_set函數來設定系統屬性的值,通過init程序解析init.rc檔案的生成的動作

的清單,最終根據傳進去的屬性值(“shutdown”),最終調用do_powerctl函數做關機的操作。

2.Android_reboot通cmd指令值做各種操作,cmd參數是在這些cmd值是在/bionic/libc/incl

ude/sys/Reboot.h中

3. 通過reboot中C++函數,最終調用到kernel層_­_reboot函數做kernel層的關機操作。

Kernel層關機

26.__reboot.s-reboot 函數

#include <private/bionic_asm.h>

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_internal

END(__reboot)

發現__reboot函數最終映射到__NR_reboot,而__NR_reboot在檔案/development/ndk/platforms/android-19/arch-mips/include/sys/Linux-syscalls.h中定義。

#define__NR_reboot     (__NR_SYSCALL_BASE + 88)

其被指定了一個固定的偏移量,其被指定了一個固定的偏移量,在被調用的時候就是通過這個偏移量去核心中尋找對應的入口的,由此可見,核心中一定有着相同的定義,否則将不能成功調用。核心中對syscall偏移量的定義在核心源碼中的arch/arm/include/asm/unistd.h,相關資訊完全一緻。已經找到了核心中的對應映射,那麼下一步就要去找尋真正的實作函數了,/Include/asm-generic/unistd.h中可以找到核心對__NR_reboot的syscall函數映射。

#define __NR_setpriority 140 

__SYSCALL(__NR_setpriority,sys_setpriority) 

#define __NR_getpriority 141 

__SYSCALL(__NR_getpriority,sys_getpriority) 

#define __NR_reboot 142 

__SYSCALL(__NR_reboot, sys_reboot)

由定義可知,__NR_reboot被映射到sys_reboot函數,該函數的定義在/kernel/inlude/linux/syscalls.h中

asmlinkagelong sys_reboot(int magic1, int magic2, unsigned int cmd,void __user *arg);

我們在檔案sys.c中沒有找到sys_reboot函數,但是發現有這樣一個函數SYSCALL_DEFINE4(reboot,int, magic1, int, magic2, unsigned int, cmd, void __user *, arg)參數和sys_reboot函數基本相同,我們/kernel/inlude/linux/syscalls.h檔案中有這樣的定義:

#define SYSCALL_DEFINE4(name, ...)SYSCALL_DEFINEx(4, _##name, __VA_ARGS__)

#define SYSCALL_DEFINEx(x, sname,...)     

#define __SYSCALL_DEFINEx(x, name,...) asmlinkage long sys##name(__MAP(x,__SC_DECL,__VA_ARGS__));

整合後的結果如下:

#defineSYSCALL_DEFINE4(name, ...)  asmlinkagelong sys##_name(__SC_DECL##4(__VA_ARGS__))

可知就是sys_reboot函數,是以我們直接進入到SYSCALL_DEFINE4函數

27. sys.c-SYSCALL_DEFINE4 函數

SYSCALL_DEFINE4(reboot, int, magic1, int, magic2,unsigned int, cmd,

       void __user*, arg)

{

    structpid_namespace *pid_ns = task_active_pid_ns(current);

    charbuffer[256];

    int ret = 0;

    if(!ns_capable(pid_ns->user_ns, CAP_SYS_BOOT))

       return-EPERM;

    if (magic1 !=LINUX_REBOOT_MAGIC1 ||

        (magic2 != LINUX_REBOOT_MAGIC2 &&

                    magic2 != LINUX_REBOOT_MAGIC2A&&

           magic2 !=LINUX_REBOOT_MAGIC2B &&

                    magic2 != LINUX_REBOOT_MAGIC2C))

       return-EINVAL;

    ret =reboot_pid_ns(pid_ns, cmd);

    if (ret)

       return ret;

    if ((cmd ==LINUX_REBOOT_CMD_POWER_OFF) && !pm_power_off)

       cmd =LINUX_REBOOT_CMD_HALT;

    mutex_lock(&reboot_mutex);

    switch (cmd) {

    caseLINUX_REBOOT_CMD_RESTART:

       kernel_restart(NULL);

       break;

    caseLINUX_REBOOT_CMD_CAD_ON:

       C_A_D = 1;

       break;

    caseLINUX_REBOOT_CMD_CAD_OFF:

       C_A_D = 0;

       break;

    caseLINUX_REBOOT_CMD_HALT:

       kernel_halt();

       do_exit(0);

       panic("cannothalt");

    caseLINUX_REBOOT_CMD_POWER_OFF:

       kernel_power_off();

// kernel 關閉電源

       do_exit(0);

       break;

    caseLINUX_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

    caseLINUX_REBOOT_CMD_KEXEC:

       ret =kernel_kexec();

       break;

#endif

#ifdef CONFIG_HIBERNATION

    caseLINUX_REBOOT_CMD_SW_SUSPEND:

       ret =hibernate();

       break;

#endif

    default:

       ret =-EINVAL;

       break;

    }

    mutex_unlock(&reboot_mutex);

    return ret;

}

在該函數中,首先檢測權限問題,隻有超級使用者才可以執行重新開機操作,否則傳回權限錯誤,對應的權限清單在/kernel/include/uapi/linux/Capability.h檔案中。CAP_SYS_BOOT的值為22,随後對magicnumber進行了校驗。接下來做了一個判斷就是如果使用者要求關機,而pm_power_off為空,則就把使用者的關機指令轉化為挂起。pm_power_off的定義位置在/kernel/arch/arm/kernel/Process.c

void (*pm_power_off)(void);

EXPORT_SYMBOL(pm_power_off);

可知pm_power_off為函數指針,而且做了全局操作,整個kernel都可以調用它。最後會調用kernel_power_off函數完成關機操作。

28.kernel_power_off 函數

void kernel_power_off(void)

{

    kernel_shutdown_prepare(SYSTEM_POWER_OFF);//準備shutdown

    if(pm_power_off_prepare)

       pm_power_off_prepare();

    migrate_to_reboot_cpu();

    syscore_shutdown();

    printk(KERN_EMERG"Power down.\n");

    kmsg_dump(KMSG_DUMP_POWEROFF);

    machine_power_off();//machine關閉電源

}

Kernel層工作總結:

1. 調用syscalldefine 關機

2. 存儲相關資訊并關閉電源