English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية
Flusso di gestione dei segnali del processo Init di Android
In Android, quando un processo esce (exit()), invia un segnale SIGCHLD al processo padre. Dopo aver ricevuto il segnale, il processo padre rilascia le risorse di sistema assegnate al processo figlio; e il processo padre deve chiamare wait() o waitpid() per attendere la fine del processo figlio. Se il processo padre non esegue questa operazione e non ha chiamato signal(SIGCHLD, SIG_IGN) durante l'inizializzazione per ignorare il trattamento del segnale SIGCHLD, in questo caso il processo figlio rimarrà in uno stato di uscita attuale, non uscirà completamente. Questo tipo di processo figlio non può essere programmato, e fa solo ciò che è necessario nel processo della lista, conservando le informazioni come PID, stato di terminazione, tempo di utilizzo della CPU等信息; chiamiamo questo processo “Zombie” processo, ossia processo zombie.
In Linux, lo scopo di impostare i processi zombie è mantenere alcune informazioni sui processi figli per future consultazioni del processo padre. Specialmente, se un processo padre termina, il processo padre di tutti i processi zombie figli viene impostato su Init processo (PID 1), e il processo Init è responsabile di recuperare questi processi zombie (Il processo Init li wait()/waitpid() e cancella le informazioni sulla lista dei processi).
Poiché i processi zombie continuano a occupare una posizione nella lista dei processi, e il numero massimo di processi supportati da Linux è limitato; una volta superato questo limite, non possiamo creare nuovi processi. Pertanto, è necessario pulire quei processi zombie per garantire il funzionamento normale del sistema.
Successivamente, analizziamo come il processo Init gestisce il segnale SIGCHLD.
In Init.cpp, iniziamo la gestione del segnale SIGCHLD attraverso signal_handler_init():
void signal_handler_init() { // Crea un meccanismo di segnalazione per SIGCHLD. int s[2]; // socketpair() crea una coppia di socket UNIX non nominati e connessi 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]; // Scrivi su signal_write_fd se catturi SIGCHLD. struct sigaction act; memset(&act, 0, sizeof(act)); act.sa_handler = SIGCHLD_handler; impostiamo il gestore del segnale, quando si verifica un segnale, viene scritto dati nel socket creato in precedenza, epoll monitora il socket fd che diventa leggibile, quindi viene chiamata la funzione registrata per gestire l'evento act.sa_flags = SA_NOCLDSTOP; impostiamo il flag, che indica di accettare il segnale SIGCHID solo quando il processo figlio termina sigaction(SIGCHLD, &act, 0); inizializziamo il modo di gestione del segnale SIGCHLD reap_any_outstanding_children(); gestisce i figli che sono usciti prima. register_epoll_handler(signal_read_fd, handle_signal); }
Iniziamo la gestione dei segnali attraverso la funzione sigaction(). Nel parametro act, specificiamo la funzione di gestione dei segnali: SIGCHLD_handler(); se arriva un segnale, verrà chiamata questa funzione per gestirlo; contemporaneamente, nel parametro act, abbiamo impostato il flag SA_NOCLDSTOP, che indica di accettare il segnale SIGCHLD solo quando il processo figlio termina.
In Linux, i segnali sono una forma di interruzione software, quindi l'arrivo di un segnale interrompe l'operazione in corso del processo. Pertanto, non dobbiamo chiamare alcune funzioni non ricorsive nella funzione di gestione del segnale registrata. E Linux non gestisce i segnali in coda, durante la gestione di un segnale, non importa quante altre segnali riceviamo, dopo che il segnale attuale è stato gestito, il kernel invierà solo un altro segnale al processo; quindi esiste la possibilità di perdita di segnali. Per evitare la perdita di segnali, l'operazione della funzione di gestione del segnale registrata dovrebbe essere il più efficace e veloce possibile.
Quando gestiamo il segnale SIGCHLD, il processo padre esegue l'operazione di attesa, che è abbastanza lunga. Per risolvere questo problema, il codice di inizializzazione del segnale sopra menzionato ha creato una coppia di socket locali non nominati e associati per la comunicazione tra thread. La funzione di gestione del segnale registrata è 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 un segnale arriva, è sufficiente scrivere dati nel socket, questo processo è molto veloce, e la gestione del segnale viene trasferita alla risposta del socket; in questo modo non influenzerà la gestione del prossimo segnale. Allo stesso tempo, la funzione write() è incapsulata in un ciclo do...while, la condizione del ciclo è che write() fallisce e l'errore corrente è EINTR (EINTR: questa chiamata è stata interrotta da un segnale), il che significa che write() è fallito a causa dell'interruzione di un segnale, l'operazione verrà eseguita di nuovo; in altri casi, la funzione write() verrà eseguita una volta sola. Dopo aver inizializzato la gestione del segnale, verrà chiamata la funzione reap_any_outstanding_children() per gestire le uscite dei processi precedenti:
static void reap_any_outstanding_children() { while (wait_for_one_process()) { } }
wait_for_one_process() chiama principalmente waitpid() per aspettare la fine del processo figlio, quando il servizio rappresentato dal processo deve essere riavviato, viene eseguita alcune configurazioni e pulizie.
Infine, tramite epoll_ctl() registrare il socket locale su epoll_fd, ascoltare se è leggibile; e registrare la funzione di gestione degli eventi epoll:
register_epoll_handler(signal_read_fd, handle_signal);
void register_epoll_handler(int fd, void (*fn)()) { epoll_event ev; ev.events = EPOLLIN; // Per il file descriptor leggibile ev.data.ptr = reinterpret_cast<void*>(fn); // Salvare il puntatore alla funzione specificata per il trattamento degli eventi successivi if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd, &ev) == -1) { // Aggiungere fd da monitorare a epoll_fd, come property, keychord e signal event listening ERROR("epoll_ctl failed: %s\n", strerror(errno)); } }
Prendiamo ad esempio l'uscita del processo Zygote per osservare il flusso di elaborazione specifico del segnale SIGCHLD. Il processo Zygote viene dichiarato come Service in init.rc e creato dal processo Init. Quando il processo Zygote termina, invia un segnale SIGCHLD al processo Init. Il codice precedente ha completato l'inizializzazione del segnale, quindi quando il segnale arriva viene chiamata la funzione SIGCHLD_handler(). Il suo trattamento è direttamente scrivere un dato attraverso il socket e tornare immediatamente; in questo momento, la gestione di SIGCHLD si trasferisce alla risposta degli eventi del socket. Abbiamo registrato il socket locale con epoll_ctl e abbiamo ascoltato se è leggibile; a questo punto, a causa della chiamata precedente write(), il socket ha dati leggibili, in questo momento verrà chiamata la funzione registrata handle_signal() per elaborare:
static void handle_signal() {}} // Annullare le richieste in sospeso. char buf[32]; read(signal_read_fd, buf, sizeof(buf)); reap_any_outstanding_children(); }
Inizializzerà i dati del socket nel buffer e chiamerà la funzione reap_any_outstanding_children() per gestire l'uscita dei processi figli e la riavvio del servizio:
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)); // Attendere la fine del processo figlio e ottenere il numero di processo pid, WNOHANG indica che se non c'è processo che termina, torna immediatamente. 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); // Secondo pid, trovare le informazioni del servizio nella coda. std::string name; if (svc) { name = android::base::StringPrintf("Service '%s' (pid %d)", svc->name, pid); } name = android::base::StringPrintf("Untracked pid %d", pid); } NOTICE("%s %s\n", name.c_str(), DescribeStatus(status).c_str()); if (!svc) { return true; } // TODO: tutti i codici da qui in giù dovrebbero essere un membro della funzione service. // Se il processo di servizio non ha impostato il flag SVC_ONESHOT o ha impostato il flag SVC_RESTART, uccide prima il processo attuale e crea un nuovo processo; // Per evitare errori durante il riavvio del processo in seguito, perché il processo di servizio attuale esiste già. if (!(svc->flags & SVC_ONESHOT) || (svc->flags & SVC_RESTART)) { NOTICE("Servizio '%s' (pid %d) uccide tutti i figli nel gruppo di processo\n", svc->name, pid); kill(-pid, SIGKILL); } // Rimuovere qualsiasi socket potremmo aver creato. // Se è stato creato un socket per questo processo di servizio in precedenza, dobbiamo cancellare questo 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);//Elimina questo file di dispositivo socket } if (svc->flags & SVC_EXEC) {//// Il servizio esce completamente, cancella tutte le informazioni e rimuove il servizio dalla lista svc-slist INFO("SVC_EXEC pid %d completato...\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); // I processi oneshot entrano nello stato disabilitato all'uscita, // eccetto quando riavviato manualmente. // Se il processo del servizio ha il flag SVC_ONESHOT e non ha il flag SVC_RESTART, allora indica che il servizio non richiede riavvio if ((svc->flags & SVC_ONESHOT) && !(svc->flags & SVC_RESTART)) { svc->flags |= SVC_DISABLED; } // I processi disabilitati e ripristinati non vengono riavviati automaticamente. // Se il servizio ha il segno SVC_RESET, significa che il servizio non ha bisogno di essere riavviato if (svc->flags & (SVC_DISABLED | SVC_RESET)) { // Dalla visualizzazione, il giudizio del segno SVC_RESET ha la priorità più alta svc->NotifyStateChange("stopped"); return true; } // Fino ad ora, possiamo sapere che un processo di servizio in init.rc, se non è stato dichiarato SVC_ONESHOT e SVC_RESET, viene riavviato quando muore; // Tuttavia, se un processo di servizio ha il segno SVC_CRITICAL e non ha il segno SVC_RESTART, quando crasha o riavvia più di 4 volte, il sistema si riavvierà automaticamente e entrerà in modalità di recupero 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 critico '%s' è uscito %d volte in %d minuti; ") "riavvio in modalità di recupero\n", svc->name, CRITICAL_CRASH_THRESHOLD, CRITICAL_CRASH_WINDOW / 60); android_reboot(ANDROID_RB_RESTART2, 0, "recovery"); return true; } } svc->time_crashed = now; svc->nr_crashed = 1; } } svc->flags &= (~SVC_RESTART); svc->flags |= SVC_RESTARTING; // Aggiungi il segno di riavvio al servizio, indicando che ha bisogno di essere riavviato; le successive operazioni devono essere eseguite in base a questo // Eseguire tutti i comandi onrestart per questo servizio. struct listnode* node; list_for_each(node, &svc->onrestart.commands) { // Se il servizio ha l'opzione onrestart, si esegue l'elenco dei comandi da eseguire durante il riavvio del processo, e si esegue command* cmd = node_to_item(node, struct command, clist); cmd->func(cmd->nargs, cmd->args); } svc->NotifyStateChange("restarting"); return true; }
Il trattamento principale di questa funzione è principalmente questi punti:
Se il servizio rappresentato da questo sotto-processo richiede un riavvio, viene aggiunto il flag SVC_RESTARTING a tale servizio.
Nella presentazione dell'inizializzazione del processo Init, abbiamo analizzato che, dopo aver completato il processo Init, entra in un ciclo diventando un demone, gestendo servizi come signal, property e keychord:
while (true) { if (!waiting_for_exec) { execute_one_command(); // Esegui il comando nella lista di comandi restart_processes(); // Avvia i processi nella lista di servizio } 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 è uno strumento di analisi delle prestazioni che utilizza la visualizzazione per monitorare il processo di avvio; è necessario svegliare periodicamente il processo epoll_event ev; int nr = TEMP_FAILURE_RETRY(epoll_wait(epoll_fd, &ev, 1, timeout)); // Inizia il polling, epoll_wait() aspetta che vengano generati eventi if (nr == -1) { ERROR("epoll_wait failed: %s\n", strerror(errno)); } else if (nr == 1) { ((void (*)()) ev.data.ptr)(); // Chiama il puntatore a funzione memorizzato nell'evento epoll_event per gestire l'evento } }
tra cui, esegue un ciclo di chiamate a restart_processes() per riavviare i servizi con il flag SVC_RESTARTING (il quale viene impostato durante la gestione di wait_for_one_process()) nella lista service_list:
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; } }
Alla fine verrà chiamata la funzione service_start() per riavviare un servizio che si è disconnesso. Il processo di service_start() è stato analizzato durante la presentazione del flusso di lavoro del processo Init, quindi non verrà ripetuto qui.
Grazie per la lettura, spero di essere stato d'aiuto, grazie per il supporto al nostro sito!