Muitas vezes me deparo com a situação no desenvolvimento, onde estou executando um arquivo binário, digamos a.out
em segundo plano, pois ele faz algum trabalho demorado. Enquanto isso, faço alterações no código C, que é produzido a.out
e compilado a.out
novamente. Até agora, não tive problemas com isso. O processo em execução a.out
continua normalmente, nunca falha e sempre executa o código antigo a partir do qual foi iniciado originalmente.
No entanto, digamos que a.out
era um arquivo enorme, talvez comparável ao tamanho da RAM. O que aconteceria neste caso? E diga que está vinculado a um arquivo de objeto compartilhado, libblas.so
e se eu modificasse libblas.so
durante o tempo de execução? O que aconteceria?
Minha principal pergunta é: o sistema operacional garante que, quando eu executo a.out
, o código original sempre será executado normalmente, conforme o binário original , independentemente do tamanho do binário ou dos .so
arquivos aos quais ele se vincula, mesmo quando esses arquivos .o
e .so
arquivos são modificados durante tempo de execução?
Sei que existem perguntas que abordam problemas semelhantes: /programming/8506865/when-a-binary-file-runs-does-it-copy-its-entire-binary-data-into-memory -at-once O que acontece se você editar um script durante a execução? Como é possível fazer uma atualização ao vivo enquanto um programa está em execução?
O que me ajudou a entender um pouco mais sobre isso, mas não acho que eles estejam perguntando exatamente o que eu quero, que é uma regra geral para as consequências de modificar um binário durante a execução
if they are read-only copies of something already on disc (like an executable, or a shared object file), they just get de-allocated and are reloaded from their source
, então eu tive a impressão de que se o seu binário é enorme, se parte do seu binário fica sem memória RAM, mas é necessário novamente, ele é "recarregado da fonte" - portanto, quaisquer alterações no o.(s)o
arquivo será refletido durante a execução. Mas é claro que podem ter entendido mal - que é por isso que estou fazendo esta pergunta mais específicaNo, it only loads the necessary pages into memory. This is demand paging.
Então, eu fiquei com a impressão de que o que pedi não pode ser garantido.Respostas:
Embora a pergunta sobre estouro de pilha parecesse ser suficiente no começo, entendo, pelos seus comentários, por que você ainda tem alguma dúvida sobre isso. Para mim, esse é exatamente o tipo de situação crítica envolvida quando os dois subsistemas UNIX (processos e arquivos) se comunicam.
Como você deve saber, os sistemas UNIX geralmente são divididos em dois subsistemas: o subsistema de arquivos e o subsistema de processo. Agora, a menos que seja instruído de outra forma por meio de uma chamada do sistema, o kernel não deve ter esses dois subsistemas interagindo entre si. No entanto, há uma exceção: o carregamento de um arquivo executável nas regiões de texto de um processo . Claro, pode-se argumentar que esta operação também é desencadeada por uma chamada de sistema (
execve
), mas isso geralmente é conhecido por ser o único caso em que o subsistema de processo faz uma solicitação implícita para o subsistema de arquivo.Como o subsistema de processo naturalmente não tem como manipular arquivos (caso contrário, não faria sentido dividir tudo em dois), ele precisava usar o que o subsistema de arquivos fornecer para acessar arquivos. Isso também significa que o subsistema de processo é enviado para qualquer medida que o subsistema de arquivos tome em relação à edição / exclusão de arquivos. Nesse ponto, eu recomendaria ler a resposta de Gilles para essa pergunta de perguntas e respostas . O resto da minha resposta é baseada nessa mais geral de Gilles.
A primeira coisa a ser observada é que internamente, os arquivos são acessíveis apenas através de inodes . Se o caminho for dado ao kernel, seu primeiro passo será convertê-lo em um inode para ser usado em todas as outras operações. Quando um processo carrega um executável na memória, ele o faz através do seu inode, que foi fornecido pelo subsistema de arquivos após a conversão de um caminho. Os inodes podem estar associados a vários caminhos (links) e os programas podem excluir apenas links. Para excluir um arquivo e seu inode, a terra do usuário deve remover todos os links existentes para esse inode e garantir que ele não seja completamente utilizado. Quando essas condições forem atendidas, o kernel excluirá automaticamente o arquivo do disco.
Se você der uma olhada na parte executável substituta da resposta de Gilles, verá que, dependendo de como editar / excluir o arquivo, o kernel reagirá / se adaptará de maneira diferente, sempre através de um mecanismo implementado no subsistema de arquivos.
ETXTBSY
). Sem consequências.mv
operação é atômica. Provavelmente, isso exigirá o uso darename
chamada do sistema e, como os processos não podem ser interrompidos enquanto no modo kernel, nada pode interferir nessa operação até que ela seja concluída (com ou sem êxito). Novamente, não há alteração no inode do arquivo antigo: um novo é criado e os processos em execução não terão conhecimento dele, mesmo que tenham sido associados a um dos links do inode antigo.Recompilando um arquivo : ao usar
gcc
(e o comportamento provavelmente é semelhante para muitos outros compiladores), você está usando a estratégia 2. Você pode ver isso executando umstrace
dos processos do seu compilador:stat
e dolstat
sistema.a.out
, seu inode e seu conteúdo permanecem no disco, enquanto estiverem sendo usados por processos já em execução.a.out
. Este é um novo inode e um conteúdo totalmente novo, com os quais os processos em execução não se importam.Agora, quando se trata de bibliotecas compartilhadas, o mesmo comportamento será aplicado. Enquanto um objeto de biblioteca for usado por um processo, ele não será excluído do disco, não importa como você altere seus links. Sempre que algo tiver que ser carregado na memória, o kernel fará isso através do inode do arquivo e, portanto, ignorará as alterações feitas nos seus links (como associá-los a novos arquivos).
fonte
df
para calcular o número de bytes livres no disco está errado, pois não leva inodes que todos os links do sistema de arquivos removidos foram levados em consideração? Então eu devo usardf -i
? (Esta é apenas uma curiosidade técnica, eu realmente não precisa saber o uso do disco exato!)rm
oumv
o inode para o arquivo original não seja removido até que todos os processos removam o link para esse inode.df
incluído) pode obter informações sobre o inode. Quaisquer novas informações que você encontrar estão relacionadas ao novo arquivo e ao novo inode. O ponto principal aqui é que o subsistema de processo não tem interesse nesse problema, portanto as noções de gerenciamento de memória (paginação por demanda, troca de processos, falhas de página, ...) são completamente irrelevantes. Este é um problema do subsistema de arquivos e é resolvido pelo subsistema de arquivos. O subsistema de processo não se preocupa com isso, não é para isso que serve aqui.df -i
: essa ferramenta provavelmente recupera informações do superbloco do fs ou seu cache, o que significa que ela pode incluir o inode do binário antigo (para o qual todos os links foram excluídos). Isso não significa que novos processos sejam livres para usar esses dados antigos.Meu entendimento é que, devido ao mapeamento de memória de um processo em execução, o kernel não permitiria atualizar uma parte reservada do arquivo mapeado. Eu acho que, no caso de um processo estar em execução, todo o seu arquivo é reservado e, portanto, atualizá-lo porque você compilou uma nova versão do seu código-fonte na verdade resulta na criação de um novo conjunto de inodes. Em resumo, as versões mais antigas do seu executável permanecem acessíveis no disco através de eventos de falha de página. Portanto, mesmo se você atualizar um arquivo enorme, ele deverá permanecer acessível e o kernel ainda deverá ver a versão intocada enquanto o processo estiver em execução. Os inodes do arquivo original não devem ser reutilizados enquanto o processo estiver em execução.
Claro que isso tem que ser confirmado.
fonte
Esse nem sempre é o caso ao substituir um arquivo .jar. Os recursos jar e alguns carregadores de classe de reflexão em tempo de execução não são lidos do disco até que o programa solicite explicitamente as informações.
Isso é apenas um problema, porque um jar é simplesmente um arquivo morto, e não um único executável que é mapeado na memória. Isso é um pouco parado, mas ainda é um desdobramento da sua pergunta e algo com que eu me atirei no pé.
Então, para executáveis: sim. Para arquivos jar: talvez (dependendo da implementação).
fonte