Como é possível fazer uma atualização ao vivo enquanto um programa está em execução?

15

Gostaria de saber como aplicativos matadores, como Thunderbird ou Firefox, podem ser atualizados através do gerenciador de pacotes do sistema enquanto eles ainda estão em execução. O que acontece com o código antigo enquanto eles estão sendo atualizados? O que tenho que fazer quando quero escrever um programa a.out que se atualize enquanto estiver em execução?

ubuplex
fonte
@derobert Não exatamente: esse segmento não entra nas peculiaridades dos executáveis. Ele fornece informações relevantes, mas não é duplicado.
Gilles 'SO- stop be evil'
@ Gilles Bem, se você deixar no resto das coisas, é muito amplo. Existem duas (pelo menos) perguntas aqui. E a outra pergunta é bem próxima, está perguntando por que a substituição de arquivos para atualização funciona no Unix.
derobert
Se você realmente deseja fazer isso com seu próprio código, consulte a linguagem de programação Erlang, onde as atualizações de código "quentes" são um recurso essencial. Você está em
Home
1
@ Gilles BTW: Tendo visto o ensaio que você adicionou em resposta, retirei minha votação. Você transformou essa pergunta em um bom lugar para indicar quem deseja saber como as atualizações funcionam.
derobert

Respostas:

19

Substituindo arquivos em geral

Primeiro, existem várias estratégias para substituir um arquivo:

  1. Abra o arquivo existente para gravação, trunque-o no tamanho 0 e escreva o novo conteúdo. (Uma variante menos comum é abrir o arquivo existente, substituir o conteúdo antigo pelo novo, truncar o arquivo para o novo tamanho, se for menor.) Em termos de shell:

    echo 'new content' >somefile
    
  2. Remova o arquivo antigo e crie um novo arquivo com o mesmo nome. Em termos de shell:

    rm somefile
    echo 'new content' >somefile
    
  3. Escreva em um novo arquivo com um nome temporário e mova o novo arquivo para o nome existente. A movimentação exclui o arquivo antigo. Em termos de shell:

    echo 'new content' >somefile.new
    mv somefile.new somefile
    

Não vou listar todas as diferenças entre as estratégias, apenas mencionarei algumas que são importantes aqui. Com a estratégia 1, se algum processo estiver usando o arquivo no momento, o processo verá o novo conteúdo enquanto ele está sendo atualizado. Isso pode causar alguma confusão se o processo esperar que o conteúdo do arquivo permaneça o mesmo. Observe que isso é apenas sobre processos que têm o arquivo aberto (como visível em lsofou em ; aplicativos interativos que têm um documento aberto (por exemplo, abrir um arquivo em um editor) geralmente não mantêm o arquivo aberto, eles carregam o conteúdo do arquivo durante o processo. Operação "abrir documento" e eles substituem o arquivo (usando uma das estratégias acima) durante a operação "salvar documento"./proc/PID/fd/

Nas estratégias 2 e 3, se algum processo tiver o arquivo somefileaberto, o arquivo antigo permanecerá aberto durante a atualização do conteúdo. Com a estratégia 2, a etapa de remover o arquivo remove apenas a entrada do arquivo no diretório O arquivo em si só é removido quando não possui uma entrada de diretório que o conduza (em sistemas de arquivos Unix típicos, pode haver mais de uma entrada de diretório para o mesmo arquivo ) e nenhum processo o abre. Aqui está uma maneira de observar isso - o arquivo é removido apenas quando o sleepprocesso é interrompido ( rmapenas remove sua entrada de diretório).

echo 'old content' >somefile
sleep 9999999 <somefile &
df .
rm somefile
df .
cat /proc/$!/fd/0
kill $!
df .

Com a estratégia 3, a etapa de mover o novo arquivo para o nome existente remove a entrada de diretório que leva ao conteúdo antigo e cria uma entrada de diretório que leva ao novo conteúdo. Isso é feito em uma operação atômica, portanto, essa estratégia tem uma grande vantagem: se um processo abrir o arquivo a qualquer momento, ele verá o conteúdo antigo ou o novo conteúdo - não há risco de obter conteúdo misto ou o arquivo não existir.

Substituindo Executáveis

Se você tentar a estratégia 1 com um executável em execução no Linux, receberá um erro.

cp /bin/sleep .
./sleep 999999 &
echo oops >|sleep
bash: sleep: Text file busy

Um "arquivo de texto" significa um arquivo que contém código executável por razões históricas obscuras . O Linux, como muitas outras variantes do unix, se recusa a sobrescrever o código de um programa em execução; algumas variantes do unix permitem isso, causando falhas, a menos que o novo código seja uma modificação muito boa do código antigo.

No Linux, você pode sobrescrever o código de uma biblioteca carregada dinamicamente. É provável que leve a uma falha do programa que o está usando. (Talvez você não consiga observar isso sleepporque ele carrega todo o código da biblioteca necessário ao iniciar. Tente um programa mais complexo que faça algo útil depois de dormir, por exemplo perl -e 'sleep 9; print lc $ARGV[0]').

Se um intérprete estiver executando um script, o arquivo de script será aberto normalmente pelo intérprete, portanto, não haverá proteção contra a substituição do script. Alguns intérpretes leem e analisam o script inteiro antes de começarem a executar a primeira linha, outros leem o script conforme necessário. Consulte O que acontece se você editar um script durante a execução? e Como o Linux lida com scripts de shell? para mais detalhes.

As estratégias 2 e 3 também são seguras para os executáveis: embora a execução de executáveis ​​(e bibliotecas carregadas dinamicamente) não sejam arquivos abertos no sentido de ter um descritor de arquivo, eles se comportam de maneira muito semelhante. Enquanto algum programa estiver executando o código, o arquivo permanecerá em disco, mesmo sem uma entrada de diretório.

Atualizando um aplicativo

A maioria dos gerenciadores de pacotes usa a estratégia 3 para substituir arquivos, devido à grande vantagem mencionada acima - a qualquer momento, a abertura do arquivo leva a uma versão válida dele.

Onde as atualizações de aplicativos podem ser interrompidas é que, enquanto a atualização de um arquivo é atômica, a atualização do aplicativo como um todo não ocorre se o aplicativo consistir em vários arquivos (programa, bibliotecas, dados, ...). Considere a seguinte sequência de eventos:

  1. Uma instância do aplicativo é iniciada.
  2. O aplicativo é atualizado.
  3. O aplicativo de instância em execução abre um de seus arquivos de dados.

Na etapa 3, a instância em execução da versão antiga do aplicativo está abrindo um arquivo de dados da nova versão. Se isso funciona ou não, depende do aplicativo, de qual arquivo é e de quanto foi modificado.

Após uma atualização, você notará que o programa antigo ainda está em execução. Se você deseja executar a nova versão, precisará sair do programa antigo e executar a nova versão. Os gerenciadores de pacotes geralmente matam e reiniciam daemons em uma atualização, mas deixam os aplicativos do usuário final em paz.

Alguns daemons têm procedimentos especiais para lidar com atualizações sem precisar matar o daemon e aguardar a nova instância reiniciar (o que causa uma interrupção no serviço). Isso é necessário no caso do init , que não pode ser morto; Os sistemas init fornecem uma maneira de solicitar que a instância em execução chame execvepara se substituir pela nova versão.

Gilles 'SO- parar de ser mau'
fonte
"mova o novo arquivo para o nome existente. A movimentação exclui o arquivo antigo." Isso é um pouco confuso, como é realmente apenas um unlink, como você verá mais tarde. Talvez "substitua o nome existente", mas isso ainda é um pouco confuso.
22414 derobert
@derobert Eu não queria me aprofundar na terminologia de "desvincular". Estou usando "delete" em referência à entrada do diretório, uma sutileza que será explicada mais adiante. É confuso nessa fase?
Gilles 'SO- stop be evil'
Provavelmente não é confuso o suficiente para justificar um parágrafo ou dois extras explicando a desvinculação. Espero algumas palavras que não sejam confusas, mas também tecnicamente corretas. Talvez apenas use "remover" novamente, o que você já colocou um link para explicar?
derobert
2

A atualização pode ser executada enquanto o programa é executado, mas o programa em execução que você vê é realmente a versão antiga. O binário antigo permanece no disco até você fechar o programa.

Explicação: nos sistemas Linux, um arquivo é apenas um inode, que pode ter vários links para ele. Por exemplo. o que /bin/bashvocê vê é apenas um link para inode 3932163no meu sistema. Você pode descobrir qual inode vincula algo emitindo ls --inode /pathno link. Um arquivo (inode) será removido apenas se houver zero links apontando para ele e não estiver em uso por nenhum programa. Quando o gerenciador de pacotes é atualizado, por exemplo. /usr/bin/firefox, ele primeiro desassocia (remove o link físico /usr/bin/firefox) e depois cria um novo arquivo chamado /usr/bin/firefoxque é um link físico para um inode diferente (aquele que contém a nova firefoxversão). O inode antigo agora está marcado como livre e pode ser reutilizado para armazenar novos dados, mas permanece no disco (os inodes são criados apenas quando você constrói seu sistema de arquivos e nunca são excluídos). No próximo começo defirefox, o novo será usado.

Se você deseja escrever um programa que se atualiza durante a execução, a única solução possível é verificar periodicamente o registro de data e hora de seu próprio arquivo binário e, se for mais recente que o horário de início do programa, recarregue-se.

psimon
fonte
1
Na verdade, é devido a como a exclusão (desvinculação) de arquivos funciona no Unix; consulte unix.stackexchange.com/questions/49299/… Além disso, pelo menos no Linux, você não pode realmente gravar em um binário em execução, receberá um erro "arquivo de texto ocupado".
derobert
Estranho ... Então, como é que, por exemplo. O apttrabalho de atualização do Debian ? Posso atualizar qualquer programa em execução sem problemas, incluindo Iceweasel( Firefox).
Psimon
2
O APT (ou melhor, dpkg) não substitui arquivos. Em vez disso, os desvincula e coloca um novo com o mesmo nome. Veja a pergunta e resposta à qual vinculei para obter uma explicação.
derobert
2
Não é apenas porque ainda está na RAM, ainda está no disco. O arquivo não é realmente excluído até que a última instância do programa seja encerrada.
derobert
1
O site não me permite sugerir uma edição (quando seu representante estiver alto o suficiente, você apenas fará edições, não poderá mais sugeri-las). Então, como um comentário: um arquivo (inode) em um sistema Unix geralmente tem um nome. Mas pode ter mais se você adicionar nomes com ln(links físicos). Você pode remover nomes com rm(desvincular). Na verdade, você não pode excluir diretamente um arquivo, apenas remover seus nomes. Quando um arquivo não tem nomes e, adicionalmente, não está aberto, o kernel o exclui. Um programa em execução possui o arquivo do qual está sendo aberto, portanto, mesmo depois de remover todos os nomes, o arquivo ainda está por aí.
derobert
0

Gostaria de saber como aplicativos matadores, como Thunderbird ou Firefox, podem ser atualizados através do gerenciador de pacotes do sistema enquanto eles ainda estão em execução? Bem, posso lhe dizer que isso realmente não funciona bem ... O Firefox quebrou-me bastante se eu o deixasse aberto enquanto uma atualização de pacote estava em execução. Às vezes, eu tinha que matá-lo com força e reiniciá-lo, porque estava tão quebrado que nem conseguia fechá-lo corretamente.

O que acontece com o código antigo enquanto eles estão sendo atualizados? Normalmente, no Linux, um programa é carregado na memória; portanto, o executável em disco não é necessário ou usado enquanto o programa está em execução. Na verdade, você pode até excluir o executável e o programa não deve se importar ... No entanto, alguns programas podem precisar do executável, e certos sistemas operacionais (como o Windows) bloquearão o arquivo executável, impedindo a exclusão ou até renomeando / movendo, enquanto o programa está sendo executado. O Firefox quebra, porque é realmente bastante complexo e usa um monte de arquivos de dados que informam como criar sua GUI (interface do usuário). Durante uma atualização do pacote, esses arquivos são substituídos (atualizados); portanto, quando um executável antigo do Firefox (na memória) tenta usar os novos arquivos da GUI, coisas estranhas podem acontecer ...

O que tenho que fazer quando quero escrever um programa a.out que se atualize enquanto estiver em execução? Já existem muitas respostas para sua pergunta. Confira: /programming/232347/how-should-i-implement-an-auto-updater A propósito, as perguntas sobre programação são melhores no StackOverflow.

Rouben Tchakhmakhtchian
fonte
1
Os executáveis ​​são realmente paginados por demanda (trocados). Eles não estão totalmente carregados na memória e podem ser descartados sempre que o sistema deseja RAM para outra coisa. A versão antiga ainda está no disco; consulte unix.stackexchange.com/questions/49299/… . No Linux, pelo menos, você não pode realmente gravar em um executável em execução, o erro "arquivo de texto está ocupado". Mesmo a raiz não pode fazer isso. (Você está certo sobre o Firefox).
derobert