天天看點

Linux上層關機和reboot流程

從reboot指令開始

void reboot_main(void)
{
  int types[] = {RB_AUTOBOOT, RB_HALT_SYSTEM, RB_POWER_OFF},
      sigs[] = {SIGTERM, SIGUSR1, SIGUSR2}, idx;

  if (!(toys.optflags & FLAG_n)) sync();

  idx = stridx("hp", *toys.which->name)+1;
  if (toys.optflags & FLAG_f) 
	  toys.exitval = reboot(types[idx]);
  else 
	  toys.exitval = kill(1, sigs[idx]);
}
           

通過檢視toybox中reboot指令的實作,如果存在-f選項,那麼會不發送信号到init,而是直接reboot,如果不存在-f選項,那麼會發送信号給init,後續關機流程由init接管。之是以要交給init處理,是為了讓init對上層app進行通知處理,進而在關機前能夠儲存使用者資料。

toys.exitval = kill(1, sigs[idx]);
           

這一條指令就是發送signal到程序号為1的程序,也就是我們常說的init程序。可以看到不同的reboot type,對應着不同的signale type:

RB_AUTOBOOT --- SIGTERM
RB_HALT_SYSTEM --- SIGUSR1
RB_POWER_OFF --- SIGUSR2
           

我們先跟一下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);
}

           

對于arm64平台它的進一步底層實作為一個系統調用bionic/libc/arch-arm64/syscalls/__reboot.S:

#include <private/bionic_asm.h>

ENTRY(__reboot)
    mov     x8, __NR_reboot
    svc     #0

    cmn     x0, #(MAX_ERRNO + 1)
    cneg    x0, x0, hi
    b.hi    __set_errno_internal

    ret
END(__reboot)
.hidden __reboot
           

svc會觸發系統調用進入核心執行對應的系統調用。對應于magic值的定義都是統一的:

#define LINUX_REBOOT_MAGIC1 0xfee1dead
#define LINUX_REBOOT_MAGIC2 672274793
           

對于另外一條線,發送信号給init程序,那麼後續的動作全部都會交由init程序去處理了,init程序處理和上面的方式不同的地方就是會進行一些關機前的操作,并且最終同樣使用reboot系統調用觸發重新開機或者關機。

static void set_default(void)
{
  sigset_t signal_set_c;

  sigatexit(SIG_DFL);
  sigfillset(&signal_set_c);
  sigprocmask(SIG_UNBLOCK,&signal_set_c, NULL);

  run_action_from_list(SHUTDOWN);
  error_msg("The system is going down NOW!");
  kill(-1, SIGTERM);
  error_msg("Sent SIGTERM to all processes");
  sync();
  sleep(1);
  kill(-1,SIGKILL);
  sync();
}

static void halt_poweroff_reboot_handler(int sig_no)
{
  unsigned int reboot_magic_no = 0;
  pid_t pid;

  set_default();

  switch (sig_no) {
    case SIGUSR1:
      error_msg("Requesting system halt");
      reboot_magic_no=RB_HALT_SYSTEM;
      break;
    case SIGUSR2:
      error_msg("Requesting system poweroff");
      reboot_magic_no=RB_POWER_OFF;
      break;
    case SIGTERM:
      error_msg("Requesting system reboot");
      reboot_magic_no=RB_AUTOBOOT;
      break;
    default:
      break;
  }

  sleep(1);
  pid = vfork();

  if (pid == 0) {
    reboot(reboot_magic_no);
    _exit(EXIT_SUCCESS);
  }

  while(1) sleep(1);
}
           

init程序處理關機的流程是:

  1. 先運作shutdown action list登出系統服務
  2. 運作kill(-1, SIGTERM);發送SIGTERM給系統中除了init外的所有程序
  3. 運作kill(-1,SIGKILL);發送SIGKILL給系統中除了init外的所有程序

Android平台的實作

做過高通項目的同學會發現高通自己實作了reboot的指令程式,而沒有使用toybox。

#define ANDROID_RB_PROPERTY "sys.powerctl"

ret = property_set(ANDROID_RB_PROPERTY, property_val);
    if (ret < 0) {
        perror(cmd);
        exit(EXIT_FAILURE);
    }
           

reboot指令最終是通過設定一個android property來做此操作的,該property是sys.powerctl。那麼設定此property之後系統會執行些什麼呢?實際上在init程序中會監控此property并觸發對應的操作。它的函數調用棧為:

property_changed -> on property sys.powerctl -> HandlePowerctlMessage ${sys.powerctl}
           

其init實作的内容和上面介紹的Linux系統的實作也大同小異,這裡就不做贅述了

繼續閱讀