Sistemas de Arquivos
Noções fundamentais:
- Arquivo contém dados;
- Sistema de Arquivos permite organização desses arquivos;
- Interface:
- Chamadas de sistemas e utilitários que permitem manipulação de
ficheiros.
- A interface tem sido estável, mas a implementação evoluiu muito:
- Múltiplos sistemas de arquivos (S5FS, UFS,
EXT2FS, FAT, LOGFS,...);
- Sistemas distribuídos
(NFS,AFS,CODA,SMBFS,...)
Arquivos contém dados:
- Arquivos são uma sequência ordenada de bytes. Estrutura é
problema da aplicação.
- Ficheiros são organizados hierárquicamente com
directórios sendo os nós da árvore.
- Nome de ficheiros podem conter qualquer caracter excepto "/" e
o caracter nulo.
- Processo tem cwd.
- Pathname indica como aceder a um arquivo.
- caminhos absolutos são desde a raíz;
- caminhos relativos são desde o cwd.
- Entrada num directorio é um hard link. Todos os hard
links são equivalentes.
- Sistemas diferentes têm estutura de directório diferente.
- Unix moderno fornece estrutura dirent e conjunto de
operações:
- opendir() abre directório;
- readdir() lê elemento;
- rewinddir() volta ao princípio;
- closedir() fecha directório.
- Tipo: normal, directório ou especial.
- Número de hard links.
- Tamanho.
- DeviceID e Nó-i
- UID e GID do dono
- Timestamps: último acesso, última modificação, últ. mod. dos
atributos.
- Permissões: leitura, escrita, acesso. Alt: ACLs.
- Flags: suid, sgid, sticky.
sgid é usada para locking em arquivos não executáveis. SVR4
usa sgid para dizer de onde se herda o GID (criador (SYSV)
ou dir (BSD). sticky é usada para impedir remoções de
ficheiros.
- syscalls: link(), unlink(), utimes(),
chown() e chmod().
- dup() e dup2() duplicam fd.
- Processos podem passar fd para outros processos (referência a
objecto):
- SVR4 usa streams,
- BSD usa sockets com sendmsg().
Usado por connection servers.
Acesso pode ser sequencial ou "random":
- Kernel mantém um offset, inicialmente 0.
- lseek() permite saltos (acesso "random").
- read() e write() são semelhantes:
nread = read(fd, buf, count);
lê no máximo count caracteres e copia-os para buf.
- Operações de I/O são atómicas entre elas.
- O_APPEND permite abrir em mode append.
- Solaris fornece pread() e pwrite() (Linux tb).
- readv() e writev() implementam
scatter-gather I/O:
- I/O sobre vector de buffers;
- Útil por ex. para construir/ler pacotes;
- Diminui número de syscalls.
- Locking pode ser mandatório ou "advisory":
- BSD inclui flock(), com locks partilhados e exclusivos,
mas só advisory;
- SVR2 suporta advisory para files e records;
- SVR3 adiciona locking mandatório, via chmod();
- SVR4 adicona BSD
através de fcntl() (F_GETLK, F_SETLK e F_SETLKW e de lockf().
- Sistemas de ficheiros têm uma hierarquia. mount()
coloca um sistema de ficheiros sobre outro e esconde o que estava
antes.
- O que existia antes desaparece.
- Disco lógico abstrai armazenamento: disco, partição. Permite também
suportar espelhos de discos, striping, e RAID:
- newfs ou mkfs constroem um novo disco;
- É possível fazer mapeamentos complexos:
- Espelhos mantêm cópias de dados;
- Striping distribuem por vários discos;
- RAID combina os espelhos e striping em vários discos;
- Volumes permitem trabalhar com vários discos como se único
disco.
fstat() permite verificar o tipo de um ficheiro:
- Arquivos Normais.
- Links simbólicos: evitam alguns problemas de hard links como links
para directórios e problemas de proteção.
- Consistem de um pathname que pode estar no próprio nó-i
ou num bloco especial.
- Pathname pode ser absoluto ou relativo.
- pipes e FIFOs:
- geralmente bloqueiam;
- fnctl(fd,O_NDELAY) faz com que não bloqueiem.
- devices que podem ser:
Torna-se necessário suportar vários sistemas de ficheiros
(s5fs, ufs, fat, NFS, ...).
Ideia:
- Fazer como com devices I/O:
- Programação por objectos!
Nó-v representa um arquivo aberto:
Vfs representa um sistema de arquivos aberto:
- Abstracção;
- Operações diferentes: mount(), umount();
- Conceitos básicos:
- Mount point;
- Lista de sistemas de arquivos montados.
Ideia é poder usar a interface em sistemas de arquivos muito diferentes:
- Cada operação deve poder ser realizada em função do processo
corrente, que pode adormecer se a função bloqueia.
- Locks para serialização devem ser libertados antes de operação
completar.
- A interface deve ser stateless (NFS), evitando
variáveis globais e campos na u_area para passar info.
entre operações.
- a interface deve ser reentrante: substituir variáveis
globais (u_error e u_rvall) por retorno de
funções.
- Implementações devem poder usar recursos globais como a cache de
buffers.
- Interface deve ser usável por um servidor.
- Evitar tabelas de tamanhos fixo.
Processo pode aceder um nó-v ou via:
- via file descriptor;
- via lookup do nome.
Alocação de FD:
- Originalmente, array estático;
- Alocação dinâmicamente, eg, como uma lista ligada de blocos de
32 entradas.
- kmem_realloc()
Cada nó-v tem as seguintes estruturas:
- v_flag: raiz de FS, ....
- v_count: número de referências (ficheiros abertos,
cdw, mount points, lookup). Importante para arquivos temporários;
- v_fsnountedwhere: para ponto de montagem;
- v_op: operações;
- v_fsp: file system;
- v_stream: stream associado;
- v_page: páginas residentes;
- v_type: tipo de arquivo;
- v_rdev: device ID;
- v_data: dados privados
Campos:
- vfs_next: VFS seguinte na lista.
- vfs_op: vector de operações.
- vfs_vnodecovered: nó onde vfs está montado.
- vfs_dev e vfs_vfstype: ID do dispositivo e
index para tipo de file system.
- vfs_data: dados privados ao FS.
- Dados privados:
- opacos;
- alocados juntamente com parte
independente.
- Operações da interface no nó-v:
vop_open() | vop_close() | |
vop_read() | vop_write() | vop_ioctl() |
vop_getattr() | vop_setattr() | vop_access() |
vop_lookup() | vop_create() | vop_remove() |
vop_link() | vop_rename() | |
vop_mkdir() | vop_rmdir() | vop_readdir() |
vop_symlink() | vop_readlink() | vop_inactive() |
vop_rwlock() | vop_rwunlock() | vop_realvp() |
vop_getpage() | vop_putpage() |
vop_map() | vop_poll() |
|
Operações da interface no vfs:
vfs_mount() | vfs_umount() | |
vfs_root() | vfs_statvfs() | vfs_sync() |
|
SVR4 usa vfssw[], um switch global com as características de
cada FS:
- mount() primeiro obtem o vnode do ponto de montagem com
lookuppn(): nó-v tem que ser
directório e nenhum outro FS pode estar montado nele;
- Procura entrada em vfssw[], dado tipo de FS;
- Chama vsw_init(), específico ao FS;
- Aloca novo vfs;
- Inclui vfs na lista comandada por rootvfs.
- vfs_op para vfsops de vfssw[];
- Instala vfs_vnodecovered para nó-v do mount
point;
- vfs_vfsmountedwhere do nó-v aponta para vfs;
- chama VFS_MOUNT();
Cada FS tem que implementar VFS_MOUNT() à sua maneira:
- Verificar permissões;
- Alocar e inicializar objeto privado;
- colocar um ptr. para ele em vfs -> vfs_data;
- aceder ao directório raiz do FS e inicializar seu nó-v.
FS locais usam superbloco, FS distribuidos chamam o servidor.
lookuppn() recebe um nome e retorna um ptr para nó-v:
- se não for último componente, usa v_type para saber se
nó-v inicial é directório.
- Se componente é .. e cwd raíz, apanhe o componente
seguinte.
- se componente é .. e cwd VROOT, acesse
v_vfsp -> vfs_vnodecovered.
- Chame VOP_LOOKUP() no directório corrente: retorna
ptr. para nó-v do arquivo e obtém um hold.
- se componente não fôr encontrado:
- se fôr último, retorne sucesso, passando ptr para o pai e
mantendo hold;
- senão ENOENT.
- se v_vfsmountedhere != NULL encontre o vfs
correspondente e chame vfs_root() para encontrar o
nó-v raiz.
- Se v_type == VLNK, traduza com
VOP_SYMLINK(), junte a tradução e reinicialize (se caminho
absoluto, comece da raíz):
- arg. de lookuppn() pode suprimir avaliação de links
simbólicos no último componente (lstat);
- MAXSYMLINKS limita o número de links simbólicos numa
travessia.
- Liberta directório (segurado ou por VOP_LOOKUP ou por
inicialização.
- Volte ao principio e procure novo componente.
- Se procurou todos, mantenha o hold e devolva um ptr para o nó-v.
- Cache LRU contendo nó-v de directório, nome de arquivo no
directório, e ptr para nó-v do arquivo:
- VOP_LOOKUP procura lá primeiro:
- se encontrar, incrementa ctr. de refs;
- senão, procura dir. e adiciona entrada na cache.
- Arquivo pode ser removido e nó-v usado para outro arquivo:
- Em SVR4 cache tem ref. para o nó-v;
- não podemos libertar o nó-v, também impede uso exclusivo por
outras rotinas;
- Em 4.3BSD cada nó-i tem uma capacidade, que é
incrementado sempre que o nó-i é entregue a um ficheiro novo. A
cache também tem uma capacidade, que é comparada com a do nó-i em
acesso.
erro = VOP_LOOKUP(vp, compname, &tvp, ...,
tvp tem o resultado:
- Primeiro procura na cache, se encontrar retorna nó-v e
incrementa referências.
- Se não encontrar itera no directório pai até encontrar o nome
(se local), ou envia pedido a servidor (remoto).
- Verifica se nó-v correspondente está em memória (tabela de hash).
- Se não estiver aloca o nó-v.
- aloca um fd.
- aloca um objecto ficheiro.
- chama lookuppn() para encontrar o nó-v.
- Chama VOP_ACCESS para verificar permissões.
- Verifica se operação é ilegal (abrir um directório ou executável
activo para escrita).
- Se O_CREAT e ficheiro não existe, chama
VOP_CREATE() no directório pai, senão ENOENT.
- Chama VOP_OPEN, que geralmente não faz nada.
- Se O_TRUNC, chama VOP_SETATTR para colocar
tamanho a 0.
- Inicializa o objecto ficheiro.
- Retorna o índice do fd.
SVR4 e Solaris:
- kernel locka o nó-v antes de fazer leitura ou escrita: garante
sequencialidade;
- Atributos são implementados por estrutura vattr,
baseada no s5fs.
- Credenciais são passadas por referência a um objecto de
u_area ou proc.
- Vantagens: portátil, genérico.
- Desvantagens:
Optimizações:
- Usa modelo stateful;
- namei() chama lookup() que pode passar vários
componentes, sem atravessar mount-point;
- argumentos de namei() estão em nameidata. Se
razão é crear ou remover, obtém lock;
- abortop() desiste do lock;
- Protocolo é implementado na componente dependente: NFS não
precisa de obter lock;
- Problema: serializa operações no directório;
- Cada processo mantém uma cadge do dir. e offset do último name
lookup.
- Objectivos:
- evitar operações redundates;
- manter staleness;
- funcionar com SMP e UP.
- Informação de estado é passado com hint, associado a,
- timestamps, para verificar se o directório não foi
alterado.
- mutex protege metadados de arquivos.
- dentry corresponde a entrada no directório de cache;
- inode corresponde a nó-v;
- vfs tem uma noção de superbloco:
struct file_system_type {
const char *name;
int fs_flags;
struct super_block *(*read_super)
(struct super_block *, void *, int);
struct module *owner;
/* For kernel mount, if it's FS_SINGLE*/
struct vfsmount *kern_mnt;
struct file_system_type * next;
};
- name eg, "ext2";
- fs_flags: FS_REQUIRES_DEV,
FS_NO_DCACHE, ...
- read_super: método a chamar quando montamos nova
instância;
- next: lista de mounts.
- read_super recebe um superbloco e opções de
mount.
- Estrutura super_block inclui:
- info. sobre lista de sbs;
- tamanho de blocos;
- lock;
- flag dirty;
- lista de operações;
- tipo;
- quota;
- pointer para dentry da raíz;
- wait_queue;
- device.
- Union para info específica.
- read_inode: lê nó-i do FS;
- write_inode: escreve nó-i no FS;
- put_inode: chamado qdo nó-i é removido da cache;
- delete_inode: chamado para remover nó-i;
- notify_change: chamado qdo atributos do nó-i são
alterados (senão write_inode()) (BGL);
- put_super: umount chama VFS que quer libertar
superbloco (superblock lock);
- write_super: VFS precisa de escrever;
- statfs: obter estatísticas do FS (BGL);
- remount_fs: chamado por remount (BGL);
- clear_inode: chamado para libertar nó-i;
- umount_begin: chamado no princípio de umount.
create() | lookup() | |
link() | unlink() | symlink() |
mkdir() | rmdir() | mknod() |
rename() | readlink() | |
readpage() | writepage() | bmap() |
truncate() | permission() | smap() |
updatepage() | revalidate() | |
|
- Chamados sem locks;
- recebem dentries;
- create() só é preciso para arquivos regulares, recebe
dentry não instanciado;
- d_instantiate() é usado para criar uma nova dentry;
- lookup() encontra nó-i em parente e chama
d_add() para adicionar nó-i numa dentry;
- lookup() segura o semáforo do directório pai.
llseek() | read() | write() |
readdir() | poll() | ioctl() |
mmap() | open() | release() |
fsync() | fasync() | check_media_change() |
revalidate() | lock() | |
|
- poll() é chamado por select() e
poll();
- open() cria um novo struct file e inicializa
f_op com defaults;
- qdo open() abre device chama rotinas no kernel que
substituem as rotinas do FS com as do device driver;
- fasync() é chamado para fsync() em modo
assíncrono;
Manipulação de dentries:
- dget() abre nova handle, incremente uso;
- dput() fecha uso: decrementa e se chegar a 0
d_delete();
- d_drop(): remove dentry da lista de hash do seu pai;
- d_delete() remover dentry, se última ref. passa a ser
negativa (d_iput());
- d_add() junta à lista dos seus pais e chama
d_instantiate();
- d_instantiate() adiciona à lista de hash do nó-i,
inicia (incrementa) i_count. Habitualmente, chamada qdo
nó-i é criado.
- d_revalidate(): é chamada para revalidar, habitualmente
NULL;
- d_hash() adiciona dentry na tabela de hash;
- d_compare(): compara duas dentries;
- d_delete() qdo a última ref. é removida;
- d_release() qdo dentry for dealocada;
- d_iput() chamado qdo entry perder o seu nó-i.
- Comece por include/linux/fs.h;
- Depois veja include/linux/dcache.h e fs/
- Directório: lista linear de registos com 16 bytes. Primeiros
2 contêm nó-i, e os outros 14 o nome do ficheiro. As primeiras
entradas são sempre . e ... Entrada a 0
não existe.
- nó-i:
Campo | Tamanho | Descrição |
di_mode | 2 | tipo e permissões |
di_nlinks | 2 | núm de hard links |
di_uid | 2 | UID do dono |
di_gid | 2 | GID do dono |
di_size | 4 | tamanho (B) |
di_addr | 39 | endereços de blocos |
di_gen | 1 | núm de geração |
di_atime | 1 | último acesso |
di_mtime | 1 | última modificação |
di_ctime | 1 | última mudança no nó-i |
|
- di_mode: suid, sgid, sticky, owner, group, others.
- Primeiros 10 campos são directos e existe um campo indirecto,
outro duplamente indirecto, e outro triplamente indirecto:
- Podem existir buracos, o que causa problema para tar e
cpio.
Inclui:
- Tamanho em blocos do sistema de ficheiros.
- Tamanho da lista de nós-i.
- Núm. de blocos e nós-i livres.
- Lista parcial de nós-i livres: se acabar kernel procura nos
nó-i.
- Lista de blocos livres que pode cobrir vários blocos. Kernel
recupera blocos quando núm. de blocos livres diminui.
Nó-i em memória inclui:
- nó-v associado;
- Device ID da partição;
- Número do nó-i no FS;
- Flags para sincronização e gestão de cache;
- Ptr. para uma lista de nós-i livres;
- Ptr. para uma lista fila de hash;
- Núm. do último bloco lido.
- lookuppn() usa VOP_LOOKUP() para encontrar
componente que chama s5lookup().
- s5lookup() primeiro procura na cache;
- Senão, anda no directório;
- se encontrar obtém o número do nó-i e chama iget() para
o encontrar;
- se nó-i na tabela de hash, tudo bem; senão
- aloca nó-i e inicializa lendo do disco;
- aloca e inicializa nó-v;
- retorna um ptr. para nó para lookuppn().
Apenas iget() aloca e inicializa nós-i.
Recebemos FD, user address e count:
- Código independente obtém struct file e verifica modos;
- Chama VOP_RWLOCK() para consistência;
- VOP_READ() chama s5read();
- s5read() traduz offset para bloco e lê uma página
de cada vez:
- mapeia bloco na VM do Kernel,
- chama uiomove() para copiar para espaço de usuário,
- uiomove() chama copyout() que gera PF se não
está mapeada,
- Fault handler chama VOP_GETPAGE().
- s5getpage() chama bmap() para converter núm.
de página e procura nó-v para ver se pág. em memória, senão, aloca
pág. livre e chama disk driver.
- Quando I/O completa processo continua em copyout(), que
deve verificar end. antes de copiar;
- s5read() regressa qdo dados tiverem sido copiados, ou
em erro.
write() é semelhante mas:
- discos modificados continuam em memória;
- write() pode obrigar a alocar mais blocos;
- write() pode alterar bloco.
- Quando o núm. de refs vai a 0 FS chama
VOP_INACTIVE() que liberta o nó-i;
- Sistemas modernos mantêm o nó-i na lista livre, mas não o
invalidam: iget() pode reusar;
- Tamanho da tabela de nós-i limita o número de nós-i activos:
SVR4 usa LRU para limpar:
- nós-i podem ter páginas, não é boa ideia removê-los;
- Ideia: colocar o nó-i no fim da lista se não tiver páginas.
- Possível usar um alocador de memória para aumentar núm. de
nós-i:
- se primeiro nó na lista livre ainda tem págs, coloca-o no fim
da fila e aloca mais um nó-i.
- A maior vantagem é simplicidade.
- Sistema depende muito do superbloco.
- Desempenho pode ser mau porque nós-i estão longe dos dados
correspondentes.
- Alocação de blocos torna-se rápidamente aleatória.
- Tamanho de blocos demasiado rígido: 512B originalmente.
- Limites na funcionalidade: tamanho máximo do ficheiro.
- Discos dividos em grupos de cilindros.
- Superbloco por grupo:
- 2 partes: informação sobre o FS e sobre o grupo;
- Colocados em offset variável do princípio do supergrupo.
- Blocos são divididos em blocos (8KB) e fragmentos (1KB ou
512B).
- Maior throughput;
- Dupla indirecção consegue 4GB;
- Apenas o último bloco pode ter fragmentos, o que pode obrigar
a cópia:
- Se o último bloco ocupar um fragmento;
- Outros arquivos tiverem outros fragmentos;
- Temos que copiar para outro bloco se arquivo crescer.
- Politica de alocação favorece grupos de cilindros:
- Arquivos do mesmo dir. no mesmo grupo;
- Novos directórios em grupos diferentes;
- Dados no mesmo grupo que nó-i;
- Mudar de frupo qdo tamanho atinge 48KB e depois 1MB;
- Tentar optimização alocação de blocos consecutivos.
- Extensões: nomes longos e links simbólicos.
- Nó-i;
- tamanho de alocação;
- tamanho do nome;
- nome.
- Ganhos substanciais em desempenho (VAX/750):
- read throughpout de 29KB/s para 221 KB/s;
- write througput de 48KB/s para 141KB/s.
- Fragmentos evitam overhead de espaço;
- Menos blocos indirectos;
- Necessário espaço livre (FFS com 4KB/1KB perto de s5 com 1KB);
- Discos modernos (SCSI e IDE) fazem gestão de cilindros.
- Melhorias possíveis:
- Escritas múltiplas;
- Pré-alocação de blocos para arquivos que estão a crescer.
ext2fs:
- Derivado de MINIXFS e de EXTFS;
- Estrutura semelhante a FFS;
- Usa bitmaps para nós-i e blocos livres;
- Suporta atributos para arquivos e directórios:
- secure deletion,
- immutable,
- append-only;
- Grupos de blocos não dependem de layout físico;
- Pré-aloca 8 blocos adjacentes quando aloca um bloco;
- Suporta readahead;
Originalmente usada para evitar acesso a disco:
- Data buffers guardam blocos;
- Cerca de 10% de memória total;
- Backing Store: disco;
- Write-back: evita problemas de performance;
- Informação sobre directórios e nós-i é write-through;
- Unificada com VM em sistemas recentes.
- Processo procura em tabelas de hash baseadas no device e
no número de bloco:
- miss: kernel aloca novo buffer e lê novos blocos do disco;
- write: kernel coloca dirty;
- Interrupt handler pode mexer no buffer: interrupts devem ser
desactivados para mexer em bloco.
- Lista de blocos livres é LRU:
- buffers inválidos (arquivo removido, erro) podem ser colocados
na frente da fila;
- dirty buffers que chegam na frente são colocados na write
queue do driver e depois enviados para a frente.
- Buffer Header:
- identifica e localiza buffer;
- sincroniza acesso (lock);
- gestão da cache;
- interface ao disco;
- Vantagens
- Reduz tráfeho (90%);
- Interface com o disk driver (eg, alinhamento)
- Problemas:
- Write-back tem problemas com falhas;
- Dados são copiados 2 vezes: problemas para acessos sequenciais
a arquivos grandes devido a cache wiping;
- Vxfs permite fornecer hints sobre arquivos.
Problema se houver crash:
- Writes de dados não são grande problema: não comprometem
consistência;
- sync() força writes;
- fsflush() executa sync() de 30 em 30 sec (ver
fs/buffer.c);
- Perda de metadados pode tornar o sistema inconsistente:
- sinronismo: garantir ordem de escrita de metadados (nós-i
antes de alteração de dir);
- fsck()
- Desempenho:
- layout de FFS ou EXT2 não usa banda total do disco:
- Atraso rotacional,
- Escrever uma pista de cada vez,
- Predominância de writes (2 para 1),
- Seeks devido a time-sharing;
- Recuperação de crash:
- não é garantida;
- Segurança:
- Permissões Unix (ACL seria melhor?)
- Tamanho:
- Arquivos têm que caber em partição, existem limites arbitrários.
Escrever tudo num arquivo append-only, o log:
- O que colocar? Todas as modificações ou metadados?
- Colocar operações (alteração do bitmap) ou os resultados
(blocos)?
- Suplementar (log-enhanced) ou substituir
(log-structured)?
- Redo-only guarda apenas os dados modificados, simplifica
recuperação mas coloca constraints em como colocar,
redo-undo guarda valor novo e antigo, maior, recuperação mais
complexa, mas > concorrência. Qual o melhor?
- Garbage Collection para log-finito?
- Commit em grupo abre janela de vulnerabilidade?
- Como retirar dados do log em log-structured?
- Única estrutura é o log;
- Writes são na cauda do log, são sequenciais, elimina seeks;
- Cada transferência são muitos dados, aproveita a banda;
- Recuperacão é muito rápida;
- Problema: buscar dados depemnde de cache;
- BSD LOG-FS baseado no trabalho de Sprite
- Mantém directório e nó-i;
- Nós-i são encontrados via inode map, que mapeia nó-i para
endereço (guardado em disco, guardado no log em check-points);
- Alocação por segmentos (1/2MB).
- Segmentos parciais devido a falta de memória ou NFS:
- CRCs;
- Endereços dos nós-i;
- Info para cada arquivo;
- Flags.
- Se controlador suporta scatter-gather discos são escritos
directamente de cache;
- Endereços de blocos disco são atribuídos apenas na fase de
escrita do bloco de disco;
- Bloco anterior é recuperável por cleaner;
- Cada write escreve todos os buffers dirty da cache:
recuperação completa é possivel.
- read-only ifile guarda modificações só em
checkpoints. Contém:
- Mapa de nós-i;
- Tabela de uso de segmentos: dados não obsolteos por segmentos,
e tempo em que segmento foi alterado;
- Recuperável dos segmentos.
- Problemas:
- Alterações num directório podem não entrar no mesmo segmento
parcial;
- Alocação de blocos ocorre quando o segmento é escrito: cuidado
com falta de espaço.
- requer muita memória física para cache.
- Vantagem em desempenho discutível:
- Performance melhor que Sun-FFS em operações que mexem em
meta-dados;
- Sun-FFS tende ser melhor em I/O intensivo
- Logging de metadados pode trazer os mesmo benefícios com menor
custos.
- Mantém estrutura normal;
- Log só é lido em caso de crash;
- Log pode ser arquivo ou externo;
- Pode afectar performance: batching combina várias
alterações numa entrada.
- Wrapping do log?
vitor@cos.ufrj.br