FAT12, FAT16 e FAT32: Sistemas de arquivos “simples”

O “simples” (entre aspas) no título está assim porque FAT não é tão fácil quanto você pode imaginar… Ele  era simples antes do aparecimento da FAT32, na época em que HDs tinham alguns megabytes de tamanho… Para começar o assunto, eis a estrutura de um superbloco que, para início de conversa, é misturado com o setor de boot:

struct superblock {
  char           unused[3];        /* JMP xxx e NOP */
  char           manufacturer[8];  /* Ex: "MSDOS5.0" */
  unsigned short bytes_per_sector; /* 512, quase sempre. */
  unsigned char  sectors_per_cluster;
  unsigned short num_of_reserved_sectors;
  unsigned char  num_of_fats;         /* Geralmente, 2 */
  unsigned short num_of_root_dir_entries; /* Geralmente, 512 */
  unsigned short num_of_disk_sectors; /* Pode ser 0! */
  unsigned char  media_descriptor;    /* Não usado! */
  unsigned short sectors_per_fat;

  /* Infos para usar com a INT 0x13. */
  unsigned short sectors_per_track;
  unsigned short heads;

  unsigned int   num_hidden_sectors;   /* Geralmente, 0 */
  unsigned int   num_of_disk_sectors2; /* Se 'num_of_disk_blocks' for zero, usa esse! */
  union {
    /* FAT12 e FAT16 */
    struct {
      unsigned char  drive_num;
      char reserved;
      unsigned char  extended_bootrecord_sig;
      unsigned int   volume_serial_num;
      char           volume_label[11];
      char           filesystem_id[8];  /* Ex: "FAT16   ". */
    } ext16;

    /* FAT32 */
    struct {
      unsigned int   sectors_per_fat;
      unsigned short flags;
      unsigned short revision;
      unsigned int   first_rootdir_cluster;
      unsigned short fsinfo_sector;
      unsigned short bk_boot_sector;
      char reserved[12];
      unsigned char  drive_num;
      char reserved2;
      unsigned char  extended_bootrecord_sig;
      unsigned int   volume_serial_num;
      char           volume_label[11];
      char           filesystem_id[8];  /* Ex: "FAT16   ". */
    } ext32; 
  } ext;
};

Claro que na nomenclatura da Microsoft isso ai não é chamado de “superbloco”. Ela chama de BPB, BIOS Paramater Block, embora isso não tenha nada a ver com a BIOS…

Os primeiros valores da estrutura são auto explicativos. O campo sectors_per_cluster nos diz quantos setores correspondem a um “bloco” (que é chamado de “cluster” ou “agrupamento”, em inglês). O campo num_reserved_sectors é usado para indicar o início da primeira FAT e assinaná-la ao cluster 0. Geralmente esse valor nos diz que 1 setor é reservado para o bootstrap (e para a BPB). Assim, as tabelas de alocação de arquivo começam logo depois desse setor.

Com relação a esse superbloco, para o formato FAT16, eis alguns desses campos assinalados:

article

Quando obtive e editei a figura acima chamei “setor” de “bloco”. Desculpe-me pela confusão…

Com base nesses dados, para esse disco temos 64 setores por cluster (0x40). Cada cluster tem, então, 32 kB (sectors\_per\_cluster \cdot bytes\_per\_sector). O diretório raiz suporta, no máximo, 512 entradas (0x0200) e cada FAT ocupa 256 setores (0x0100), isso significa que o sistema de arquivos inteiro suporta 65536 arquivos (cada setor suporta 256 words e, assim, se temos 256 setores por FAT é só fazer as contas!).

É óbvio que, se temos duas cópias de FAT com 256 setores cada e um cluster tem 64 setores, então 4 clusters são reservados para uma única cópia da FAT, neste disco, e temos 8 clusters reservados. Essa é a primeira complicação: O sistema de arquivos exige uma cópia da FAT, obviamente, mas a segunda cópia é feita na área de dados. Mas, apesar disso, os clusters 0 e 1 (da área de dados) são reservados e não constam da FAT. Se a FAT ocupa 256 setores e cada cluster 64, então uma cópia ocupa 4 clusters. Precisamos marcar, na FAT, dois clusters como reservados além dos dois clusters implícitos.

Assim, a FAT começa a partir do cluster 2 e, no nosso caso, os clusters 2 e 3 não podem ser usados.

Determinando o tipo de FAT:

Você pode achar que a string condida no campo campo filesystem_id é usado para determinarmos se estamos lidando com um tipo específico de FAT, mas não é assim que funciona… Esse campo existe apenas como uma string de fácil identificação visual. A especificação da Microsoft, que detém a patente da FAT, nos diz que o jeito correto de sabermos qual é a FAT em uso é pelo tamanho do filesystem, ou seja, pela quantidade de clusters. Esse valor obviamente pode ser obtido através dos campos da BPB.

Para menos de 4085 clusters a FAT12 estará em uso, acima disso até o limite de 65525, FAT16, caso contrário, FAT32.

A diferença entre essas FATs é o tamanho das entradas na tabela de alocação (inodes, ou FAT). A FAT12 usa entradas de 12 bits, mesclando 2 entradas em 3 bytes. A FAT16 usa entradas de 2 bytes (16 bits) e a FAT32… well… hipotéticamente usa entradas de 4 bytes (32 bits), mas não é bem assim…

Organização do disco:

Diferente de sistemas como o ext2fs, FAT só suporta um único grupo de blocos e, por isso, não há um bloco descritor. Não há também um bitmap de blocos ou um bitmap de indes. A maneira como o sistema operacional obtém a quantidade de bytes livres é percorrendo toda a FAT e contando quantos clusters estão marcados com zeros! Felizmente isso não é tão problemático. No nosso exemplo a FAT tem 128 kB e contém 65536 entradas de 2 bytes. Contar quantos zeros existem em 128 kB não é tão lento assim…

Embora FAT não use bitmaps para manter a lista de uso e nem mesmo um registro para facilitar a obtenção do espaço livre ou em uso, há uma tabela de inodes que dá o nome ao sistema de arquivos. Como falei antes, as entradas da FAT não são descrições do objeto referenciado… isso fica junto com os diretórios. E, para isso, o diretório raiz é implícito e obrigatório, logo depois da segunda cópia da FAT. Cada entrada tem 32 bytes, onde a estrutura é esta:

struct dir {
  char filename[8];   /* Nome do arquivo,
                         preenchido com espaços. 
                         0x00... - Entrada não usada.
                         0x05... - Se o primeiro caracter for, de fato, 0xe5.
                         0xe5... - Arquivo deletado. 
                         0x2e... - Entrada é um subdiretório ('.' ou '..'). */
  char extension[3];  /* Extensão do arquivo,
                         preenchido com espaços. */
  char attributes;    /* 0b00xxxxxx
                             ||||||
                             |||||+--- read-only
                             ||||+---- hidden
                             |||+----- system file (io.sys, msdos.sys etc).
                             ||+------ volume label (apenas no root dir).
                             |+------- subdir.
                             +-------- flag Archive. */
  char reserved[10];
  short time;         /* 0bhhhhhmmmmmmsssss.
                         Onde: sssss é medido de 2 em 2 segundos. */
  short date;         /* 0byyyyyyymmmmddddd.
                         Onde: yyyyyyy = (ano - 1980). */
  short inode;        /* número da entrada na fat. */
  unsigned long fsize; /* Tamanho em bytes. */
};

Assim, se no diretório raiz tivermos um arquivo chamado IO.SYS e este apontar no inode 3 e ocupar 4 clusters contíguos teremos uma lista assim:

inode        próximo-inode
---------    ----------------
3:           4
4:           5
5:           6
6:           0xffff

Um arquivo que possua dados que caibam em apenas 1 cluster terá apenas uma entrada na FAT, marcada como 0xffff. E, é claro, ocupará um cluster inteiro, cabendo ao sistema operacional, com base no campo fsize, determinar o tamanho correto do arquivo…Se a entrada fsize for zero, a entrada inode também será, já que o arquivo não ocupa espaço algum em disco. E, da mesma forma que no extfs, um subdiretório é um “arquivo” especial contendo uma lista dessa mesma estrutura… Ele ocupa espaço em disco e, portanto, tem inodes.

Graças ao esquema “filename.extensão” não podemos ter arquivos com nomes começados com ‘.’, e a FAT usa isso para alocar duas entradas num subdiretório (‘.’ e ‘..’) para apontar para os inodes de sí mesmo e do diretório imediatamente abaixo, respectivamente. Na FAT, um diretório que tenha a primeira entrada para ‘.’ é um subdiretório, em oposição ao diretório raiz.

Nomes de arquivos longos não é exclusivo da FAT32:

Eu sempre achei isso estranho… Antes dos IBM PCs a Apple já implementava arquivos com nomes de tamanhos variáveis no antigo Apple DOS, do Apple ][ (ver próxima figura). Por que diabos o PC-DOS, e depois o MS-DOS, não suportam nomes longos?

dos32

O esquema 8.3 em nomes de arquivos do PC-DOS origina-se do antigo CP/M, que era para ser a base do DOS dos PCs, se a Microsoft não tivesse trapaceado e “desenvolvido” o QDOS para a IBM. Somente com o lançamento do Windows 95 é que a feature do nome longo foi adicionada à fatfsQualquer FAT: 12, 16, 32 ou a experimental exfat

Acontece que, para suportar nomes longos, deve-se manter a compatibilidade com as estruturas  de diretório pré existentes… Assim, entradas “especiais” nos diretórios são necessárias para incorporar nomes longos. Essas novas entradas precedem a entrada tradicional mostrada anteriormente. Cada entrada desse conjunto contém 13 caracteres do nome longo (no formato UNICODE, cada caracter tem 2 bytes de tamanho), numa estrutura assim:

struct longdir {
  unsigned char order;
  wchar_t name1[5];       /* primeira parte do nome. */
  unsigned char attribute;
  unsigned char type;       /* sempre 0 */
  unsigned char cksum;
  wchar_t name2[6];      /* segunda parte do nome. */
  unsigned short reserved;
  wchar_t name3[2];      /* terceira parte do nome. */
};

O número de ordem é decrementado, começando com N até 1, mas a primeira entrada tem os bits 7 e 6 fixados em 01(2), as demais entradas são bytes simples. É claro que isso complica as coisas um bocado… Especialmente porque os registros de nome longo são opcionais, mesmo na FAT32! Arquivos que possuem apenas nomes “curtos” não possuem entradas de “nomes longos”. Para determinar se um nome longo está sendo usado ou não é necessário ler, pelo menos, duas entradas, já ue 0b01xxxxxx cai exatamente na faixa dos caracteres que podem ser usados no nome curto… Lembre-se que nomes curtos são case insensitive.

Além do mais, a coisa é ainda mais chata porque os nomes longos não são apresentados em sequência dentro da estrutura. Há o particionamento mostrado acima e, além disso, a ordem com que os registros aparecem no diretório é de trás para frente de maneira parcial! Um arquvo chamao “Este é meu arquivo” aparece em dois registros como “é meu…arquiv.. o” e “Este “, onde os pontos são os campos intermediários, entre as partições num único registro…

Uma entrada para esse arquivo ficaria mais ou menos assim:

0x42 L"é meu" 0x?? 0x00 0x?? L"arquiv" 0x00 0x00 L"o"
0x01 L"Este " 0x?? 0x00 0x?? 0x0000 [5*0xffff] 0x00 0x00 0x0000
"ESTEE~1" "   " 0x40 ... <- entrada de nome "curto"...

Note a complicação adicional que além de marcar fim de string com ‘\x0000’, as entradas adicionais, não usadas, são marcadas com ‘\xffff’! O nome curto também precisa ser calculado e verificado contra outros arquivos contidos no diretório para evitar conflitos…

Obter uma ordenação de nomes de arquivos longos ou pesquisar por um nome específico demanda uma rotina bem complicada…

Mentiram para nós: FAT32 não é 32, é 28!

Diferente da FAT12 e FAT16, que usam entradas de 12 ou 16 bits na tabela de alocação, a FAT32 usa 28, não 32. Os 4 bits superiores são “reservados” e necessariamente zerados. Entradas zeradas continuam sendo marcas de clusters livres, mas o final de uma lista de clusters é marcada com 0x0fffffff. Clusters reservados são marcados com 0xf7fffff… Mas, de novo, para complicar as coisas, valores como 0xf9fffff são entradas válidas!

Anúncios

Deixe um comentário

Faça o login usando um destes métodos para comentar:

Logotipo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair / Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair / Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair / Alterar )

Foto do Google+

Você está comentando utilizando sua conta Google+. Sair / Alterar )

Conectando a %s