Suponha, por exemplo, que você tenha um script de shell semelhante a:
longrunningthing &
p=$!
echo Killing longrunningthing on PID $p in 24 hours
sleep 86400
echo Time up!
kill $p
Deveria fazer o truque, não deveria? Exceto que o processo pode ter terminado mais cedo e seu PID pode ter sido reciclado, o que significa que algum trabalho inocente recebe uma bomba na fila de sinais. Na prática, isso possivelmente importa, mas ainda assim está me preocupando. Hackear tudo para cair morto por si só, ou manter / remover seu PID no FS faria, mas estou pensando na situação genérica aqui.
killall
quais correspondências no nome, para que pelo menos você esteja apenas matando um processo com o mesmo nome quelongrunningthing
. Supondo que você terá apenas uma dessas opções por vez.Respostas:
O melhor seria usar o
timeout
comando, se você o tiver, para isso:A implementação atual (8.23) GNU pelo menos funciona usando
alarm()
ou equivalente enquanto aguarda o processo filho. Parece não proteger contra aSIGALRM
entrega entrewaitpid()
retorno etimeout
saída (cancelamento efetivo desse alarme ). Durante essa pequena janela,timeout
pode até escrever mensagens no stderr (por exemplo, se a criança despejar um núcleo), o que aumentaria ainda mais essa janela de corrida (indefinidamente se o stderr for um canal completo, por exemplo).Pessoalmente, posso conviver com essa limitação (que provavelmente será corrigida em uma versão futura).
timeout
também terá um cuidado extra para relatar o status de saída correto, lidar com outros casos de canto (como o SIGALRM bloqueado / ignorado na inicialização, lidar com outros sinais ...) melhor do que você provavelmente conseguiria fazer manualmente.Como uma aproximação, você pode escrever
perl
como:Existe um
timelimit
comando em http://devel.ringlet.net/sysutils/timelimit/ (antecede o GNUtimeout
em alguns meses).Esse usa um
alarm()
mecanismo semelhante, mas instala um manipuladorSIGCHLD
(ignorando crianças paradas) para detectar a morte da criança. Ele também cancela o alarme antes de executarwaitpid()
(que não cancela a entrega,SIGALRM
se estava pendente, mas da maneira como está escrito, não vejo problema) e mata antes de ligarwaitpid()
(por isso, não é possível matar um pid reutilizado) )O netpipes também possui um
timelimit
comando. Essa é anterior a todas as outras por décadas, adota outra abordagem, mas não funciona corretamente para comandos interrompidos e retorna um1
status de saída após o tempo limite.Como resposta mais direta à sua pergunta, você pode fazer algo como:
Ou seja, verifique se o processo ainda é filho nosso. Novamente, há uma pequena janela de corrida (entre
ps
recuperar o status desse processo ekill
matá-lo) durante o qual o processo pode morrer e seu pid ser reutilizado por outro processo.Com algumas conchas (
zsh
,bash
,mksh
), você pode passar especificações trabalho em vez de PIDs.Isso só funciona se você gerar apenas um trabalho em segundo plano (caso contrário, nem sempre é possível obter a especificação de trabalho correta).
Se isso é um problema, basta iniciar uma nova instância do shell:
Isso funciona porque o shell remove o trabalho da tabela de trabalhos quando a criança morre. Aqui, não deve haver nenhuma janela de corrida, pois no momento em que o shell chama
kill()
, o sinal SIGCHLD não foi tratado e o pid não pode ser reutilizado (pois não foi esperado), ou foi tratado e o sinal o trabalho foi removido da tabela de processos (ekill
reportaria um erro).bash
ékill
, pelo menos, blocos SIGCHLD antes de ele acessa sua tabela de trabalho para expandir o%
e desbloqueia-lo após okill()
.Outra opção para evitar a interrupção desse
sleep
processo, mesmo após acmd
morte, combash
ouksh93
é usar um cano com, emread -t
vez desleep
:Aquele ainda tem condições de corrida e você perde o status de saída do comando. Ele também assume
cmd
que não fecha seu fd 4.Você pode tentar implementar uma solução sem raça,
perl
como:(apesar de precisar ser aprimorado para lidar com outros tipos de caixas de canto).
Outro método sem raça pode estar usando grupos de processos:
No entanto, observe que o uso de grupos de processos pode ter efeitos colaterais se houver E / S em um dispositivo terminal envolvido. Porém, ele tem o benefício adicional de matar todos os outros processos extras gerados
cmd
.fonte
timeout
não é portátil, a resposta mencionou uma solução portátil primeiro.jobs
e depois saber que (como é seu próprio shell, no qual você tem controle sobre o que acontece a seguir), o próximo plano de fundo o trabalho será N + 1? [então você pode salvar N e depois matar% N + 1])Em geral, você não pode. Todas as respostas dadas até agora são heurísticas de bugs. Há apenas um caso em que você pode usar o pid com segurança para enviar sinais: quando o processo de destino é filho direto do processo que enviará o sinal e o pai ainda não o esperou. Nesse caso, mesmo que ele tenha saído, o pid é reservado (é o que é um "processo de zumbi") até que o pai espere. Não conheço nenhuma maneira de fazer isso de maneira limpa com a concha.
Uma maneira alternativa segura de eliminar processos é iniciá-los com um conjunto tty de controle em um pseudo-terminal para o qual você possui o lado mestre. Você pode então enviar sinais através do terminal, por exemplo, escrevendo o caractere para
SIGTERM
ouSIGQUIT
sobre o pty.Outra maneira mais conveniente para o script é usar uma
screen
sessão nomeada e enviar comandos para a sessão da tela para finalizá-la. Esse processo ocorre em um soquete pipe ou unix nomeado de acordo com a sessão de tela, que não será reutilizado automaticamente se você escolher um nome exclusivo e seguro.fonte
Ao iniciar o processo, salve seu horário de início:
Antes de tentar interromper o processo, pare com isso (isso não é realmente essencial, mas é uma maneira de evitar as condições de corrida: se você parar o processo, o pid não poderá ser reutilizado)
Verifique se o processo com esse PID tem a mesma hora de início e, se sim, mate-o, caso contrário, deixe o processo continuar:
Isso funciona porque pode haver apenas um processo com o mesmo PID e a hora de início em um determinado sistema operacional.
Parar o processo durante a verificação torna as condições da corrida um problema. Obviamente, isso tem o problema de que, algum processo aleatório pode ser interrompido por alguns milissegundos. Dependendo do tipo de processo, isso pode ou não ser um problema.
Pessoalmente, eu simplesmente usaria python e
psutil
lida com a reutilização do PID automaticamente:fonte
ps -o start=
formato muda de 18:12 para Jan26 depois de um tempo. Cuidado com as alterações de horário de verão também. Se no Linux, você provavelmente prefereTZ=UTC0 ps -o lstart=
.lstart
, eu vou editá-lo no.Em um sistema linux, você pode garantir que um pid não seja reutilizado mantendo seu espaço de nome pid ativo. Isso pode ser feito através do
/proc/$pid/ns/pid
arquivoman namespaces
-init
.man pid_namespaces
-util-linux
pacote fornece muitas ferramentas úteis para manipular os espaços para nome. Por exemplo, nounshare
entanto, se você ainda não organizou seus direitos em um espaço para nome de usuário, serão necessários direitos de superusuário:Se você não organizou um espaço para nome de usuário, ainda poderá executar comandos arbitrários com segurança, removendo imediatamente os privilégios. O
runuser
comando é outro binário (não setuid) fornecido peloutil-linux
pacote e a incorporação pode se parecer com:...e assim por diante.
No exemplo acima, duas opções são passadas para
unshare(1)
o--fork
sinalizador que torna osh -c
processo invocado o primeiro filho criado e garante seuinit
status, e o--pid
sinalizador que instruiunshare(1)
a criar um espaço para nome pid.O
sh -c
processo gera cinco shells filhos em segundo plano - cada um com umwhile
loop inifinito que continuará anexando a saídadate
até o final dolog
tempo, enquantosleep 1
retornar verdadeiro. Após gerar esses processos,sh
são necessáriossleep
5 segundos adicionais e termina.Talvez valha a pena notar que, se a
-f
bandeira não fosse usada, nenhum doswhile
loops em segundo plano terminaria, mas com ela ...SAÍDA:
fonte
Considere tornar seu
longrunningthing
comportamento um pouco melhor, um pouco mais parecido com um daemon. Por exemplo, você pode criar um pidfile que permitirá pelo menos algum controle limitado do processo. Existem várias maneiras de fazer isso sem modificar o binário original, todas envolvendo um wrapper. Por exemplo:um script de wrapper simples que iniciará o trabalho necessário em segundo plano (com redirecionamento de saída opcional), grave o PID desse processo em um arquivo e aguarde a conclusão do processo (usando
wait
) e remova o arquivo. Se durante a espera o processo é interrompido, por exemplo, por algo comoo wrapper apenas garantirá que o pidfile seja removido.
um wrapper de monitor, que colocará seu próprio PID em algum lugar e captará (e responderá) os sinais enviados a ele. Exemplo simples:
Agora, como @R .. e @ StéphaneChazelas apontaram, essas abordagens geralmente têm uma condição de corrida em algum lugar ou impõem uma restrição ao número de processos que você pode gerar. Além disso, ele não lida com os casos, nos quais o
longrunningthing
garfo pode e as crianças se separam (o que provavelmente não é o problema na pergunta original).Com os kernels Linux recentes (leia alguns anos), isso pode ser bem tratado usando o cgroups , ou seja, o freezer - que, suponho, é o que alguns sistemas modernos Linux init usam.
fonte
longrunningthing
é que você não tem controle sobre o que é. Também dei um exemplo de script de shell, porque explicava o problema. Eu gosto da sua e de todas as outras soluções criativas aqui, mas se você estiver usando Linux / bash, há um "tempo limite" incorporado para isso. Suponho que eu deveria obter a fonte disso e ver como isso acontece!timeout
é um shell embutido. Houve várias implementações de um comando para Linux, uma delas foi recentemente (2008) adicionada ao GNU coreutils (portanto, não específica do Linux), e é isso que a maioria das distribuições Linux usa atualmente.timeout
Se você estiver executando no Linux (e alguns outros * nixes), poderá verificar se o processo que pretende matar ainda é usado e se a linha de comando corresponde ao seu longo processo. Algo como :
Uma alternativa pode ser verificar por quanto tempo o processo que você pretende matar está em execução, com algo parecido
ps -p $p -o etime=
. Você pode fazê-lo extraindo essas informações/proc/$p/stat
, mas isso seria complicado (o tempo é medido em instantes e você também precisará usar o tempo de atividade do sistema/proc/stat
).De qualquer forma, você geralmente não pode garantir que o processo não seja substituído após sua verificação e antes de matá-la.
fonte
cat pidfile
resultado sem rodeios . Não me lembro de uma maneira limpa de fazê-lo apenas com casca. A resposta namespace proposta parece um um intersting no entanto ...Esta é realmente uma pergunta muito boa.
A maneira de determinar a exclusividade do processo é observar (a) onde ele está na memória; e (b) o que essa memória contém. Para ser específico, queremos saber onde está na memória o texto do programa para a chamada inicial, porque sabemos que a área de texto de cada encadeamento ocupará um local diferente na memória. Se o processo morrer e outro for lançado com o mesmo pid, o texto do programa para o novo processo não ocupará o mesmo lugar na memória e não conterá a mesma informação.
Portanto, imediatamente após o lançamento do processo, faça
md5sum /proc/[pid]/maps
e salve o resultado. Mais tarde, quando você quiser interromper o processo, faça outro md5sum e compare-o. Se combinar, mate o pid. Se não, não.para ver isso por si mesmo, inicie duas conchas do bash idênticas. Examine o
/proc/[pid]/maps
para eles e você verá que eles são diferentes. Por quê? Porque, mesmo sendo o mesmo programa, eles ocupam locais diferentes na memória e os endereços da pilha são diferentes. Portanto, se seu processo morrer e seu PID for reutilizado, mesmo com o mesmo comando sendo reiniciado com os mesmos argumentos , o arquivo "maps" será diferente e você saberá que não está lidando com o processo original.Veja: proc man page para detalhes.
Observe que o arquivo
/proc/[pid]/stat
já contém todas as informações que outros pôsteres mencionaram em suas respostas: idade do processo, pai ou mãe, etc. Este arquivo contém informações estáticas e dinâmicas, portanto, se você preferir usar esse arquivo como base de comparação, depois de iniciar o seulongrunningthing
, você precisa extrair os seguintes campos estáticos dostat
arquivo e salvá-los para comparação mais tarde:pid, nome do arquivo, número do pai, identificação do grupo de processos, terminal de controle, tempo do processo iniciado após a inicialização do sistema, tamanho do conjunto residente, endereço do início da pilha,
juntos, os itens acima identificam exclusivamente o processo e, portanto, isso representa outro caminho a percorrer. Na verdade, você pode se safar com nada além de "pid" e "processo de tempo iniciado após a inicialização do sistema" com alto grau de confiança. Simplesmente extraia esses campos do
stat
arquivo e salve-os em algum lugar ao iniciar seu processo. Mais tarde, antes de matá-lo, extraia-o novamente e compare. Se eles corresponderem, você terá certeza de que está observando o processo original.fonte
/proc/[pid]/maps
alterações ao longo do tempo, pois a memória extra é alocada ou a pilha cresce ou novos arquivos são mapeados ... E o que significa imediatamente após o lançamento ? Depois que todas as bibliotecas foram mapeadas? Como você determina isso?md5sum
em seus arquivos de mapas. Vou deixá-lo funcionar por um dia ou dois e relatar aqui com os resultados.Outra maneira seria verificar a idade do processo antes de matá-lo. Dessa forma, você pode ter certeza de que não está matando um processo que não é gerado em menos de 24 horas. Você pode adicionar uma
if
condição com base nisso antes de interromper o processo.Essa
if
condição verificará se o ID do processo$p
é inferior a 24 horas (86400 segundos).PS: - O comando
ps -p $p -o etime=
terá o formato<no.of days>-HH:MM:SS
fonte
mtime
de/proc/$p
nada tem a ver com a hora de início do processo.if
condição. Por favor, sinta-se livre para comentar se o seu buggy.O que faço é, depois de ter encerrado o processo, fazê-lo novamente. Toda vez que faço isso, a resposta volta: "não existe esse processo"
Não poderia ser mais simples e eu venho fazendo isso há anos sem problemas.
fonte