Что произойдет с дочерними процессами процесса которому был отправлен сигнал sigkill
SIGTERM против SIGKILL: в чем разница?
Главное меню » Linux » SIGTERM против SIGKILL: в чем разница?
Поскольку управление процессами является отдельной темой, мы не будем подробно останавливаться на управлении пользовательскими процессами и обработке сигналов в этом посте.
Мы собираемся сосредоточиться на использовании сигналов SIGTERM и SIGKILL для завершения процесса. А также расскажем о том, почему вы должны избегать использования SIGKILL.
Что такое SIGTERM?
В UNIX-подобных системах сигнал SIGTERM используется для завершения программы. Вы можете догадаться об этом по его названию, которое состоит из SIGnal и TERMinate.
SIGTERM также может называться «мягким уничтожением», поскольку процесс, который получает сигнал SIGTERM, может игнорировать его.
Как отправить SIGTERM процессу в Linux?
Команда kill в Linux используется для отправки всех таких сигналов процессам.
Вам нужно знать pid процесса, чтобы использовать эту команду следующим образом:
Вы можете использовать команду ps в Linux, чтобы получить идентификатор процесса.
Что такое SIGKILL?
SIGKILL используется для немедленного прекращения процесса. Этот сигнал нельзя игнорировать или заблокировать. Процесс будет завершен вместе с его потоками (если есть).
Как отправить SIGKILL процессу в Linux?
SIGTERM vs SIGKILL: Почему вы должны использовать SIGTERM вместо SIGKILL?
Хотя оба эти сигнала используются для уничтожения процесса, между ними есть некоторые различия:
С помощью SIGTERM процесс получает время для отправки информации своим родительским и дочерним процессам. Это дочерние процессы обрабатываются init.
Использование SIGKILL может привести к созданию процесса зомби, потому что уничтоженный процесс не получает возможности сообщить своему родительскому процессу, что он получил сигнал уничтожения.
Если у вас есть какие-либо вопросы или предложения, пожалуйста, не стесняйтесь оставлять комментарии.
Если вы нашли ошибку, пожалуйста, выделите фрагмент текста и нажмите Ctrl+Enter.
Практика работы с сигналами
Функция обработчик сигналов
Данная функция вызывается, когда процесс (или нить) получает неблокируемый сигнал. Дефолтный обработчик завершает наш процесс (нить). Но мы можем сами определить обработчики для интересующих нас сигналов. Следует очень осторожно относится к написанию обработчика сигналов, это не просто функция, выполняющаяся по коллбеку, происходит прерывание текущего потока выполнения без какой либо подготовительной работы, таким образом глобальные объекты могут находится в неконсистентном состоянии. Автор не берется приводить свод правил, так как сам их не знает, и призывает последовать совету Kobolog (надеюсь он не против, что я ссылаюсь на него) и изучить хотя бы вот этот материал FAQ.
Установить новый обработчик сигнала можно двумя функциями
Здесь мы установили наш обработчик для сигналов SIGUSR1 и SUGUSR2, а также указали, что необходимо блокировать эти же сигналы пока выполняется обработчик.
С обработчиком сигналов есть один не очень удобный момент, он устанавливается на весь процесс и все порожденные нити сразу. Мы не имеет возможность для каждой нити установить свой обработчик сигналов.
Но при этом следует понимать что когда сигнал адресуется процессу, обработчик вызывается именно для главной нити (представляющей процесс). Если же сигнал адресуется для нити, то обработчик вызывается из контекста этой нити. См пример 1.
Блокирование сигналов
Для того, чтобы заблокировать некоторый сигналы для процесса, необходимо добавить их в маску сигналов данного процесса. Для этого используется функция
Мы можем к уже существующей маске сигналов добавить новые сигналы (SIG_BLOCK), можем из этой маски убрать часть сигналов (SIG_UNBLOCK), а так же установить полностью нашу маску сигналов (SIG_SETMASK).
Для работы с маской сигналов внутри нити используется функция
которая позволяет сделать все тоже, но уже для каждой нити в отдельности.
Невозможно заблокировать сигналы SIGKILL или SIGSTOP при помощи этих функций. Попытки это сделать будут игнорироваться.
sigwait
Данная функция позволяет приостановить выполнении процесса (или нити) до получения нужного сигнала (или одного из маски сигналов). Особенностью этой функции является то, что при получении сигнала не будет вызвана функции обработчик сигнала. См. пример 2.
Посыл сигнала
Для того, чтобы послать сигнал процессу можно использовать две функции
С первой все понятно. Вторая нужна для того, чтобы послать сигнал самому себе, и по сути равносильна kill(getpid(), signal). Функция getpid() возвращает PID текущего процесса.
Для того, чтобы послать сигнал отдельной нити, используется функция
Пример использования сигналов
Все, что я описал выше, не дает ответа на вопрос «Зачем мне использовать сигналы». Теперь я хотел бы привести реальный пример использования сигналов и где без них попросту не обойтись.
Представьте, что вы хотите читать или писать какие-то данные в какое то устройство, но это может привести к блокированию. Ну например, чтение в случае работы с сокетами. Или может быть запись в пайп. Вы можете вынести это в отдельный поток, чтобы не блокировать основную работу. Но что делать когда вам нужно завершить приложение? Как корректно прервать блокирующую операцию IO? Можно было бы задавать таймаут, но это не очень хорошее решение. Для этого есть более удобные средства: функции pselect и ppoll. Разница между ними исключительно в юзабельности, поведение у них одинаковое. В первую очередь эти функции нужны для мультиплексирования работы с IO (select/poll). Префикс ‘p’ в начале функции указывает на то, что данная функция может быть корректно прервана сигналом.
Итак, сформулируем требование:
Необходимо разработать приложение, открывающее сокет (для простоты UDP) и выполняющее в потоке операцию чтения. Данное приложение должно корректно без задержек завершаться по требованию пользователя.
Функция треда выглядит вот так
Устанавливаем наш обработчик для SIGINT, и когда нужно завершить дочерний поток шлем ему этот сигнал.
Полный листинг см. пример 3.
На мой взгляд, недостатком данного способа является то, что в случае нескольких потоков мы можем завершить их только все сразу. Нет возможности устанавливать свой обработчик сигналов для каждого треда. Таким образом, нет возможности реализовать полноценное межпоточное взаимодействие через сигналы. Linux way это не предусматривает.
PS. Исходные коды разместил на сервисе PasteBin (ссылку не даю, а то еще за рекламу посчитают).
PPS. Прошу простить за обилие ошибок. Язык, слабая моя сторона. Спасибо, всем кто помог их исправить.
Данная статья не претендует на полное (и глубокое) описание работы с сигналами и нацелена в первую очередь на тех, кто до этого момента не сталкивались с понятием «сигнал». Для более глубоко понимания работы сигналов автор призывает обратиться в более компетентные источники и ознакомиться с конструктивной критикой в комментариях.
Русские Блоги
Сигналы для взаимодействия процессов Linux-C
Сигналы для взаимодействия процессов Linux-C
1. Краткое описание
Сигналы ноты используются для связи между процессами. Асинхронные сигналы включают в себя: сигналы не в реальном времени и сигналы в реальном времени.
Сигналы в реальном времени должны отвечать, информационные номера не в реальном времени могут не отвечать (могут быть проигнорированы или потеряны)
Сигнал обычно имеет следующие настройки:
1, захват 1 (получение сигнала, выполнение указанного действия, а не по умолчанию)
2, 2, игнорировать (принять сигнал, ничего не делать)
3. Блокировка (принять сигнал, сначала выполнить текущую операцию, а затем ответить на сигнал)
4 в соответствии с действием по умолчанию
Примечание: SIGKILL, SIGSTOP не могут быть захвачены
Пожалуйста, обратитесь к руководству man: man 7 signal для подробного описания сигналов.
Во-вторых, signal () и kill ()
функция | Простая функция обработки сигнала (функция захвата сигнала) |
Заголовочный файл | #include |
прототип | |
замечание | Пожалуйста, обратитесь к руководству man для деталей: сигнал человека 2 (также в седьмой книге). Может использоваться вместе с функцией kill (), которая отправляет указанный сигнал процессу. |
Тестовый код 1: захват сигнала прерывания (SIGINT), после получения сигнала прерывания выведите предложение «получить сигнал% d \ n». Сигнал терминала отправляется, когда терминал нажимает Ctrl + C. Перед захватом сигнал терминала завершает программу по умолчанию.
Захват сигнала терминала:
функция | Отправить сигнал процессу |
Заголовочный файл | #include #include |
прототип | int kill(pid_t pid, int sig); |
параметры | |
замечание | Пожалуйста, проверьте руководство man для деталей: man 2 kill |
Тестовый код 2: Используйте функцию kill () для отправки сигнала SIGUSR1 в сигнальную программу. После того, как программа перехватит его, напечатайте предложение «получить сигнал% d \ n» и затем выйдите.
Три, sigprocmask () блокировка сигнала
Блокировка сигнала: во время процесса блокировки сигнал не будет потерян, он будет приостановлен, и он ответит, когда снятие блокировки.
функция | Заблокируйте сигнал и задержите ответ. (Проверьте и измените заблокированный сигнал) |
Заголовочный файл | #include |
прототип | int sigprocmask(int how, const sigset_t *set, sigset_t *oldset); |
параметры | |
замечание | В состоянии блокировки сигналы не в реальном времени могут быть потеряны |
4. Отправка и обработка сигналов с данными:sigaction() И сигку ()
функция | Проверка и изменение действий сигналов (захват сигналов, указание действий обработки) |
Заголовочный файл | #include |
прототип | int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact); |
параметры | |
замечание | siginfo_t < int si_signo; /* Signal number */ int si_errno; /* An errno value */ int si_code; /* Signal code */ int si_trapno; /* Trap number that caused hardware-generated signal (unused on most architectures) */ pid_t si_pid; /* Sending process ID */ uid_t si_uid; /* Real user ID of sending process */ int si_status; /* Exit value or signal */ clock_t si_utime; /* User time consumed */ clock_t si_stime; /* System time consumed */ sigval_t si_value; /* Signal value */ int si_int; /* POSIX.1b signal */ void *si_ptr; /* POSIX.1b signal */ int si_overrun; /* Timer overrun count; POSIX.1b timers */ int si_timerid; /* Timer ID; POSIX.1b timers */ void *si_addr; /* Memory location which caused fault */ long si_band; /* Band event (was int in glibc 2.3.2 and earlier) */ int si_fd; /* File descriptor */ short si_addr_lsb; /* Least significant bit of address (since Linux 2.6.32) */ void *si_lower; /* Lower bound when address violation occurred (since Linux 3.19) */ void *si_upper; /* Upper bound when address violation occurred (since Linux 3.19) */ int si_pkey; /* Protection key on PTE that caused fault (since Linux 4.6) */ void *si_call_addr; /* Address of system call instruction (since Linux 3.5) */ int si_syscall; /* Number of attempted system call (since Linux 3.5) */ unsigned int si_arch; /* Architecture of attempted system call (since Linux 3.5) */ >; |
функция | Отправить сигналы и данные в указанный процесс |
Заголовочный файл | #include |
прототип | int sigqueue(pid_t pid, int sig, const union sigval value); |
параметры | |
замечание | Используйте с подписью |
Пример: программа sigqueue отправляет сигнал SIGUSR1 в программу sigaction вместе с данными 123 типа int. После того, как sigaction перехватывает сигнал, она печатает PID и дополнительные данные отправителя сигнала и затем завершает работу.
Тестовый код:
файл sigaction.c
файл sigqueue.c
Результат операции:
V. Дополнение
1. Просмотр информации, связанной с процессом
Ps: просмотр статуса процесса (мгновенный статус процесса, статический)
top: «Просмотр в реальном времени», нажмите q для выхода (динамическое отображение в реальном времени)
древовидное представление дерева (вы можете видеть дочерний процесс)
2. Отправить сигнал процессу
Среди них SIGKILL и SIGSTOP могут выполняться только в соответствии с действием системы по умолчанию и не могут быть захвачены, заблокированы и проигнорированы
Следующие 31 являются сигналами в реальном времени:
1, сигнал не будет потерян
2. Приоритет выше, чем сигналы не в реальном времени
3. Наследование сигналов
1, параметр инициализации сигнала будет унаследован дочерним процессом
2, настройка блокировки сигнала будет унаследована дочерним процессом
3, приостановленный сигнал не будет унаследован дочерним процессом
4. На что следует обратить внимание в функции обработки сигналов:
1, глобальные переменные не должны обрабатываться в функциях обработки сигналов (если вы хотите работать, не забудьте заблокировать)
2, вы должны обратить внимание на безопасность некоторых вызовов функций, вы можете проверить сигнал man 7
5. Проверьте приоритет ответа всех сигналов
Сигнал реального времени 34-64: приоритетный ответ с большими числами;
Вызов kill для дочернего процесса с помощью SIGTERM завершает родительский процесс, но вызов с помощью SIGKILL сохраняет родительский процесс
несмотря на то, что SIGKILL работает, я хочу использовать SIGTERM, потому что это похоже на лучшую идею в целом из того, что я читал об этом, давая процессу сигнал прекратить шанс очистить себя.
приведенный ниже код является раздели пример того, что я придумал
может кто-нибудь объяснить, почему это происходит?
как я отмечаю, я не в восторге от моего
1 ответов
у вас слишком много ошибок в коде (не сняв маску сигналов на struct sigaction ) для любого, чтобы объяснить эффекты, которые вы видите.
вместо этого, рассмотрим следующий пример рабочего кода, скажем example.c :
скомпилируйте его, используя, например,
и запустите, используя, например,
вы можете выйти из sqlite3 С помощью или клавишей Ctrl + D в начале строки.
можно использовать ps f чтобы посмотреть на дерево ваших терминальных процессов, и таким образом узнать PIDs родительских и дочерних процессов и отправить сигналы либо одному, чтобы наблюдать, что происходит.
отметим, что SIGSTOP сигнал нельзя уловить, преградить, или проигнорировать, было бы нетривиально отразить сигналы управлением работы (как в когда вы используете Ctrl + Z ). Для надлежащего управления заданием родительскому процессу потребуется настроить новый сеанс и группу процессов, а также временно отсоединиться от терминала. Это тоже вполне возможно, но немного выходит за рамки здесь, поскольку это включает в себя довольно подробное поведение сеансов, групп процессов и терминалов для правильного управления.
давайте разберем приведенный выше пример программы.
на internal_child_pid переменной, и set_child_pid() и get_child_pid() функции, используются для управления дочерним процессом атомарно. The __atomic_store_n() и __atomic_load_n() предоставляются компилятором встроенные модули; для GCC, посмотреть здесь для сведения. Им избежать проблемы сигнал возникает, когда ребенок пид назначается только частично. На некоторых распространенных архитектурах это не может произойти, но это предназначено в качестве осторожного примера, поэтому атомарные обращения используются для обеспечения только полностью (old или new) значение всегда видно. Мы могли бы полностью избежать их использования, если бы временно заблокировали соответствующие сигналы во время перехода. Опять же, я решил, что атомарный доступ проще и может быть интересно посмотреть на практике.
на forward_handler() функция получает ребенок процесс PID атомарно, затем проверяет, что он ненулевой (что мы знаем, что у нас есть дочерний процесс), и что мы не передаем сигнал, отправленный дочерним процессом (просто чтобы убедиться, что мы не вызываем шторм сигнала, два бомбардируют друг друга сигналами). Различные поля в siginfo_t структура, перечислены в man 2 sigaction man page.
важно использовать sigemptyset(&act.sa_mask) очистить маску сигнала. Простой установки структуры на ноль недостаточно, даже если она работает (вероятно) на практике на многих машинах. (Не знаю, я даже не проверял. Я предпочитаю крепкий и надежный над ленивым и хрупким в любой день!)
используемые флаги включают SA_SIGINFO потому что обработчик использует форму трех аргументов (и использует the о siginfo_t ). SA_RESTART флаг существует только потому, что OP хотел его использовать; это просто означает, что, если возможно, библиотека C и ядро пытаются избежать возврата errno == EINTR ошибка, если сигнал поставляется с использованием потока, в настоящее время блокирующего в syscall (например, wait() ). Вы можете удалить SA_RESTART флаг, и добавить отладки fprintf(stderr, «Hey!\n»); в подходящем месте в цикле в Родительском процессе, чтобы увидеть, что произойдет потом.
на execlp() функция принимает два аргумента: имя двоичного файла (будут использоваться каталоги, указанные в переменной среды PATH для поиска такого двоичного файла), а также массив указателей на аргументы этого двоичного файла. Первым аргументом будет argv[0] в новом двоичном файле, т. е. само имя команды.
если бы мы сначала развили, а затем установили обработчики сигналов, у нас было бы окно, во время которого дочерний процесс уже существует, но у родителя все еще есть диспозиции по умолчанию (в основном завершение) для сигналов.
вместо этого мы могли бы просто блокировать эти сигналы, используя, например, sigprocmask() в Родительском процессе перед разветвлением. Блокировка сигнала означает, что он «ждет»; он не будет доставлен, пока сигнал не будет разблокирован. В дочерний процесс, сигналы могут оставаться заблокированными, так как диспозиции сигналов сбрасываются по умолчанию над exec() в любом случае. В Родительском процессе мы могли бы затем-или перед раздвоением, это не имеет значения-установить обработчики сигналов и, наконец, разблокировать сигналы. Таким образом, нам не нужен атомарный материал и даже не нужно проверять, равен ли дочерний pid нулю, поскольку дочерний pid будет установлен на его фактическое значение задолго до того, как какой-либо сигнал может быть доставлен!
на while цикл в основном просто петля вокруг waitpid() вызов, пока точный дочерний процесс, который мы начали выходить, или что-то смешное происходит (дочерний процесс исчезает каким-то образом). Этот цикл содержит довольно тщательную проверку ошибок, а также правильную EINTR вручать если обработчики сигнала должны были быть установлены без SA_RESTART флаги.
если дочерний процесс мы разветвляли выходы, мы проверяем состояние выхода и / или причину его смерти и печатаем диагностическое сообщение до стандартной ошибки.