Trilha 08 · Sistema operacional

Sistema de arquivos

Arquivos e diretórios são a abstração que o SO oferece para o disco. Por baixo: inodes, blocos alocados, permissões em bits e estruturas de diretório que mapeiam nomes para números de inode.

① Intuição

Nome, metadado e conteúdo são três coisas separadas

Um arquivo em Linux tem três componentes independentes: o nome (que vive no diretório pai), os metadados (no inode: permissões, dono, tamanho, timestamps, lista de blocos), e o conteúdo (nos blocos de dados apontados pelo inode). Copiar um arquivo cria um novo inode e novos blocos. Criar um hard link apenas adiciona uma entrada no diretório apontando para o mesmo inode.

Tudo é arquivo no Unix: dispositivos (/dev/sda), pipes (/dev/stdin), sockets de rede, e até informações do kernel (/proc/cpuinfo) são acessíveis como arquivos via VFS (Virtual File System). O VFS é a camada de abstração do kernel que permite que read() e write() funcionem para qualquer "arquivo" independente do sistema de arquivos subjacente.
② Visualização interativa

Navegador de sistema de arquivos

Clique em diretórios para navegar e em arquivos para ver os detalhes do inode — número, permissões, blocos alocados e metadados.

INODE #2/
Tipo: diretório
Permissões: drwxr-xr-x
Dono: root
Links: 8
Modificado: Jun 10 2024
ESTRUTURA DE DIRETÓRIO

Diretórios não têm conteúdo de arquivo — são um mapeamento de nome → número de inode. O inode contém todos os metadados (permissões, tamanho, blocos). Dois hard links apontam para o mesmo inode — deletar um não apaga os dados.

③ Explicação técnica

Estrutura do inode no ext4

// Estrutura de um inode no ext4 (simplificada)

struct ext4_inode {
  __le16 i_mode;        // tipo de arquivo + permissões (rwxrwxrwx)
  __le16 i_uid;         // ID do dono
  __le32 i_size_lo;     // tamanho em bytes (parte baixa)
  __le32 i_atime;       // último acesso (timestamp)
  __le32 i_ctime;       // última mudança de metadados
  __le32 i_mtime;       // última modificação de conteúdo
  __le16 i_links_count; // número de hard links
  __le32 i_blocks_lo;   // blocos alocados em 512-byte units
  __u8   i_block[60];   // ponteiros para blocos de dados
                        // ext4 usa extents (contíguos), não blocos individuais
};

// O que o inode NÃO contém: o nome do arquivo!
// O nome fica no diretório pai, que mapeia nome → número de inode.

// stat("arquivo.txt") retorna info do inode:
//   File: arquivo.txt
//   Size: 2048       Blocks: 8         IO Block: 4096
//   Inode: 1234567   Links: 1
//   Access: 2024-06-13 10:30:00
//   Modify: 2024-06-12 08:15:00

Hard links, symlinks e journaling

// Hard links vs. Symbolic links

// Hard link: segunda entrada de diretório apontando para o MESMO inode
ln arquivo.txt hardlink.txt
// Agora dois nomes → mesmo inode (links_count = 2)
// Deletar arquivo.txt: links_count → 1, dados preservados
// Deletar hardlink.txt: links_count → 0, blocos liberados
// Limitação: não funciona entre sistemas de arquivos diferentes

// Symbolic link: arquivo especial que contém um caminho
ln -s /home/alice/docs/readme.md symlink.md
// symlink.md tem SEU PRÓPRIO inode com tipo "symlink"
// Conteúdo do inode é o string "/home/alice/docs/readme.md"
// Se o alvo for deletado: symlink fica "quebrado" (dangling)
// Pode apontar para outro filesystem, para diretórios, caminhos relativos

// Journaling (ext3/ext4, APFS, NTFS):
// Antes de modificar metadados, escreve intenção no journal
// Se houver queda de energia durante escrita: replay o journal no boot
// Sem journal (ext2): fsck após crash — pode levar horas
Fragmentação e extents: o ext2 usava blocos individuais de 4KB para cada arquivo — um arquivo grande gerava centenas de ponteiros de bloco no inode. O ext4 usa extents: ao invés de listar cada bloco, armazena faixas contíguas (início + comprimento). Um arquivo de 100MB contíguo em disco usa apenas 1 extent no inode em vez de 25.600 ponteiros. Além de mais eficiente, reduz a fragmentação — o alocador de blocos do ext4 tenta alocar espaço contíguo.
④ Projeto para programar

Explorando o sistema de arquivos

Mini projeto: escreva um script Python (ou C) que percorre recursivamente um diretório e gera um "mapa de disco" — lista arquivos ordenados por tamanho, calcula o espaço total usado por extensão de arquivo, e encontra duplicatas por conteúdo (hash MD5 do conteúdo). A versão de produção desse script é o que o ncdu faz.

Projeto principal: implemente um sistema de arquivos simples em memória em Python: classe FileSystem com mkdir(), touch(), write(), read(), ln() (hard link), ls() e rm(). Use um dicionário de inodes (int → dict) e um dicionário de diretórios (path → {name: inode_num}). Mantenha o links_count correto.

Desafio extra: use FUSE (Filesystem in Userspace) para implementar um filesystem personalizado em Python com fusepy. Exemplo: um filesystem que apresenta os arquivos de um arquivo ZIP como se fossem diretórios e arquivos reais — mount.zip arquivo.zip /mnt/zip. O kernel roteia chamadas open()/read() para o seu código Python via FUSE.

⑤ Exercícios rápidos

Teste sua intuição

O que um inode contém e o que ele NÃO contém?
Qual é a diferença entre hard link e symbolic link?
Para que serve o journaling em sistemas de arquivos como ext4 e NTFS?
⑥ Aplicações no mundo real

Onde você encontra isso

☁️

Sistemas de arquivos distribuídos

HDFS (Hadoop) e GFS (Google) replicam blocos em múltiplas máquinas. O namenode guarda os metadados (equivalente ao inode); datanodes guardam os blocos. Para um arquivo de 1TB em 3 réplicas: 3TB de armazenamento distribuído em centenas de nós. Falha de um nó é tratada pelo sistema sem perda de dados.

📱

APFS (Apple File System)

APFS (2017) traz copy-on-write, snapshots instantâneos (para Time Machine), criptografia nativa por arquivo, e compartilhamento de blocos entre snapshots. Um snapshot ocupa 0 bytes extras imediatamente após ser criado — apenas as modificações subsequentes geram novos blocos (diferença de blocos).

🗄️

Databases e bypass do VFS

Bancos de dados de alta performance como PostgreSQL e Oracle frequentemente usam O_DIRECT para bypassing o page cache do kernel — leitura/escrita direto do disco para o buffer do banco. O banco de dados tem seu próprio cache (shared_buffers) otimizado para seus padrões de acesso, sem o overhead de dois layers de cache (page cache + buffer do banco).

← Anterior: Memória virtual Próxima: Deadlock →