Предположим, например, что у вас есть скрипт оболочки, похожий на:
longrunningthing &
p=$!
echo Killing longrunningthing on PID $p in 24 hours
sleep 86400
echo Time up!
kill $p
Должен сделать трюк, не так ли? За исключением того, что процесс мог завершиться досрочно, а его PID, возможно, был переработан, а это означает, что некоторые невинные работы вместо этого получают бомбу в своей очереди сигналов. На практике это, возможно, имеет значение, но, тем не менее, меня это беспокоит. Взлом longrunningthing для того, чтобы упасть замертво, или сохранить / удалить свой PID на FS, подойдет, но я думаю об общей ситуации здесь.
killall
который соответствует по имени, так что, по крайней мере, вы убиваете только процесс с тем же именем, что иlongrunningthing
. Предполагая, что у вас будет только один из них одновременно.Ответы:
Лучше всего использовать
timeout
команду, если она у вас есть:Текущая реализация (8.23) GNU по крайней мере работает с использованием
alarm()
или эквивалентным образом, ожидая дочерний процесс. Похоже, он не защищает отSIGALRM
доставки междуwaitpid()
возвращением иtimeout
выходом (фактически отменяя эту тревогу ). В течение этого небольшого окнаtimeout
может даже писать сообщения на stderr (например, если дочерний объект сбросил ядро), что еще больше увеличит это окно гонки (на неопределенное время, если, например, stderr - полный канал).Я лично могу жить с этим ограничением (которое, вероятно, будет исправлено в будущей версии).
timeout
будет также уделять особое внимание сообщению о правильном состоянии выхода, обрабатывать другие угловые случаи (например, SIGALRM блокируется / игнорируется при запуске, обрабатывать другие сигналы ...) лучше, чем вы, вероятно, сможете сделать вручную.В качестве приближения вы можете написать это
perl
так:На http://devel.ringlet.net/sysutils/timelimit/ есть
timelimit
команда (предшествующая GNU на несколько месяцев).timeout
Тот использует
alarm()
механизм, похожий на другой, но устанавливает обработчикSIGCHLD
(игнорируя остановленных потомков), чтобы обнаружить умирающего ребенка. Он также отменяет сигнал тревоги перед запускомwaitpid()
(это не отменяет доставку,SIGALRM
если она ожидала, но так, как написано, я не вижу в этом проблемы) и убивает перед вызовомwaitpid()
(поэтому не может убить повторно использованный pid ).У netpipes также есть
timelimit
команда. Тот, кто предшествует всем остальным на десятилетия, использует еще один подход, но не работает должным образом для остановленных команд и возвращает состояние1
выхода по истечении времени ожидания.Как более прямой ответ на ваш вопрос, вы можете сделать что-то вроде:
То есть проверьте, что этот процесс все еще является нашим ребенком. Опять же, есть небольшое окно гонки (между
ps
получением статуса этого процесса иkill
его уничтожением), во время которого процесс может умереть и его pid может быть использован другим процессом.С некоторыми оболочками (
zsh
,bash
,mksh
), вы можете передать функции задания вместо ИДП.Это работает только в том случае, если вы создаете только одну фоновую работу (в противном случае получение правильной спецификации задания не всегда возможно надежно).
Если это проблема, просто запустите новый экземпляр оболочки:
Это работает, потому что оболочка удаляет задание из таблицы заданий после смерти ребенка. Здесь не должно быть никакого гоночного окна, так как к тому времени, когда оболочка вызывает
kill()
, либо сигнал SIGCHLD не был обработан, и pid не может быть повторно использован (так как он не ожидался), либо он был обработан и задание было удалено из таблицы процессов (иkill
будет сообщать об ошибке).bash
«S поkill
крайней мере блоков SIGCHLD , прежде чем он получает доступ к таблице заданий , чтобы расширить%
и разблокирует его послеkill()
.Еще один вариант, чтобы избежать
sleep
зависания этого процесса даже послеcmd
его смерти, с помощьюbash
илиksh93
использовать трубуread -t
вместоsleep
:У этого все еще есть условия гонки, и вы теряете статус выхода команды. Это также предполагает,
cmd
что не закрывает свой fd 4.Вы можете попробовать внедрить решение без гонок,
perl
например:(хотя это должно было бы быть улучшено, чтобы обращаться с другими типами угловых случаев).
Другим методом без гонки может быть использование групп процессов:
Тем не менее, обратите внимание, что использование групп процессов может иметь побочные эффекты, если имеется ввод-вывод для терминального устройства. Это имеет дополнительное преимущество, хотя убивает все другие дополнительные процессы, порожденные
cmd
.источник
timeout
не переносимо, в ответе сначала упоминается переносимое решение.jobs
а затем узнать, что (поскольку это ваша собственная оболочка, в которой вы можете контролировать то, что происходит дальше), следующая фоновая среда работа будет N + 1? [тогда вы можете спасти N, а затем убить% N + 1])В общем, вы не можете. Все ответы, данные до сих пор, являются ошибочной эвристикой. Существует только один случай, когда вы можете безопасно использовать pid для отправки сигналов: когда целевой процесс является прямым потомком процесса, который будет отправлять сигнал, а родитель еще не ожидал его. В этом случае, даже если он вышел, pid зарезервирован (это и есть «процесс зомби»), пока родитель не будет ожидать его. Я не знаю ни одного способа сделать это чисто с оболочкой.
Альтернативный безопасный способ уничтожения процессов - запуск их с управляющим набором tty для псевдотерминала, для которого у вас есть главная сторона. Затем вы можете отправлять сигналы через терминал, например, записывая символ для
SIGTERM
илиSIGQUIT
над pty.Еще один способ, более удобный для сценариев, - использовать именованный
screen
сеанс и посылать команды сеансу экрана для его завершения. Этот процесс происходит по каналу или сокету unix, названному в соответствии с сеансом экрана, который не будет автоматически использоваться повторно, если вы выберете безопасное уникальное имя.источник
При запуске процесса сохраните время его запуска:
Прежде чем пытаться убить процесс, остановите его (это не очень важно, но это способ избежать условий гонки: если вы остановите процесс, его pid не может быть повторно использован)
Убедитесь, что процесс с этим PID имеет одинаковое время запуска, и если да, завершите его, в противном случае позвольте процессу продолжиться:
Это работает, потому что в данной ОС может быть только один процесс с одинаковым PID и временем запуска.
Остановка процесса во время проверки делает условия гонки несущественными. Очевидно, что проблема заключается в том, что некоторый случайный процесс может быть остановлен на несколько миллисекунд. В зависимости от типа процесса это может или не может быть проблемой.
Лично я бы просто использовал Python,
psutil
который автоматически обрабатывает повторное использование PID:источник
ps -o start=
формат меняется с 18:12 на 26 января через некоторое время. Остерегайтесь изменений летнего времени. Если на Linux, вы, вероятно, предпочтетеTZ=UTC0 ps -o lstart=
.lstart
, я отредактирую его.В системе linux вы можете гарантировать, что pid не будет использоваться повторно, поддерживая его пространство имен pid живым. Это можно сделать через
/proc/$pid/ns/pid
файл.man namespaces
-init
.man pid_namespaces
-util-linux
Пакет предоставляет множество полезных инструментов для манипулирования пространств имен. Например, еслиunshare
, однако, вы еще не упорядочили его права в пространстве имен пользователя, ему потребуются права суперпользователя:Если вы не организовали пространство имен пользователя, вы все равно можете безопасно выполнять произвольные команды, немедленно отбрасывая привилегии. Команда
runuser
- это еще один (не setuid) двоичный файл, предоставляемыйutil-linux
пакетом, и его включение может выглядеть следующим образом:...и так далее.
В приведенном выше примере два переключателя передаются
unshare(1)
на--fork
флаг , который делает Вызванныйsh -c
процесс создан первый ребенок и обеспечивает егоinit
статус, и--pid
флаг , который инструктируетunshare(1)
для создания Pid пространства имен.Этот
sh -c
процесс порождает пять дочерних оболочек, каждая из которых является бесконечнымwhile
циклом, который будет продолжать добавлять выходные данныеdate
в конец доlog
тех пор, покаsleep 1
возвращается значение true. После запуска этих процессовsh
вызовыsleep
дополнительных 5 секунд, а затем завершается.Возможно, стоит отметить, что если бы
-f
флаг не использовался, ни один из фоновыхwhile
циклов не завершился бы, но с этим ...ВЫХОД:
источник
Подумайте о том, чтобы заставить
longrunningthing
себя вести себя немного лучше, чуть более похожим на демона. Например, вы можете сделать так, чтобы он создал pid-файл , который позволит хотя бы ограничить контроль над процессом. Есть несколько способов сделать это без изменения исходного двоичного файла, все с использованием обертки. Например:простой скрипт-обертка, который запустит требуемое задание в фоновом режиме (с необязательным перенаправлением вывода), запишите PID этого процесса в файл, затем дождитесь завершения процесса (используя
wait
) и удалите файл. Если во время ожидания процесс будет убит, например, чем-то вродеОболочка просто убедится, что pid-файл удален.
монитор-обертка, который поставит свой собственный PID куда-нибудь и поймает (и ответит) сигналы, посланные ему. Простой пример:
Теперь, как отметили @R .. и @ StéphaneChazelas, эти подходы часто имеют где-то состояние гонки или накладывают ограничение на количество процессов, которые вы можете порождать. Кроме того, он не обрабатывает случаи, когда
longrunningthing
майский ответвление и дочерние элементы отсоединяются (что, вероятно, не было проблемой в первоначальном вопросе).С недавними (читай пару лет) ядрами Linux это можно легко решить с помощью cgroups , а именно морозильной камеры - которую, я полагаю, используют некоторые современные системы инициализации Linux.
источник
longrunningthing
том, что вы не можете контролировать, что это такое. Я также привел пример сценария оболочки, потому что он объяснил проблему. Мне нравятся ваши и все другие креативные решения здесь, но если вы используете Linux / bash, для этого есть встроенный тайм-аут. Я полагаю, я должен получить источник этого и посмотреть, как он это делает!timeout
это не встроенная оболочка. Было несколько реализацийtimeout
команды для Linux, одна из которых (2008) была добавлена в GNU coreutils (поэтому не специфична для Linux), и это то, что в настоящее время используется большинством дистрибутивов Linux.Если вы работаете в Linux (и некоторых других * nixes), вы можете проверить, используется ли все еще процесс, который вы собираетесь убить и соответствует ли командная строка вашему длинному процессу. Что-то типа :
Альтернативой может быть проверка того, как долго выполняется процесс, который вы намереваетесь уничтожить, например
ps -p $p -o etime=
. Вы можете сделать это самостоятельно, извлекая эту информацию из/proc/$p/stat
, но это будет непросто (время измеряется в несколько мгновений, и вам также придется использовать время работы системы/proc/stat
).В любом случае, вы обычно не можете гарантировать, что процесс не будет заменен после вашей проверки и до ее уничтожения.
источник
cat pidfile
результат. Я не могу вспомнить чистый способ сделать это только в оболочке. Предложенный ответ пространства имен кажется интересным, однако ...Это на самом деле очень хороший вопрос.
Способ определения уникальности процесса состоит в том, чтобы посмотреть: а) где он находится в памяти; и (б) что эта память содержит. В частности, мы хотим знать, где в памяти находится текст программы для первоначального вызова, потому что мы знаем, что текстовая область каждого потока будет занимать другое место в памяти. Если процесс умирает, а другой запускается с тем же pid, текст программы для нового процесса не будет занимать одно и то же место в памяти и не будет содержать ту же информацию.
Итак, сразу после запуска вашего процесса, сделайте
md5sum /proc/[pid]/maps
и сохраните результат. Позже, когда вы захотите завершить процесс, сделайте еще одну md5sum и сравните ее. Если это соответствует, тогда убейте pid. Если нет, не надо.чтобы убедиться в этом, запустите две одинаковые оболочки bash. Изучите
/proc/[pid]/maps
их, и вы обнаружите, что они разные. Зачем? Потому что, хотя это одна и та же программа, они занимают разные места в памяти и адреса их стека различны. Таким образом, если ваш процесс умирает и его PID используется повторно, даже при повторном запуске одной и той же команды с теми же аргументами , файл «maps» будет другим, и вы будете знать, что не имеете дело с исходным процессом.Подробности смотрите на странице справочника proc .
Обратите внимание, что файл
/proc/[pid]/stat
уже содержит всю информацию, которую другие авторы упомянули в своих ответах: возраст процесса, родительский pid и т. Д. Этот файл содержит как статическую, так и динамическую информацию, поэтому, если вы предпочитаете использовать этот файл в качестве основы сравнения, после запускаlongrunningthing
вам нужно извлечь изstat
файла следующие статические поля и сохранить их для сравнения позже:pid, имя файла, pid родителя, идентификатор группы процессов, управляющий терминал, время запуска процесса после загрузки системы, размер резидентного набора, адрес начала стека,
вместе взятые, выше однозначно идентифицируют процесс, и поэтому это представляет собой другой путь. На самом деле, вы можете с легкостью доверять только «pid» и «процесс времени, запущенный после загрузки системы». Просто извлеките эти поля из
stat
файла и сохраните их где-нибудь после запуска вашего процесса. Позже, прежде чем убить его, извлеките его снова и сравните. Если они совпадают, то вы уверены, что смотрите на оригинальный процесс.источник
/proc/[pid]/maps
течением времени, так как выделяется дополнительная память, увеличивается размер стека или появляются новые файлы ... А что означает сразу после запуска ? После того, как все библиотеки были отображены? Как вы это определяете?md5sum
на своих картах файлы. Я позволю этому работать в течение дня или два и сообщу здесь с результатами.Другим способом было бы проверить возраст процесса, прежде чем убить его. Таким образом, вы можете быть уверены, что не убиваете процесс, который не был создан менее чем за 24 часа. Вы можете добавить
if
условие, основанное на этом, прежде чем убить процесс.Это
if
условие проверяет, является ли идентификатор процесса$p
менее 24 часов (86400 секунд).PS: - команда
ps -p $p -o etime=
будет иметь формат<no.of days>-HH:MM:SS
источник
mtime
Из/proc/$p
не имеет ничего общего с момента начала процесса.if
условие. Пожалуйста, не стесняйтесь комментировать, если он глючит.Что я делаю, после того, как убил процесс, сделайте это снова. Каждый раз, когда я делаю это, ответ возвращается, "нет такого процесса"
Не может быть проще, и я делал это годами без проблем.
источник