English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية
Android Init process signal handling flow
In Android, when a process exits (exit()), it sends a SIGCHLD signal to its parent process. Upon receiving this signal, the parent process will release the system resources allocated to the child process; and the parent process needs to call wait() or waitpid() to wait for the child process to end. If the parent process does not handle this and has not called signal(SIGCHLD, SIG_IGN) to explicitly ignore the handling of SIGCHLD during initialization, then the child process will remain in its current exit state and will not exit completely. Such child processes cannot be scheduled and only occupy a position in the process list, saving information such as the PID, termination status, and CPU usage time of the process; we call this type of process a 'Zombie' process, that is, a zombie process.
In Linux, the purpose of setting zombie processes is to maintain some information of child processes for the parent process to query later. Specifically, if a parent process terminates, then the parent process of all zombie child processes will be set to the Init process (PID of1),and the Init process is responsible for reclaiming these zombie processes (Init process will wait()/waitpid() them, and clear their information from the process list).
Since zombie processes still occupy a position in the process list, and the maximum number of processes supported by Linux is limited; after exceeding this limit, we cannot create new processes. Therefore, it is necessary to clean up these zombie processes to ensure the normal operation of the system.
Next, let's analyze how the Init process handles the SIGCHLD signal.
In Init.cpp, we initialize the SIGCHLD signal handling through signal_handler_init():
void signal_handler_init() { // Create a signaling mechanism for SIGCHLD. int s[2]; //socketpair() creates a pair of unnamed, interconnected UNIX domain sockets 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]; // Write to signal_write_fd if we catch SIGCHLD. struct sigaction act; memset(&act, 0, sizeof(act)); act.sa_handler = SIGCHLD_handler;//Set the signal handling function handle, when a signal occurs, it will write data to the socket created above, and when epoll monitors the readable fd in the socket pair, it will call the registered function to handle the event act.sa_flags = SA_NOCLDSTOP;//Set the flag to indicate that SIGCHID signal is only accepted when a child process terminates sigaction(SIGCHLD, &act, 0);//Initialize the SIGCHLD signal handling method reap_any_outstanding_children();//Handle the child processes that exited before this register_epoll_handler(signal_read_fd, handle_signal); }
We initialize the signal through the sigaction() function. In the act parameter, we specify the signal handling function: SIGCHLD_handler(); if a signal arrives, the function will be called to handle it; at the same time, in the parameter act, we also set the SA_NOCLDSTOP flag, indicating that SIGCHLD signal is only accepted when a child process terminates.
In Linux, signals are a type of software interrupt, so the arrival of a signal will terminate the operation being processed by the current process. Therefore, we should not call some non-reentrant functions in the registered signal handling function. Moreover, Linux does not queue signals; during the handling of a signal, regardless of how many signals are received, the kernel will only send one signal to the process after the current signal is handled; therefore, there is a possibility of signal loss. To avoid signal loss, the operations of the registered signal handling function should be as efficient and fast as possible.
When we handle the SIGCHLD signal, the parent process will perform a wait operation, which takes a long time. To solve this problem, the signal initialization code above creates a pair of unnamed and associated local sockets for inter-thread communication. The registered signal handling function is 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; \ }
When a signal arrives, it is very fast to write data to the socket, and the signal handling is transferred to the socket response; this will not affect the handling of the next signal. At the same time, a do...while loop is nested around the write() function, with the loop condition being that write() fails and the current error code is EINTR (EINTR: This call is interrupted by a signal), that is, the write() fails due to an interruption, and the operation will be executed again; in other cases, the write() function will only be executed once. After initializing the signal handling, reap_any_outstanding_children() will be called to handle the process exit situation before this:
static void reap_any_outstanding_children() { while (wait_for_one_process()) { } }
wait_for_one_process() mainly calls waitpid() to wait for the child process to end. When the service represented by this process needs to be restarted, some settings and cleanup work will be done for it.
Finally, register the local socket with epoll_ctl() to epoll_fd, monitor whether it is readable; and register the epoll event handling function:
register_epoll_handler(signal_read_fd, handle_signal);
void register_epoll_handler(int fd, void (*fn)()) { epoll_event ev; ev.events = EPOLLIN;//File descriptor is readable ev.data.ptr = reinterpret_cast<void*(fn);//Save the specified function pointer for subsequent event handling if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd, &ev) == -1) {//Add the fd to be monitored by epoll_fd, such as property, keychord, and signal event monitoring 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 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, and its handling 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 have registered the local socket through epoll_ctl and listened to whether it is readable; at this time, due to the previous write() call, the socket has data to be read, 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 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 been set with the SVC_ONESHOT flag or has set the SVC_RESTART flag, kill the current process first and then create a new process; //To avoid errors when restarting the process later due to the existing service process, 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); // Oneshot processes go into the disabled state on exit, // except when manually restarted. //If the service process has the SVC_ONESHOT flag and does not have the SVC_RESTART flag, it indicates that the service does not need to be restarted if ((svc->flags & SVC_ONESHOT) && !(svc->flags & SVC_RESTART)) { svc->flags |= SVC_DISABLED; } // Disabled and reset processes do not get restarted automatically. //If the service has the SVC_RESET flag, it means that the service does not need to be restarted if (svc->flags & (SVC_DISABLED | SVC_RESET)) {//From the results, the judgment priority of the SVC_RESET flag is the highest svc->NotifyStateChange("stopped"); return true; } //Up to this point, we can know that if a service process in init.rc does not declare the SVC_ONESHOT and SVC_RESET flags, when the process dies, it will be restarted; //However, if a service process has the SVC_CRITICAL flag and does not have the SVC_RESTART flag, when it crashes and restarts more than4At this moment, the system will automatically restart and enter recovery mode 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("critical process '%s' exited %d times in %d minutes; " "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;//Add the restart flag to the service to indicate that it needs to be restarted; subsequent operations should be based on this judgment // Execute all onrestart commands for this service. struct listnode* node; list_for_each(node, &svc->onrestart.commands) {//If the service has the onrestart option, traverse the list of commands that need to be executed when the process restarts and execute them command* cmd = node_to_item(node, struct command, clist); cmd->func(cmd->nargs, cmd->args); } svc->NotifyStateChange("restarting"); return true; }
The main points of the processing in this function are as follows:
If the service represented by this child process needs to be restarted, the SVC_RESTARTING flag will be added to the service.
When introducing the initialization process of the Init process, we analyzed that after the Init process is processed, it will enter a loop and become a daemon process to handle services such as signal, property, and keychord:
while (true) { if (!waiting_for_exec) { execute_one_command();//Execute the commands in the command list restart_processes();//Start the processes in the service list } 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 is a tool for performance analysis of the boot process using visualization; it is necessary to periodically wake up the process epoll_event ev; int nr = TEMP_FAILURE_RETRY(epoll_wait(epoll_fd, &ev, 1, timeout));//Start polling, epoll_wait() waits for the occurrence of events if (nr == -1) { ERROR("epoll_wait failed: %s\n", strerror(errno)); } else if (nr == 1) { ((void (*)()) ev.data.ptr)();//Call the function pointer stored in epoll_event to handle the event } }
Among them, it will cyclically call restart_processes() to restart the services with the SVC_RESTARTING flag (this flag is set in the wait_for_one_process() processing) in the service_list 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; } }
Finally, the service_start() function will be called to restart a service that has exited. The processing process of service_start() has been analyzed when introducing the process of handling the Init process, so it will not be repeated here.
Thank you for reading, I hope it can help everyone, thank you for your support of this site!