Как я могу убить процесс и убедиться, что PID не был повторно использован

Предположим, например, у вас есть сценарий оболочки, похожий на:

longrunningthing & p=$! echo Killing longrunningthing on PID $p in 24 hours sleep 86400 echo Time up! kill $p 

Должен делать трюк, не так ли? За исключением того, что процесс, возможно, закончился раньше, и его PID, возможно, был переработан, что означает, что некоторая невинная работа получает бомбу в сигнальной очереди. На практике это, возможно, имеет значение, но тем не менее беспокоит меня. Взломать longrunningthing, чтобы отбросить само по себе, или сохранить / удалить его PID на FS, будет, но я думаю об общей ситуации здесь.

  • Как убить все процессы с использованием данного GPU?
  • Закрытие многих окон gnuplot
  • Не удается завершить или приостановить фоновое задание
  • Убийство кометы зомби
  • убийство процесса в одной строке
  • Убивать процесс, когда ПИД постоянно меняется
  • Патрулирование PIDs в kill не работает
  • Сделать процесс unkillable в Linux
  • 9 Solutions collect form web for “Как я могу убить процесс и убедиться, что PID не был повторно использован”

    Лучше всего было бы использовать команду timeout если у вас есть это, которая предназначена для этого:

     timeout 86400 cmd 

    Текущая (8.23) реализация GNU по крайней мере работает с использованием alarm() или эквивалента во время ожидания дочернего процесса. Кажется, он не SIGALRM от SIGALRM который доставляется между waitpid() return и timeout exiting (эффективно отменяя этот сигнал ). Во время этого небольшого окна timeout может даже писать сообщения на stderr (например, если ребенок сбрасывал ядро), что еще больше увеличило бы это окно гонки (неопределенно, если stderr – полный канал, например).

    Я лично могу жить с этим ограничением (которое, вероятно, будет исправлено в будущей версии). timeout также будет проявлять особую осторожность, чтобы сообщать о правильном статусе выхода, обрабатывать другие угловые случаи (например, SIGALRM заблокирован / проигнорирован при запуске, обрабатывает другие сигналы …) лучше, чем вам, вероятно, удастся сделать вручную.

    В качестве приближения вы можете написать его в perl например:

     perl -MPOSIX -e ' $p = fork(); die "fork: $!\n" unless defined($p); if ($p) { $SIG{ALRM} = sub { kill "TERM", $p; exit 124; }; alarm(86400); wait; exit (WIFSIGNALED($?) ? WTERMSIG($?)+128 : WEXITSTATUS($?)) } else {exec @ARGV}' cmd 

    Команда timelimit адресу http://devel.ringlet.net/sysutils/timelimit/ (предшествует timeout GNU на несколько месяцев).

      timelimit -t 86400 cmd 

    Этот использует механизм alarm() но устанавливает обработчик SIGCHLD (игнорируя остановленных детей), чтобы обнаружить, что ребенок умирает. Он также отменяет аварийный сигнал перед запуском waitpid() (который не отменяет доставку SIGALRM если он был отложен, но так, как он написан, я не вижу, что это проблема) и убивает перед вызовом waitpid() (так не может убить повторно используемый pid).

    netpipes также имеет команду timelimit . Этот предшествует всем остальным за десятилетия, принимает еще один подход, но не работает должным образом для остановленных команд и возвращает 1 статус выхода после таймаута.

    Как более прямой ответ на ваш вопрос, вы можете сделать что-то вроде:

     if [ "$(ps -o ppid= -p "$p")" -eq "$$" ]; then kill "$p" fi 

    То есть, убедитесь, что этот процесс по-прежнему является нашим ребенком. Опять же, есть небольшое окно гонки (в промежутке между ps получение статуса этого процесса и убийство его), во время которого процесс может умереть и его pid повторно использовать другим процессом.

    С некоторыми оболочками ( zsh , bash , mksh ) вы можете передавать спецификации работы вместо pids.

     cmd & sleep 86400 kill % wait "$!" # to retrieve the exit status 

    Это работает только в том случае, если вы создаете только одно фоновое задание (в противном случае получение правильного задания не всегда возможно надежно).

    Если это проблема, просто запустите новый экземпляр оболочки:

     bash -c '"$@" & sleep 86400; kill %; wait "$!"' sh cmd 

    Это работает, потому что оболочка удаляет задание из таблицы заданий после смерти ребенка. Здесь не должно быть никакого окна гонки, так как к моменту, когда оболочка вызывает kill() , сигнал SIGCHLD не обрабатывается, и pid нельзя использовать повторно (поскольку он не ожидал), или он был и задание было удалено из таблицы процессов (и kill сообщит об ошибке). bash , по крайней мере, блокирует SIGCHLD, прежде чем он обратится к своей таблице заданий, чтобы развернуть % и разблокировать ее после kill() .

    Еще один вариант, чтобы избежать того, чтобы этот процесс sleep ksh93 даже после cmd , с bash или ksh93 заключается в использовании трубы с read -t вместо sleep :

     { { cmd 4>&1 >&3 3>&- & printf '%d\n.' "$!" } | { read p read -t 86400 || kill "$p" } } 3>&1 

    У этого еще есть условия гонки, и вы теряете статус выхода команды. Он также предполагает, что cmd не закрывает его fd 4.

    Вы можете попробовать внедрить решение без гонок в perl например:

     perl -MPOSIX -e ' $p = fork(); die "fork: $!\n" unless defined($p); if ($p) { $SIG{CHLD} = sub { $ss = POSIX::SigSet->new(SIGALRM); $oss = POSIX::SigSet->new; sigprocmask(SIG_BLOCK, $ss, $oss); waitpid($p,WNOHANG); exit (WIFSIGNALED($?) ? WTERMSIG($?)+128 : WEXITSTATUS($?)) unless $? == -1; sigprocmask(SIG_UNBLOCK, $oss); }; $SIG{ALRM} = sub { kill "TERM", $p; exit 124; }; alarm(86400); pause while 1; } else {exec @ARGV}' cmd args... 

    (хотя его нужно было бы улучшить для обработки других типов угловых случаев).

    Другой метод без гонок может использовать группы процессов:

     set -m ((sleep 86400; kill 0) & exec cmd) 

    Однако обратите внимание, что использование групп процессов может иметь побочные эффекты, если на подключаемое терминальное устройство есть входы / выходы. У этого есть дополнительное преимущество, но чтобы убить все другие дополнительные процессы, порожденные cmd .

    В общем, вы не можете. Все ответы, полученные до сих пор, являются ошибочными эвристиками. Существует только один случай, когда вы можете безопасно использовать pid для отправки сигналов: когда целевой процесс является прямым дочерним процессом, который будет посылать сигнал, а родитель еще не ждал на нем. В этом случае, даже если он вышел, pid зарезервирован (это то, что «зомбический процесс»), пока родитель не ждет его. Я не знаю, как это сделать с оболочкой.

    Альтернативный безопасный способ убить процессы – запустить их с помощью управляющего набора tty на псевдотерминал, для которого вы владеете главной стороной. Затем вы можете отправлять сигналы через терминал, например, записывать символ для SIGTERM или SIGQUIT поверх pty.

    Еще один способ, который более удобен при написании сценариев, – использовать сеанс именованного screen и отправить команды на сеанс экрана, чтобы закончить его. Этот процесс происходит через сокет pipe или unix, названный в соответствии с сеансом экрана, который не будет автоматически использоваться повторно, если вы выберете безопасное уникальное имя.

    1. При запуске процесса сохраните время начала:

       longrunningthing & p=$! stime=$(TZ=UTC0 ps -p "$p" -o lstart=) echo "Killing longrunningthing on PID $p in 24 hours" sleep 86400 echo Time up! 
    2. Прежде чем пытаться убить процесс, остановите его (это не имеет особого значения, но это способ избежать условий гонки: если вы остановите процесс, его pid нельзя использовать повторно)

       kill -s STOP "$p" 
    3. Убедитесь, что процесс с этим PID имеет одинаковое время начала и, если да, убейте его, иначе продолжите процесс:

       cur=$(TZ=UTC0 ps -p "$p" -o lstart=) if [ "$cur" = "$stime" ] then # Okay, we can kill that process kill "$p" else # PID was reused. Better unblock the process! echo "long running task already completed!" kill -s CONT "$p" fi 

    Это работает, потому что может быть только один процесс с тем же PID и временем запуска на данной ОС.

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


    Лично я бы просто использовал python и psutil который автоматически обрабатывает повторное использование PID:

     import time import psutil # note: it would be better if you were able to avoid using # shell=True here. proc = psutil.Process('longrunningtask', shell=True) time.sleep(86400) # PID reuse handled by the library, no need to worry. proc.terminate() # or: proc.kill() 

    В linux-системе вы можете убедиться, что pid не будет повторно использоваться, сохранив пространство имен pid. Это можно сделать через файл /proc/$pid/ns/pid .

    • man namespaces

    Привязка (см. mount(2) ) один из файлов в этом каталоге в другом месте в файловой системе сохраняет соответствующее пространство имен процесса, указанное pid, даже если все процессы, находящиеся в настоящее время в пространстве имен, завершаются.

    Открытие одного из файлов в этом каталоге (или файла, привязанного к одному из этих файлов) возвращает дескриптор файла для соответствующего пространства имен процесса, заданного pid. Пока этот файловый дескриптор остается открытым, пространство имен останется в живых, даже если все процессы в пространстве имен завершатся. Дескриптор файла можно передать в setns(2) . Вы можете выделить группу процессов – в основном любое количество процессов – по именам, в которых их init .

    • man pid_namespaces

    Первый процесс, созданный в новом пространстве имен (т. Е. Процесс, созданный с использованием clone(2) с флагом CLONE_NEWPID или первый дочерний элемент, созданный процессом после вызова для unshare(2) с использованием флага CLONE_NEWPID ) имеет PID 1 , и является процессом init для пространства имен (см. init(1) ) . Детский процесс, осиротевший в пространстве имен, будет повторно представлен для этого процесса, а не init(1) (если только один из предков ребенка в одном и том же пространстве имен PID не использовал команду prctl(2) PR_SET_CHILD_SUBREAPER, чтобы отметить себя как осиротевших потомков) .

    Если процесс init пространства имен PID завершается, ядро ​​завершает все процессы в пространстве имен через сигнал SIGKILL . Такое поведение отражает тот факт, что процесс init необходим для правильной работы пространства имен PID . Пакет util-linux предоставляет множество полезных инструментов для управления пространствами имен. Например, нет unshare , хотя, если вы еще не устроили свои права в пространстве имен пользователей, для этого потребуются права суперпользователя:

     unshare -fp sh -c 'n= echo "PID = $$" until [ "$((n+=1))" -gt 5 ] do while sleep 1 do date done >>log 2>/dev/null & done; sleep 5' >log cat log; sleep 2 echo 2 secs later... tail -n1 log 

    Если вы не настроили пространство имен пользователей, вы можете спокойно выполнять произвольные команды, сразу же отбрасывая привилегии. Команда runuser – это другой (не setuid) двоичный файл, предоставленный пакетом util-linux , и его включение может выглядеть так:

     sudo unshare -fp runuser -u "$USER" -- sh -c '...' 

    …и так далее.

    В приведенном выше примере два переключателя передаются для unshare(1) флага --fork который заставляет вызванный sh -c обработать первый дочерний --pid созданный и обеспечивающий его статус init , и флаг --pid который инструктирует unshare(1) создать пространство имен pid.

    Процесс sh -c порождает пять фоновых дочерних оболочек – каждый из них является inifinite while loop, который будет продолжать добавлять выходные date до конца log до тех пор, пока sleep 1 возвращает true. После нереста этих процессов sh вызывает sleep на 5 секунд, а затем завершается.

    Возможно, стоит отметить, что если флаг -f не использовался, ни один из фоновых циклов не завершится, но с ним …

    ВЫВОД:

     PID = 1 Mon Jan 26 19:17:45 PST 2015 Mon Jan 26 19:17:45 PST 2015 Mon Jan 26 19:17:45 PST 2015 Mon Jan 26 19:17:45 PST 2015 Mon Jan 26 19:17:45 PST 2015 Mon Jan 26 19:17:46 PST 2015 Mon Jan 26 19:17:46 PST 2015 Mon Jan 26 19:17:46 PST 2015 Mon Jan 26 19:17:46 PST 2015 Mon Jan 26 19:17:46 PST 2015 Mon Jan 26 19:17:47 PST 2015 Mon Jan 26 19:17:47 PST 2015 Mon Jan 26 19:17:47 PST 2015 Mon Jan 26 19:17:47 PST 2015 Mon Jan 26 19:17:47 PST 2015 Mon Jan 26 19:17:48 PST 2015 Mon Jan 26 19:17:48 PST 2015 Mon Jan 26 19:17:48 PST 2015 Mon Jan 26 19:17:48 PST 2015 Mon Jan 26 19:17:48 PST 2015 2 secs later... Mon Jan 26 19:17:48 PST 2015 

    Подумайте о том, чтобы сделать ваш longrunningthing более longrunningthing , немного более похожим на демоны. Например, вы можете создать файл pidfile , который позволит хотя бы ограниченный контроль над процессом. Существует несколько способов сделать это, не изменяя исходный двоичный файл, все из которых связаны с оболочкой. Например:

    1. простой сценарий оболочки, который запустит требуемое задание в фоновом режиме (с дополнительным перенаправлением вывода), напишите PID этого процесса в файл, а затем дождитесь завершения процесса (используя wait ) и удалите файл. Если во время ожидания процесс будет убит, например, чем-то вроде

       kill $(cat pidfile) 

      оболочка будет просто убедиться, что pidfile удален.

    2. обертка монитора, которая будет помещать свой собственный PID где-нибудь и поймать (и реагировать на) сигналы, отправленные на него. Простой пример:

      #!/bin/bash p=0 trap killit USR1 killit () { printf "USR1 caught, killing %s\n" "$p" kill -9 $p } printf "monitor $$ is waiting\n" therealstuff & p=%1 wait $p printf "monitor exiting\n" 

    Теперь, как отмечали @R .. и @ StéphaneChazelas, эти подходы часто имеют какое-либо условие гонки или накладывают ограничение на количество процессов, которые вы можете вызвать. Кроме того, он не обрабатывает случаи, когда longrunningthing время, longrunningthing может longrunningthing вилка, и дети отделяются (что, вероятно, не является тем, что было проблемой в исходном вопросе).

    С недавними (прочитанными пару лет) ядрами Linux это может быть красиво обработано с помощью групп , а именно морозильника, что, я полагаю, является тем, что используют некоторые современные Linux-системы.

    Если вы работаете с Linux (и несколькими другими * nixes), вы можете проверить, все ли используется процесс, который вы собираетесь убить, и что командная строка соответствует длительному процессу. Что-то вроде :

     echo Time up! grep -q longrunningthing /proc/$p/cmdline 2>/dev/null if [ $? -eq 0 ] then kill $p fi 

    Альтернативой может быть проверка того, как долго выполняется процесс, который вы собираетесь убить, с чем-то вроде ps -p $p -o etime= . Вы можете сделать это сами, извлекая эту информацию из /proc/$p/stat , но это было бы сложно (время измеряется в jiffies, и вам придется использовать время безотказной работы системы в /proc/stat тоже).

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

    На самом деле это очень хороший вопрос.

    Способ определения уникальности процесса состоит в том, чтобы смотреть на (а), где он находится в памяти; и (б), что содержит эта память. Чтобы быть конкретным, мы хотим знать, где в памяти – текст программы для начального вызова, потому что мы знаем, что текстовая область каждого потока будет занимать другое место в памяти. Если процесс умирает, а другой запускается с тем же pid, текст программы для нового процесса не будет занимать одно и то же место в памяти и не будет содержать ту же информацию.

    Итак, сразу после запуска вашего процесса, сделайте md5sum /proc/[pid]/maps и сохраните результат. Позже, когда вы хотите убить процесс, сделайте еще один md5sum и сравните его. Если это соответствует, то убить pid. Если нет, не делайте этого.

    чтобы убедиться в этом, запустите две одинаковые оболочки bash. Изучите /proc/[pid]/maps для них, и вы обнаружите, что они разные. Зачем? Потому что, хотя это одна и та же программа, они занимают разные места в памяти, а адреса их стека различны. Таким образом, если ваш процесс умирает и его PID повторно используется, даже если одна и та же команда перезаписывается с теми же аргументами , файл «maps» будет другим, и вы будете знать, что вы не имеете дело с исходным процессом.

    Подробнее см. Страницу proc man .

    Обратите внимание, что файл /proc/[pid]/stat уже содержит всю информацию, которую другие авторы указали в своих ответах: возраст процесса, родительский pid и т. Д. Этот файл содержит как статическую информацию, так и динамическую информацию, поэтому, если вы предпочитают использовать этот файл в качестве базы для сравнения, а затем при запуске вашего longrunningthing вам нужно извлечь следующие статические поля из файла stat и сохранить их для сравнения позже:

    pid, filename, pid родителя, идентификатор группы процессов, управляющий терминал, процесс времени запускается после загрузки системы, размер резидентного набора, адрес начала стека,

    взятые вместе, вышеупомянутое однозначно идентифицирует процесс, и поэтому это представляет собой еще один способ пойти. На самом деле вы могли бы с максимальной степенью уверенности избегать «pid» и «процесс времени, начатый после загрузки системы». Просто извлеките эти поля из файла stat и сохраните их где-нибудь после запуска вашего процесса. Позже, прежде чем убить его, извлеките его снова и сравните. Если они совпадают, то вы уверены, что смотрите на исходный процесс.

    Другой способ – проверить возраст процесса, прежде чем убить его. Таким образом, вы можете убедиться, что вы не убиваете процесс, который не создается в течение менее 24 часов. Вы можете добавить условие if основанное на этом, прежде чем убить процесс.

     if [[ $(ps -p $p -o etime=) =~ 1-. ]] ; then kill $p fi 

    if условие будет проверяться, будет ли идентификатор процесса $p меньше 24 часов (86400 секунд).

    PS: – Команда ps -p $p -o etime= будет иметь формат <no.of days>-HH:MM:SS

    То, что я делаю, после того, как вы убили процесс, сделайте это снова. Каждый раз, когда я делаю это, возвращается ответ: «нет такого процесса»,

     allenb 12084 5473 0 08:12 pts/4 00:00:00 man man allenb@allenb-P7812 ~ $ kill -9 12084 allenb@allenb-P7812 ~ $ kill -9 12084 bash: kill: (12084) - No such process allenb@allenb-P7812 ~ $ 

    Не может быть проще, и я делал это в течение многих лет без каких-либо проблем.

    Linux и Unix - лучшая ОС в мире.