Sistemas de arquivo Union: Implementações, parte I



Este artigo trazido a você por assinantes da LWN

Subscribers to LWN.net tornou este artigo – e tudo o que o cerca – possível. Se você apreciar nosso conteúdo, por favor compre uma assinatura e torne possível o próximo conjunto de artigos.

25 de março de 2009

Este artigo foi contribuído por Valerie Aurora (antiga Henson)

No artigo da semana passada, eu revisei os casos de uso, conceitos básicos e problemas comuns de design de sistemas de arquivos unioning. Esta semana, descreverei vários exemplos de sistemas de arquivos unioning em detalhes técnicos. Os sistemas de ficheiros de união que vou cobrir neste artigo são os directórios de união do Plano 9, montagens de união BSD, montagens de união Linux. O próximo artigo irá cobrir unionfs, aufs, e possivelmente um ou dois outros sistemas de ficheiros unioning, e embrulhar a série.

Para cada sistema de ficheiros, irei descrever a sua arquitectura básica, funcionalidades e implementação. A discussão da implementação irá focar em particular nos whiteouts e na leitura de diretórios. Vou terminar com um olhar sobre os aspectos de engenharia de software de cada implementação; por exemplo, tamanho e complexidade do código, invasividade e carga nos desenvolvedores de sistemas de arquivos.

Antes de ler este artigo, você pode querer checar o artigo do AndreasGruenbacher que acabou de ser publicado no workshop de montagem de union mount em novembro passado. É um bom resumo das características dos sistemas de arquivos unioning que são mais urgentes para os desenvolvedores de distribuição. Desde a introdução: “Todos os casos de uso que estamos interessados em basicamente resumir-se à mesma coisa: ter uma imagem ou sistema de arquivos que é usado somente para leitura (ou porque não é gravável, ou porque escrever na imagem não é desejado), e fingir que esta imagem ou sistema de arquivos é gravável, armazenando as mudanças em outro lugar”

Diretórios do plano 9 do unioning

O Plan 9 operatingystem(browseablesource code aqui) implementa o unioning em seu próprio Plano 9 especial. Nos diretórios do Plan 9 union, apenas o espaço de directórios de nível superior é fundido, não qualquer subdiretório. Sem restrições pelas normas UNIX, os diretórios de união do Plano 9 não implementam whiteouts e nem mesmo exibem entradas duplicadas – se o mesmo nome de arquivo aparecer em dois sistemas de arquivos, ele é simplesmente retornado duas vezes nas listas de diretórios.

Um diretório de união do Plano 9 é criado assim:

 bind -a /home/val/bin/ /bin

Isso faria com que o diretório/home/val/binfosse montado “depois” (a opção-a)/bin; outras opções são colocar o novo diretório antes do diretório existente, ou substituir totalmente o diretório existente. (Isto parece-me estranho, pois gosto de comandos no meu pessoalbin/para ter precedência sobre os comandos de todo o sistema, mas este é o exemplo da documentação do Plano 9). Brian Kernighanexplica um dos usos dos diretórios sindicais: “Este mecanismo de diretórios sindicais substitui o caminho de busca das shells UNIX convencionais. Asfar como você está preocupado, todos os programas executáveis estão em /bin”. Os diretórios union podem teoricamente substituir muitos usos dos blocos de construção fundamentais doUNIX de links simbólicos e caminhos de busca.

Sem whiteouts ou eliminação de duplicatas, readdir() os diretórios onunion são triviais de implementar. O offset da entrada de diretório do sistema de arquivos subjacente corresponde diretamente aos inbytes do offset da entrada de diretório desde o início do diretório. Diretório de sessões é tratado como se o conteúdo das diretórios subjacentes fossem concatenados juntos.

Plan 9 implementa uma alternativa a readdir() digno de nota, dirread().dirread() retorna estruturas do tipo Dir,descritas na página stat()man. A parte importante do Dir é o membro Qid. A Qid é:

…uma estrutura contendo campos de caminho e vers: caminho é garantido para ser único entre todos os nomes de caminho atualmente no servidor de arquivos, e vers muda cada vez que o arquivo é modificado. O caminho é longo (64 bits, vlong)e os versos são longos (32 bits, ulong).

Então por que isso é interessante? Uma das razões readdir() é uma dor tão grande de implementar é que itra o membro d_off de struct dirent, asingle off_t (32 bits a menos que a aplicação seja compilada com suporte a arquivos grandes), para marcar a entrada de diretório onde a aplicação deve continuar lendo no próximo readdir()call. Isto funciona bem desde que d_off seja um simples byteoffset em um arquivo plano de menos de 232 bytes e os diretórios existentes nunca sejam movidos – não é o caso de muitos sistemas de arquivos modernos (XFS, btrfs, ext3 com índices htree). O96-bit Qid é um marcador de lugar muito mais útil do que o 32 ou 64-bit off_t. Para um bom resumo das questões envolvidas inimplicando readdir(),leia TheodoreY. O excelente post do Ts’o sobre o tópico na lista de discussão btrfs.

Do ponto de vista da engenharia de software, os diretórios do Plano 9 são muito úteis. Sem whiteouts, eliminação de entradas duplicadas, offsets complicados de diretórios ou fusão de namespaces além do diretório de nível superior, a implementação é simples e fácil de manter. No entanto, qualquer implementação prática de sistemas de arquivos unioning para Linux (ou qualquer outro UNIX) teria que resolver esses problemas. Para nossos propósitos, os diretórios de união do Plano 9 servem principalmente como inspiração.

BSD union mounts

BSD implementa duas formas de união: o"-o union"opção para o comandomount, que produz um diretório de união similar ao do Plano 9, e omount_unionfscomando, que implementa um sistema de arquivos de união mais completo com whiteouts e fusão de todo o namespace. Vamos focar neste último.

Para este artigo, nós usamos dois fontes para detalhes específicos de implementação: a implementação original de montagem do BSD union como descrito no paperUnionmounts do USENIX de 1995 no 4.4BSD-Lite , e o FreeBSD7.1 mount_unionfs página man e código fonte. OtherBSDs podem variar.

Um diretório pode ser montado em “abaixo” ou “acima” de um diretório existente ou montagem em união, desde que o ramo superior de uma união escrevível seja escrevível. Dois modos de whiteouts são suportados: ou awhiteout é sempre criado quando um diretório é removido, ou ele só é criado se outra entrada de diretório com esse nome existir atualmente no ramo abaixo do ramo escrevível. Três modos para definir a propriedade e modo de arquivos copiados são suportados. O mais simples é , no qual o novo arquivo mantém o mesmo dono e modo do original. O modo masquerade makescopied-up files de propriedade de um usuário em particular e suporta um conjunto de opções de montagem para determinar o novo modo de arquivo. O modo traditional define o dono do arquivo para o usuário que deseja o comando union mount, e define o modo de acordo com a máscara na hora do union mount.

Quando um diretório é aberto, um diretório com o mesmo nome é criado na camada superior gravável se ele ainda não existir. A partir do paper:

Ao criar diretórios de sombras agressivamente durante a pesquisa o sistema de arquivos union evita ter que verificar e possivelmente criar a cadeia de diretórios desde a raiz da montagem até o ponto de uma copy-up.Como o espaço em disco consumido por um diretório é insignificante, criar diretórios quando eles foram atravessados pela primeira vez pareceu uma melhor alternativa.

Como resultado, um "find /union" resultará na cópia de todos os diretórios (mas não de entradas de diretório apontando para não-diretórios) para a camada gravável. Para a maioria das imagens do sistema de ficheiros, isto irá usar uma quantidade insignificante de espaço (menos do que, por exemplo, o espaço reservado para o utilizador root, ou aquele ocupado por inodes não utilizados num sistema de ficheiros ao estilo FFS).

Um ficheiro é copiado até à camada superior quando é aberto com permissão de escrita ou os atributos do ficheiro são alterados. (Como os directórios são copiados quando são abertos, o directório que os contém é garantido que já existe na camada gravável). Se o arquivo a ser aberto tem múltiplos links rígidos, os outros links são ignorados e então o novo arquivo tem uma contagem de links de um. Isto pode quebrar aplicações que usam links rígidos e esperam modificações através de um nome de link para mostrar quando referenciado através de um link rígido diferente. Tais aplicações são relativamente incomuns, mas ninguém fez um estudo sistemático para ver quais aplicações falharão nesta situação.

Whiteouts são implementados com um tipo especial de entrada de diretório, DH_WHT. As entradas do diretório whiteout não se referem a nenhum inode real, mas para facilitar a compatibilidade com as facilidades do sistema de arquivos existentes, como fsck, cada entrada do diretório whiteout inclui um número de inode falso, o número WINO reservado do whiteoutinode. O sistema de arquivo subjacente deve ser modificado para suportar o tipo de entrada do diretório de whiteout. Novos diretórios que substituem a entrada awhiteout são marcados como opacos através de um novo atributo inode “opaco” que as buscas não viajam através deles (mais uma vez requerendo um suporte mínimo do sistema de arquivos subjacente).

Duplicate directory entries and whiteouts are handled in the userspacereaddir() implementation. Em opendir()time, a biblioteca C lê o diretório de uma só vez, remove duplicatas, aplica whiteouts e armazena os resultados.

BSD union mounts não tentam lidar com mudanças nos ramos abaixo do ramo superior gravável (embora eles sejam permitidos). O caminho rename() não é tratado.

Um exemplo da página man:mount_unionfs página man:

 The commands mount -t cd9660 -o ro /dev/cd0 /usr/src mount -t unionfs -o noatime /var/obj /usr/src mount the CD-ROM drive /dev/cd0 on /usr/src and then attaches /var/obj on top. For most purposes the effect of this is to make the source tree appear writable even though it is stored on a CD-ROM. The -o noatime option is useful to avoid unnecessary copying from the lower to the upper layer.

Outro exemplo (notando que eu acredito que o controle de fontes é melhor implementado fora do sistema de arquivos):

 The command mount -t unionfs -o noatime -o below /sys $HOME/sys attaches the system source tree below the sys directory in the user's home directory. This allows individual users to make private changes to the source, and build new kernels, without those changes becoming visible to other users.

Linux union mounts

Like BSD union mounts, Linux union mounts implementam o file systemunioning na camada VFS, com algum suporte menor de sistemas de arquivos subjacentes para whiteouts e tags de diretórios opacos. Várias versões destes patches existem, escritas e modificadas por Jan Blunck,Bharata B. Rao, e Miklos Szeredi, entre outros.

Uma versão deste código funde apenas os diretórios de nível superior, similar aos diretórios de união do Plano 9 e a opção BSD -o unionmount. Esta versão de montagens union, que eu chamo de diretórios union, é descrita com algum detalhe no arecent LWN article de Goldwyn Rodrigues e no recente poste de Miklos Szeredi de um conjunto de correções atualizado. Para o resto deste artigo, vamos focar em versões de monta union que fundem o espaço de nomes completo.

montagens union estão actualmente em desenvolvimento activo. Este artigo descreve a versão lançada por Jan Blunck contra Linux2.6.25-mm1, util-linux 2.13, e e2fsprogs 1.40.2. Os conjuntos de correções, série asquilt, podem ser baixados do site ftp do Jan:

Kernel patches: ftp://ftp.suse.com/pub/people/jblunck/patches/

Utilities: ftp://ftp.suse.com/pub/people/jblunck/union-mount/

Criei uma página web com links para versões git dos patches acima e alguma documentação no estilo HOWTO em http://valerieaurora.org/union.

Uma união é criada montando um sistema de arquivos com MS_UNION flagset. (Os MS_BEFORE, MS_AFTER,e MS_REPLACE são definidos na base de código mount mas não são usados atualmente). Se o flag MS_UNION for especificado, então o sistema de arquivo montado deve ser somente leitura ou whiteouts de suporte. Nesta versão de montagens union, a bandeira de montagem union é especificada pela opção “-o union” para mount. Por exemplo, para criar uma união de dois sistemas de arquivo loopbackdevice, /img/ro e /img/rw, você executaria:

 # mount -o loop,ro,union /img/ro /mnt/union/ # mount -o loop,union /img/rw /mnt/union/

Cada mount union cria umstruct union_mount:

 struct union_mount {atomic_t u_count;/* reference count */struct mutex u_mutex;struct list_head u_unions;/* list head for d_unions */struct hlist_node u_hash;/* list head for searching */struct hlist_node u_rhash;/* list head for reverse searching */struct path u_this;/* this is me */struct path u_next;/* this is what I overlay */ };

Como descrito emDocumentation/filesystems/union-mounts.txt, “Allunion_mount structures are cached in two hash tables, one for lookupsof the next lower layer of the union stack and one for reverse lookupsof the next upper layer of the union stack.”

Diretórios brancos e opacos são implementados da mesma forma que no BSD. O sistema de arquivo subjacente deve explicitamente suportar whiteouts definindo a operação .whiteout inode para diretórios (atualmente, whiteouts são implementados apenas para ext2, ext3 e tmpfs). As implementações ext2 e ext3 usam o tipo de entrada de diretório whiteout, DT_WHT, que tem sido definido em include/linux/fs.h por anos mas não usado fora do sistema de arquivo Coda até agora. Um número de inodenumber reservado, EXT3_WHT_INO, é definido mas ainda não usado; as entradas de whiteout atualmente alocam um inode normal. Um novo inodeflag, S_OPAQUE, é definido para marcar diretórios como opacos.Como no BSD, diretórios só são marcados opacos quando substituem a entrada awhiteout.

Arquivos são copiados quando o arquivo é aberto para escrita. Se necessário, cada directório no caminho para o ficheiro é copiado para o topbranch (copy-on-demand das directorias). Atualmente, copiar para cima só é suportado para arquivos e diretórios regulares.

readdir() é um dos pontos mais fracos da atual implementação. Ele é implementado da mesma forma que o BSD union mountreaddir(), mas no kernel. O campo d_off é definido para o offset dentro do diretório subjacente atual, menos os tamanhos dos diretórios anteriores. Entradas de diretórios de diretórios abaixo da camada superior devem ser verificadas em relação aos diretórios anteriores para duplicatas ou whiteouts. Como implementado atualmente,cada readdir() (tecnicamente, getdents())chamada de sistema lê todas as entradas de diretório anteriores no cache do kernel, então compara cada entrada a ser retornada com aquelas já existentes no cache antes de copiá-la para o buffer do usuário. O resultado é que readdir() é complexo, lento epotencialmente aloca uma grande quantidade de memória do kernel.

Uma solução é fazer a abordagem BSD e fazer o cache, o whiteout e o processamento duplicado no espaço do usuário. Bharata B. Raois designingsupport for union mount readdir() in glibc.(O padrão POSIX permite que readdir() seja implementado ao nível da libc se a chamada de sistema do kernel nu não preencher todos os requisitos). Isto moveria o uso de memória para a aplicação e tornaria o cache persistente. Outra solução seria tornar o cache no kernel persistente de alguma forma.

A minha sugestão é pegar uma técnica dos mounts de união BSD e extendit: copiar proativamente não apenas entradas de diretório para diretórios, mas todas as entradas de diretório de sistemas de arquivos inferiores, processduplicações e whiteouts, tornar o diretório opaco, e gravá-lo no disco. Com efeito, você está processando as entradas de diretório para whiteouts e duplicatas na primeira abertura do diretório, e então escrevendo o “cache” resultante das entradas de diretório para o disco. As entradas de directório que apontam para ficheiros nos sistemas de ficheiros subjacentes precisam de significar de alguma forma que são entradas “falhadas” (ooposite de um whiteout – pede explicitamente para procurar um objecto num sistema de ficheiros inferior). Um efeito colateral desta abordagem é que não são mais necessários whiteouts.

Um problema que precisa ser resolvido com esta abordagem é como as entradas de diretório que apontam para sistemas de arquivos inferiores são rasgadas. Uma série de soluções se apresentam: a entrada poderia apontar para um número de inode reservado, o sistema de arquivos poderia alocar um inode para cada entrada, mas marcá-lo com um novo atributo S_LOOKOVERTHERE inode,poderia criar um link simbólico para um alvo reservado, etc. Esta abordagem usaria mais espaço no sistema de arquivos suprajacente, mas todas as outras abordagens requerem alocação do mesmo espaço na memória, e geralmente a memória é mais cara que o disco.

Um problema menos urgente com a implementação atual é que os inodenumbers não são estáveis através do boot (veja o artigo anterior sobre sistemas de arquivos unioning para detalhes sobre porque isto é um problema). Outra opção é armazenar um mapa inode apersistente em algum lugar – em um arquivo no diretório de nível superior, ou em um sistema de arquivos externo, talvez.

Ligações difíceis são manipuladas – ou, mais precisamente, não manipuladas – da mesma forma que as montagens BSD union. Novamente, não está claro quantas aplicações dependem da modificação de um arquivo através de um caminho vinculado e da visualização das mudanças através de outro caminho vinculado (ao contrário do symboliclink). O único método que eu posso encontrar para lidar corretamente com isso é manter um cache persistente em algum lugar no disco dos inodes que encontramos com múltiplos links rígidos.

Aqui está um exemplo de como isso funcionaria: Digamos que começamos uma cópia no forinode 42 e descobrimos que tem uma contagem de links de três. Nós criaríamos uma entrada para o banco de dados de links rígidos que inclui o id do sistema de arquivos, o número do inode, a contagem de links e o número do inode da nova cópia no sistema de arquivos de nível superior. Poderia ser armazenado num ficheiro em formato CSV, ou como um link simbólico num directório reservado no directório raiz (por exemplo, “/.hardlink_hack/<fs_id>/42“, que é um link para “<new_inode_num> 3“), ou numa base de dados real. Cada vez que abrirmos um inode num sistema de ficheiros subjacente, introduzimo-lo na nossa base de dados de links rígidos; se existir uma entrada, descriminamos a contagem dos links e criamos um link rígido para o inode correcto no novo sistema de ficheiros. Quando todos os caminhos são encontrados, a contagem de links cai para zero e a entrada pode ser apagada da base de dados. O bom desta abordagem é que a quantidade de sobrecarga é limitada e desaparece completamente quando todos os caminhos para os inodes relevantes tiverem sido ignorados. Entretanto, isso ainda introduz uma quantidade significativa de complexidadepossivelmente desnecessária; a implementação do BSD mostra que muitas aplicações serão executadas com um comportamento de hardlink não-quite-POSIXLY-correto.

Currentemente, rename() de diretórios através de branchesreturns EXDEV, o erro para tentar renomear um arquivo através de diferentes sistemas de arquivos. O espaço do usuário normalmente lida com isso de forma transparente (já que ele já tem que lidar com esse caso para diretórios de diferentes sistemas de arquivos) e cai de volta para copiar os conteúdos do diretório um por um. A implementação de rename() de diretórios entre ramos no kernel não é uma idéia brilhante pelas mesmas razões que renomeia sistemas de arquivos regulares; provavelmente retornando EXDEV é a melhor solução.

Do ponto de vista da engenharia de software, montagens union parecem ser um compromisso de áreas entre características e facilidade de manutenção. Mostof as alterações VFS são isoladas em fs/union.c, um arquivo de cerca de 1000 linhas. Cerca de 1/3 deste arquivo é a implementação do kernel readdir(), que será quase certamente substituída por outra coisa antes de qualquer possível fusão. As alterações nos sistemas de arquivos subjacentes são mínimas e necessárias apenas para sistemas de arquivos montados como ramos escrevíveis. O mainobstacle para fundir este código é o readdir()implementação. Caso contrário, os mantenedores de sistemas de arquivos têm sido notavelmente mais positivos sobre montagens union do que qualquer outra implementação unioning.

Um bom resumo das montagens union pode ser encontrado no Bharata B. Os slides de montagem union do Rao para FOSS.IN .

Coming next

No próximo artigo, vamos rever unionfs e aufs, e comparar as várias implementações de sistemas de arquivos unioning para Linux. Fique atento!

Entradas de índice para este artigo
Kernel Filesystems/Union
Kernel Union mounts
GuestArticles Aurora (Henson), Valerie

Deixe uma resposta

O seu endereço de email não será publicado.