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

Подробное объяснение процесса обработки сигналов в процессе Init Android

Пример обработки сигналов процессом Init в Android

В Android, когда процесс завершается (exit()), он отправляет сигнал SIGCHLD своему родительскому процессу. После получения этого сигнала родительский процесс освобождает системные ресурсы, выделенные под этот подпроцесс; и родительский процесс должен вызвать wait() или waitpid(), чтобы ждать завершения подпроцесса. Если родительский процесс не выполняет такое действие и не вызывал signal(SIGCHLD, SIG_IGN) для игнорирования обработки сигнала SIGCHLD при инициализации, то подпроцесс останется в текущем состоянии завершения и не выйдет полностью. Такой процесс не может быть спланирован и выполняет только то, что занимает место в списке процессов, сохраняет информацию о PID, состоянии завершения, времени использования CPU и т.д.; мы называем такие процессы «Zombie» (зомби-процессы).

В Linux, цель установки процессов зомби - поддерживать некоторые данные о подпроцессах для последующего запроса их родительским процессом. Специально, если родительский процесс завершается, то родитель процесса всех процессов-зомби устанавливается в процесс Init (PID 1), и процесс Init отвечает за回收 этих процессов-зомби (Init будет ждать их с помощью wait()/waitpid() и удалить информацию о них из списка процессов).

Поскольку процессы зомби продолжают занимать место в списке процессов, а максимальное количество процессов, поддерживаемых Linux, ограничено; после превышения этого предела мы не можем создать процесс. Поэтому, нам необходимо очищать эти процессы зомби, чтобы обеспечить нормальное функционирование системы.

Далее, мы рассмотрим, как процесс Init обрабатывает сигнал SIGCHLD.

В Init.cpp мы инициализируем обработку сигнала SIGCHLD через signal_handler_init():

void signal_handler_init() { 
  // Создание механизма сигнализации для SIGCHLD. 
  int s[2]; 
  // socketpair() создает пару unnamed, взаимосвязанных UNIX-доменных套окетов 
  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]; 
  // Записать в signal_write_fd, если мы уловим SIGCHLD. 
  struct sigaction act; 
  memset(&act, 0, sizeof(act)); 
  act.sa_handler = SIGCHLD_handler; устанавливаем обработчик сигнала, при возникновении сигнала данные записываются в созданный socket, epoll отслеживает, когда fd в socket становится читаемым, и вызывается зарегистрированная функция для обработки события 
  act.sa_flags = SA_NOCLDSTOP; устанавливаем флаг, что означает, что сигнал SIGCHID принимается только при завершении подпроцесса 
  sigaction(SIGCHLD, &act, 0); инициализация способа обработки сигнала SIGCHLD 
  reap_any_outstanding_children(); обработка вышедших из подпроцесса 
  register_epoll_handler(signal_read_fd, handle_signal); 
} 

Мы инициализируем сигнал через функцию sigaction(). В параметре act указывается обработчик сигнала: SIGCHLD_handler(); если приходит сигнал, вызывается эта функция для обработки; в то же время, в параметре act мы устанавливаем флаг SA_NOCLDSTOP, что означает, что сигнал SIGCHLD принимается только при завершении подпроцесса.

В Linux сигнал является软 прерыванием, поэтому的到来 сигнала будет прерывать текущую операцию обработки процесса. Поэтому, в функции обработки сигнала не следует вызывать некоторые функции, которые не могут быть повторно вызываемыми. Кроме того, Linux не выполняет排队 обработки сигналов; во время обработки сигнала, если поступает еще несколько сигналов, после завершения обработки текущего сигнала内核 отправит только один сигнал процессу; поэтому существует возможность потери сигнала. Чтобы избежать потери сигнала, операции функции обработки сигнала должны быть как можно более эффективными и быстрыми.

Когда мы обрабатываем сигнал SIGCHLD, родительский процесс выполняет операцию ожидания, которая длится довольно долго. Чтобы решить эту проблему, в вышеуказанном коде инициализации сигнала было создано пару анонимных связанных локальных сокетов для межтредового comunicación. Зарегистрированная функция обработки сигнала - 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;                  \
 
 ) 

Когда приходит сигнал, достаточно записать данные в сокет, и этот процесс очень быстрый; в этот момент обработка сигнала переходит к ответу сокета; таким образом, это не повлияет на обработку следующего сигнала. В то же время, функция write() окружена циклом do...while, условия которого - ошибка write() и текущий код ошибки EINTR (EINTR: этот вызов был прерван сигналом), то есть, если ошибка write() произошла из-за прерывания сигналом, операция будет повторена; в других случаях, функция write() будет выполняться только один раз. После инициализации обработки сигнала вызывается функция reap_any_outstanding_children() для обработки ситуации退出 процесса:

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

wait_for_one_process() в основном вызывает waitpid() для ожидания завершения подпроцесса, при необходимости перезапуска услуги, представляющей этот процесс, выполняются некоторые настройки и очистка.
В конце концов, через epoll_ctl() регистрируется локальный socket в epoll_fd, наблюдается за тем, открыт ли он для чтения; и регистрируется обработчик событий epoll:

register_epoll_handler(signal_read_fd, handle_signal); 
void register_epoll_handler(int fd, void (*fn)()) { 
  epoll_event ev; 
  ev.events = EPOLLIN; // Для файлового описателя доступно чтение 
  ev.data.ptr = reinterpret_cast<void*>(fn); // Сохранение указателя на указанную функцию для последующей обработки событий 
  if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd, &ev) == -1) { // Добавление в epoll_fd файла для наблюдения за событиями, такими как property, keychord и signal 
    ERROR("epoll_ctl failed: %s\n", strerror(errno)); 
  } 
} 

Примером выхода процесса Zygote, рассмотрим конкретный процесс обработки сигнала SIGCHLD. Процесс Zygote в init.rc объявлен как Service и создается процессом Init. При выходе процесса Zygote, к процессу Init отправляется сигнал SIGCHLD. Предыдущий код уже выполнил инициализацию сигнала, поэтому при поступлении сигнала вызывается функция обработки SIGCHLD_handler(). Ее обработка заключается в прямом写入 данных через socket и немедленном возвращении; в этот момент обработка сигнала SIGCHLD переходит к обработке событий socket. Мы зарегистрировали локальный socket через epoll_ctl и следим за тем, открыт ли он для чтения; в этот момент, благодаря предыдущему вызову write(), socket имеет данные для чтения, и в этот момент вызывается зарегистрированная функция handle_signal() для обработки:

static void handle_signal() {}} 
  // Удалить ожидающие запросы. 
  char buf[32]; 
  read(signal_read_fd, buf, sizeof(buf)); 
  reap_any_outstanding_children(); 
} 

Он поместит данные socket в буфер buf и вызовет функцию reap_any_outstanding_children() для обработки выхода подпроцесса и перезапуска услуги:

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)); // Ожидать завершения подпроцесса и получить его pid, WNOHANG означает, что если процесс не завершится, то немедленно вернется. 
  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); // Найти информацию о службе по pid в списке. 
  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: все код, начиная с этой строки, должен быть членом функции service. 
  // если сервисный процесс не имеет флага SVC_ONESHOT или имеет флаг SVC_RESTART, то сначала нужно杀掉 текущий процесс, а затем создать новый процесс; 
  // чтобы избежать ошибок при перезапуске процесса, так как текущий сервисный процесс уже существует. 
  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); 
  } 
  // Удаляем все сокеты, которые мы могли создать. 
  // если для этого сервисного процесса был создан сокет, в этом случае нам нужно удалить этот сокет 
  for (socketinfo* si = svc->sockets; si; si = si->next) { 
    char tmp[128]; 
    snprintf(tmp, sizeof(tmp), ANDROID_SOCKET_DIR"/%s", si->name); 
    unlink(tmp);// удаляет этот файл устройства сокета 
  } 
  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 переходят в состояние отключения при выходе из системы; 
  // за исключением случая, когда он был перезапущен вручную. 
  // если сервисный процесс содержит флаг SVC_ONESHOT и не содержит флага SVC_RESTART, то это означает, что сервис не требует перезапуска 
  if ((svc->flags & SVC_ONESHOT) && !(svc->flags & SVC_RESTART)) { 
    svc->flags |= SVC_DISABLED; 
  } 
  // Отключенные и сброшенные процессы автоматически не перезапускаются. 
  // если сервис имеет флаг SVC_RESET, это означает, что сервис не требует перезапуска 
  if (svc->flags & (SVC_DISABLED | SVC_RESET)) { // по результатам, флаг SVC_RESET имеет наибольший приоритет 
    svc->NotifyStateChange("stopped"); 
    return true; 
  } 
  //至此, мы можем понять, что процесс сервиса в init.rc будет перезапускаться при смерти, если у него нет флагов SVC_ONESHOT и SVC_RESET; 
  // но, если процесс сервиса имеет флаг SVC_CRITICAL и не имеет флага SVC_RESTART, при превышении числа сбоев и перезапусков более 4 раз, система автоматически перезапустится и перейдет в режим восстановления 
  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; 
      } 
    } 
      svc->time_crashed = now; 
      svc->nr_crashed = 1; 
    } 
  } 
  svc->flags &= (~SVC_RESTART); 
  svc->flags |= SVC_RESTARTING; // добавляет флаг перезапуска сервису, чтобы он потребовал перезапуска; последующие работы должны определяться этим 
  // Execute all onrestart commands for this service. 
  struct listnode* node; 
  list_for_each(node, &svc->onrestart.commands) { // Если у службы есть опция onrestart, итерируется список команд, которые необходимо выполнить при перезапуске процесса, и выполняются эти команды 
    command* cmd = node_to_item(node, struct command, clist); 
    cmd->func(cmd->nargs, cmd->args); 
  } 
  svc->NotifyStateChange("restarting"); 
  return true; 
} 

Обработка в этой функции 主要 вот эти几点:

  1. Вызов waitpid() для ожидания завершения подпроцесса, значение возвращаемое waitpid() является процессным номером подпроцесса. Если подпроцесс не exited, из-за установки флага WONHANG, waitpid() немедленно вернется, не掛起. Наподобие того, как было описано ранее, TEMP_FAILURE_RETRY() вложено, при возвращении ошибки waitpid() с кодом ошибки EINTR, повторно вызывается waitpid().
  2. На основе pid, из списка service_list находим информацию о соответствующем процессе службы. Если определение процесса службы в init.rc не содержит флага SVC_ONESHOT или был установлен флаг SVC_RESTART, то сначала убиваем текущий процесс, а затем создаем новый процесс; чтобы избежать ошибок при повторном создании процесса, так как текущий процесс уже существует.
  3. Если для текущей службы был создан сокет, то этот сокет необходимо удалить.
  4. Если процесс службы имеет флаг SVC_ONESHOT и не имеет флага SVC_RESTART, это означает, что служба не требует перезапуска.
  5. Если служба имеет флаг SVC_RESET, это означает, что служба не требует перезапуска.
  6. Если процесс службы имеет флаг SVC_CRITICAL и не имеет флага SVC_RESTART, при сбое или превышении числа перезапусков более 4 раз, система автоматически перезапускается и переходит в режим восстановления.
  7. Если служба определяется как требующая перезапуска, для этой службы добавляется флаг SVC_RESTARTING, что указывает на то, что она требует перезапуска; дальнейшие действия должны быть основаны на этом. // Важно
  8. В конце, если у службы есть опция onrestart, итерируется список команд, которые необходимо выполнить при перезапуске службы, и выполняются эти команды

Если служба, представленная этим подпроцессом, требует перезапуска, то для этой службы устанавливается флаг SVC_RESTARTING.

Когда мы рассматривали процесс инициализации Init, мы анализировали, что после завершения обработки Init он входит в цикл, превращаясь в демон, обрабатывая signal, property и keychord услуги:

while (true) { 
   if (!waiting_for_exec) { 
     execute_one_command(); // Выполнение команды из списка команд 
     restart_processes(); // Запуск процессов в списке 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 - это инструмент для визуального анализа производительности процесса запуска; необходимо периодически будить процесс 
   epoll_event ev; 
   int nr = TEMP_FAILURE_RETRY(epoll_wait(epoll_fd, &ev, 1, timeout)); // Начало опроса, epoll_wait() ждеит событий 
   if (nr == -1) { 
     ERROR("epoll_wait failed: %s\n", strerror(errno)); 
   } else if (nr == 1) { 
     ((void (*)()) ev.data.ptr)(); // Вызов функции указателя функции, хранящейся в epoll_event 
   } 
 } 

в которой она будет циклически вызывать restart_processes() для перезапуска служб, имеющих флаг SVC_RESTARTING (этот флаг устанавливается в wait_for_one_process()) в списке 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; 
  } 
} 

В конечном итоге вызывается функция service_start() для повторного запуска вышедшей из строя услуги. Процесс обработки функции service_start() был рассмотрен при介绍 процесса обработки Init, поэтому повторяться не будем.

Спасибо за чтение, надеюсь, это поможет вам, спасибо за поддержку нашего сайта!

Вам может понравиться