Виртуальные файловые системы

На протяжении нескольких лет не вступаю в священные holywars. А вот последнее время чувствую, что если не вступать в споры, то тебя никто и не заметит. Получается, с одной стороны, споры отнимают много времени, а с другой стороны - без них никак не обойтись. Главное - знать с кем и о чём спорить.

Недавно нарочно ввязался в спор C vs C++, чтобы маленько попиарить Хамелеон. На примере сравнения виртуальных файловых систем. Эта ссылка ведёт на информацию об устройстве виртуальной файловой системы Linux

В Хамелеоне это реализовано несколько иначе:

class ISuperblock
{
public:
	virtual ~ISuperblock();

	virtual int				ConnectToDevice(class IBlockDevicePartition * Device) = 0;

	// Mount service
	virtual class IBlockDevicePartition	*	GetDevice(void) = 0;
	virtual class Inode			*	GetRootInode(void) = 0;
	virtual class ISuperblock		*	GetParentSuperblock(void) = 0;
	virtual const ino_t				GetParenInode(void) = 0;
	virtual void					SetParent(class Inode * inode) = 0;

	// Controls how many objects use Superblock
	virtual void			AddReference() = 0;
	virtual void			DelReference() = 0;
	virtual const int		GetReferenceCount(void) = 0;

	// Inode service
	virtual class Inode *	GetInode(int nInodeNumber, int nParenNumber ) = 0;
	virtual class Inode *	MakeInode (unsigned short uid, unsigned short gid, unsigned short mode) = 0;
	virtual int		RemoveInode(class Inode * inode) const = 0;
	virtual int		ReleaseInode(class Inode * inode) = 0;

	// Directory service
	virtual typeDirentry *		ReadDir(class DirectoryHandler * dir) = 0;
	virtual int			InsertDirectoryEntry (class Inode * where, class Inode * which, const char *szName) = 0;
	virtual int			DeleteDirectoryEntry (class Inode * where, const char *szName) = 0;
	virtual bool			IsDirectoryEmpty ( class ISuperblock * sb, class Inode * inode_dir) = 0;

	// Symbolic link service
	virtual int			MakeSymbolicLink(class Inode * inode, const char * szLinkTo) = 0;
	virtual class Inode	*	GetSymlink (class IProcess * process, class Inode * node) = 0;

	// File system name and status service
	virtual int			StatFS( statfs_t * stat) = 0;
	virtual char * 			GetFileSystemName(void) = 0;

	// Prepare Inode to read/write operations
	virtual int			OpenNode(class Inode * inode) = 0;
	virtual int			CloseNode(class Inode * inode) = 0;

	// Disk blocks service
	virtual block_t			AllocateFreeBlocks(int nCount) = 0;
	virtual int			ReleaseBlock(block_t nBlock) = 0;
	virtual block_t			GetBlockNumberByIndex(class Inode * node, unsigned int nIndex) = 0;
	virtual int			SetBlockNumberByIndex(class Inode * node, unsigned int nIndex, block_t nBlockNumber) = 0;
	virtual int			TruncateInode(class Inode * inode, unsigned int nSize = 0) = 0;

	// Flushing service
	virtual int			FlushInode( class Inode * inode, FlushMode_t flush_mode ) = 0;
	virtual int			FlushDiskCaches(void) = 0;

	// File path service
	virtual int			GetCurrentDirectoryName (class IProcess * process, class Inode * inode, char * dirname) = 0;
};

Т.е. "движок" любой файловой системы должен "всего-лишь" поддерживать этот интерфейс для предоставления POSIX совместимого сервиса. Естественно, напрашивается желание завернуть всё это в некое подобие COM технологии, наследовать от IUnknown или, тем паче, от ISubject... Но это в перспективе, а пока от этого класса наследованы MinixSuperblock, Ext2Superblock, IsoSuperblock, FatSuperblock и DevSuperblock. Последний - это суперблок для devfs, файловой системы, где регистрируются сервисы (в т.ч. драйвера устройств).

Как это ни странно, наиболее сложной оказалась поддержка FAT. Дело в том, что в файловая система FAT не использует Inode. Это было бы полбеды, поскольку в момент чтения FAT директории, для каждого файла в оперативной памяти автоматически создаются inode. Такой же подход используется и в реализации IsoSuperblock и этот метод работает. Проблемы начинаются когда в FAT директории находится файл нулевой длины. Соответственно, у этого файла не имеется записи в таблице FAT и поэтому у пустого файла невозможно однозначно индексировать inode. Как ты уже понял, для ненулевого файла в качестве индекса inode брался номер первого элемента в таблице FAT. О, какая тафтология получается, но я верю, ты понял о чём речь. Пока я не придумал ничего лучше, чем использовать счётчик отрицательных чисел, для индексации inode, принадлежащих пустым файлам на FAT. И при записи первого байта в такую "виртуальную" inode, она автоматически переиндексируется номером первого элемента в таблице FAT.

Ох, чувствую, я тебя утомил излишними подробностями. Но если ты был внимателен, то у тебя возник закономерный вопрос: "А где же методы чтения и записи файла?".

Методы Read и Write реализованы в классе Inode. Каким образом удалось не задействовать класс Superblock для чтения и записи файлов? Таки он задействован. Дело в том, что ISuperblock предоставляет целых три метода, которые используются при чтении/записи файлов. Вот они:

block_t AllocateFreeBlocks(int nCount);
block_t GetBlockNumberByIndex(class Inode * node, unsigned int nIndex);
int SetBlockNumberByIndex(class Inode * node, unsigned int nIndex, block_t nBlockNumber);

Например, при чтении файла с некоторой позиции, мы сначала находим индекс блока для чтения по формуле: index = position / block_size, а затем получаем логический номер блока посредством метода GetBlockNumberByIndex. И уже полученный номер мы передаём в качестве аргумента методу ReadBlock интерфейса IBlockDevicePartition. Запись происходит по аналогии, только номер блока для записи предварительно возвращается методом AllocateFreeBlocks. Разумеется, методы выделения свободных блоков и индексации уникальны для каждой файловой системы. Помимо этого, не каждая файловая система обязана поддерживать все методы интерфейса ISuperblock. К примеру, методы AllocateFreeBlocks и SetBlockNumberByIndex не имеют смысла для файловой системы ISO и должны возвращать код ошибки EROFS (файловая система только для чтения). Другой пример - методы MakeSymbolicLink и GetSymlink не имеют смысла для файловой системы FAT, поскольку она не поддерживает символические ссылки. Соответсвенно, заглушки этих методов должны возвращать код ошибки ENOSYS.

В заключение хотелось бы сказать, что вышеприведённый интерфейс окончательно не устоялся. За время написания этого поста я обнаружил несколько мест, которые возможно и необходимо оптимизировать. Другой вопрос, я пока не уверен, что в эту схему красиво ложатся файловые системы, которые умеют хранить несколько файлов в одном логическом блоке. Хотелось бы услышать мнение экспертов по этому поводу.

Алексей Мандрыкин
7 ноября 2007 года