Вы правы в своем предположении, что, хотя все записи каталога удаляются сразу после вызова unlink(), фактические блоки, которые физически составляют файл, очищаются на диске только тогда, когда ничто больше не использует inode. (Я говорю « записи каталога », потому что в vfat файл может иметь несколько таких файлов, потому что реализована поддержка длинных имен файлов в vfat.)
В этом контексте под индексом я подразумеваю структуру в памяти, которую ядро Linux использует для обработки файлов. Он используется даже тогда, когда файловая система не "основана на inode". В случае vfat, inode просто поддерживается некоторыми блоками на диске.
Взглянув на исходный код ядра Linux, мы видим, что vfat_unlink
, который реализует системный вызов unlink()
для vfat, делает примерно следующее (чрезвычайно упрощено для иллюстрации):
static int vfat_unlink(struct inode *dir, struct dentry *dentry)
{
fat_remove_entries(dir, &sinfo);
clear_nlink(inode);
}
Итак, что происходит:
fat_remove_entries
просто удаляет запись для файла в своем каталоге.
clear_nlink
устанавливает счетчик ссылок для индекса на 0
, что означает, что ни один файл (то есть, нет записи в каталоге) больше не указывает на этот индекс.
Обратите внимание, что на этом этапе ни inode, ни его физическое представление никак не затрагиваются (за исключением уменьшенного количества ссылок), поэтому он все еще счастливо существует в памяти и на диске, как будто ничего не произошло!
(Кстати, также интересно отметить, что vfat_unlink
всегда устанавливает счетчик ссылок на 0
а не просто уменьшает его с помощью drop_link
. Это должно предупредить вас, что файловые системы FAT не поддерживают жесткие ссылки! И еще одно указание на то, что FAT сама не знает какой-либо отдельной концепции inode.)
Теперь давайте посмотрим, что происходит, когда инод выселяется. evict_inode
вызывается, когда нам больше не нужен индекс в памяти. В самом начале, это, конечно, может произойти только тогда, когда ни один процесс больше не удерживает дескриптор открытого файла для этого inode (но теоретически может произойти и позже). Реализация FAT для evict_inode
выглядит (опять же, упрощенно) так:
static void fat_evict_inode(struct inode *inode)
{
truncate_inode_pages(&inode->i_data, 0);
if (!inode->i_nlink) {
inode->i_size = 0;
fat_truncate_blocks(inode, 0);
}
invalidate_inode_buffers(inode);
clear_inode(inode);
}
Магия происходит именно в if
-clause: если счетчик ссылок inode был равен 0, это означает, что ни одна запись каталога не указывает на него. Таким образом, мы устанавливаем его размер равным 0 и фактически усекаем его до 0 байт, что фактически удаляет его с диска путем очистки блоков, из которых он был сделан.
Итак, повреждение, которое вы испытываете в своих экспериментах, легко объяснимо: как вы и подозревали, запись каталога уже была удалена (с помощью vfat_unlink
), но, поскольку индекс не был удален, фактические блоки все еще не были затронуты и были все еще отмечен в FAT (сокращение от таблицы размещения файлов) как используется. Однако fsck.vfat
обнаруживает, что в каталоге нет записи, которая больше указывает на эти блоки, жалуется и исправляет ее.
Кстати, CHKDSK
не только очистит эти блоки, пометив их как свободные, но и создаст новые файлы в корневом каталоге, указывающие на первый блок в каждой цепочке, с такими именами, как FILE0001.CHK
.