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.
- Se você tentar a estratégia um ( abrir / truncar para zero / gravar ou abrir / gravar / truncar para novo tamanho ), verá que o kernel não se incomodará em lidar com sua solicitação. Você receberá um erro 26: Arquivo de texto ocupado (
ETXTBSY
). Sem consequências.
- Se você tentar a estratégia dois, o primeiro passo é excluir o seu executável. No entanto, como está sendo usado por um processo, o subsistema de arquivos entra em ação e impede que o arquivo (e seu inode) seja realmente excluído do disco. A partir desse ponto, a única maneira de acessar o conteúdo do arquivo antigo é fazê-lo por meio do inode, que é o que o subsistema de processo faz sempre que precisa carregar novos dados nas seções de texto (internamente, não há sentido em usar caminhos, exceto ao traduzi-los em inodes). Mesmo que você tenha desvinculadoo arquivo (removido todos os caminhos), o processo ainda pode usá-lo como se você não tivesse feito nada. Criar um novo arquivo com o caminho antigo não muda nada: o novo arquivo receberá um inode completamente novo, do qual o processo em execução não tem conhecimento.
As estratégias 2 e 3 também são seguras para 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.
- A estratégia três é bastante semelhante, pois a
mv
operação é atômica. Provavelmente, isso exigirá o uso da rename
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.
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.
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 um strace
dos processos do seu compilador:
stat("a.out", {st_mode=S_IFREG|0750, st_size=8511, ...}) = 0
unlink("a.out") = 0
open("a.out", O_RDWR|O_CREAT|O_TRUNC, 0666) = 3
chmod("a.out", 0750) = 0
- O compilador detecta que o arquivo já existe através das chamadas
stat
e do lstat
sistema.
- O arquivo está desvinculado . Aqui, embora não seja mais acessível por meio do nome
a.out
, seu inode e seu conteúdo permanecem no disco, enquanto estiverem sendo usados por processos já em execução.
- Um novo arquivo é criado e tornado executável sob o nome
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).