O que acontece quando um arquivo 100% paginado no cache da página é modificado por outro processo

14

Eu sei que quando uma página de cache de página é modificada, ela é marcada como suja e requer uma write-back, mas o que acontece quando:

Cenário: O arquivo / apps / EXE, que é um arquivo executável, é paginado no cache da página completamente (todas as suas páginas estão no cache / memória) e sendo executado pelo processo P

A liberação contínua substitui / apps / EXE por um novo executável.

Suposição 1: Presumo que o processo P (e qualquer outra pessoa com um descritor de arquivo que faça referência ao antigo executável) continuará usando o antigo, na memória / apps / EXE sem problemas, e qualquer novo processo que tente executar esse caminho será iniciado. o novo executável.

Suposição 2: Presumo que, se nem todas as páginas do arquivo forem mapeadas na memória, tudo ficará bem até que haja uma falha de página exigindo páginas do arquivo que foram substituídas e provavelmente ocorrerá um segfault?

Pergunta 1: Se você bloquear todas as páginas do arquivo com algo como vmtouch, isso muda o cenário?

Pergunta 2: Se / apps / EXE estiver em um NFS remoto, isso faria alguma diferença? (Presumo que não)

Corrija ou valide minhas 2 suposições e responda minhas 2 perguntas.

Vamos assumir que esta é uma caixa do CentOS 7.6 com algum tipo de kernel 3.10.0-957.el7

Atualização: Pensando nisso mais, me pergunto se esse cenário não é diferente de qualquer outro cenário de página suja.

Suponho que o processo que escreve o novo binário faça uma leitura e obtenha todas as páginas de cache, pois todas estão paginadas e, em seguida, todas essas páginas serão marcadas como sujas. Se estiverem bloqueados, serão apenas páginas inúteis que ocupam a memória principal depois que a contagem de referências for zero.

Eu suspeito que, quando os programas em execução no momento terminarem, qualquer outra coisa usará o novo binário. Supondo que tudo esteja correto, acho que só é interessante quando apenas parte do arquivo é paginada.

Gregg Leventhal
fonte
Apenas para torná-lo explícito, substituir um arquivo não será uma grande coisa (dependendo se ele for reaberto pelo aplicativo e como o aplicativo reage ao conteúdo modificado), mas a modificação de arquivos mmaped pode causar severidade em aplicativos de falha (é um problema comum no mundo java quando um arquivo zip com uma entrada de diretório mmaped é alterado). No entanto, depende da plataforma, não é garantido que as regiões mapeadas vejam a alteração ou não.
Eckes

Respostas:

12

A liberação contínua substitui / apps / EXE por um novo executável.

Essa é a parte importante.

A maneira como um novo arquivo é lançado é criando um novo arquivo (por exemplo /apps/EXE.tmp.20190907080000), escrevendo o conteúdo, definindo permissões e propriedade e, finalmente, renomeie (2) o nome final /apps/EXE, substituindo o arquivo antigo.

O resultado é que o novo arquivo possui um novo número de inode (o que significa que, na verdade, é um arquivo diferente.)

E o arquivo antigo tinha seu próprio número de inode, que ainda está presente , embora o nome do arquivo não esteja mais apontando para ele (ou não há mais nomes de arquivos apontando para esse inode).

Portanto, a chave aqui é que, quando falamos de "arquivos" no Linux, na maioria das vezes estamos realmente falando de "inodes", pois uma vez que um arquivo foi aberto, o inode é a referência que mantemos no arquivo.

Suposição 1 : Presumo que o processo P (e qualquer outra pessoa com um descritor de arquivo que faça referência ao antigo executável) continuará usando o antigo, na memória / apps / EXE sem problemas, e qualquer novo processo que tente executar esse caminho será iniciado. o novo executável.

Corrigir.

Suposição 2 : Presumo que, se nem todas as páginas do arquivo forem mapeadas na memória, tudo ficará bem até que haja uma falha na página exigindo páginas do arquivo que foram substituídas e provavelmente ocorrerá um segfault?

Incorreta. O inode antigo ainda está disponível, portanto, as falhas de página no processo usando o binário antigo ainda poderão encontrar essas páginas no disco.

Você pode ver alguns efeitos disso observando o /proc/${pid}/exelink simbólico (ou, equivalente, a lsofsaída) do processo que está executando o binário antigo, que mostrará /app/EXE (deleted)para indicar que o nome não está mais lá, mas o inode ainda está por aí.

Você também pode ver que o espaço em disco usado pelo binário só será liberado após a morte do processo (supondo que seja o único processo com esse inode aberto.) Verifique a saída dfantes e depois de encerrar o processo, você verá que ela cairá pelo tamanho daquele velho binário que você pensou que não estava mais por perto.

BTW, isso não é apenas com binários, mas com qualquer arquivo aberto. Se você abrir um arquivo em um processo e removê-lo, ele será mantido em disco até que o processo feche o arquivo (ou morra). De maneira semelhante à maneira como os hardlinks mantêm um contador de quantos nomes apontam para um inode no disco, o O driver do sistema de arquivos (no kernel do Linux) mantém um contador de quantas referências existem para esse inode na memória e libera o inode do disco apenas quando todas as referências do sistema em execução também forem liberadas.

Pergunta 1 : Se você bloquear todas as páginas do arquivo com algo como vmtouch, isso muda o cenário

Esta pergunta é baseada na suposição incorreta 2 de que não bloquear as páginas causará segfaults. Não vai.

Pergunta 2 : Se / apps / EXE estiver em um NFS remoto, isso faria alguma diferença? (Presumo que não)

Ele deve funcionar da mesma maneira e na maioria das vezes, mas existem algumas "dicas" com o NFS.

Às vezes, você pode ver os artefatos de excluir um arquivo que ainda está aberto no NFS (aparece como um arquivo oculto nesse diretório).

Você também tem uma maneira de atribuir números de dispositivo às exportações do NFS, para garantir que eles não sejam "reorganizados" quando o servidor NFS for reiniciado.

Mas a ideia principal é a mesma. O driver do cliente NFS ainda usa inodes e tentará manter os arquivos ao redor (no servidor) enquanto o inode ainda estiver relacionado.

filbranden
fonte
1
Renomear (2) é bloqueado até que a contagem de ref do arquivo antigo atinja zero?
Gregg Leventhal
2
Não, renomear (2) não será bloqueado. O inode antigo é mantido por um tempo potencialmente muito longo.
filbranden
1
Veja a resposta de @ mosvy para saber por que você não pode gravar em um arquivo que está sendo executado (você obtém ETXTBSY). Desvincular e criar novo tem o mesmo efeito de renomear: você acaba com um novo inode. (Rename é melhor, porque então não há nenhum momento em que o nome do arquivo não existe, é uma operação atômica substituindo o nome para apontar para o novo inode.)
filbranden
4
@GreggLeventhal: "Que suposição você está fazendo sobre o processo de liberação contínua que estou usando que garante que ele use arquivos temporários?" - Porque enquanto o Unix existir, essa é e tem sido a única maneira sensata de fazê-lo. renameé praticamente a única operação de arquivo e sistema de arquivos que é garantida como atômica (supondo que não ultrapassemos os limites do sistema de arquivos ou do dispositivo), portanto, "crie arquivo temporário e depois rename" é o padrão padrão para atualizar arquivos. É também o que todo editor de texto no Unix usa, por exemplo.
Jörg W Mittag
1
@ grahamj42: renamefaz parte do POSIX. Concedido, ele é incluído por referência à ISO C (seção 7.21.4.2 no rascunho atual), mas está lá.
Jörg W Mittag
6

Suposição 2: Presumo que, se nem todas as páginas do arquivo forem mapeadas na memória, tudo ficará bem até que haja uma falha de página exigindo páginas do arquivo que foram substituídas e provavelmente ocorrerá um segfault?

Não, isso não acontecerá, porque o kernel não permitirá que você abra para escrever e substituir qualquer coisa dentro de um arquivo que está sendo executado no momento. Tal ação falhará com ETXTBSY[1]:

cp /bin/sleep sleep; ./sleep 3600 & echo none > ./sleep
[9] 5332
bash: ./sleep: Text file busy

Quando o dpkg, etc atualiza um binário, ele não o substitui, mas usa o rename(2)que simplesmente aponta a entrada do diretório para um arquivo completamente diferente, e qualquer processo que ainda tenha mapeamentos ou identificadores abertos no arquivo antigo continuará a usá-lo sem problemas .

[1] essa proteção não é estendida a outros arquivos que também podem ser considerados "texto" (código ativo / executável): bibliotecas compartilhadas, classes java, etc; modificar esse arquivo um tempo mapeados por outro processo vai fazer com que ele deixe de funcionar. No linux, o vinculador dinâmico passa a MAP_DENYWRITEbandeira obedientemente mmap(2), mas não se engane - não tem nenhum efeito.

mosvy
fonte
1
No cenário dpkg, em que momento a renomeação é concluída, de modo que o dentry para / apps / EXE faça referência ao inode do novo binário? Quando não há mais referências ao antigo? Como isso funciona?
Gregg Leventhal
2
rename(2)é atômico; assim que for concluída, a entrada dir se refere ao novo arquivo. Os processos que ainda estavam usando o arquivo antigo naquele momento só poderiam acessá-lo por meio de mapeamentos existentes ou por identificadores abertos (que podem fazer referência a um dentry órfão, não mais acessível a não ser via /proc/PID/fd).
mosvy
1
Gosto mais da sua resposta porque sua menção ETXTBSY me levou a esse utcc.utoronto.ca/~cks/space/blog/unix/WhyTextFileBusyError, que responde a todas as minhas perguntas.
Gregg Leventhal
4

A resposta de filbranden está correta, assumindo que o processo de liberação contínua faça a substituição atômica adequada dos arquivos via rename. Se não, mas modifica o arquivo no local, as coisas são diferentes. No entanto, seu modelo mental ainda está errado.

Não há possibilidade de as coisas serem modificadas no disco e serem inconsistentes com o cache da página, porque o cache da página é a versão canônica e a que foi modificada. Qualquer gravação em um arquivo ocorre através do cache da página. Se já estiver presente lá, as páginas existentes serão modificadas. Se ainda não estiver presente, as tentativas de modificar uma página parcial farão com que a página inteira seja armazenada em cache, seguida pela modificação como se já estivesse em cache. Gravações que abrangem uma página inteira ou mais podem (e quase certamente o fazem) otimizar a etapa de leitura que está sendo paginada. .

(*) Menti levemente. Para NFS e outros sistemas de arquivos remotos, pode haver mais de um, e eles normalmente (dependendo de qual e quais opções de montagem e de servidor são usadas) não implementam corretamente a atomicidade e a semântica de pedidos para gravações. É por isso que muitos de nós os consideramos fundamentalmente quebrados e nos recusamos a usá-los para situações em que haverá gravações simultâneas ao uso.

R .. GitHub PARE DE AJUDAR O GELO
fonte