Виртуальная файловая система VFS
Для организации доступа к разнообразным файловым системам (ФС) в Unix используется промежуточный слой абстракции - виртуальная файловая система (VFS).
С точки зрения программиста VFS организована как интерфейс или абстрактный класс в объектно ориентированном языке программирования типа C++.
VFS объявляет API доступа к файловой системе, а реализацию этого API отдаёт на откуп драйверам конкретных ФС, которые можно рассматривать, как производные классы, наследующие интерфейс VFS.
Каждый драйвер ФС должен реализовать вызовы для работы с файлами, inode и с ФС в целом, описанные в заголовочном файле ядра linux/fs.h. При монтировании ФС соответствующие структуры заполняются указателями на соответствующие реализации в драйвере.
Если какая-нибудь функция отсутствует в драйвере, то указатель ссылается на функцию заглушку, которая возвращает ошибку "не реализовано" - ENOSYS.
struct file_operations {
loff_t (*llseek) (struct file *, loff_t, int);
ssize_t (*read) (struct file *, char *, size_t, loff_t *);
ssize_t (*write) (struct file *, const char *, size_t, loff_t *);
...
struct inode_operations {
struct dentry * (*lookup) (struct inode *,struct dentry *);
int (*create) (struct inode *,struct dentry *,int);
int (*link) (struct dentry *,struct inode *,struct dentry *);
int (*symlink) (struct inode *,struct dentry *,const char *);
int (*mkdir) (struct inode *,struct dentry *,int);
...
struct super_operations {
int (* ) (struct super_block *, int *, char *);
void (*umount_begin) (struct super_block *);
...
Для файловой системы ext2fs объявление соответствующих функций выглядит так:
extern struct dentry *ext2_lookup (struct inode *, struct dentry *);
extern int ext2_create (struct inode *,struct dentry *,int);
extern int ext2_mkdir (struct inode *,struct dentry *,int);
extern int ext2_rmdir (struct inode *,struct dentry *);
extern int ext2_unlink (struct inode *,struct dentry *);
...
Вызовы remount_fs()
и umount_begin()
можно использовать как конструктор
и деструктор класса, которые вызываются в момент монтирования файловой системы
и в момент размонтирования.
Кроме виртуальных функций VFS описывает обобщённые структуры superblock, dentry (directory entry запись в каталоге),inode (в некоторых ОС называется vnode). Эти структуры содержит все основные структуры данных суперблока ФС, каталогов и inode из классической ФС Unix. Кроме того структура file содержит информацию, необходимую для работы с открытыми файлами Поскольку VFS является интерфейсом, то перечисленные структуры не содержат технических подробностей, таких как информации о размещении блоков данных файла или IP адреса сервера сетевой ФС. Для хранения деталей реализации, драйверу ФС в каждой из структур предоставляется дополнительное поле для хранения указателя на специфические для ФС структуры данных. В inode в Linux это поле выглядит так:
union {
struct pipe_inode_info pipe_i;
struct minix_inode_info minix_i;
struct ext2_inode_info ext2_i;
...
void *generic_ip;
} u;
На основе перечисленных
Драйвер ФС должен уметь конвертировать атрибуты файла, фактически хранящиеся в ФС, в поля inode. Например, драйвер NTFS должен уметь преобразовывать SID пользователя Windows в UID пользователя Unix и наоборот.
Если ФС не позволяет хранить необходимые атрибуты inode, то при монтировании драйверу можно передать некоторые дополнительные параметры, содержащие фиксированные значения для этих атрибутов. Драйвер FAT позволяет задать:
Благодаря VFS в Unix возможно представление в виде ФС любых иерархических структур данных. Самый известный пример, это файловая система Procfs , которая отображает в виде дерева каталогов внутренние структуры ядра. Чаще всего, она смонтирована в каталог /proc, но может быть смонтирована и в другой каталог или не смонтирована вовсе.
В каталоге /proc в Linux присутствуют, по сути, два дерева ФС. В основном
дереве, каждый каталог имеет числовое имя и соответствует процессу,
с соответствующим PID. Файлы в этих каталогах соответствуют структурам данных,
связанных с процессом. Каталог /proc/self в Linux является символической
ссылкой, указывающей на каталог процесса, который к ней обратился.
Например, cat /proc/self/cmdline
покажет аргументы запуска cat
т.е.
cat /proc/self/cmdline, а ls -l /proc/self/exe
покажет ссылку на
исполняемый файл ls
- /proc/self/exe -> /usr/bin/ls.
В дереве /proc/sys отображаются внутренние переменные ядра. Операции чтения/записи в каталоге /proc/sys позволяют настраивать такие параметры ядра как маршрутизация - /proc/sys/net/ipv4/ip_forward или максимальный объём разделяемой между процессами памяти /proc/sys/kernel/shmmax. В исторических Unix, таких как Solaris 5 такие настройки делались через отладчик, который подключался к ядру как к программе и менял значения переменных.
В последних версиях ядра Linux прослеживается тенденция вынесения доступа к новым переменным в отдельную ФС Sysfs, которая монтируется в каталог /sys.
В Linux (а в последнее время и в Windows) есть возможность зарегистрировать в VFS свой драйвер ФС без написания кода в ядре. Адаптер fuse транслирует вызовы из ядра в обычный пользовательский процесс. Благодаря этому механизму возможно написание драйверов, написанных на любом языке программирования: C++, python, Java и т.д. Главное, написать соответствующий набор функций и через API fuse зарегистрировать их в ядре.
Известные примеры: sshfs - монтирование дисков через sftp, NTFS-3G - драйвер NTFS через fuse, различные ФС на основе баз данных.
Файловые системы (ФС) в Unix доступны через промежуточный слой абстракции - виртуальную файловую систему (VFS). VFS организована в виде дерева, в котором узлы ветвления это всегда каталоги, а листья - любые допустимые в VFS объекты (файлы, каталоги, символические ссылки, сокеты, и т.д.). Чтобы подключить в VFS новый носитель данных, выполняется операция монтирования, которая сопоставляет одному из каталогов, уже присутствующему в дереве, ссылку на корневой каталог в ФС на носителе. Доступ к ранее существовавшему содержимому каталога до отмены операции монтирования прекращается. Говорят, что в результате монтирования носитель становится смонтированным в каталог. Корневой каталог всей VFS также должен быть смонтирован на корневой каталог какой-либо конкретной ФС.
Имена объектов в VFS - это байтовые строки с завершающим нулевым байтом. Интерпретацией кодировки символов VFS не занимается. При записи путей символ "слэш" "/" используется как разделитель каталогов и потому не может быть использован в именах объектов VFS. Кроме слэша внутри имён объектов не может присутствовать нулевой байт '\0'. Длина имени не должна превышать NAME_MAX (определена в файле linux/limits.h как 255 байт). В остальном в VFS ограничений на имена нет, но такие ограничения могут быть обусловлены реализацией конкретной файловой системы.
В каждом каталоге существует два зарезервированных имени "." и "..", обозначающие текущий и вышележащий каталоги соответственно. Если ФС на носителе не поддерживает такие имена, то они должны быть сэмулированы драйвером. При выполнении монтирования значение "." берётся из корневого каталога на носителе, а ".." из того каталога, в который было произведено монтирование. В корневом каталоге (см. ниже) ".." ссылается на сам каталог как и ".".
С каждым процессом в Unix связано два каталога:
Эти каталоги используются при разборе путей в VFS теми системными вызовами,
которые получают пути в качестве аргументов
(open("path", ...)
, unlink("path")
и т.п.). Если путь начинается с символа
"слэш" "/" то он называется абсолютным и отсчитывается от корневого каталога
процесса, а если начинается с любого другого символа, то называется
относительным и отсчитывается от текущего рабочего каталога.
Разбор пути идет по следующим правилам (man path_resolution
):
Смена текущего каталога выполняется вызовом chdir(const char *dir)
или fchdir(int fd)
. Второй способ требует предварительно получить файловый
дескриптор, ссылающийся на каталог.
Смена корневого каталога процесса выполняется вызовом
chroot(const char *dir)
. В новом корневом каталоге имя ".."
указывает на него самого, что не позволяет с помощью абсолютных путей
подняться по дереву выше корневого каталога процесса. После выполнения этого
вызова вся файловая система "выше" указанного корневого каталога становится
невидимой для последующих системных вызовов, но открытые файлы за пределами
видимости остаются доступными. Более того, текущий каталог может оказаться
за пределами корневого, так что при практическом применении, например
для ограничения доступа процесса к ФС, надо сочетать
chroot()
и chdir()
.
Чтобы запустить программу с переопределённым корневым каталогом используется программа
chroot NEWROOT [COMMAND [ARG]...]
Если команда COMMAND не указана, то выполняется shell, указанный в файле passwd для текущего пользователя.
Внутри chroot
выполняются следующая последовательность вызовов:
chroot("NEWROOT");
chdir("/");
execve("COMMAND", ...);
Последний вызов означает, что исполняемый файл COMMAND и динамические библиотеки необходимые для его выполнения должны находиться внутри нового корневого каталога.
Если мы хотим запереть пользователя внутри chroot
нам необходимо скопировать
вовнутрь /bin/bash, /lib/libc.so, /etc/passwd, /dev/tty и ещё ряд важных
файлов, полный состав которых зависит от версии ОС и набора решаемых в "тюрьме"
задач.
Сокращенный вариант структур данных в ядре Linux 2.2.26. Структуры описаны в include/linux/{sched.h,fs.h,dcache.h}.
ФД -файловый дескриптор, ФС - файловая система
struct task_struct {
...
struct files_struct *files; //информация об открытых файлах процесса
...
}
struct files_struct {
atomic_t count; //количество открытых файлов
int max_fds; //максимальное доступное количество ФД
int max_fdset; //максимальный номер использованного ФД
int next_fd; //минимальный свободный номер ФД
fd_set *close_on_exec; //битовая карта ФД, которые необходимо закрыть при вызове exec()
fd_set *open_fds; //битовая карта открытых ФД
struct file * fd_array[NR_OPEN_DEFAULT]; //массив указателей на открытые файлы
...
};
struct file {
struct dentry *f_dentry; //ссылка на запись в каталоге (имя и inode файла)
struct file_operations *f_op; //указатели на функции драйвера ФС для работы с файлом
atomic_long_t f_count; //количество открытых файловых дескрипторов процесса, указывающих на файл
unsigned int f_flags; //флаги, указанные при открытии
loff_t f_pos; //позиция головки ввода/вывода
fmode_t f_mode; //права доступа в момент открытия
struct fown_struct f_owner; //uid,gid файла в момент открытия
...
void *private_data; //место для хранения специфичных для драйвера данных
};
struct dentry {
struct inode *d_inode;
struct qstr d_name;
...
}
struct inode {
...
kdev_t i_dev; //номер устройства, на котором расположен inode
unsigned long i_ino; //номер inode
umode_t i_mode;//права доступа
nlink_t i_nlink; //число имён
uid_t i_uid;
gid_t i_gid;
...
struct super_block *i_sb; // ссылка на суперблок ФС
...
unsigned int i_count; //общее число ФД, ссылающихся на этот файл
struct file_lock * i_flock; //блокировки файла
...
}
struct file_operations {
int (*open) (struct inode *, struct file *);
loff_t (*llseek) (struct file *, loff_t, int);
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
int (*flush) (struct file *);
int (*release) (struct inode *, struct file *);
...
}
Прикрепленный файл | Размер |
---|---|
fd_tables.png | 10.19 КБ |