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

Detailed Introduction to the Signal Processing Flow of Android Init Process

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:

  1. Call waitpid() to wait for the child process to end, and the return value of waitpid() is the process ID of the child process. If there is no child process to exit, due to the setting of the WONHANG flag, waitpid() will return immediately without hanging. The nested TEMP_FAILURE_RETRY() has the same meaning as the previously introduced one, and when waitpid() returns an error and the error code is EINTR, waitpid() will be called repeatedly.
  2. According to the pid, find the corresponding service information of the corresponding process from the service_list list. If the definition of the service process in init.rc does not set the SVC_ONESHOT flag, or sets the SVC_RESTART flag, kill the current process first, and then create a new process to avoid errors when creating a new process later due to the existence of the current service process.
  3. If a socket is created for the current service, clear this socket.
  4. 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.
  5. If the service has the SVC_RESET flag, it indicates that the service does not need to be restarted.
  6. If a service process has the SVC_CRITICAL flag and does not have the SVC_RESTART flag, when it crashes or restarts more than4At this time, the system will automatically restart and enter recovery mode.
  7. If the service is judged to require a restart, add the restart flag SVC_RESTARTING to the service to indicate that it needs to be restarted; subsequent operations should be based on this judgment.//Important}}
  8. Finally, if the service has the onrestart option, iterate through the command list that needs to be executed when the service is restarted, and execute these commands

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!