A VFS
Uma VFS (Virtual Filesystem, às vezes Virtual Filesystem Switch) é a camada que permite a um kernel expor uma interface de arquivo única e uniforme (open, read, write, travessia de diretório) independentemente de qual formato de sistema de arquivos de fato sustenta um determinado arquivo. Sem ela, todo trecho de código que toca arquivos precisaria de lógica separada para todo tipo de sistema de arquivos que pudesse encontrar; com ela, essa lógica é escrita uma vez, contra as próprias abstrações da VFS, e cada implementação de sistema de arquivos é responsável apenas por traduzir entre seu próprio formato em disco e essas abstrações.
Por que uma interface uniforme importa
Seção intitulada “Por que uma interface uniforme importa”Um kernel que suporta mais de um formato de sistema de arquivos (FAT e ext2, digamos) enfrenta uma escolha: ou todo trecho de código que abre ou lê um arquivo precisa saber com qual formato de sistema de arquivos está lidando e ramificar de acordo, ou uma interface comum absorve essa diferença uma vez, na fronteira entre código genérico de tratamento de arquivo e código específico de sistema de arquivos. A VFS é essa fronteira. Código acima dela, um programa chamando open, ou o próprio código do kernel carregando um executável, só interage com objetos da VFS; código abaixo dela, uma implementação por formato de sistema de arquivos suportado, é responsável por popular esses objetos corretamente a partir do que quer que o formato subjacente de fato armazene.
Abstrações centrais
Seção intitulada “Abstrações centrais”Quatro objetos, espelhando de perto o design tradicional de VFS do Unix, aparecem em praticamente toda implementação desse padrão, cada um representando um nível diferente da hierarquia de sistema de arquivos.
Um superblock representa uma instância inteira de sistema de arquivos montado: existe um por volume montado, contendo informação de todo o sistema de arquivos (tamanho total, tamanho de bloco, um ponteiro para o inode do diretório raiz) e um conjunto de operações específicas daquele tipo de sistema de arquivos para tarefas como alocar um novo inode ou escrever metadados de todo o sistema de arquivos de volta ao disco.
Um inode representa os metadados de um único arquivo ou diretório (tamanho, permissões, timestamps, e, criticamente, onde seus dados reais vivem no armazenamento subjacente), independentemente de como aquele arquivo está atualmente nomeado ou onde atualmente está na hierarquia de diretórios, já que o mesmo arquivo subjacente pode ser alcançável através de mais de uma entrada de diretório (um hard link) ou através de nenhuma (um arquivo mantido aberto depois de ter o link removido).
Uma dentry (entrada de diretório) representa um mapeamento de nome para inode dentro de um diretório específico (o fato de que o nome "passwd" dentro de um diretório particular atualmente se refere a um inode particular), e é o que a travessia de diretório de fato percorre, já que resolver um caminho como /etc/passwd significa seguir uma cadeia de dentries, cada uma resolvendo um componente do caminho para o inode (e, se esse inode for ele mesmo um diretório, as dentries seguintes) necessário para continuar.
Um objeto file (às vezes chamado de estrutura de kernel subjacente a um descritor de arquivo, distinta do pequeno inteiro que um processo enxerga) representa uma instância aberta de um arquivo, rastreando um offset atual de leitura/escrita e modo de acesso, separado do próprio inode, já que dois processos podem ter o mesmo arquivo aberto simultaneamente com offsets inteiramente independentes nele.
Despachando para o sistema de arquivos certo
Seção intitulada “Despachando para o sistema de arquivos certo”Cada um desses objetos carrega um conjunto de ponteiros de função (uma tabela de operações) populada por qualquer implementação de sistema de arquivos que seja dona do inode ou superblock subjacente, e código genérico de VFS chama através desses ponteiros, em vez de conter lógica específica de sistema de arquivos por conta própria.
struct inode_operations { int (*read)(struct inode *inode, void *buf, size_t count, off_t offset); int (*write)(struct inode *inode, const void *buf, size_t count, off_t offset); struct inode *(*lookup)(struct inode *dir, const char *name);};
// código genérico de VFS, sem conhecimento algum de a qual sistema de arquivos "inode" pertenceint vfs_read(struct inode *inode, void *buf, size_t count, off_t offset) { return inode->ops->read(inode, buf, count, offset);}O read de uma implementação FAT percorre a cadeia de cluster da FAT descrita no estilo tabela de alocação; o read de uma implementação ext2, em vez disso, percorre uma árvore de ponteiros de bloco. É lógica inteiramente diferente, invisível para qualquer coisa que chame vfs_read, que só enxerga um ponteiro de função que invoca uniformemente, independentemente de qual implementação de fato está por trás dele.
Montagem
Seção intitulada “Montagem”Montar é a operação que anexa o diretório raiz de um sistema de arquivos em um ponto específico dentro da hierarquia de diretório de outro sistema de arquivos já montado: o mecanismo que permite que uma única árvore de diretório unificada abranja múltiplos sistemas de arquivos subjacentes e até múltiplos dispositivos de armazenamento subjacentes, sem que um programa navegando nela precise saber onde um sistema de arquivos termina e outro começa. No nível da VFS, montar associa um superblock (criado inicializando o driver específico de sistema de arquivos para o armazenamento sendo montado) a uma dentry específica na hierarquia já montada, de modo que a resolução de caminho cruza transparentemente para as próprias estruturas de dentry e inode do sistema de arquivos recém-montado, uma vez que alcança esse ponto.
Notas de implementação
Seção intitulada “Notas de implementação”Uma implementação mínima de VFS para um kernel hobby não precisa de toda abstração descrita acima desde o início: um kernel que suporta exatamente um formato de sistema de arquivos, sem intenção de suportar um segundo, ganha comparativamente pouco com uma camada completa de indireção de VFS e pode razoavelmente chamar funções específicas de sistema de arquivos diretamente. O padrão VFS justifica sua complexidade especificamente quando um segundo formato de sistema de arquivos precisa ser suportado, momento em que adaptar retroativamente a abstração a código que foi escrito presumindo apenas um formato tende a ser consideravelmente mais trabalho do que projetar em torno da indireção desde o início, mesmo antes de um segundo sistema de arquivos de fato existir. É um caso onde antecipar um requisito futuro quase certo é mais defensável do que generalização prematura costuma ser.
Referências
Seção intitulada “Referências”- ^ S. R. Kleiman, “Vnodes: An Architecture for Multiple File System Types in Sun UNIX,” USENIX Summer 1986 (o artigo original descrevendo a arquitetura vnode/VFS da qual esse padrão deriva).
Ver também
Seção intitulada “Ver também”- FAT: uma implementação concreta de sistema de arquivos que ficaria atrás dessa camada.
- Physical Memory: o alocador de frames do qual buffers de disco, em última instância, tiram memória.
- Buffer Cache: a camada logo abaixo desta, guardando blocos de disco lidos ou escritos recentemente em memória.
- Journaling: uma técnica que muda a recuperação de falhas sem mudar nada que os chamadores desta camada veem.
- ISO 9660: uma terceira implementação de sistema de arquivos, somente leitura, atrás das mesmas abstrações, estruturada de forma muito diferente de ext2 ou FAT.
- Tabelas de Partição, MBR e GPT: o que localiza um volume específico antes de essa camada sequer vê-lo.
- NVMe: um driver em nível de bloco através de cujos pares de fila os buffers de disco desta camada eventualmente fluem.
- initrd: a raiz temporária da qual a abstração de montagem desta camada se afasta via pivot uma vez que o sistema de arquivos real está disponível.