Кеш Inode и взаимодействие с Dcache
Для поддержки различных файловых систем Linux предоставляет специальный интерфейс уровня ядра, который называется VFS (Virtual Filesystem Switch). Он подобен интерфейсу vnode/vfs, имеющемуся в производных от SVR4 (изначально пришедшему из BSD и реализаций Sun)
Реализация inode cache для Linux находится в единственном файле fs/inode.c, длиной в 977 строк (Следует понимать, что размер файла может колебаться от версии к версии, так например в ядре 2.4.18, длина этого файла составляет 1323 строки прим. перев.). Самое интересное, что за последние 5 - 7 лет этот файл претерпел незначительные изменения, в нем до сих пор можно найти участки кода, дошедшие до наших дней с версии, скажем, 1.3.42
Inode cache в Linux представляет из себя:
Через поле inode->i_list с inode вставляется в список определенного типа, через поле inode->i_hash
- в хеш-массив. Каждый inode может входить в хеш-массив и в один и только в один список типа (in_use, unused или dirty).
Списки эти защищаются блокировкой (spinlock) inode_lock.
Подсистема inode cache инициализируется при вызове функции inode_init() из init/main.c:start_kernel(). Эта функция имеет один входной параметр - число страниц физической памяти в системе. В соответсвии с этим параметром inode cache конфигуририруется под существующий объем памяти, т.е. при большем объеме памяти создается больший хеш-массив.
Единственная информация о inode cache, доступная пользователю - это количество неиспользованных inodes из inodes_stat.nr_unused. Получить ее можно из файлов /proc/sys/fs/inode-nr и /proc/sys/fs/inode-state.
Можно исследовать один из списков с помощью gdb:
(gdb) printf "%d\n", (unsigned long)(&((struct inode *)0)->i_list) 8 (gdb) p inode_unused $34 = 0xdfa992a8 (gdb) p (struct list_head)inode_unused $35 = {next = 0xdfa992a8, prev = 0xdfcdd5a8} (gdb) p ((struct list_head)inode_unused).prev $36 = (struct list_head *) 0xdfcdd5a8 (gdb) p (((struct list_head)inode_unused).prev)->prev $37 = (struct list_head *) 0xdfb5a2e8 (gdb) set $i = (struct inode *)0xdfb5a2e0 (gdb) p $i->i_ino $38 = 0x3bec7 (gdb) p $i->i_count $39 = {counter = 0x0}
Заметьте, что от адреса 0xdfb5a2e8 отнимается число 8, чтобы получить адрес struct inode (0xdfb5a2e0), согласно определению макроса list_entry() из include/linux/list.h.
Для более точного понимания принципа работы inode cache, давайте рассмотрим цикл жизни обычного файла в файловой системе ext2 с момента его открытия и до закрытия.
fd = open("file", O_RDONLY); close(fd);
Системный вызов open(2) реализован в виде функции fs/open.c:sys_open, но основную работу выполняет функция fs/open.c:filp_open(), которая разбита на две части:
Функция open_namei() взаимодействует с dentry cache через path_walk(), которая, в свою очередь, вызывает real_lookup(), откуда вызывается метод inode_operations->lookup(). Назначение последнего - найти вход в родительский каталог и получить соответствующий inode вызовом iget(sb, ino) При считывании inode, значение dentry присваивается посредством d_add(dentry, inode). Следует отметить, что для UNIX-подобных файловых систем, поддерживающих концепцию дискового номера inode, в ходе выполнения метода lookup(). производится преобразование порядка следования байт числа (endianness) в формат CPU, например, если номер inode хранится в 32-битном формате с обратным порядком следования байт (little-endian), то выполняются следующие действия:
(Считаю своим долгом подробнее остановиться на понятии endianness. Под этим термином понимается порядок хранения байт в машинном слове (или двойном слове). Порядок может быть "прямым" (т.е. 32-битное число хранится так 0x12345678) и тогда говорят "big endianness" (на отечественном жаргоне это звучит как "большой конец", т.е. младший байт лежит в старшем адресе) или "обратным" (т.е. 32-битное число хранится так 0x78563412 - такой порядок следования байт принят в архитектуре Intel x86) и тогда говорят "little endianness" (на отечественном жаргоне это звучит как "маленький конец", т.е. младший байт лежит в младшем адресе). прим. перев.)
unsigned long ino = le32_to_cpu(de->inode); inode = iget(sb, ino); d_add(dentry, inode);
Таким образом, при открытии файла вызывается iget(sb, ino), которая, фактически, называется iget4(sb, ino, NULL, NULL), эта функция:
Теперь рассмотрим действия, производимые при закрытии файлового дескриптора. Системный вызов close(2) реализуется функцией fs/open.c:sys_close(), которая вызывает do_close(fd, 1). Функция do_close(fd, 1)
записывает NULL на место дескриптора файла в таблице дескрипторов процесса и вызывает функцию filp_close(), которая и выполняет большую часть действий. Вызывает интерес функция fput(), которая проверяет была ли это последняя ссылка на файл и если да, то через fs/file_table.c:_fput()
вызывается __fput(), которая взаимодействует с dcache (и таким образом с inode cache - не забывайте, что dcache является "хозяином" inode cache!). Функция fs/dcache.c:dput() вызывает dentry_iput(), которая приводит нас обратно в inode cache через iput(inode). Разберем fs/inode.c:iput(inode) подробнее:
без захвата блокировки (так что он может быть блокирован).
Дополнительные действия, выполняемые по закрытию в случае последней ссылки функцией iput(), достаточно сложны, поэтому они рассматриваются отдельно:
(например ramfs), то вызывается clear_inode(inode), откуда производится вызов s_op->clear_inode(), если этот метод зарегистрирован и inode соответствует блочному устройству. Счетчик ссылок на это устройство уменьшается вызовом bdput(inode->i_bdev).