06 сем Лекция 5 19.04 - chrislvt/OS GitHub Wiki
Лекция 5
struct dentry_operations
{
/*определяет является ли указанный объект элемента каталога действительным. VFS вызывает эту функцию когда она пытается использовать объект dentry из кеша(d_cache). Для большинства файловых систем этот метод установлен в null, так как denty которые находятся в кеше всегда действительны. Почему? Потому что LRU. */
/*Обратное утверждение является истинным для сетевых файловых систем, когда кеш хранится на одной машине, а файлы на другой */
int(*d_revalidate)(struct dentry*, unsigned int);
...
/*создаёт значение хеш-ключа для указанного объекта dentry, Эта функция вызывается vfs когда выполняется объекта dentry в hash - таблицу.*.
int(*d_hash)(const struct dentry*, struct qstr*);
// сравнение двух имён файлов
int(*d_compare)(const struct dentry*, unsigned int, const char*, const struct qstr*)
// вызывается vfs когда количесто ссылок на dentry становится равным 0
int (*d_delete)(const struct dentry*);
int(*d_init)(struct dentry*);
/*вызывается когда объект dentry становитcя негативным, то есть он потерял свой inode. В результатье VFS вызывает input чтобы освободить соответствующий объект inode.*/
int (*d_input)(struct dentry*, struct inode*);
...
}
Объекты dentry хранятся в slab-кеше, по другому называется slab allocator, который называется dentry_cache. Объекты dentry в этом slab allocator создаются и уничтожаются с помощью kmem_cache_alloc() и kmem_cache_free(). Необходимо обратить внимание на то, что самих объектов dentry нет на диске, поэтому в структуру dentry не включено поле, указывающее на то, что объект был изменен (dirty). Как мы только что узнали, все эти функции вызываются собственно VFS для того чтобы выполнить соответствующие действия.
Распределение slab.
В лабораторной работе которую будем выполнять требуется inode записать в slab cache.
Slab в переводе на русский обозначает пластина, лист, брусок. Распределение slab - механизм управления памятью, обеспечивающий устранение фрагментации и следовательно более эффективное её распределение. Основой способа является хранение информации о ранее размещённых, а затем удалённых из памяти объектов. Такой подход основан на наблюдении, показывающем что часто удалённый из памяти объект загружается в память снова. Загрузка и выгрузка различных объектов приводит к фрагментации памяти. Поэтому в slab распределении удаление или уничтожение объектов не приводит к моментальному освобождению памяти, а как говорится открывает слот куда помещает список свободных слотов. В результате следующий вызов для выделения памяти для такого же объекта(такого же размера) вернёт указатель на слот памяти из списка свободных слотов. Система предоставляет специальные системные вызовы для работы с cache slab в ядре. Фактически slab представляет собой непрерывный участок памяти, обычно это несколько смежных страниц, в котором выполняются указанные действия, смысл которых заключаются в том, что например нам надо кешировать объект dentry, это struct dentry и это 1 размер, для другой структуры(например struct inode) будет другой размер. inode создаются, уничтожаются. Если возникает потребность создания нового inode, то не надо будет выделять память заново (память выделяется страницами, в этом вся проблема помещение в страницу данного объекта), а существует cache inode, при добавлении нового объекта выделенное адресное пространство в этом слабе (его адрес) берется из соответствующего списка слотов.
<linux/slab.h>
#cat/proc/slabinfo
...
inode_cache 7005 14792 486 1598 ....
(имя кеша) (число активных объектов)(общее число объектов)(доступные объекты)(размер byte)
dentry_cache 5469 5880 128 183 ....
Для создания кеша соответственно используется вызов
struct kmem_cache *kmem_cache_create(const char *name, size_t size, size_t offset, unsigned long flags, void (*ctor)(void *));
Для удаления используется
kmem_cache_destroy()
для выделения соответствующего объекта
kmem_cache_alloc()
для освобождения памяти
kmem_cache_free()
Создание нового кеша slab.
struct static kmem_cache *my_cachep;
static void init_my_cache(void)
{
my_cachep = kmem_cache_create("my_cache", 32, 0, SLAB_HWCACHE_ALING, NULL, NULL); // SLAB_HWCACHE_ALING, - флаг указывает способ выравнивания кеша в памяти
return;
}
Выделение и освобождение объектов
int slab_test(void)
{
void *object;
printk("Cache name is %s\n", kmem_cache_name(my_cachep));
printk("Cache object size is %d\n", kmem_cache_size(my_cache));
object = kmem_cache_alloc(my_cachep, GEP_KERNEL); // флаг GEP_KERNEL указывает, что память должна быть выделена в RAM ядра
if(object)
{
kmem_cache_free(my_cachep, object);
}
return 0;
}
Удаление slab_cache
static void remove_my_cache(void)
{
if(my_cachep) kmem_cache_destroy(my_cachep);
return;
}
доп материалы - "Анатомия распределителя памяти slab в Linux"
INODE_CACHE
В Linux исходный код реализации inode_cache можно посмотреть в fs/inode.c
Структура в Linux для inode cache:
-
Глобальная хеш-таблица inode_hash_table в которой каждый inode хешируется значением указателя суперблока и 32х-разрядным кодом inode. Если у inode нет суперблока, то есть inode ->i_sb = null, в этом случае эти inode добавляются к двунаправленному связанному списку который начинается anon_hash_chain. Примеры анонимных inod - сокеты, созданные с помощью функции sosk_alloc().
-
Список in_use Содержит inode у которых i_count > 0 и i_nlink > 0.
-
Список неиспользуемых inode - unused. inode_unused. Содержит inode у которых i_count = 0.
-
Список per_superblock(наш суперблок) содержит используемы inode, то есть i_count > 0, i_nlink > 0, i_state = dirty. Когда inode отмечен как dirty, он добавляется к списку sb -> s_dirty. Это делается для того чтобы синхронизировать информацию в ядре и на диске. Чтобы контролировать, что данный inode должен быть изменен и на диске.
-
inode_cache_proper - SLAB-cache, который называется inode_cachep
Объект inode, когда он создаётся, то он берется из slab_cache, а когда удаляется, то возвращается в этот slab_cache.
Каждый inode в хеш-таблице может находиться только в одном типе списка. Типы списка это пункты 2, 3, 4.
Регистрация и дерегистрация файловой системы.
Код, реализующий файловую систему может быть выполнен в виде или загружаемого модуля ядра или статически связан с ядром. Для создания новой файловой системы надо заполнить поля структуры struct file_system_type. Она была приведена. Существует досаточно большое количество примеров, они все приводят довольно похожие примеры.
Рассмотрим пример.
struct file_system_type my_fs_type =
{
.owner = THIS_MODULE,
.name = "my",
.mount = my_mount, // хранит указатель на соответствующую функцию
.kill_sb = my_kill_superblock, // хранит указатель на соответствующую функцию
.fs_flags = FS_REQUIRES_DEV, // флаг - требуется блочное устройство. НО надо подумать если мы пишем VFS и она не связана в блочным устройством, то какой флаг надо использовать.
};
Необходимо внимательно отнестись к выбору функции mount. Функций mount несколько. Если ориентироваться на данную структуру, надо выбрать mount_bdev;
ФУНКЦИИ mount
extern struct dentry *mount_bdev(
struct file_system_type *fs_type,
int flags, const *dev_name, void *data,
int (*fill_super)(struct syper_block*, void*, int));
функция fill_super делает все действия по заполнению(созданию) суперблока. Но если нет блочного устройства, то нужна другая функция для монтирования.
extern struct dentry *mount_nodev(
struct file_system_type *fs_type, int flags, void *data, int(*fill_super)(struct super_block*, void*, int));
есть еще функции, например
--//--mount_ns (--//--)
--//--mount_single (--//--)
Параметры такие же.
Противоположная mount функция
void kill_block_super(struct super_block *sb);
Поле owner нужно для организации счётчика ссылок на модуль. Счётчик ссылок нужен для того чтобы модуль не был выгружен когда файловая система примонтирована, так как это может привести к краху. Счётик ссылок не позволит выгрузить модуль если он используется, то есть до тех пор пока файловая система не будет отмонтирована(демонтирована).
Для того чтобы зарегестрировать свою файловую систему используется функция register_filesystem. Для удаления используется функция unregister_fylesystem.
static int __init my_init(void)
{
int ret;
// перед тем как вызывать такую функцию необходимо определить структуру my_inode.
my_inode_cachep = kmem_cache_create("my_inode_cachep", sizeof(struct my_inode), 0, (SLAB_RECLAIM_ACCOUNT | SLAB_MEM_SPREAD), NULL);
if (! my_inode_cachep)
{
return -ENDMEM;
}
ret = register_filesystem(&my_fs_type);
if (likely (ret == 0))
printk(KERN_INFO "Success\n");
else printk(KERN_ERR "Failed.Error -%d\n", ret);
return ret;
}
static void __exit my_cleanup(void)
{
int ret;
ret = unregister_fylesystem (&f_my_fs_type);
kmem_cache_destroy(my_inode_cachep);
if (likely (ret == 0))
printk(KERN_INFO "Success\n");
else
printk(LERN_ERR "Faild. Error = %d\n", ret);
}