Почему SIGKILL не завершает остановленную программу (да)?

8

Я использую Ubuntu 14.04 и у меня такое поведение, которое я не могу понять:

  1. Запустите yesкоманду (в оболочке по умолчанию: Bash )
  2. Тип, CtrlZчтобы остановитьyes
  3. Беги jobs. Вывод:
    [1]+ Stopped yes
  4. Беги, kill -9 %1чтобы остановиться yes. Вывод:
    [1]+ Stopped yes
  5. Беги jobs. Вывод:
    [1]+ Stopped yes

Это на Ubuntu, 3.16.0-30-genericработающем в виртуальной машине Parallels.

Почему моя kill -9команда не завершила команду yes ? Я думал, что SIGKILL не может быть пойман или проигнорирован? И как я могу прекратить команду да ?

s1m0n
источник
1
Это интересно. SIGKILL должен работать, и он работает на моем Linux Mint 17. Для любого другого сигнала вам обычно нужно отправить его SIGCONT впоследствии, чтобы убедиться, что сигнал получен остановленной целью.
PSkocik
Действительно ли bash печатает «Остановлено» для приостановленного процесса ?
edmz
Версия ядра ( uname -a) пожалуйста
roaima
Linux ubuntu 3.16.0-30-generic #40~14.04.1-Ubuntu SMP Thu Jan 15 17:43:14 UTC 2015 x86_64 x86_64 x86_64 GNU/Linux, Я использую Ubuntu в Parallels Desktop.
s1m0n
1
@ Чёрное большинство снарядов говорят "Остановлено". tcsh говорит «приостановлено», а zsh говорит «приостановлено». Косметическая разница. Несколько более важным является тот факт, что bash печатает идентичные сообщения для STOP и TSTP, где все другие оболочки помечают аннотацию сообщением STOP, (signal)чтобы вы могли заметить разницу.

Ответы:

10

Сигналы заблокированы для приостановленных процессов. В терминале:

$ yes
...
y
y
^Zy

[1]+  Stopped                 yes

Во втором терминале:

$ killall yes

В первом терминале:

$ jobs
[1]+  Stopped                 yes

$ fg
yes
Terminated

Однако SIGKILLне может быть заблокирован. Выполнение того же действия со killall -9 yesвторым терминалом немедленно дает это в yesтерминале:

[1]+  Killed                  yes

Следовательно, если kill -9 %1процесс не завершается сразу же, либо bashвы фактически не отправляете сигнал, пока не завершите fgпроцесс, либо вы обнаружили ошибку в ядре.

lcd047
источник
4
Некоторые общие сведения: при вводе Ctrl + Z в вашем терминале bash отправляет SIGTSTP(который является блокируемой версией SIGSTOP) активному процессу. Это переводит процесс в замороженное состояние, когда ядро ​​не будет его планировать. Это также препятствует обработке сигнала (за исключением SIGCONTсигнала, который размораживает процесс) и, следовательно, предотвращает немедленное завершение процесса.
mreithub
1
SIGKILL, в отличие от других сигналов, не блокируется для приостановленных процессов. Посылка сигнала KILL приостановленному процессу убивает его - асинхронно, но на практике в основном немедленно.
Жиль "ТАК - перестань быть злым"
1
@Gilles Вот что я пытался проиллюстрировать выше: SIGTERMзаблокировано, но SIGKILLнет. В любом случае, согласно комментарию от OP, проблема, похоже, заключается в том, jobsчто не обнаруживается, что процесс умер, а не процесс не был убит kill -9 %1.
lcd047
1
Но я могу воспроизвести поведение s1m0n в моей системе (Debian, amd64, bash 4.3.30).
Жиль "ТАК - перестань быть злым"
1
Хотя SIGKILLне может быть заблокировано, нет никакой гарантии, что оно будет доставлено в любое значимое время. Например, если процесс приостановлен в ожидании блокирующего ввода-вывода, SIGKILLон не поступит, пока процесс не проснется. Это потенциально может быть никогда, если не происходит ввода-вывода.
Сапи
7

Не паникуйте.

В этом нет ничего прикольного. Здесь нет ошибки ядра. Это совершенно нормальное поведение оболочки Bourne Again и многозадачной операционной системы.

Следует помнить, что процесс убивает себя , даже в ответ SIGKILL. Здесь происходит то, что оболочка Bourne Again перебирает вещи до того, как процесс, который он только что велел убить сам, доходит до самоубийства.

Подумайте, что происходит с точки, где yesвы остановились, SIGTSTPи вы только что выполнили killкоманду с оболочкой Bourne Again:

  1. Оболочка отправляет SIGKILLв yesпроцесс.
  2. Параллельно :
    1. yesПроцесс планируется запустить и сразу же убивает себя.
    2. Оболочка Bourne Again продолжается и выдает еще один запрос.

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

В интересном случае деталь шага # 2 такова:

  1. Оболочка Bourne Again продолжается.
  2. Как часть внутренних функций встроенной killкоманды, она помечает запись в своей таблице заданий как нуждающуюся в уведомлении, напечатанном в следующей доступной точке.
  3. Он завершает killкоманду и непосредственно перед печатью снова проверяет, следует ли печатать уведомления о каких-либо заданиях.
  4. У yesпроцесса еще не было возможности покончить с собой, поэтому, что касается оболочки, задание все еще находится в остановленном состоянии. Таким образом, оболочка печатает строку состояния задания «Остановлено» для этого задания и сбрасывает флаг ожидания уведомления.
  5. yesПроцесс получает регулярные и убивает себя.
  6. Ядро сообщает оболочке, которая занята своим редактором командной строки, о том, что процесс покончил с собой. Оболочка отмечает изменение статуса и снова помечает задание как ожидающее уведомления.
  7. Простое нажатие enterдля повторного выполнения печати подсказок дает оболочке возможность распечатать новое состояние задания.

Важными моментами являются:

  • Процессы убивают себя. SIGKILLне волшебно Процессы проверяют наличие ожидающих сигналов при возврате в режим приложения из режима ядра, что происходит в конце ошибок страниц, (не вложенных) прерываний и системных вызовов. Единственная особенность заключается в том, что ядро ​​не позволяет действию в ответ SIGKILLна что-либо, кроме немедленного и безусловного самоубийства, без возврата в режим приложения. Важно отметить, что процессы должны как переходить из режима ядра в режим приложения, так и быть запланированы для запуска, чтобы реагировать на сигналы.
  • Виртуальный процессор - это просто поток в операционной системе хоста. Нет гарантии, что хост запланировал запуск виртуального процессора. Операционные системы хоста тоже не волшебны.
  • Уведомления не распечатываются, когда происходят изменения состояния задания (если вы не используете set -o notify). Они печатаются, когда следующая оболочка достигает точки в своем цикле выполнения, которая проверяет, ожидают ли какие-либо уведомления.
  • Флаг ожидания уведомления устанавливается дважды, один раз killи один раз SIGCHLDобработчиком сигнала. Это означает, что можно увидеть два сообщения, если оболочка запущена раньше yesзапланированного процесса, чтобы убить себя; одно сообщение «Остановлено» и одно сообщение «Убито».
  • Очевидно, что /bin/killпрограмма не имеет доступа к внутренней таблице заданий оболочки; так что вы не увидите такого поведения с /bin/kill. Флаг ожидания уведомления устанавливается SIGCHLDобработчиком только один раз .
  • По той же причине вы не увидите такого поведения, если вы killвыполняете yesпроцесс из другой оболочки.
JdeBP
источник
3
Это интересная теория, но ОП набирает текст, jobsи оболочка все еще видит процесс как живой. Это было бы одно необычно длинное расписание гонки. :)
lcd047
3
Прежде всего, спасибо за ваш подробный ответ! Я, конечно, имеет смысл и проясняю немало вещей ... Но, как указано выше, я могу запускать jobsкоманды умножения, после killкоторых все еще указывают, что процесс просто остановлен. Однако вы вдохновили меня на продолжение экспериментов, и я обнаружил следующее: сообщение [1]+ Terminated yesпечатается, как только я запускаю другую внешнюю команду (а не встроенную оболочку, подобную echoили jobs). Так что я могу бегать jobsстолько, сколько мне нравится, и он продолжает печатать [1]+ Stopped yes. Но как только я бегу, lsнапример, Bash печатает[1]+ Terminated yes
s1m0n
lcd047 не прочитал ваш комментарий к вопросу; это было важно и должно было быть правильно отредактировано в начале вопроса. Операционную систему хоста легко перегрузить, чтобы гости выглядели очень странно изнутри. Просто так и многое другое. (Однажды мне удалось вызвать довольно странное планирование с помощью беглого рабочего стола Bing, потребляющего большую часть процессорного времени хоста.)
JdeBP
1
@ Жиль Проблема, похоже, заключается в том, jobsчто не замечает, что процесс на самом деле умер ... Не уверен, что делать с обновлением статуса, выполнив другую команду.
lcd047
1
Даже Жиль не видел комментарий. Вот почему вы должны ставить такие важные вещи в вопросе , а не хоронить их в комментариях. Жиль, ответ ясно говорит о задержках в доставке сигнала, а не о задержках в его отправке . Вы перепутали их. Кроме того, прочитайте комментарий спрашивающего (и действительно пункт, который он здесь приведен) и увидите очень важное неверное фундаментальное предположение, которое вы делаете. Виртуальные процессоры не обязательно работают в режиме блокировки и не могут магически работать всегда на полной скорости.
JdeBP
2

Что-то странное может происходить в вашей системе, по моему ваш рецепт работает хорошо как с, так и без -9:

> yes
...
^Z
[1]+  Stopped                 yes
> jobs
[1]+  Stopped                 yes
> kill %1
[1]+  Killed                  yes
> jobs
> 

Получить Пид с jobs -pи попытаться убить его как root.

Дэн Корнилеску
источник
Могу я спросить, какую версию дистрибутива / ядра / bash вы используете? Может быть, внутренняя killкоманда вашего bash делает лишнюю милю и проверяет, заморожено ли задание (вы можете попытаться узнать PID задания и уничтожить его, используя env kill <pid>. Таким образом, вы будете использовать реальную killкоманду, а не встроенную в bash.
mreithub
bash-4.2-75.3.1.x86_64 на opensuse 13.2. Команда kill не является внутренней:which kill /usr/bin/kill
Dan Cornilescu
1
whichне является встроенным в bash, поэтому which <anything>всегда будет указывать путь к фактической команде. Но попробуйте сравнить kill --helpс /usr/bin/kill --help.
mreithub
Ах, верно. Действительно, это встроенный kill.
Дэн Корнилеску
2

То, что вы наблюдаете, является ошибкой в ​​этой версии bash.

kill -9 %1убивает работу немедленно. Вы можете наблюдать это с ps. Вы можете отслеживать процесс bash, чтобы видеть, когда killвызывается системный вызов, и отслеживать подпроцесс, чтобы видеть, когда он получает и обрабатывает сигналы. Более того, вы можете пойти и посмотреть, что происходит с процессом.

bash-4.3$ sleep 9999
^Z
[1]+  Stopped                 sleep 9999
bash-4.3$ kill -9 %1

[1]+  Stopped                 sleep 9999
bash-4.3$ jobs
[1]+  Stopped                 sleep 9999
bash-4.3$ jobs -l
[1]+  3083 Stopped                 sleep 9999
bash-4.3$ 

В другом терминале:

% ps 3083
  PID TTY      STAT   TIME COMMAND
 3083 pts/4    Z      0:00 [sleep] <defunct>

Подпроцесс - это зомби . Он мертв: от него остается только запись в таблице процессов (но без памяти, кода, открытых файлов и т. Д.). Запись остается до тех пор, пока ее родитель не заметит и не получит статус выхода, вызвав waitсистемный вызов или одного из своих братьев и сестер .

Предполагается, что интерактивная оболочка проверяет наличие мертвых детей и собирает их перед печатью приглашения (если не указано иное). Эта версия bash не может сделать это при некоторых обстоятельствах:

bash-4.3$ jobs -l
[1]+  3083 Stopped                 sleep 9999
bash-4.3$ true
bash-4.3$ /bin/true
[1]+  Killed                  sleep 9999

Вы можете ожидать, что bash сообщит «Killed», как только напечатает приглашение после killкоманды, но это не гарантировано, потому что есть условие гонки. Сигналы доставляются асинхронно: killсистемный вызов возвращается, как только ядро ​​выяснило, в какой процесс (ы) доставить сигнал, не дожидаясь его фактической доставки. Возможно, и на практике это происходит, когда bash успевает проверить состояние своего подпроцесса, обнаружить, что он все еще не мертв ( wait4не сообщает о смерти ребенка), и напечатать, что процесс все еще остановлен. Что неправильно, так это то, что перед следующим приглашением сигнал был доставлен ( psсообщает, что процесс мертв), но bash все еще не вызвалwait4(мы видим это не только потому, что он по-прежнему сообщает о задании как «Остановлено», но и потому, что зомби все еще присутствует в таблице процессов). На самом деле, bash получает зомби только в следующий раз wait4, когда ему нужно позвонить , когда он запускает какую-то другую внешнюю команду.

Ошибка периодически, и я не мог воспроизвести ее, пока отслеживается bash (возможно, потому что это состояние гонки, где bash должен реагировать быстро). Если сигнал доставляется до bash-проверок, все происходит как положено.

Жиль "ТАК - перестань быть злым"
источник