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