English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية

Introdução detalhada ao processo de tratamento de sinais do processo Init do Android

Fluxo de tratamento de sinais do processo Init no Android

No Android, quando um processo sai (exit()) ele envia um sinal SIGCHLD para o processo pai. Após receber o sinal, o processo pai libera os recursos do sistema alocados para o filho; e o processo pai precisa chamar wait() ou waitpid() para esperar o término do filho. Se o processo pai não fizer isso e não tiver chamado signal(SIGCHLD, SIG_IGN) para ignorar o tratamento do sinal SIGCHLD no inicialização, o filho continuará no estado de saída atual, sem sair completamente. Este tipo de processo não pode ser escalonado e ocupa apenas uma posição na lista de processos, salvando informações como PID, estado de terminação, tempo de uso da CPU etc.; chamamos esse processo de 'processo zumbi'.

No Linux, o objetivo de configurar processos zombis é manter algumas informações dos filhos para que o pai possa consultá-las posteriormente. Especialmente, se um processo pai for finalizado, todos os filhos zombis do pai serão configurados como processo pai do Init (PID1),e o processo Init é responsável por recolher esses processos zombis (O processo Init executará wait()/esperar por eles e remover suas informações da lista de processos).

Os processos zombis ainda ocupam uma posição na lista de processos, enquanto o Linux suporta um número máximo de processos limitado; após esse limite, não podemos criar novos processos. Portanto, é necessário limpar esses processos zombis para garantir o funcionamento normal do sistema.

A seguir, analisamos como o processo Init lida com o sinal SIGCHLD.

No Init.cpp, inicializamos o tratamento do sinal SIGCHLD através de signal_handler_init():

void signal_handler_init() { 
  // Crie um mecanismo de sinalização para SIGCHLD. 
  int s[2]; 
  //socketpair() cria um par de sockets UNIX anônimos, interligados 
  if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0, s) === -1) { 
    ERROR("socketpair failed: %s\n", strerror(errno)); 
    exit(1); 
  } 
  signal_write_fd = s[0]; 
  signal_read_fd = s[1]; 
  // Escrever em signal_write_fd se capturarmos SIGCHLD. 
  struct sigaction act; 
  memset(&act, 0, sizeof(act)); 
  act.sa_handler = SIGCHLD_handler;//Definir o manipulador de sinal, ao ser gerado um sinal, será escrito dados no socket criado acima, o epoll monitora o fd do socket quando é possível ler, então será chamada a função registrada para lidar com o evento 
  act.sa_flags = SA_NOCLDSTOP;//Definir o sinal, para que aceitemos o sinal SIGCHID apenas quando o filho terminar 
  sigaction(SIGCHLD, &act, 0);//Inicializar o modo de tratamento do sinal SIGCHLD 
  reap_any_outstanding_children();//Tratar o filho que saiu antes 
  register_epoll_handler(signal_read_fd, handle_signal); 
} 

Inicializamos o sinal através da função sigaction(). No parâmetro act, especificamos a função de tratamento de sinal: SIGCHLD_handler(); se um sinal vier, essa função será chamada para tratamento; ao mesmo tempo, no parâmetro act, também configuramos o sinal SA_NOCLDSTOP, indicando que aceitamos o sinal SIGCHLD apenas quando o filho termina.

No Linux, os sinais são uma espécie de interrupção suave, então a chegada de um sinal pode interromper a operação em andamento do processo atual. Portanto, não devemos chamar funções não reentrantes nas funções de tratamento de sinais registradas. Além disso, o Linux não faz fila de sinais; durante o tratamento de um sinal, independentemente de quantos sinais forem recebidos, após o tratamento do sinal atual, o kernel enviará apenas um sinal para o processo; portanto, existe a possibilidade de perda de sinais. Para evitar a perda de sinais, as operações da função de tratamento de sinais registrada devem ser o mais eficiente e rápida possível.

E quando lidamos com o sinal SIGCHLD, o processo pai realiza uma operação de espera, que é relativamente longa. Para resolver esse problema, o código de inicialização do sinal mencionado acima cria um par de sockets locais não nomeados e associados para comunicação entre threads. A função de tratamento de sinal registrada é SIGCHLD_handler():

static void SIGCHLD_handler(int) { 
  if (TEMP_FAILURE_RETRY(write(signal_write_fd, "1" 1=== -1) { 
    ERROR("write(signal_write_fd) failed: %s\n", strerror(errno)); 
  } 
}
#define TEMP_FAILURE_RETRY(exp)      \
 
 ({                    \
 
  decltype(exp) _rc;           \
 
  do {                  \
 
   _rc = (exp);             \
 
  } while (_rc == -1 && errno == EINTR); \
 
  _rc;                  \
 
 } 

Quando um sinal chega, basta escrever dados no socket, este processo é muito rápido, então o tratamento do sinal é transferido para a resposta do socket; assim, não afeta o tratamento do próximo sinal. Além disso, a função write() está envolta em um loop do...while, a condição do loop é que write() ocorra um erro e o código de erro atual seja EINTR (EINTR: esta chamada foi interrompida por um sinal), ou seja, o erro atual de write() é devido a uma interrupção, a operação será executada novamente; em outras situações, a função write() será executada apenas uma vez. Após inicializar o tratamento do sinal, chama reap_any_outstanding_children() para lidar com a situação de saída do processo anterior:

static void reap_any_outstanding_children() { 
  while (wait_for_one_process()) { 
  } 
} 

wait_for_one_process() principalmente chama waitpid() para esperar o encerramento do subprocesso, quando o serviço representado pelo processo precisa ser reiniciado, faz algumas configurações e limpezas.
Por fim, registre o socket local no epoll_fd usando epoll_ctl() para monitorar se é legível; e registrou a função de tratamento de eventos epoll:

register_epoll_handler(signal_read_fd, handle_signal); 
void register_epoll_handler(int fd, void (*fn)()) { 
  epoll_event ev; 
  ev.events = EPOLLIN;//O descritor de arquivo é legível 
  ev.data.ptr = reinterpret_cast<void*>(fn);//Salvar o ponteiro de função específico, usado para o processamento de eventos posterior 
  if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd, &ev) == -1) {//Adicionar os fd a serem monitorados em epoll_fd, por exemplo, eventos de property, keychord e signal 
    ERROR("epoll_ctl failed: %s\n", strerror(errno)); 
  } 
} 

Let's take the Zygote process exit as an example to see the specific process of handling the SIGCHLD signal. The Zygote process is declared as a Service in init.rc and created by the Init process. When the Zygote process exits, it will send a SIGCHLD signal to the Init process. The previous code has completed the initialization operation of the signal, so when the signal arrives, it will call the SIGCHLD_handler() function to handle it, which is to write a piece of data through the socket and return immediately; at this time, the handling of SIGCHLD is transferred to the response of the socket event. We registered the local socket through epoll_ctl and listened to whether it was readable; at this time, due to the previous write() call, the socket has readable data, and at this moment, the registered handle_signal() function will be called to handle it:

static void handle_signal() { 
  // Clear outstanding requests. 
  char buf[32]; 
  read(signal_read_fd, buf, sizeof(buf)); 
  reap_any_outstanding_children(); 
} 

It will put the socket data into the unique buf and call the reap_any_outstanding_children() function to handle the child process exit and service restart operation:

static void reap_any_outstanding_children() { 
  while (wait_for_one_process()) { 
  } 
} 
static bool wait_for_one_process() { 
  int status; 
  pid_t pid = TEMP_FAILURE_RETRY(waitpid(-1, &status, WNOHANG));//Wait for the child process to end and get its pid process number, WNOHANG indicates that if no process ends, it will return immediately. 
  if (pid == 0) { 
    return false; 
  } else if (pid == -1) { 
    ERROR("waitpid failed: %s\n", strerror(errno)); 
    return false; 
  } 
  service* svc = service_find_by_pid(pid);//According to pid, find this service information in the linked list 
  std::string name; 
  if (svc) { 
    name = android::base::StringPrintf("Service '%s' (pid %d)", svc->name, pid); 
  else { 
    name = android::base::StringPrintf("Untracked pid %d", pid); 
  } 
  NOTICE("%s %s\n", name.c_str(), DescribeStatus(status).c_str()); 
  if (!svc) { 
    return true; 
  } 
  // TODO: all the code from here down should be a member function on service. 
  //If the service process has not set the SVC_ONESHOT flag or has set the SVC_RESTART flag, then kill the current process first and create a new process. 
  //to avoid errors when restarting the process later, because the current service process already exists. 
  if (!(svc->flags & SVC_ONESHOT) || (svc->flags & SVC_RESTART)) { 
    NOTICE("Service '%s' (pid %d) killing any children in process group\n", svc->name, pid); 
    kill(-pid, SIGKILL); 
  } 
  // Remove any sockets we may have created. 
  //如果之前为这个服务进程创建过socket,这时我们需要清除掉该socket 
  for (socketinfo* si = svc->sockets; si; si = si->next) { 
    char tmp[128]; 
    snprintf(tmp, sizeof(tmp), ANDROID_SOCKET_DIR"/%s", si->name); 
    unlink(tmp);//删除这个socket设备文件 
  } 
  if (svc->flags & SVC_EXEC) {////服务完全退出,清除掉所有信息,并将该服务从svc-slist中移除 
    INFO("SVC_EXEC pid %d finished...\n", svc->pid); 
    waiting_for_exec = false; 
    list_remove(&svc->slist); 
    free(svc->name); 
    free(svc); 
    return true; 
  } 
  svc->pid = 0; 
  svc->flags &= (~SVC_RUNNING); 
  // Os processos oneshot entram no estado desativado ao sair 
  // exceto quando reiniciado manualmente. 
  //Se o processo do serviço tiver o sinal SVC_ONESHOT e não tiver o sinal SVC_RESTART, isso significa que o serviço não precisa ser reiniciado 
  if ((svc->flags & SVC_ONESHOT) && !(svc->flags & SVC_RESTART)) { 
    svc->flags |= SVC_DISABLED; 
  } 
  // Os processos desativados e resetados não são reiniciados automaticamente. 
  //Se o serviço tiver o sinal SVC_RESET, isso significa que o serviço não precisa ser reiniciado 
  if (svc->flags & (SVC_DISABLED | SVC_RESET)) {//De acordo com os resultados, a prioridade de decisão do sinal SVC_RESET é a mais alta 
    svc->NotifyStateChange("parado"); 
    return true; 
  } 
  //Até aqui, podemos saber que, se um processo de serviço não declarar os sinais SVC_ONESHOT e SVC_RESET no init.rc, quando o processo morrer, ele será reiniciado; 
  //Mas, se um processo de serviço tiver a marca SVC_CRITICAL e não tiver a marca SVC_RESTART, quando ele cair, reiniciar, a quantidade de vezes que ele cair, reiniciar exceder4Neste momento, o sistema reiniciará automaticamente e entrará no modo de recuperação 
  time_t now = gettime(); 
  if ((svc->flags & SVC_CRITICAL) && !(svc->flags & SVC_RESTART)) { 
    if (svc->time_crashed + CRITICAL_CRASH_WINDOW >= now) { 
      if (++svc->nr_crashed > CRITICAL_CRASH_THRESHOLD) { 
        ERROR("processo crítico '%s' saiu %d vezes em %d minutos; " 
           "rebooting into recovery mode\n", svc->name, 
           CRITICAL_CRASH_THRESHOLD, CRITICAL_CRASH_WINDOW / 60); 
        android_reboot(ANDROID_RB_RESTART2, 0, "recovery"); 
        return true; 
      } 
    else { 
      svc->time_crashed = now; 
      svc->nr_crashed = 1; 
    } 
  } 
  svc->flags &= (~SVC_RESTART); 
  svc->flags |= SVC_RESTARTING;//Adicione o sinal de reinício ao serviço, indicando que ele precisa ser reiniciado; as próximas operações devem ser julgadas com base nisso 
  // Execute todos os comandos onrestart para esse serviço. 
  struct listnode* node; 
  list_for_each(node, &svc->onrestart.commands) {//Se o serviço tiver a opção onrestart, percorra a lista de comandos a serem executados ao reiniciar o processo e execute 
    command* cmd = node_to_item(node, struct command, clist); 
    cmd->func(cmd->nargs, cmd->args); 
  } 
  svc->NotifyStateChange("restarting"); 
  return true; 
} 

O tratamento principal dessa função é sobre esses pontos:

  1. Chame waitpid() para esperar o fim do subprocesso, o valor de retorno de waitpid() é o número do processo do subprocesso. Se não houver subprocesso que saia, devido ao sinal WONHANG configurado, waitpid() retornará imediatamente sem suspender. O significado do TEMP_FAILURE_RETRY() aninhado é semelhante ao介绍的 anteriormente, quando waitpid() retorna um erro e o código de erro é EINTR, waitpid() será chamado novamente.
  2. Segundo o pid, encontre a informação do processo correspondente no serviço da lista service_list. Se a definição do processo de serviço no init.rc não estabelecer o sinal SVC_ONESHOT ou estabelecer o sinal SVC_RESTART, mate primeiro o processo atual e crie um novo processo; para evitar que ocorra um erro ao criar novamente o processo, porque o processo de serviço atual já existe.
  3. Se foi criado um socket para o serviço atual, limpe esse socket.
  4. Se o processo de serviço tiver o sinal SVC_ONESHOT e não tiver o sinal SVC_RESTART, isso indica que o serviço não precisa ser reiniciado.
  5. Se o serviço tiver o sinal SVC_RESET, significa que o serviço não precisa ser reiniciado.
  6. Se um processo de serviço tiver o sinal SVC_CRITICAL e não tiver o sinal SVC_RESTART, quando ele cair ou reiniciar a quantidade de vezes4Neste momento, o sistema reiniciará automaticamente e entrará no modo de recuperação.
  7. Se o serviço for julgado necessário para reiniciar, será adicionado o sinal de reinício SVC_RESTARTING, indicando que ele precisa ser reiniciado; as próximas operações devem ser julgadas com base nisso.//Importante}}
  8. Por fim, se o serviço tiver a opção onrestart, percorrerá a lista de comandos a serem executados ao reiniciar o serviço e executará esses comandos

Se o serviço representado por este subprocesso precisar ser reiniciado, ele adicionará o sinalizador SVC_RESTARTING para esse serviço.

No momento em que introduzimos o processo Init e sua seqüência de inicialização, analisamos que, após o processo Init ser tratado, ele entra em um ciclo se tornando um daemon, tratando sinais, propriedades e keychords等服务:

while (true) { 
   if (!waiting_for_exec) { 
     execute_one_command();//Executa os comandos na lista de comandos 
     restart_processes();//Inicia os processos na lista de serviços 
   } 
   int timeout = -1; 
   if (process_needs_restart) { 
     timeout = (process_needs_restart - gettime()) * 1000; 
     if (timeout < 0) 
       timeout = 0; 
   } 
   if (!action_queue_empty() || cur_action) { 
     timeout = 0; 
   } 
   bootchart_sample(&timeout);//bootchart é uma ferramenta de análise de desempenho que utiliza visualização para o processo de inicialização; é necessário acordar o processo periodicamente 
   epoll_event ev; 
   int nr = TEMP_FAILURE_RETRY(epoll_wait(epoll_fd, &ev, 1, timeout));//Começa a rodar, epoll_wait() espera pela geração de eventos 
   if (nr == -1) { 
     ERROR("epoll_wait failed: %s\n", strerror(errno)); 
   } else if (nr == 1) { 
     ((void (*)()) ev.data.ptr)();//Chama a função ponteiro armazenada no epoll_event para tratar o evento 
   } 
 } 

Dentre eles, ele fará chamadas recursivas ao restart_processes() para reiniciar os serviços na lista service_list que possuam o sinalizador SVC_RESTARTING (este sinalizador é configurado no wait_for_one_process()):

static void restart_processes() 
{ 
  process_needs_restart = 0; 
  service_for_each_flags(SVC_RESTARTING,} 
              restart_service_if_needed); 
} 
void service_for_each_flags(unsigned matchflags, 
              void (*func)(struct service *svc)) 
{ 
  struct listnode *node; 
  struct service *svc; 
  list_for_each(node, &service_list) { 
    svc = node_to_item(node, struct service, slist); 
    if (svc->flags & matchflags) { 
      func(svc); 
    } 
  } 
} 

static void restart_service_if_needed(struct service *svc) 
{ 
  time_t next_start_time = svc->time_started + 5; 
  if (next_start_time <= gettime()) { 
    svc->flags &= (~SVC_RESTARTING); 
    service_start(svc, NULL); 
    return; 
  } 
  if ((next_start_time < process_needs_restart) || 
    (process_needs_restart == 0)) { 
    process_needs_restart = next_start_time; 
  } 
} 

Eventualmente, a função service_start() será chamada para reiniciar um serviço que saiu. O processo de service_start() já foi analisado na introdução ao fluxo de processamento do processo Init, portanto, não será repetido aqui.

Obrigado por ler, espero que ajude, obrigado pelo apoio ao site!

Você também pode gostar