Как Linux «убивает» процесс?

91

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

Сегодня я подумал о killкоманде, и хотя я использую ее несколько раз в день (как в «обычном», так и в -9привычном стиле), я должен признать, что совершенно не представляю, как она работает за кулисами.

С моей точки зрения, если запущенный процесс «завис», я вызываю killего PID, а затем он внезапно перестает работать. Магия!

Что на самом деле там происходит? Manpages говорят о «сигналах», но это, безусловно, просто абстракция. Отправка kill -9в процесс не требует взаимодействия процесса (например, обработки сигнала), он просто убивает его.

  • Как Linux не дает процессу продолжать занимать процессорное время?
  • Это удалено из планирования?
  • Отключает ли он процесс от своих файловых дескрипторов?
  • Как освобождается виртуальная память процесса?
  • Есть ли что-то вроде глобальной таблицы в памяти, где Linux хранит ссылки на все ресурсы, занятые процессом, и когда я «убиваю» процесс, Linux просто просматривает эту таблицу и освобождает ресурсы одну за другой?

Я действительно хотел бы знать все это!

seratoninant
источник

Ответы:

72

Отправка kill -9 процессу не требует взаимодействия процесса (например, обработки сигнала), она просто убивает его.

Вы предполагаете, что, поскольку некоторые сигналы могут быть уловлены и проигнорированы, все они связаны с сотрудничеством. Но согласно man 2 signal« сигналы SIGKILL и SIGSTOP не могут быть пойманы или проигнорированы». SIGTERM может быть пойман, поэтому plain killне всегда эффективна - обычно это означает, что что-то в обработчике процесса не работает. 1

Если процесс не (или не может) определить обработчик для данного сигнала, ядро выполняет действие по умолчанию. В случае SIGTERM и SIGKILL это должно завершить процесс (если его PID не равен 1; ядро ​​не будет завершено init) 2 означает, что его файловые дескрипторы закрыты, его память возвращена в системный пул, его родитель получает SIGCHILD, его сирота дети наследуются init и т. д., как если бы он вызвал exit(см. man 2 exit). Процесс больше не существует - если только он не заканчивается как зомби, и в этом случае он все еще перечисляется в таблице процессов ядра с некоторой информацией; это происходит, когда его родитель неwaitи правильно обращаться с этой информацией. Однако у процессов зомби больше нет выделенной памяти, и поэтому они не могут продолжать выполняться.

Есть ли что-то вроде глобальной таблицы в памяти, где Linux хранит ссылки на все ресурсы, занятые процессом, и когда я «убиваю» процесс, Linux просто проходит через эту таблицу и освобождает ресурсы одну за другой?

Я думаю, что это достаточно точно. Физическая память отслеживается по странице (одна страница обычно равна фрагменту размером 4 КБ), и эти страницы извлекаются и возвращаются в глобальный пул. Это немного сложнее в том, что некоторые освобожденные страницы кэшируются на случай, если данные, которые они содержат, снова нужны (то есть данные, которые были прочитаны из все еще существующего файла).

Manpages говорят о «сигналах», но это, безусловно, просто абстракция.

Конечно, все сигналы являются абстракцией. Они концептуальны, как «процессы». Я немного играю в семантику, но если вы имеете в виду, что SIGKILL качественно отличается от SIGTERM, то да и нет. Да в том смысле, что его невозможно поймать, но нет в том смысле, что они оба являются сигналами. По аналогии, яблоко - это не апельсин, а яблоки и апельсины, согласно предвзятому определению, оба являются фруктами. SIGKILL кажется более абстрактным, поскольку вы не можете его уловить, но это все еще сигнал. Вот пример обработки SIGTERM, я уверен, что вы видели это раньше:

#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <string.h>

void sighandler (int signum, siginfo_t *info, void *context) {
    fprintf (
        stderr,
        "Received %d from pid %u, uid %u.\n",
        info->si_signo,
        info->si_pid,
        info->si_uid
    );
}

int main (void) {
    struct sigaction sa;
    memset(&sa, 0, sizeof(sa));
    sa.sa_sigaction = sighandler;
    sa.sa_flags = SA_SIGINFO;
    sigaction(SIGTERM, &sa, NULL);
    while (1) sleep(10);
    return 0;
}

Этот процесс будет просто спать вечно. Вы можете запустить его в терминале и отправить с помощью SIGTERM kill. Это выплевывает такие вещи, как:

Received 15 from pid 25331, uid 1066.

1066 это мой UID. PID будет тем из оболочки, из которой killвыполняется, или PID kill, если вы его разветвляете ( kill 25309 & echo $?).

Опять же, нет смысла устанавливать обработчик для SIGKILL, потому что он не будет работать. 3 Если я kill -9 25309закончу процесс. Но это все еще сигнал; ядро имеет информацию о том, кто послал сигнал , что это за сигнал и т. д.


1. Если вы еще не посмотрели список возможных сигналов , смотрите kill -l.

2. Другое исключение, как упоминает Тим ​​Пост ниже, относится к процессам в непрерывном сне . Они не могут быть разбужены до тех пор, пока не будет решена основная проблема, и поэтому все сигналы (включая SIGKILL) будут отложены на время. Однако процесс не может специально создать такую ​​ситуацию.

3. Это не означает, kill -9что лучше использовать на практике. Мой примерный обработчик плох в том смысле, что он не приводит к exit(). Настоящая цель обработчика SIGTERM - дать процессу возможность выполнять такие вещи, как очистка временных файлов, а затем добровольно завершать работу. Если вы используете kill -9, у вас не будет такого шанса, так что делайте это только в том случае, если часть «добровольно выйти», похоже, не удалась.

лютик золотистый
источник
Хорошо, но чем убить процесс, -9потому что это реальная проблема, как кто захочет умереть! ;)
Kiwy
@Kiwy: ядро. МПК, включая сигналы, проходят через него; ядро реализует действия по умолчанию.
Златовласка
12
Стоит упомянуть, что спящий диск (D) прерывает все сигналы, пока процесс находится в этом состоянии. Следовательно, попытка kill -9определенных процессов, связанных с вводом / выводом, не будет работать, по крайней мере, не сразу.
Тим Пост
7
Я бы добавил, что, поскольку kill -9не может быть перехвачено, процесс, получающий его, не может выполнить какую-либо очистку (например, удаление временных файлов, освобождение общей памяти и т. Д.) До своего выхода. Следовательно, используйте kill -9(иначе kill -kill) только в качестве крайней меры. Начните с и kill -hupи / или kill -termсначала, а затем используйте kill -killв качестве последнего удара.
JRFerguson
«Процесс больше не существует - если только он не заканчивается как зомби, и в этом случае он все еще числится в таблице процессов ядра с некоторой информацией», фактически, все процессы переходят в состояние зомби, когда они умирают, и зомби исчезает, когда родитель делает waitpid для ребенка, обычно это происходит слишком быстро, чтобы вы могли это увидеть
умный
3

Каждый процесс выполняется в запланированное время, а затем прерывается аппаратным таймером, чтобы передать свое ядро ​​ЦП для других задач. Вот почему возможно иметь гораздо больше процессов, чем ядер ЦП, или даже запустить всю операционную систему с большим количеством процессов на одном одноядерном ЦП.

После прерывания процесса управление возвращается к коду ядра. Затем этот код может принять решение не возобновлять выполнение прерванного процесса без какого-либо сотрудничества со стороны процесса. kill -9 может закончить выполнение в любой строке вашей программы.

h22
источник
0

Вот идеализированное описание того, как убивает процесс. На практике любой вариант Unix будет иметь много дополнительных сложностей и оптимизаций.

Ядро имеет структуру данных для каждого процесса, в которой хранится информация о том, какую память он отображает, какие у него потоки и когда они запланированы, какие файлы у него открыты и т. Д. Если ядро ​​решает уничтожить процесс, оно делает пометку в структура данных процесса (и, возможно, в структуре данных каждого из потоков), что процесс должен быть уничтожен.

Если один из потоков процесса в настоящее время запланирован на другом процессоре, ядро ​​может вызвать прерывание на этом другом процессоре, чтобы заставить этот поток прекратить выполнение быстрее.

Когда планировщик замечает, что поток находится в процессе, который должен быть прерван, он больше не будет его планировать.

Когда ни один из потоков процесса больше не запланирован, ядро ​​начинает освобождать ресурсы процесса (память, файловые дескрипторы и т. Д.). Каждый раз, когда ядро ​​освобождает ресурс, оно проверяет, есть ли у его владельца живые ресурсы. Как только у процесса больше нет живого ресурса (отображение памяти, дескриптор открытого файла и т. Д.), Структура данных для самого процесса может быть освобождена, и соответствующая запись может быть удалена из таблицы процесса.

Некоторые ресурсы могут быть освобождены немедленно (например, освобождение памяти, которая не используется операцией ввода-вывода). Другие ресурсы должны ждать, например, данные, которые описывают операцию ввода-вывода, не могут быть освобождены во время выполнения операции ввода-вывода (во время выполнения прямого доступа к памяти, используемой памяти, и для отмены прямого доступа к памяти требуется связываться с периферией). Драйвер для такого ресурса уведомляется и может попытаться ускорить отмену; как только операция больше не выполняется, драйвер завершит освобождение этого ресурса.

(Запись в таблице процессов на самом деле является ресурсом, принадлежащим родительскому процессу, который освобождается, когда процесс умирает, и родитель подтверждает событие .)

жилль
источник