Learning Platform
Глоссарий Troubleshooting
Урок 10.01 · 22 мин
Начальный
FilesystemsVFSLinuxKernel

VFS — единый интерфейс над ext4, xfs, btrfs, tmpfs

Когда ваша программа делает open("/var/log/app.log") — ей всё равно, на каком физическом диске этот файл, в какой файловой системе он лежит, экспортирован ли он через NFS, или вообще это псевдо-файл из /proc. Один и тот же open() syscall работает везде. Это Virtual File System (VFS) — абстракция в ядре Linux, благодаря которой существуют десятки разных файловых систем под одним API.

VFS — одна из самых элегантных абстракций в Linux. Она появилась как ответ на простую проблему: в 80-90-е годы появлялось много новых файловых систем (ext2, NFS, ISO 9660 для CD, FAT для USB), и каждое приложение могло захотеть с ними работать. Без VFS пришлось бы каждой программе знать про все ФС. С VFS приложение видит один API, а ядро транслирует вызовы в конкретный driver. Это и есть «дешёвая абстракция»: API простой, реализаций много.

В этом уроке: что такое VFS на уровне kernel, как работают inode/dentry/file structures, что физически делает mount, и почему /proc — это тоже файловая система (но без диска).


Архитектура: один API, много реализаций

VFS -- единый API над разными ФС
Your app: open / read / writeПрограмма делает обычные syscalls. Не знает, какая ФС под капотом. Работает одинаково с локальными файлами, сетевыми, виртуальными
VFS layer (kernel)Virtual File System -- абстракция в ядре. Принимает syscalls, маршрутизирует к нужному filesystem driver на основе того, где (path-wise) находится файл
ext4 driverDrivers для ext4: знает on-disk формат, как читать/писать блоки, журналирование. Реализует операции VFS interface (inode_operations, file_operations)
xfs driverXFS driver: 64-bit, оптимизирован под большие файлы и параллелизм. Был разработан Silicon Graphics для high-performance
btrfs driverBtrfs: copy-on-write, снапшоты, subvolumes, integrated RAID. Современная ФС от Oracle/SUSE
tmpfs drivertmpfs: in-memory FS, нет on-disk хранилища. Используется для /dev/shm, /tmp на современных Linux, /run
procfs driverprocfs: виртуальная ФС, файлы генерируются ядром по запросу. /proc/cpuinfo, /proc/[pid]/status -- не на диске, это API-обёртки над kernel state
Disk: ext4 partitionext4 driver работает с реальным блочным устройством (/dev/sda1). Чтение/запись sectors
Disk: xfs partitionXFS partition на другом устройстве (/dev/sdb1)
RAMtmpfs и procfs живут в памяти, без диска. Скорость = RAM speed, но данные исчезают при перезагрузке

Когда open("/foo/bar") приходит в kernel, VFS делает path lookup: проходит по компонентам пути, находя соответствующие inode (которые могут принадлежать разным ФС из-за mount-точек). В итоге получает inode конкретной ФС и вызывает её специфический open метод. Дальше read/write через указатели функций.


Три ключевые структуры: inode, dentry, file

VFS оперирует тремя главными структурами:

inode (index node) — метаданные о файле: размер, владелец, права, время модификации, указатели на блоки данных. Один файл = один inode, независимо от имени. Имя файла — это отдельная сущность.

dentry (directory entry) — связь имени и inode. Один dentry на каждое имя файла. Когда вы делаете ls /foo, kernel ищет dentry «foo» в директории «/», находит её, идёт по ссылке на inode.

file — открытый дескриптор. Когда вы делаете open(), kernel создаёт struct file — это экземпляр «открытого файла». Содержит позицию чтения, флаги, ссылку на inode. Несколько file могут указывать на один inode (если файл открыт несколько раз).

# inode конкретного файла:
stat /etc/hostname
# File: /etc/hostname
# Size: 12        Blocks: 8          IO Block: 4096   regular file
# Device: 802h/2050d  Inode: 786433      Links: 1
# Access: (0644/-rw-r--r--)  Uid: (    0/    root)   Gid: (    0/    root)
# Access: 2026-05-18 09:30:15.000000000 +0000
# Modify: 2024-02-15 12:00:00.000000000 +0000
# Change: 2024-02-15 12:00:00.000000000 +0000

# Видите 'Inode: 786433' -- это номер inode в этой ФС
# Размер 12 байт, права 644, два timestamp-а
# Открытые file descriptors процесса:
ls -la /proc/self/fd/
# 0 -> /dev/pts/0   (stdin)
# 1 -> /dev/pts/0   (stdout)
# 2 -> /dev/pts/0   (stderr)
# 3 -> /etc/hostname

# Каждый fd -- это struct file в ядре, с ссылкой на dentry/inode

dentry cache в ядре — одна из главных оптимизаций. Каждый name lookup без dcache был бы дорогим (поход на диск). С dcache часто-используемые пути резолвятся в наносекунды.


mount — как ФС встают в дерево

Linux имеет одно глобальное дерево файлов с корнем /. Разные физические устройства и разные ФС присоединяются в это дерево через mount. После mount-а соответствующего drive, файлы под mount-point становятся видны через единое дерево.

mount -- подключение ФС в дерево
/ (root)Корневая ФС, обычно ext4 на /dev/sda1 или /dev/nvme0n1p2. Содержит /etc, /usr, /var, /home (или /home -- отдельный mount)
/dev/sdb1 (ext4)Второй диск с ext4. Само устройство не видно в дереве, пока не mount-нут
mount /dev/sdb1 /homeПосле mount-а: всё, что лежало в исходном /home, скрывается; вместо него видны файлы с /dev/sdb1. Inode-numbers в /home теперь принадлежат этой ФС
tmpfsIn-memory ФС. Создаётся kernel-ом, без блочного устройства
mount tmpfs /tmpСтандарт современных Linux: /tmp в RAM через tmpfs. Освобождает диск, быстрее, чистится при перезагрузке
NFS serverСетевой NFS-сервер (например, NAS). NFS-driver делает RPC-вызовы к серверу для каждой VFS-операции
mount nas:/share /mnt/dataПосле mount: /mnt/data выглядит как локальная директория, но каждое чтение/запись идёт по сети. Тот же open/read/write API
# Все mount-точки:
mount
# /dev/nvme0n1p2 on / type ext4 (rw,relatime)
# proc on /proc type proc (rw,nosuid,nodev,noexec,relatime)
# sysfs on /sys type sysfs (rw,nosuid,nodev,noexec,relatime)
# tmpfs on /run type tmpfs (rw,nosuid,nodev,size=783584k,mode=755)
# tmpfs on /tmp type tmpfs (rw,nosuid,nodev,size=7906112k)
# tmpfs on /dev/shm type tmpfs (rw,nosuid,nodev,inode64)
# cgroup2 on /sys/fs/cgroup type cgroup2 (rw,nosuid,nodev,noexec,relatime)

# Современный читаемый формат:
findmnt
# TARGET                       SOURCE                     FSTYPE  OPTIONS
# /                            /dev/nvme0n1p2             ext4    rw,relatime
# |-/proc                      proc                       proc    rw,nosuid,nodev
# |-/sys                       sysfs                      sysfs   rw,nosuid,nodev
# |-/tmp                       tmpfs                      tmpfs   rw,nosuid,nodev,size=7G
# ...

findmnt — более удобный, чем mount без аргументов. Показывает иерархию.


Виртуальные ФС: /proc и /sys

/proc и /sys — это файловые системы, но без диска. Их файлы генерируются ядром по требованию.

# /proc/cpuinfo не существует на диске -- генерируется на лету:
cat /proc/cpuinfo | head
# processor    : 0
# vendor_id    : GenuineIntel
# cpu family   : 6
# model        : 158
# model name   : Intel(R) Core(TM) i7-8650U CPU @ 1.90GHz
# ...

# Это не файл -- это API к kernel state.
# При каждом cat ядро формирует текст заново.

ls -la /proc/cpuinfo
# -r--r--r-- 1 root root 0 May 18 10:00 /proc/cpuinfo
#                       ^
#                  размер 0 -- ядро не знает заранее, сколько будет

stat /proc/cpuinfo
# Size: 0  Blocks: 0  -- никаких блоков на диске

/proc/[pid]/ — информация о конкретном процессе:

# Полный набор данных о процессе:
ls /proc/self/
# attr/  cmdline  comm  cwd  environ  exe  fd/  fdinfo/  limits  maps
# mem  mounts  net/  ns/  oom_score  smaps  stack  stat  status  ...

cat /proc/self/status | head
# Name:    bash
# State:   R (running)
# Tgid:    12345
# Pid:     12345
# PPid:    12340
# Uid:     1000  1000  1000  1000
# Gid:     1000  1000  1000  1000
# VmSize:  17452 kB
# VmRSS:   4824 kB

/sys — интерфейс к kernel objects (devices, modules, drivers):

# Информация о CPU:
ls /sys/devices/system/cpu/cpu0/
# cache/  cpufreq/  topology/  online  ...

cat /sys/class/net/eth0/operstate
# up  -- состояние сетевого интерфейса

# Что-то можно даже записать (управление kernel-ом):
echo 1 > /sys/class/leds/something/brightness
echo 7000 > /proc/sys/vm/swappiness

Это и есть мощь VFS: ядро экспортирует state как файлы, тулзы (cat, grep, awk) сразу работают с этим без специальных API. Принцип «everything is a file».

NOTE

sysctl — более красивая обёртка над /proc/sys. `sysctl net.ipv4.ip_forward` это то же, что `cat /proc/sys/net/ipv4/ip_forward`. Но cat работает в любом контейнере, sysctl может быть не установлен.


Mount options и mount namespaces

mount — мощная команда с десятками опций:

# Стандартный mount с опциями:
mount -o ro,noexec,nosuid,nodev /dev/sdb1 /mnt/data
#         ^^   ^^^^^^  ^^^^^^  ^^^^^
#         read-only, нельзя exec, нельзя setuid, без device-nodes

# Remount без отмонтирования (изменить опции):
mount -o remount,rw /mnt/data

# Bind mount -- сделать каталог видимым в другом месте:
mount --bind /home/user/work /mnt/work
# Теперь /mnt/work показывает то же, что /home/user/work

# Mount image-файла как ФС (через loop device):
mount -o loop /tmp/disk.img /mnt/image

Опции типа noexec, nosuid — security-классика. noexec запрещает запускать программы из этой ФС (защита от executable malware в /tmp). nosuid отключает setuid-биты.

Mount namespaces — ключевая Linux фишка для контейнеров. Каждый namespace имеет своё дерево mount-ов. Это позволяет:

  • Docker контейнеру видеть свой root (как chroot, но мощнее).
  • Запустить процесс в изоляции, где /tmp — свой, не общий с host.
  • Создать read-only mount для чувствительных директорий в одном namespace.
# Создать новый mount namespace:
unshare --mount --propagation private -- bash
# Внутри новой shell:
mount -t tmpfs none /mnt   # tmpfs тут не виден из host
ls /mnt   # пусто
# В другом терминале (host): ls /mnt  -- не видно tmpfs
exit

systemd с PrivateTmp=yes использует mount namespaces, чтобы каждый сервис имел свой /tmp.

Namespaces в основе контейнеров: mount, PID, net

fstab — автоматический mount при загрузке

/etc/fstab — описание, какие ФС должны быть смонтированы при загрузке:

cat /etc/fstab
# <device>            <mount point>  <fstype>  <options>           <dump> <pass>
# UUID=abc-123-def    /              ext4      defaults,relatime   0      1
# UUID=def-456-abc    /home          ext4      defaults,relatime   0      2
# UUID=...            none           swap      sw                  0      0
# tmpfs               /tmp           tmpfs     defaults,size=4G    0      0

UUID — стабильный идентификатор раздела (blkid показывает). Лучше device-paths типа /dev/sda1, потому что устройства могут перенумероваться (особенно USB).

mount -a — смонтировать все из fstab. Используется при загрузке через systemd-fstab-generator.


Попробуй сам

Исследуйте VFS-иерархию своей системы:

# Все mount-точки и их типы:
findmnt --types ext4,xfs,btrfs,tmpfs

# Размер каждой ФС:
df -h
# Filesystem      Size  Used Avail Use% Mounted on
# /dev/nvme0n1p2  500G  100G  400G  20% /
# tmpfs           7.7G   12K  7.7G   1% /tmp
# tmpfs           7.7G  120M  7.6G   2% /dev/shm

# Использование inode-ов (отдельно от disk space):
df -i
# На ext4 ФС inode-количество фиксировано при создании!
# Можно исчерпать inodes, при свободном диске:
#   df -h  показывает 50% used
#   df -i  показывает 100% used
#   touch file  -> No space left on device

Поиграйте с mount namespaces:

# Создать tmpfs только для текущей shell:
sudo unshare --mount bash
mount -t tmpfs -o size=100M none /tmp
df -h /tmp     # 100 МБ tmpfs

# В другом терминале (host) -- df /tmp обычный
# exit вернёт shell в обычный namespace

Посмотрите на /proc и /sys:

# Размеры всегда 0 -- это не файлы, это API:
ls -la /proc/cpuinfo /proc/meminfo /proc/version
# -r--r--r-- 1 root root 0 May 18 10:00 /proc/cpuinfo
# -r--r--r-- 1 root root 0 May 18 10:00 /proc/meminfo
# -r--r--r-- 1 root root 0 May 18 10:00 /proc/version

# Но cat работает -- ядро генерирует содержимое:
cat /proc/cpuinfo | wc -l   # обычно несколько десятков строк
cat /proc/meminfo | grep MemFree

Проверка знанийKnowledge check
Программа делает open('/tmp/file.txt') в Docker-контейнере. Что именно делает kernel при этом open вызове, учитывая, что /tmp может быть mount-нут как tmpfs, как часть overlayfs, и контейнер живёт в своём mount namespace?
ОтветAnswer
Полный путь обработки: 1) Kernel получает open syscall. 2) Lookup-процесс начинается с корня mount namespace процесса (контейнер видит свой /). Это уже не host root -- через mount namespace и chroot-like setup. 3) VFS идёт по path components: для каждого '/', 'tmp', 'file.txt' выполняется lookup в parent directory inode. Каждый lookup сначала смотрит в dcache (кэш dentry в RAM), при miss -- спускается в filesystem driver. 4) Для '/tmp' VFS может встретить mount point -- /tmp в контейнере часто mount-нут как tmpfs (через docker --tmpfs=/tmp) ИЛИ как overlayfs (rwlayer/lowerlayer). Mount point замещает inode parent-а: путь продолжается с корнем mount-нутой ФС. 5) Если /tmp на overlayfs -- driver проверяет upper layer (writable) сначала, если файла нет -- спускается в lower layer (read-only base image). 'file.txt' может быть в upper, в lower, или whiteout (помечен как deleted). 6) После lookup VFS вызывает inode->i_op->lookup конкретной ФС, открывает file (создаётся struct file), возвращает fd. 7) Всё это видно через strace -e openat -- обычно один openat системный вызов, инкапсулирующий всё описанное. 8) Через /proc/PID/maps можно увидеть, что 'file.txt' открыт через какую конкретно ФС (тип в /proc/PID/mounts).

Проверьте понимание

Результат: 0 из 0
Концептуальный
Вопрос 1 из 5. Что такое VFS (Virtual File System) в Linux и зачем она нужна?

Закончили урок?

Отметьте его как пройденный, чтобы отслеживать свой прогресс

Войдите чтобы оценить урок

Прогресс модуля
0 из 5