English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية
Пример обработки сигналов процессом 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; }
Обработка в этой функции 主要 вот эти几点:
Если служба, представленная этим подпроцессом, требует перезапуска, то для этой службы устанавливается флаг 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, поэтому повторяться не будем.
Спасибо за чтение, надеюсь, это поможет вам, спасибо за поддержку нашего сайта!