Acabei de perceber que sou capaz de mover um programa ativo em execução para um diretório diferente. Na minha experiência, isso não foi possível em MacOs ou Windows. Como isso funciona no Ubuntu?
Edit: Eu pensei que não era possível no Mac, mas aparentemente é possível como verificar comentários. Talvez apenas não seja possível no Windows. Obrigado por todas as respostas.
rename(2)
executar um executável no OS X? O que acontece, você recebeEBUSY
ou algo assim? Por que isso não funciona? A página de manual rename (2) não documentaETXTBUSY
essa chamada do sistema e apenas fala sobreEBUSY
a possibilidade de renomear diretórios, então eu não sabia que um sistema POSIX poderia impedir a renomeação de executáveis.Respostas:
Deixe-me explicar.
Quando você executa um executável, uma sequência de chamadas do sistema é executada, principalmente
fork()
eexecve()
:fork()
cria um processo filho do processo de chamada, que é (principalmente) uma cópia exata do pai, ambos ainda executando o mesmo executável (usando páginas de memória de cópia na gravação, para que seja eficiente). Ele retorna duas vezes: no pai, ele retorna o PID filho. No filho, ele retorna 0. Normalmente, o processo filho chama execve imediatamente:execve()
toma um caminho completo para o executável como argumento e substitui o processo de chamada pelo executável. Nesse ponto, o processo recém-criado obtém seu próprio espaço de endereço virtual, ou seja, memória virtual, e a execução começa no ponto de entrada (em um estado especificado pelas regras da plataforma ABI para novos processos).Nesse momento, o carregador ELF do kernel mapeou os segmentos de texto e dados do executável na memória, como se tivesse usado a
mmap()
chamada do sistema (com mapeamentos compartilhados somente leitura e leitura / gravação privados, respectivamente). O BSS também é mapeado como se estivesse com MAP_ANONYMOUS. (BTW, estou ignorando o vínculo dinâmico aqui por simplicidade: o vinculador dinâmicoopen()
es emmap()
todas as bibliotecas dinâmicas antes de pular para o ponto de entrada do executável principal.)Somente algumas páginas são realmente carregadas na memória do disco antes que um recém-exec () ed comece a executar seu próprio código. As páginas adicionais serão paginadas conforme necessário, se / quando o processo tocar essas partes do seu espaço de endereço virtual. (Pré-carregar qualquer página de código ou dados antes de começar a executar o código do espaço do usuário é apenas uma otimização de desempenho.)
O arquivo executável é identificado pelo inode no nível inferior. Depois que o arquivo começa a ser executado, o kernel mantém o conteúdo do arquivo intacto pela referência do inode, não pelo nome do arquivo, como para descritores de arquivos abertos ou mapeamentos de memória suportados por arquivos. Assim, você pode mover o executável facilmente para outro local do sistema de arquivos ou mesmo para um sistema de arquivos diferente. Como uma observação lateral, para verificar as várias estatísticas do processo, você pode espiar no
/proc/PID
diretório (PID é o ID do processo do processo fornecido). Você pode até abrir o arquivo executável como/proc/PID/exe
, mesmo que tenha sido desvinculado do disco.Agora vamos cavar o movimento:
Quando você move um arquivo dentro de um mesmo sistema de arquivos, a chamada do sistema que é executada é
rename()
, que apenas renomeia o arquivo para outro nome, o inode do arquivo permanece o mesmo.Enquanto entre dois sistemas de arquivos diferentes, duas coisas acontecem:
O conteúdo do arquivo foi copiado primeiro para o novo local, por
read()
ewrite()
Depois disso, o arquivo é desvinculado do diretório de origem
unlink()
e, obviamente, o arquivo receberá um novo inode no novo sistema de arquivos.rm
está na verdade apenasunlink()
inserindo o arquivo fornecido na árvore de diretórios, portanto, ter a permissão de gravação no diretório garante o direito de remover qualquer arquivo desse diretório.Agora, por diversão, imagine o que acontece quando você move arquivos entre dois itens e não tem permissão para
unlink()
o arquivo da fonte?Bem, o arquivo será copiado para o destino primeiro (
read()
,write()
) e depoisunlink()
falhará devido à permissão insuficiente. Portanto, o arquivo permanecerá nos dois sistemas de arquivos !!fonte
mmap
eunmap
system não são usadas para carregar e descarregar as páginas sob demanda; as páginas são carregadas pelo kernel ao acessá-las, geram uma falha de página; as páginas são descarregadas da memória quando o sistema operacional considera que a RAM seria melhor usada para outra coisa. Nenhuma chamada do sistema está envolvida nessas operações de carregamento / descarregamento.Bem, isso é bem direto. Vamos pegar um executável chamado / usr / local / bin / whoopdeedoo. Isso é apenas uma referência ao chamado inode (estrutura básica de arquivos no Unix Filesystems). É o inode que é marcado como "em uso".
Agora, quando você exclui ou move o arquivo / usr / local / whoopdeedoo, a única coisa que é movida (ou limpa) é a referência ao inode. O próprio inode permanece inalterado. É basicamente isso.
Devo verificar, mas acredito que você também pode fazer isso nos sistemas de arquivos do Mac OS X.
O Windows adota uma abordagem diferente. Por quê? Quem sabe...? Não estou familiarizado com os internos do NTFS. Teoricamente, todos os sistemas de arquivos que usam referências a estruturas intenais para nomes de arquivos devem poder fazer isso.
Eu admito, simplifiquei demais, mas leia a seção "Implicações" na Wikipedia, que faz um trabalho muito melhor do que eu.
fonte
Uma coisa que parece estar faltando em todas as outras respostas é que: depois que um arquivo é aberto e um programa contém um descritor de arquivo aberto, o arquivo não será removido do sistema até que o descritor de arquivo seja fechado.
As tentativas de excluir o inode referenciado serão adiadas até que o arquivo seja fechado: renomear no mesmo sistema de arquivos ou em um sistema de arquivos diferente não pode afetar o arquivo aberto, independentemente do comportamento da renomeação, nem excluir ou substituir explicitamente o arquivo por um novo. A única maneira pela qual você pode atrapalhar um arquivo é abrindo explicitamente seu inode e mexendo com o conteúdo, não por operações no diretório, como renomear / excluir o arquivo.
Além disso, quando o kernel executa um arquivo, ele mantém uma referência ao arquivo executável e isso impede novamente qualquer modificação dele durante a execução.
Portanto, mesmo que pareça que você possa excluir / mover os arquivos que compõem um programa em execução, na verdade o conteúdo desses arquivos é mantido na memória até o término do programa.
fonte
execve()
não retorna nenhum FD, ele simplesmente executa o programa. Assim, por exemplo, se você executartail -f /foo.log
em seguida, a sua é uma FD (/proc/PID/fd/<fd_num>
) associadotail
para ofoo.log
, mas não para o próprio executável,tail
e não em seu pai também. Isso também é válido para os executáveis únicos.execve
então não vejo como isso é relevante. Uma vez que o kernel comece a executar um arquivo, tentar substituí-lo não modificará o programa que o kernel carregará, tornando o ponto discutível. Se você deseja "atualizar" o executável enquanto está em execução, você pode chamarexecve
em algum momento para fazer o kernel reler o arquivo, mas não vejo como isso importa. O ponto é: excluir um "executável em execução" realmente não aciona nenhuma exclusão de dados até que o executável pare.execve()
e um DF quando não houver DF envolvido neste caso.open()
retorna um descritor de arquivo , com o qual o heemayl está falando aquiexecve()
. Sim, um processo em execução tem uma referência ao seu executável, mas esse não é um descritor de arquivo. Provavelmente, mesmo quemunmap()
editasse todos os mapeamentos de seu executável, ele ainda teria uma referência (refletida em / proc / self / exe) que impedisse a liberação do inode. (Isso seria possível sem travar se fizesse isso a partir de uma função de biblioteca que nunca retornou.) Aliás, truncar ou modificar um executável em uso pode fornecerETXTBUSY
, mas pode funcionar.Em um sistema de arquivos Linux, quando você move um arquivo, desde que ele não ultrapasse os limites do sistema de arquivos (leia-se: permanece no mesmo disco / partição), tudo o que você está alterando é o inode de
..
(diretório pai) para o novo local . Os dados reais não foram movidos no disco, apenas o ponteiro para que o sistema de arquivos saiba onde encontrá-los.É por isso que as operações de movimentação são tão rápidas e, provavelmente, não há problema em mover um programa em execução, pois você não está movendo o próprio programa.
fonte
É possível porque a movimentação de um programa não afeta os processos em execução iniciados por iniciá-lo.
Depois que um programa é iniciado, seus bits no disco ficam protegidos contra a substituição, mas não há necessidade de proteger o arquivo a ser renomeado, movido para um local diferente no mesmo sistema de arquivos, equivalente a renomear o arquivo ou movido para um sistema de arquivos diferente, que é equivalente a copiar o arquivo em outro lugar e depois removê-lo.
A remoção de um arquivo em uso, seja porque um processo possui um descritor de arquivo aberto ou porque um processo está sendo executado, não remove os dados do arquivo, que permanece referenciado pelo inode do arquivo, mas remove apenas a entrada do diretório, ou seja, um caminho a partir do qual o inode pode ser alcançado.
Observe que o lançamento de um programa não carrega tudo de uma vez na memória (física). Pelo contrário, apenas o mínimo estrito necessário para o início do processo é carregado. Em seguida, as páginas necessárias são carregadas sob demanda durante toda a vida útil do processo. isso é chamado de paginação por demanda. Se houver falta de RAM, o sistema operacional poderá liberar a RAM contendo essas páginas, portanto, é possível que um processo carregue várias vezes a mesma página a partir do inode executável.
A razão pela qual isso não era possível com o Windows é originalmente provável devido ao fato de o sistema de arquivos subjacente (FAT) não suportar o conceito de divisão de entradas de diretório vs inodes. Essa limitação não estava mais presente no NTFS, mas o design do sistema operacional foi mantido por um longo tempo, levando à restrição desagradável de ter que reiniciar ao instalar uma nova versão de um binário, o que não acontece mais nas versões recentes do Windows.
fonte
Basicamente, no Unix e no seu gênero, um nome de arquivo (incluindo o caminho do diretório que leva a ele) é usado para associar / encontrar um arquivo ao abri- lo (executar um arquivo é uma maneira de abri-lo de uma maneira). Após esse momento, a identidade do arquivo (via seu "inode") é estabelecida e não é mais questionada. Você pode remover o arquivo, renomeá-lo e alterar suas permissões. Desde que qualquer processo ou caminho de arquivo tenha um identificador nesse arquivo / inode, ele permanecerá, como um canal entre processos (na verdade, no UNIX histórico, um canal era um inodo sem nome com um tamanho que apenas cabia no arquivo referência de armazenamento em disco de "blocos diretos" no inode, algo como 10 blocos).
Se você tiver um visualizador de PDF aberto em um arquivo PDF, poderá excluí-lo e abrir um novo com o mesmo nome e, enquanto o visualizador antigo estiver aberto, continuará acessando o arquivo antigo (a menos que assista ativamente) o sistema de arquivos para perceber quando o arquivo desaparece com o nome original).
Os programas que precisam de arquivos temporários podem simplesmente abrir esse arquivo com algum nome e removê-lo imediatamente (ou melhor, sua entrada de diretório) enquanto ele ainda estiver aberto. Posteriormente, o arquivo não será mais acessível por nome, mas qualquer processo que possua um descritor de arquivo aberto ainda poderá acessá-lo e, se houver uma saída inesperada do programa posteriormente, o arquivo será removido e o armazenamento recuperado automaticamente.
Portanto, o caminho para um arquivo não é uma propriedade do próprio arquivo (na verdade, os links físicos podem fornecer vários caminhos diferentes) e é necessário apenas para abri-lo, não para o acesso contínuo por processos que já o abrem.
fonte