WWW.DISSERS.RU

БЕСПЛАТНАЯ ЭЛЕКТРОННАЯ БИБЛИОТЕКА

   Добро пожаловать!

Pages:     | 1 |   ...   | 4 | 5 || 7 | 8 |   ...   | 9 |

«; ...»

-- [ Страница 6 ] --

} Управление памятью Память освобождается с помощью функции kmem_freepages (), которая вызыва ет функцию f r e e _ p a g e s () для освобождения необходимых страниц кэша. Конечно, назначение уровня слябового распределения — это воздержаться от выделения и освобождения страниц памяти. На самом деле слябовый распределитель использует функции выделения памяти только тогда, когда в данном кэше не доступен ни один частично заполненный или пустой сляб. Функция освобождения памяти вызывается только тогда, когда становится мало доступной памяти и система пытается освобо дить память или когда кэш полностью ликвидируется.

Уровень слябового распределения управляется с помощью простого интерфейса, и это можно делать отдельно для каждого кэша. Интерфейс позволяет создавать или ликвидировать новые кэши, а также выделять или уничтожать объекты в этих кэ шах. Все механизмы оптимизации управления кэшами и слябами полностью управля ются внутренними элементами уровня слябового распределения памяти. После того как кэш создан, слябовый распределитель памяти работает, как специализированная система создания объектов определенного типа.

Интерфейс слябового распределителя памяти Новый кэш можно создать с помощью вызова следующей функции.

kmern_cache_t * kmem_cache_create (const char *name, size_t size, size_t offset, unsigned long flags, void (*ctor) (void*, kmem_cache_t *,unsigned long), void (*dtor) (void*, kmem_cache_t *,unsigned long)) Первый параметр — это строка, которая содержит имя кэша. Второй параметр — это размер каждого элемента кэша. Третий параметр — это смещение первого объек та в слябе. Он нужен для того, чтобы обеспечить необходимое выравнивание по гра ницам страниц памяти. Обычно достаточно указать значение, равное нулю, которое соответствует выравниванию по умолчанию. Параметр f l a g s указывает опциональ ные параметры, которые управляют поведением кэша. Он может быть равен нулю, что выключает все специальные особенности поведения, или состоять из одного или более значений, показанных ниже и объединенных с помощью логической опе рации ИЛИ.

• SLAB_NO_REAP — этот флаг указывает, что кэш не должен автоматически "уби рать мусор" (т.е. освобождать память, в которой хранятся неиспользуемые объ екты) при нехватке памяти в системе. Обычно этот флаг не нужно устанавли вать, поскольку если этот флаг установлен, то созданный кэш может помешать нормальной работе системы при нехватке памяти.

• SLAB_HWCACHE_ALIGN — этот флаг указывает уровню слябового распределения памяти, что расположение каждого объекта в слябе должно выравниваться по строкам процессорного кэша. Это предотвращает так называемое "ошибочное распределение", когда два или более объектов отображаются в одну и ту же строку системного кэша, несмотря на то что они находятся по разным адре сам памяти. При использовании этого флага возрастает производительность, но это делается ценой увеличения занимаемой памяти, потому что строгое вы равнивание приводит к тому, что часть памяти сляба не используется. Степень 252 Глава увеличения занимаемой памяти зависит от размера объектов кэша и от того, каким образом происходит их выравнинание по отношению к строкам систем ного кэша. Для кэшей, которые часто используются в коде, критичном к про изводительности, будет правильным установить этот флаг, в остальных случаях следует подумать, стоит ли это делать.

• SLAB_MUST_HWCACHE_ALIGN. Если включен режим отладки, то может оказаться невозможным одновременная отладка и выравнивание положения объектов по строкам системного кэша. Этот флаг указывает уровню слябового распределе ния памяти, что необходимо форсировать выравнивание положения объектов по строкам системного кэша. В обычной ситуации этот флаг указывать необя зательно, и достаточно использовать предыдущий. Установка этого флага в ре жиме отладки слябового распределителя памяти (по умолчанию отладка запре щена) может привести к значительному увеличению затрат памяти. Только для объектов, которые критичны к выравниванию по строкам системного кэша, таких как дескрипторы процессов, необходимо указывать данный флаг.

• SLABPOSON— этот флаг указывает на необходимость заполнения слябов из вестным числовым значением (а5а5а5а5). Эта операция называется "отравле нием" (poisoning) и служит для отслеживания доступа к неинициализированной памяти.

• SLAB_RED_ZONE — этот флаг указывает на необходимость выделения так назы ваемых "красных зон" (red zone) для облегчения детектирования переполнений буфера.

• SLAB_PANIC — этот флаг указывает на необходимость перевода ядра в состоя ние паники, если выделение памяти было неудачным. Данный флаг полезен, если выделение памяти всегда должно завершаться успешно, как, например, в случае создания кэша структур VMA (областей виртуальной памяти, см. гла ву 14, "Адресное пространство процесса") при загрузке системы.

• SLAB_CACHE_DMA — этот флаг указывает уровню слябового распределения, что все слябы должны выделяться в памяти, с которой возможны операции пря мого доступа к памяти. Это необходимо, когда объекты используются в опе рациях ПДП и должны находиться в зоне ZONE_DMA. В противном случае эта возможность не нужна и этот флаг не нужно устанавливать.

Два последних параметра c t o r и d t o r — это конструктор и деструктор кэша со ответственно. Конструктор вызывается, когда в кэш добавляются новые страницы памяти. Деструктор вызывается, когда из кэша удаляются страницы памяти. Если указан деструктор, то должен быть указан и конструктор. На практике кэши ядра ОС Linux обычно не используют функции конструктора и деструктора. В качестве этих параметров можно указывать значение NULL.

В случае успешного выполнения функция kmem_cache_create () возвращает ука затель на созданный кэш. В противном случае возвращается NULL. Данная функция не может вызываться в контексте прерывания, так как она может переводить про цесс в состояние ожидания. Для ликвидации кэша необходимо вызвать следующую функцию.

int kmem_cache_destroy(kmem_cache_t *cachep) Управление памятью Эта функция ликвидирует указанный кэш. Она обычно вызывается при выгрузке модуля, который создает свой кэш. Из контекста прерывания эту функцию вызывать нельзя, так как она может переводить вызывающий процесс в состояние ожидания.

Перед вызовом этой функции необходимо, чтобы были выполнены следующие два условия.

• Все слябы кэша являются пустыми. Действительно, если в каком-либо слябе су ществует объект, который все еще используется, то как можно ликвидировать кэш?

• Никто не будет обращаться к кэшу во время и особенно после вызова функции kmem_cache_destroy (). Эту синхронизацию должен обеспечить вызывающий код.

В случае успешного выполнения функция возвращает нуль, в других случаях воз вращается ненулевое значение.

После того как кэш создан, из него можно получить объект путем вызова следую щей функции.

void * kmem_cache_alloc(kmem_cache_t *cachep, int flags) Эта функция возвращает указатель на объект из кэша, на который указывает пара метр cachep. Если ни в одном из слябов нет свободных объектов, то уровень слябо вого распределения должен получить новые страницы памяти с помощью функции kmem_getpages (), значение параметра f l a g s передается в функциюget_free_ pages (). Это те самые флаги, которые были рассмотрены ранее. Скорее всего, не обходимо указывать GFP_KERNEL или GFP_ATOMIC.

Далее для удаления объекта и возвращения его в сляб, из которого он был выде лен, необходимо использовать следующую функцию.

void kmem_cache_free(kmem_cache_t *cachep, void *objp) Данная функция помечает объект, на который указывает параметр objp, как сво бодный.

Пример использования слябового распределителя памяти Давайте рассмотрим пример из реальной жизни, связанный с работой со струк турами t a s k _ s t r u c t (дескрипторы процессов). Показанный ниже код в несколько более сложной форме приведен в файле kernel/fork.с.

В ядре определена глобальная переменная, в которой хранится указатель на кэш объектов t a s k _ s t r u c t :

kmem_cache_t *task_struct_cachep;

Во время инициализации ядра, в функции f o r k i n i t (), этот кэш создается сле дующим образом.

task_struct_cachep = kmem_cache_create("task_struct", sizeof(struct task_struct), ARCH_M1N_TASKALIGN, SLAB_PANIC, NULL, NULL);

254 Глава Данный вызов создает кэш с именем " t a s k _ s t r u c t ", который предназначен для хранения объектов тина s t r u c t t a s k _ s t r u c t. Объекты создаются с начальным сме щением в слябе, равным ARCH_MIN_TASKALIGN байт, и положение всех объектов вы равнивается по границам строк системного кэша, значение этого выравнивания зави сит от аппаратной платформы. Обычно значение выравнивания задается для каждой аппаратной платформы с помощью определения препроцессора LI_CACHE_BYTES, которое равно размеру процессорного кэша первого уровня в байтах. Конструктор и деструктор отсутствуют. Следует обратить внимание, что возвращаемое значение не проверяется на рапенство NULL, поскольку указан флаг SLAB_PANIC. В случае, когда при выделении памяти произошла ошибка, слябовый распределитель памяти вызо вет функцию p a n i c ( ). Если этот флаг не указан, то нужно проверять возвращаемое значение на равенство NULL, что сигнализирует об ошибке. Флаг SLAB_PANIC здесь используется потому, что этот каш является необходимым для работы системы (без дескрипторов процессов работать как-то не хорошо).

Каждый раз, когда процесс вызывает функцию f o r k ( ), должен создаваться но вый дескриптор процесса (вспомните главу 3, "Управление процессами"). Это выпол няется следующим образом в функции d u p _ t a s k _ s t r u c t (), которая вызывается из функции do_fork ().

struct task_struct *tsk;

tsk = kmem_cache_alloc(task struct_cachep, GFP_KERNEL);

if (!tsk) return NULL;

Когда процесс завершается, если нет порожденных процессов, которые ожидают на завершение родительского процесса, то дескриптор освобождается и возвраща ется обратно в кэш t a s k _ s t r u c t _ c a c h e p. Эти действия выполняются в функции f r e e _ t a s k _ s t r u c t (), как показано ниже (где параметр t s k указывает на удаляе мый дескриптор).

kmem_cache_free(task_struct_cachep, tsk);

Так как дескрипторы процессов принадлежат к основным компонентам ядра и всегда необходимы, то кэш t a s k _ s t r u c t _ c a c h e p никогда не ликвидируется. Если бы он ликвидировался, то делать это необходимо было бы следующим образом.

int err;

err = kmem_cache_destroy (task_struct_cachep);

if (err) /* ошибка ликвидации кэша */ Достаточно просто, не так ли? Уровень слябопого распределения памяти скрыва ет все низкоуровневые операции, связанные с выравниванием, "раскрашипанием", выделением и освобождением памяти, "сборкой мусора" в случае нехватки памяти.

Коли часто необходимо создавать много объектов одного типа, то следует подумать об использовании слябового кэша. И уж точно не нужно писать свою реализацию списка свободных ресурсов!

Управление памятью Статическое выделение памяти в стеке В пространстве пользователя многие операции выделения памяти, в частности некоторые рассмотренные ранее примеры, могут быть выполнены с использовани ем стека, потому что априори известен размер выделяемой области памяти. В про странстве пользователя доступна такая роскошь, как очень большой и динамически увеличивающийся стек задачи, однако в режиме ядра такой роскоши нет — стек ядра маленький и фиксирован по размеру. Когда процессу выделяется небольшой и фик сированный по размеру стек, то затраты памяти уменьшаются и ядру нет необходи мости выполнять дополнительные функции по управлению памятью.

Размер стека зависит как от аппаратной платформы, так и от конфигурационных параметров, которые были указаны на этапе компиляции. Исторически размер сте ка ядра был равен двум страницам памяти для каждого процесса. Это соответствует 8 Кбайт для 32-разрядных аппаратных платформ и 16 Кбайт для 64-разрядных аппа ратных платформ.

В первых версиях ядер серии 2.6 была введена возможность конфигурации, для которой размер стека ядра равен одной странице памяти. Когда устанавливается та кая конфигурация, то процесс получает стек, по размеру равный всего одной страни це памяти: 4 Кбайт на 32-разрядных аппаратных платформах и 8 Кбайт — на 64-раз рядных. Это сделано по двум причинам. Во-первых это уменьшает затраты памяти на одну страницу для каждого процесса. Во-вторых, что наиболее важно, при увели чении времени работы системы (uptime) становится все тяжелее искать две физиче ски смежные страницы памяти. Физическая память становится все более фрагмен тированной, и нагрузка на систему управления виртуальной памятью при создании новых процессов становится все более существенной.

Существует еще одна сложность (оставайтесь с нами, и Вы узнаете все о стеках ядра). Вся последовательность вложенных вызовов функций в режиме ядра должна поместиться в стеке. Исторически обработчики прерываний используют стек того процесса, выполнение которого они прервали. Это означает, что в худшем случае 8 Кбайт стека должно использоваться совместно всеми вложенными вызовами функ ций и еще парой обработчиков прерываний. Все это эффективно и просто, но это накладывает еще больше ограничений на использование стека ядра. Когда размер стека сократился до одной страницы памяти, обработчики прерываний туда пере стали помещаться.

Для решения указанной проблемы была реализована еще одна возможность — сте ки обработчиков прерываний. Стеки прерываний представляют собой один стек на каждый процессор, которые используются для обработки прерываний. При такой конфигурации обработчики прерываний больше не используют стеки ядра тех про цессов, которые этими обработчиками прерываются. Вместо этого они используют свои собственные стеки. Это требует только одну страницу памяти на процессор.

Подведем итоги. Стек ядра занимает одну или две страницы памяти, в зависимо сти от конфигурации, которая выполняется перед компиляцией ядра. Следовательно, размер стека ядра может иметь диапазон от 4 до 16 Кбайт. Исторически обработчи ки прерываний совместно использовали стек прерванного ими процесса. При по явлении стеков ядра размером в одну страницу памяти обработчикам прерываний были назначены свои стеки. В любом случае неограниченная рекурсия и использова ние функций вроде a l l o c a ( ) явно не допустимы.

256 Глава Честная игра со стеком В любой функции необходимо сокращать использование стека до минимума. Хотя не существует твердых правил, тем не менее следует поддерживать максимальный суммарный объем всех локальных переменных (также известных как автоматические переменные или переменные, выделенные в стеке) не больше нескольких сотен бай тов. Опасно статически выделять большие объекты в стеке, такие как большие мас сивы структур. В противном случае выделение памяти в стеке будет выполняться так же, как и в пространстве пользователя. Переполнение стека происходит незаметно и обычно приводит к проблемам. Так как ядро не выполняет никакого управления стеком, то данные стека просто перепишут все, что находится за стеком. В первую очередь пострадает структура t h r e a d _ i n f o, которая расположена в самом конце стека процесса (вспомните главу 3). За пределами стека все данные ядра могут про пасть. В лучшем случае при переполнении стека произойдет сбой в работе машины.

В худших случаях может произойти повреждение данных.

В связи с этим, для выделения больших объемов памяти необходимо использо вать одну из динамических схем выделения памяти, которые были рассмотрены раньше в этой главе.

Отображение верхней памяти По определению, страницы верхней памяти не могут постоянно отображаться в адресное пространство ядра. Поэтому страницы памяти, которые были выделены с помощью функции a l l o c _ p a g e s (), при использовании флага GFPHIGHMEM мо гут не иметь логического адреса.

Для аппаратной платформы х86 вся физическая память свыше 896 Мбайт помеча ется как верхняя память, и она не может автоматически или постоянно отображать ся в адресное пространство ядра, несмотря на то что процессоры платформы х могут адресовать до 4 Гбайт физической памяти (до 64 Гбайт при наличии расшире ния РАЕ6). После выделения эти страницы должны быть отображены в логическое адресное пространство ядра. Для платформы х86 страницы верхней памяти отобра жаются где-то между отметками 3 и 4 Гбайт.

Постоянное отображение Для того чтобы отобразить заданную структуру page в адресное пространство ядра, необходимо использовать следующую функцию.

void *kmap(struct page *page) Эта функция работает как со страницами нижней, так и верхней памяти. Если структура page соответствует странице нижней памяти, то просто возвращается вир туальный адрес. Если страница расположена в верхней памяти, то создается посто янное отображение этой страницы памяти и возвращается полученный логический РАЕ — Physical Address Extension (расширение физической адресации). Эта функция процессоров х86 позволяет физически адресовать до 36 разрядов (64 Гбайт) памяти, несмотря на то что размер виртуального адресного пространства соответствует только 32 бит.

Управление памятью адрес. Функция kmap () может переводить процесс в состояние ожидания, поэтому ее можно вызывать только в контексте процесса.

Поскольку количество постоянных отображений ограничено (если бы это было не так, то мы бы не мучились, а просто отобразили всю необходимую память), то отображение страниц верхней памяти должно быть отменено, если оно больше не нужно. Это можно сделать с помощью вызова следующей функции.

void kunmap(struct page *page) Данная функция отменяет отображение страницы памяти, связанной с параме тром page.

Временное отображение В случаях, когда н е о б х о д и м о создать о т о б р а ж е н и е с т р а н и ц п а м я т и в адресное пространство, а текущий контекст не может переходить в состояние ожидания, ядро предоставляет функцию временного отображении ( к о т о р о е также называется атомар ным отображением). Существует н е к о т о р о е количество з а р е з е р в и р о в а н н ы х постоян ных отображений, которые могут временно выполнять отображение "на лету". Ядро может автоматически отображать страницу верхней памяти в одно из зарезервиро ванных о т о б р а ж е н и й. Временное отображение может использоваться в коде, кото р ы й не может переходить в состояние ожидания, как, например, контекст прерыва ния, потому что полученное отображение никогда не блокируется.

Установка временного о т о б р а ж е н и я в ы п о л н я е т с я с помощью следующей функ ции.

void *kmap_atomic(struct page *page, enum km_type type) Параметр t y p e — это одно из значений показанного ниже перечисления, опреде ленного в файле < a s m / k m a p _ t y p e s. h >, которое описывает цель временного отобра жения.

enum km_type { KM_BOUNCE_READ, KM_SKB_SUNRPC_DATA, KM_SKB_DATA_SOFTIRQ, KM_USER0, KM_USER1, KM_BIO_SRC_IRQ, KM_BIO_DST_IRQ, KM_PTE0, KM_PTE1, KM_PTE2, KM_IRQ0, KM_IRQ1, KM_SOFTIRQ0, KM_SOFTIRQ1, KM TYPE_NR );

Данная функция не блокируется и поэтому может использоваться в контексте прерывания и в других случаях, когда нельзя перепланировать выполнение. Эта 258 Глава функция также запрещает преемптивность ядра, что необходимо потому, что отобра жения являются уникальными для каждого процессора (а перепланирование может переместить задание для выполнения на другом процессоре).

Отменить отображение можно с помощью следующей функции.

void kunmap_atomic(void *kvaddr, enum km_type type) Эта функция также не блокирующая. На самом деле для большинства аппаратных платформ она ничего не делает, за исключением разрешения преемптивности ядра, потому что временное отображение действует только до тех пор, пока не создано новое временное отображение. Поэтому ядро просто "забывает" о вызове функции kmap_atomic (), и функции kunmap atomic () практически ничего не нужно делать.

Следующее атомарное отображение просто заменяет предыдущее.

Выделение памяти, связанной с определенным процессором В современных операционных системах широко используются данные, связанные с определенными процессорами (per-CPU data). Это данные, которые являются уни кальными для каждого процессора. Данные, связанные с процессорами, хранятся в массиве. Каждый элемент массива соответствует своему процессору системы. Номер процессора является индексом в этом массиве. Таким образом была реализована ра бота с данными, связанными с определенным процессором, в ядрах серии 2.4. В та ком подходе нет ничего плохого, поэтому значительная часть кода ядра в серии 2. все еще использует этот интерфейс. Данные объявляются следующим образом, unsigned long my_percpu[NR_CPUS];

Доступ к этим данным выполняется, как показано ниже.

int cpu;

cpu = get_cpu();

/* получить номер текущего процессора и запретить вытеснение в режиме ядра */ my_percpu[cpu]++;

printk("значение данных процессора cpu=%d равно %ld\n", cpu, my_percpu[cpu]);

put_cpu();

/* разрешить вытеснение в режиме ядра */ Обратите внимание, что не нужно использовать никаких блокировок, потому что данные уникальны для каждого процессора. Поскольку никакой процессор, кроме текущего, не может обратиться к соответствующему элементу данных, то не может возникнуть и никаких проблем с конкурентным доступом, а следовательно, текущий процессор может безопасно обращаться к данным без блокировок.

Возможность вытеснения процессов в режиме ядра— единственное, из-за чего могут возникнуть проблемы. В преемптивном ядре могут возникнуть следующие две проблемы.

• Если выполняющийся код вытесняется и позже планируется для выполнения на другом процессоре, то значение переменной cpu больше не будет действи тельным, потому что эта переменная будет содержать номер другого процессо Управление памятью pa. (По той же причине, после получения номера текущего процессора, нельзя переходить в состояние ожидания.) • Если некоторый другой код вытеснит текущий, то он может параллельно об ратиться к переменной my_percpu на том же процессоре, что соответствует состоянию гонок за ресурс.

Однако все опасения напрасны, потому что вызов функции g e t c p u (), которая возвращает номер текущего процессора, также запрещает вытеснение в режиме ядра. Соответствующий вызов функции put_cpu () разрешает вытеснение кода в ре жиме ядра. Обратите внимание, что функция smp_processor_icl (), которая также позволяет получить номер текущего процессора, не запрещает вытеснения кода в режиме ядра, поэтому для безопасной работы следует использовать указанный выше метод.

Новый интерфейс percpu В ядрах серии 2.6 предложен новый интерфейс, именуемый percpu, который слу жит для создания данных и работы с данными, связанными с определенным процес сором. Этот интерфейс обобщает предыдущий пример. При использовании нового подхода работа с per-CPU-данными упрощается.

Рассмотренный ранее метод работы с данными, которые связаны с определен ным процессором, является вполне законным. Новый интерфейс возник из необ ходимости иметь более простой и мощный метод работы с данными, связанными с процессорами, на больших компьютерах с симметричной мультипроцессорностью.

Все подпрограммы объявлены в файле < l i n u x / p e r c p u. h >. Описания же нахо дятся в файлах mm/slab.с и.

Работа с данными, связанными с процессорами, на этапе компиляции Описать переменную, которая связана с определенным процессором, на этапе компиляции можно достаточно просто следующим образом.

DEFINE_PER_CPU(type, name);

Это описание создает переменную типа t y p e с именем name, которая имеет ин терфейс связи с каждым процессором в системе. Если необходимо объявить соот ветствующую переменную с целью избежания предупреждений компилятора, то не обходимо использовать следующий макрос.

DECLARE_PER_CPU(type, name);

Работать с этими переменными можно с помощью функций g e t _ c p u _ v a r () и p u t _ c p u _ v a r (). Вызов функции g e t _ c p u _ v a r () возвращает 1-значенис (левый операнд, 1-value) указанной переменной на текущем процессоре. Этот вызов так же запрещает вытеснение кода в режиме ядра, а соответственный вызов функции p u t _ c p u _ v a r () разрешает вытеснение.

260 Глава get_cpu_var(name)++;

/* увеличить на единицу значение переменной name, связанное с текущим процессором */ put_cpu_var();

/* разрешить вытеснение кода в режиме ядра */ М о ж н о также получить доступ к переменной, связанной с другим процессором.

per_cpu(name, cpu)++;

/* увеличить значение переменной name на указанном процессоре */ Использовать функцию per_cpu () необходимо осторожно, так как этот вызов не запрещает вытеснение кода и не обеспечивает никаких блокировок. Необходимость использования блокировок при работе с данными, связанными с определенным про цессором, отпадает, только если к этим данным может обращаться один процессор.

Если процессоры обращаются к данным других процессоров, то необходимо исполь зовать блокировки. Будьте осторожны! Применение блокировок рассматривается в главе 8, "Введение в синхронизацию выполнения кода ядра", и главе 9, "Средства синхронизации в ядре".

Необходимо сделать еще одно важное замечание относительно создания данных.

связанных с процессорами, на этапе компиляции. Загружаемые модули не могут ис пользовать те из них, которые объявлены не в самом модуле, потому что компонов щик создает эти данные в специальных сегментах кода (а именно,. d a t a. p e r c p u ).

Если необходимо использовать данные, связанные с процессорами, в загружаемых модулях ядра, то нужно создать эти данные для каждого модуля отдельно или ис пользовать динамически создаваемые данные.

Работа с данными процессоров на этапе выполнения Для динамического создания данных, связанных с процессорами, в ядре реа лизован специальный распределитель памяти, который имеет интерфейс, анало гичный kmalloc (). Эти функции позволяют создать экземпляр участка памяти для каждого процессора в системе. Прототипы этих функций объявлены в файле следующим образом.

void *alloc percpu(type);

/* макрос */ void *alloc_percpu(size_t size, size_t align);

void free_percpu(const void * ) ;

Функция a l l o c percpu () создает экземпляр объекта заданного типа (выделяет память) для каждого процессора в системе. Эта функция является оболочкой вокруг функции alloc_percpu (). Последняя функция принимает в качестве аргументов количество байтов памяти, которые необходимо выделить, и количество байтов, но которому необходимо выполнить выравнивание этой области памяти. Функция alloc_percpu () выполняет выравнивание по той границе, которая используется для указанного типа данных. Такое выравнивание соответствует обычному поведе нию, как показано в следующем примере.

struct rabid_cheetah = alloc_percpu(struct rabid_cheetah);

, что аналогично следующему вызову.

struct rabid_cheetah = alloc_percpu(sizeof (struct rabid_cheetah), alignof (struct rabid_cheetah));

Управление памятью Оператор _ _ a l i g n o f _ _ — это расширение, предоставляемое компилятором gcc, который возвращает количество байтов, по границе которого необходимо выпол нять выравнивание (или рекомендуется выполнять для тех аппаратных платформ, у которых нет жестких требований к выравниванию данных в памяти). Синтаксис этого вызова такой же как и у оператора s i z e o f (). В примере, показанном ниже, для аппаратной платформы х86 будет возвращено значение 4.

alignof (unsigned long) При передаче 1-значения (левое значение, lvalue) возвращается максимально возможное выравнивание, которое может потребоваться для этого 1-значения.

Например, 1-значение внутри структуры может иметь большее значение выравнива ния, чем это необходимо для хранения того же типа данных за пределами структуры, что связано с особенностями выравнивания структур данных в памяти. Проблемы выравнивания более подробно рассмотрены в главе 19, "Переносимость".

Соответствующий вызов функции f r e e p e r c p u () освобождает память, которую занимают соответствующие данные на всех процессорах.

Функции a l l o c _ p e r c p u () и _ _ a l l o c _ p e r c p u () возвращают указатель, который используется для косвенной ссылки на динамически созданные данные, связанные с каждым процессором в системе. Для простого доступа к данным ядро предоставляет два следующих макроса.

get_cpu_ptr(ptr) ;

/* возвращает указатель типа void на данные, соответствующие параметру ptr, связанные с текущим процессом*/ put_cpu_ptr(ptr);

/* готово, разрешаем вытеснение кода в режиме ядра */ Макрос g e t _ c p u _ p t r () возвращает указатель на экземпляр данных, связанных с текущим процессором. Этот вызов также запрещает вытеснение кода в режиме ядра, которое снова разрешается вызовом функции p u t _ c p u _ p t r ( ).

Рассмотрим пример использования этих функций. Конечно, этот пример не со всем логичный, потому что память обычно необходимо выделять один раз (напри мер, в некоторой функции инициализации), использовать ее в разных необходимых местах, а затем освободить также один раз (например, в некоторой функции, кото рая вызывается при завершении работы). Тем не менее этот пример позволяет по яснить особенности использования.

void *percpu_ptr;

unsigned long *foo;

percpu_ptr = alloc_percpu(unsigned long);

if (!ptr) /* ошибка выделения памяти.. */ foo = get_cpu_ptr(percpu_ptr);

/* работаем с данными foo.. */ put_cpu_ptr(percpu_ptr);

Еще одна функция — p e r _ c p u _ p t r ( ) — возвращает экземпляр данных, связанных с указанным процессором.

per_cpu_ptr(ptr, cpu);

262 Глава Эта функция не запрещает вытеснение в режиме ядра. Если вы "трогаете" дан ные, связанные с другим процессором, то, вероятно, необходимо применить блоки ровки.

Когда лучше использовать данные, связанные с процессорами Использование данных, связанных с процессорами, позволяет получить ряд пре имуществ. Во-первых, это ослабление требований по использованию блокировок.

В зависимости от семантики доступа к данным, которые связаны с процессорами, может оказаться, что блокировки вообще не нужны. Следует помнить, что правило "только один процессор может обращаться к этим данным " является всего лишь рекомен дацией для программиста. Необходимо специально гарантировать, что каждый про цессор работает только со своими данными. Ничто не может помешать нарушению этого правила.

Во-вторых, данные, связанные с процессорами, позволяют существенно умень шить недостоверность данных, хранящихся в кэше. Это происходит потому, что процессоры поддерживают свои кэши в синхронизированном состоянии. Если один процессор начинает работать с данными, которые находятся в кэше другого процес сора, то первый процессор должен обновить содержимое своего кэша. Постоянное аннулирование находящихся в кэше данных, именуемое перегрузкой кэша (cash thrashing), существенно снижает производительность системы. Использование дан ных, связанных с процессорами, позволяет приблизить эффективность работы с кэшем к максимально возможной, потому что в идеале каждый процессор работает только со своими данными.

Следовательно, использование данных, которые связаны с процессорами, часто избавляет от необходимости использования блокировок (или снижает требования, связанные с блокировками). Единственное требование, предъявляемое к этим дан ным для безопасной работы, — это запрещение вытеснения кода, который работает в режиме ядра. Запрещение вытеснения — значительно более эффективная опера ция по сравнению с использованием блокировок, а существующие интерфейсы вы полняют запрещение и разрешение вытеснения автоматически. Данные, связанные с процессорами, можно легко использовать как в контексте прерывания, так и в кон тексте процесса. Тем не менее следует обратить внимание, что при использовании данных, которые связаны с текущим процессором, нельзя переходить в состояние ожидания {в противном случае выполнение может быть продолжено на другом про цессоре).

Сейчас нет строгой необходимости где-либо использовать новый интерфейс ра боты с данными, которые связаны с процессорами. Вполне можно организовать та кую работу вручную (на основании массива, как было рассказано ранее), если при этом запрещается вытеснение кода в режиме ядра. Тем не менее новый интерфейс более простой в использовании и, возможно, позволит в будущем выполнять допол нительные оптимизации. Если вы собираетесь использовать в своем коде данные, связанные с процессорами, то лучше использовать новый интерфейс. Единственный недостаток нового интерфейса — он не совместим с более ранними версиями ядер.

Управление памятью Какой способ выделения памяти необходимо использовать Если необходимы смежные страницы физической памяти, то нужно использовать один из низкоуровневых интерфейсов выделения памяти, или функцию kmalloc().

Это стандартный способ выделения памяти в ядре, и, скорее всего, в большинстве случаев следует использовать именно его. Необходимо вспомнить, что два наи более часто встречающихся флага, которые передаются этой функции, это флаги GFP_ATOMIC и GFP_KERNEL. Для высокоприоритетных операций выделения памяти, которые не переводят процесс в состояние ожидания, необходимо указывать флаг GFP_ATOMIC. Это обязательно для обработчиков прерываний и других случаев, когда нельзя переходить в состояние ожидания. В коде, который может переходить в со стояние ожидания, как, например код, выполняющийся в контексте процесса и не удерживающий спин-блокировку, необходимо использовать флаг GFP_KERNEL. Такой флаг указывает, что должна выполняться операция выделения памяти, которая при необходимости может перейти в состояние ожидания для получения необходимой памяти.

Если есть необходимость выделить страницы верхней памяти, то следует исполь зовать функцию a l l o c _ p a g e s ( ). Функция alloc_pages () возвращает структуру s t r u c t page, а не логический адрес. Поскольку страницы верхней памяти могут не отображаться в адресное пространство ядра, единственный способ доступа к этой памяти — через структуру s t r u c t page. Для получения "настоящего" указателя на область памяти необходимо использовать функцию kmap(), которая позволяет ото бразить верхнюю память в логическое адресное пространство ядра.

Если нет необходимости в физически смежных страницах памяти, а необходима только виртуально непрерывная область памяти, то следует использовать функцию vmalloc() (также следует помнить о небольшой потере производительности при использовании функции vmalloc() по сравнению с функцией kmalloc() ) Функция vmalloc() выделяет область памяти, которая содержит только виртуально смежные страницы, но не обязательно физически смежные. Это выполняется почти так же, как и в программах пользователя путем отображения физически несмежных участ ков памяти в логически непрерывную область памяти.

Если необходимо создавать и освобождать много больших структур данных, то следует рассмотреть возможность построения слябового кэша. Уровень слябового распределения памяти позволяет поддерживать кэш объектов (список свободных объектов), уникальный для каждого процессора, который может значительно улуч шить производительность операций выделения и освобождения объектов. Вместо того чтобы часто выделять и освобождать память, слябовый распределитель сохра няет кэш уже выделенных объектов. При необходимости получения нового участка памяти для хранения структуры данных, уровню слябового распределения часто нет необходимости выделять новые страницы памяти, вместо этого можно просто воз вращать объект из кэша.

264 Глава Виртуальная файловая система В иртуальная файловая система (Virtual File System), иногда называемая виртуаль ным файловым коммутатором (Virtual File Switch) или просто VFS, — это подси стема ядра, которая реализует интерфейс пользовательских программ к файловой системе. Все файловые системы зависят от подсистемы VFS, что позволяет не только сосуществовать разным файловым системам, но и совместно функционировать. Это также дает возможность использовать стандартные системные вызовы для чтения и записи данных на различные файловые системы, которые находятся на различных физических носителях, как показано на рис. 12.1, Жесткий диск с файловой системой ext VFS CP(1) Гибкий диск с файловой системой ext Рис. 12.1. Подсистема VFS в действии: использование команды ср (1) для копирования данных с жeсткого диска, на котором монтируется файловая система, ext3, на гибкий диск, на котором монтируется файловая система ext Общий интерфейс к файловым системам Подсистема VFS— это связующее звено, которое позволяет таким системным вы зовам, как open (), read () и write (), работать независимо от файловой системы и физической среды носителя информации. Сегодня это может не впечатлять, по скольку такая возможность принимается как должное. Тем не менее сделать так, что бы общие системные вызовы работали для всех поддерживаемых файловых систем и физических сред хранения данных, — задача не тривиальная. Более того, эти систем ные вызовы позволяют выполнять операции между различными файловыми система ми и различными физическими носителями — мы можем копировать и перемещать данные с одной файловой системы на другую с помощью стандартных системных вызовов. В старых операционных системах (например, DOS) таких возможностей не было. Любые операции доступа к "неродным" файловым системам требовали исполь зования специальных утилит. Сейчас такие возможности существуют, потому что все современные операционные системы, включая Linux, абстрагируют доступ к файло вым системам с помощью виртуального интерфейса, который дает возможность со вместной работы с данными и обобщенного доступа к данным. В операционной си стеме Linux может появиться поддержка новых типов файловых систем или новых физических средств хранения данных, при этом нет необходимости переписывать или перекомпилировать существующие программы.

Уровень обобщенной файловой системы Общий интерфейс для всех типов файловых систем возможен только благодаря тому, что в ядре реализован обобщающий уровень, который скрывает низкоуров невый интерфейс файловых систем. Данный обобщающий уровень позволяет опе рационной системе Linux поддерживать различные файловые системы, даже если эти файловые системы существенно отличаются друг от друга своими функциями и особенностями работы. Это в свою очередь становится возможным благодаря тому, что подсистема VFS реализует общую файловую модель, которая в состоянии пред ставить общие функции и особенности работы потенциально возможных файловых систем. Конечно, эта модель имеет уклон в сторону файловых систем в стиле Unix (что представляют собой файловые системы в стиле Unix, будет рассказано в сле дующем разделе). Несмотря на это в ОС Linux поддерживается довольно большой диапазон различных файловых систем.

Обобщенный уровень работает путем определения базовых интерфейсов и струк тур данных, которые нужны для поддержки всех файловых систем. Код поддержки каждой файловой системы должен формировать все концепции своей работы в со ответствии с шаблонными требованиями подсистемы VFS, например "так открываем файл", а "так представляем каталог". Код файловой системы скрывает все детали реа лизации. По отношению к уровню VFS и остальным частям ядра все файловые систе мы выглядят одинаково, т.е. все файловые системы начинают поддерживать такие объекты, как файлы и каталоги, и такие операции, как создание и удаление файла.

В результате получается общий уровень абстракции, который позволяет ядру лег ко и просто поддерживать множество типов файловых систем. Код файловых си стем программируется таким образом, чтобы поддерживать общие интерфейсы и структуры данных, которые нужны для работы с виртуальной файловой системой.

266 Глава В свою очередь, ядро легко может работать со всеми файловыми системами, и соот ветственно, экспортируемый ядром интерфейс пользователя также позволяет анало гично работать со всеми файловыми системами.

В ядре нет необходимости поддерживать низкоуровневые детали реализации файловых систем нигде, кроме кода самих файловых систем. Например, рассмотрим следующую простую программу, работающую в пространстве пользователя.

write(f, &buf, len);

Этот системный вызоп записывает len байт из области памяти по адресу &buf в файл, представленный с помощью дескриптора f, начиная с текущей позиции файла.

Этот системный вызов вначале обрабатывается общей функцией ядра sys_write (), которая определяет функцию записи в файл для той файловой системы, на которой находится файл, представленный дескриптором f. Далее общий системный вызов вызывает найденную функцию, которая является частью реализации файловой си стемы и служит для записи данных на физический носитель (или для других дей ствий, которые файловая система выполняет при записи файла). На рис. 12.2 показа на диаграмма выполнения операции записи, начиная от пользовательской функции write () и заканчивая поступлением данных на физический носитель. Далее в этой главе будет показано, как подсистема VFS позволяет достичь необходимой абстрак ции и какие для этого обеспечиваются интерфейсы.

Файловые системы Unix Исторически так сложилось, что ОС Unix обеспечивает четыре абстракции, свя занные с файловыми системами: файлы, элементы каталогов (directory entry), индек сы (inode) и точки монтирования (mount point).

Файловая система — это иерархическое хранилище данных определенной струк туры. Файловые системы содержат файлы, каталоги и соответствующую управляю щую информацию. Обычные операции, которые выполняются с файловыми систе мами, — это создание (create), удаление (delete) и монтирование (mount). В ОС Unix файловые системы монтируются на определенную точку монтирования в общей ие рархии1, которая называется пространством имен (namespace). Это позволяет все фай ловые системы сделать элементами одной древовидной структуры2.

Файл (file) — это упорядоченный поток байтов. Первый байт соответствует нача лу файла, а последний байт - концу файла. Каждому файлу присваивается удобочита емое имя, по которому файл идентифицируется как пользователями, так и системой.

Обычные файловые операции— это чтение (read), запись (write), создание (create) и удаление (delete).

Сейчас в операционной системе Linux эта иерархическая структура является уникальной для каж дого процесса, т.е. каждый процесс имеет свое пространство имен. По умолчанию каждый процесс наследует пространство имен своего родительского процесса,. поэтому кажется, что существует одно глобальное пространство имен.

В отличие от указания буквы, которая соответствует определенному диску, например С:. В послед нем случае пространство имен разбивается на части, которые соответствуют различным устрой ствам или разделам устройств. Поскольку такое разделение выполняется случайным образом, в качестве представления для пользователя его можно считать не самым идеальным вариантом.

Виртуальная файловая система Файлы помещаются в каталогах (directory). Каталог — это аналог папки, которая обычно содержит связанные между собой файлы. Каталоги могут содержать подката логи. В этой связи каталоги могут быть вложены друг в друга и образуют пути (path).

Каждый компонент пути называется элементом каталога (directory entry). Пример пути— "/home/wolfman/foo". Корневой каталог "/", каталоги home и wolfman, a также файл f оо — это элементы каталогов, которые называются dentry. В операци онной системе Unix каталоги представляют собой обычные файлы, которые про сто содержат список файлов каталога. Так как каталог по отношению к виртуальной файловой системе — это файл, то с каталогами можно выполнять те же операции, что и с файлами.

Unix-подобные операционные системы отличают концепцию файла от любой ин формации об этом файле (права доступа, размер, владелец, время создания и т.д.).

Последняя информация иногда называется метаданными файла (file, metadata), т.е. дан ные о данных, и хранится отдельно от файлов в специальных структурах, которые называются индексами (inode). Это сокращенное название от index node (индексный узел), хотя в наши дни термин "inode" используется значительно чаще.

Вся указанная информация, а также связанная с ней информация о самой файло вой системе хранится в суперблоке (superblock). Суперблок— это структура данных, которая содержит информацию о файловой системе в целом. Иногда эти общие дан ные называются метаданными файловой системы. Метаданные файловой системы со держат информацию об индивидуальных файлах и о файловой системе в целом.

Традиционно файловые системы ОС Unix реализуют эти понятия как структу ры данных, которые определенным образом расположены на физических дисках.

Например, информация о файлах хранится в индексе, в отдельном блоке диска, каталоги являются файлами, информация по управлению файловой системой хра нится централизованно в суперблоке и т.д. Подсистема VFS операционной системы Linux рассчитана на работу с файловыми системами, в которых поддерживаются аналогичные концепции. Не Unix-подобные файловые системы, такие как FAT или NTFS, также работают в ОС Linux, однако их программный код должен обеспечить наличие аналогичных концепций. Например, если файловая система не поддержи вает отдельные индексы файлов, то код должен построить в оперативной памяти структуры данных таким образом, чтобы казалось, что такая поддержка работает.

Если файловая система рассматривает каталоги как объекты специальных типов, для VFS каталоги должны представляться как обычные файлы. Часто код не файловых систем не в стиле Unix требует выполнять некоторую дополнительную обработку чтобы уложиться в парадигму Unix и требования VFS. Такие файловые системы так же поддерживаются, и обычно качество не особенно страдает.

268 Глава Метод записи write() sys_write() файловой системы Пространство Файловая Физический VFS пользователя система носитель Рис, 12.2. Схема прохождения данных из пространства пользователя, где вызывается функция wri te (), через общий системный вызов VFS, к специфическому методу за писи файловой системы и, наконец, поступление па физический носитель Объекты VFS и их структуры данных Виртуальная файловая система (VFS) объектно-ориентированна 3. Общая файло вая модель представлена набором структур данных. Эти структуры данных очень похожи на объекты. Так как ядро программируется строго на языке С, то, при от сутствии возможностей прямой поддержки парадигм ООП в языке программирова ния, структуры данных представляются структурами языка С. Структуры содержат как указатели на элементы данных, так и указатели на функции, которые работают с этими данными.

Существуют следующие четыре основных типа объектов VFS.

• Объект суперблок (superblock), который представляет определенную смонтирован ную файловую систему.

• Объект файловый индекс (inode), который представляет определенный файл.

• Объект элемент каталога (denlry), который представляет определенный элемент каталога.

• Объект файл (file), который представляет открытый файл, связанный с процес сом.

Следует обратить внимание, что поскольку подсистема VFS рассматривает катало ги как обычные файлы, то не существует специальных объектов для каталогов. Как рассказывалось ранее, объект dentry представляет компонент пути, который может содержать обычный файл. Другими словами, deniry — это не то же самое, что ката лог, а каталог — это то же, что и файл. Все понятно?

Каждый из рассмотренных основных объектов содержит объект operations (опера ции). Эти объекты описывают методы, которые ядро может применять для основ ных объектов.

Часто многие этого не замечают и даже отрицают, но тем не менее в ядре много примеров объ ектно-ориентированного программирования. Хотя разработчики ядра и сторонятся языка C++ и других явно объектно-ориентированных языков программировании (ООП), иногда очень полезно мыслить в терминах объектов. Подсистема VFS— это хороший пример того, как просто и эффек тивно объектно-ориентированное программирование реализуется на языке С, в котором нет объ ектно-ориентированных конструкций.

Виртуальная файловая система В частности, существуют следующие объекты операций.

• Объект super_operations (операции с суперблоком файловой системы) со держит методы, которые ядро может вызывать для определенной файловой системы, как, например, read_inode () или sync_fs ().

• Объект i n o d e o p e r a t i o n s (операции с файловыми индексами) содержит ме тоды, которые ядро может вызывать для определенного файла, как, например, c r e a t e d или l i n k ().

• Объект d e n t r y _ o p e r a t i o n s (операции с элементами каталогов) содержит методы, которые ядро может вызывать для определенного элемента каталога, как, например, d_compare () или d_delete ().

• Объект f ile_operations (операции с файлами) содержит методы, которые про цесс может вызывать для открытого файла, как например, read () и wri te ().

Объекты операций реализованы в виде структур, содержащих указатели на функ ции. Эти функции оперируют объектом, которому принадлежит объект операций.

Для многих методов объект может унаследовать общую функцию, если для работы достаточно базовой функциональности. В противном случае каждая файловая систе ма присваивает указателям адреса своих специальных методов.

И еще раз повторимся, что под объектами мы будем понимать структуры, которые явно не являются объектными типами (в отличие от языков программирования C++ и Java). Однако эти структуры представляют определенные экземпляры объектов, данные связанные с объектами, и методы, которые ими оперируют. Это практиче ски то же, что и объектные типы.

Другие объекты подсистемы VFS Структуры для VFS — это самая "любимая" вещь, и в этой подсистеме существуют не только рассмотренные структуры, но и еще некоторые. Каждая зарегистрирован ная файловая система представлена структурой file_system_type, Объекты этого типа описывают файловую систему и ее свойства. Более того, каждая точка монти рования предстаплена в виде структуры vfsmount. Эта структура содержит инфор мацию о точке монтирования, такую как ее положение и флаги, с которыми выпол нена операция монтирования.

И наконец, каждый процесс имеет три структуры, которые описывают файловую систему и файлы, связанные с процессом. Это структуры f i l e _ s t r u c t, f s _ s t r u c t и namespace.

Далее в этой главе будут рассматриваться эти объекты и их роль в функциониро вании уровня VFS.

Объект superblock Объект суперблок должен быть реализован для каждой файловой системы. Он ис пользуется для хранения информации, которая описывает определенную файловую систему. Этот объект обычно соответствует суперблоку (superblock) или управляющему блоку (control block) файловой системы, который хранится в специальном секторе дис ка (отсюда и имя объекта). Файловые системы, которые не располагаются на дисках 270 Глава (например, файловые системы в виртуальной памяти, как sysfs), генерируют инфор мацию суперблока "на лету" и хранят в памяти.

Объект суперблак представляется с помощью структуры s t r u c t super_block, ко торая определена в файле . Она выглядит следующим образом (ком ментарии описывают назначение каждого поля).

struct super_block { struct list_head s_list;

/* список всех суперблоков */ dev_t s_dev;

/* идентификатор */ unsigned long s_blocksize;

/* размер блока в байтах */ unsigned long s_old_blocksize;

/*старый размер блока в байтах*/ unsigned char s_blocksize_bits;

/* размер блока в битах */ unsigned char s_dirt;

/*флаг того, что суперблок изменен*/ unsigned long long s_maxbytes;

/* максимальный размер файла */ struct file_system_type *s_type;

/* тип файловой системы */ struct super_operations *s_op;

/* операции суперблока */ struct dquot_operations *dq_op;

/* операции с квотами */ struct quotactl_ops *s_qcop;

/* операции управления квотами */ struct export_operations *s_export_op;

/* операции экспортирования */ unsigned long s_flags;

/* флаги монтирования */ unsigned long s_magic;

/* магический номер файловой системы*/ struct dentry *s_root;

/* каталог, точка монтирования */ struct rw_semaphore s_umount;

/* семафор размонтирования */ struct semaphore s_lock;

/* семафор суперблока */ int s_count;

/* счетчик ссылок на суперблок */ int s_syncing;

/*флаг синхронизации файловой системы*/ int s_nesd_sync_fs;

/*флаг того, что файловая система еще не синхронизирована*/ atomic_t s_active;

/* счетчик активных ссыпок */ void *s_security;

/* модуль безопасности */ struct list_head s_dirty;

/* список измененных индексов */ struct list_head s_io;

/*список обратной записи */ struct hlist_head s_anon;

/* анонимные элементы каталога для экспортирования */ struct list_head s_files;

/* список связанных файлов */ struct block_device *s_bdev;

" /* соответствующий драйвер блочного устройства */ struct list_head s_instances;

/* список файловых систем данного типа */ struct quota_info s_dquot;

/* параметры квот */ char s_id[32];

/* текстовое имя */ void *s_fs_info;

/* специфическая информация файловой системы */ struct semaphore s_vfs_rename_sem;

/* семафор переименования */ };

Код для создания, управления и ликвидации объектов суперблок находится в фай ле f s / s u p e r. с. Объект суперблок создается и инициализируется в функции a l l o c _ super (). Эта функция вызывается при монтировании файловой системы, которая считывает суперблок файловой системы с диска и заполняет поля объекта суперблок.

Виртуальная файловая система Операции суперблока Наиболее важный элемент суперблока — это поле s_op, которое является указа телем на таблицу операций суперблока. Таблица операций суперблока представле на с помощью структуры s t r u c t super_operations, которая определена в файле . Она выглядит следующим образом.

struct super_operations { struct inode *(*alloc_inode) (struct super_block *sb);

void (*destroy_inode) (struct inode * ) ;

void (*read_inode) (struct inode * ) ;

void (*dirty_inode) (struct inode * ) ;

void (*write_inode) (struct inode *, int);

void (*put inode) (struct inode * ) ;

void (*drop_inode) (struct inode *) ;

void (*delete_inode) (struct inode * ) ;

void (*put_super) (struct super_block * ) ;

void (*write_super) (struct super block * ) ;

int (*sync_fs) (struct super_block *, int};

void (*write_super_lockfs) (struct super_block * ) ;

void (*unlockfs) (struct super_block * ) ;

int (*statfs) (struct super_block *, struct statfs * } ;

int (*remount_fs) (struct super_block *, int *, char * ) ;

void (*clear_inode) (struct inode * ) ;

void (*umount_begin) (struct super block * ) ;

int (*show_options) (struct seq_file *, struct vfsmount * ) ;

};

Каждое поле этой структуры представляет собой указатель на функцию, которая работает с объектом суперблок. Операции суперблока выполняют низкоуровневые действия с файловой системой и ее файловыми индексами.

Когда для файловой системы необходимо выполнить операции с суперблоком, то выполняется разыменование указателя на суперблок, и далее получается указатель на необходимый метод. Например, если файловой системе необходимо записать су перблок, то вызывается следующая функция.

sb->s_op->write_super(sb);

где параметр sb — это указатель на суперблок файловой системы. Следуя по указате лю s_op, получаем таблицу операций суперблока и, наконец, необходимую функцию write_super (), которая вызывается непосредственно. Следует обратить внимание на то, что вызову функции write_super () необходимо передать указатель на супер блок в качестве параметра, несмотря на то что метод связан с суперблоком. Это про исходит от того, что язык программирования С не объектно-ориентирован. В C++ аналогичный вызов может быть выполнен следующим образом.

sb.write_super();

В языке С нет простого способа получить указатель на объект, для которого вы зван метод, поэтому его необходимо передавать явно.

Рассмотрим операции суперблока, которые описаны в структуре super_opera tions.

272 Глава • s t r u c t inode * a l l o c _ i n o d e ( s t r u c t super_block *sb) — эта функция создает и инициализирует новый объект файлового индекса, связанного с дан ным суперблоком.

• void d e s t r o y _ i n o d e ( s t r u c t inode *inode) — эта функция уничтожает дан ный объект индекса файла.

• void read_inode ( s t r u c t inode *inode) — эта функция считывает с диска файловый индекс с номером i n o d e - > i _ i n o и заполняет все остальные поля структуры данных индекса.

• void d i r t y _ i n o d e ( s t r u c t inode *inode) — эта функция вызывается под системой VFS, когда в индекс вносятся изменения (dirty). Журналируемые фай ловые системы (как, например, ext3) используют эту функцию для обновления журнала.

• void w r i t e _ i n o d e ( s t r u c t inode inode*, i n t wait) — эта функция запи сывает указанный индекс на диск. Параметр wait указывает, должна ли данная операция выполняться синхронно.

• void put_inode ( s t r u c t inode *inode) — эта функция освобождает указан ный индекс.

• void drop_inode ( s t r u c t inode *inode) — эта функция вызывается подси стемой VFS, когда исчезает последняя ссылка на индекс. Обычные файловые системы Unix никогда не определяют эту функцию, в таком случае подсистема VFS просто удаляет индекс. Вызывающий код должен удержипать блокировку inode_lock.

• void d e l e t e _ i n o d e ( s t r u c t inode *inode) — эта функция удаляет индекс файла с диска.

• void put_super ( s t r u c t super_block *sb) — эта функция вызывается под системой VFS при раэмонтировании файловой системы, чтобы освободить ука занный суперблок.

• void write_super ( s t r u c t super_block *sb) — эта функция обновляет су перблок на диске данными из указанного суперблока. Подсистема VFS вызыва ет эту функцию для синхронизации измененного суперблока в памяти с данны ми суперблока на диске.

• i n t sync_fs ( s t r u c t super_block *sb, i n t wait) — эта функция синхро низирует метаданные файловой системы с данными на диске. Параметр wait указывает, должна ли операция быть синхронной или асинхронной.

• void write_super_lockfs ( s t r u c t super_block *sb) — эта функция пре дотвращает изменения файловой системы и затем обновляет данные супербло ка на диске данными из указанного суперблока. Сейчас она используется дис петчером логических томов (LVM, Logical Volume Manager).

• void unlockfs ( s t r u c t super_block *sb) — эта функция разблокирует фай ловую систему после выполнения функции write_super_lockf s ().

• i n t s t a t f s ( s t r u c t super_block *sb, s t r u c t s t a t f s * s t a t f s ) — эта функ ция вызывается подсистемой VFS для получения статистики файловой системы, Статистика указанной файловой системы записывается в структуру s t a t f s.

Виртуальная файловая система • i n t remount_fs (struct super_block *sb, i n t *flags, char *data) —эта функция вызывается подсистемой VFS, когда файловая система монтируется с другими параметрами монтирования.

• void clear_inode ( s t r u c t inode *) — эта функция вызывается подсистемой VFS для освобождения индекса и очистки всех страниц памяти, связанных с индексом.

• void umount_begin ( s t r u c t super_block *sb) — эта функция вызывается подсистемой VFS для прерывания операции монтирования. Она используется сетевыми файловыми системами, такими как NFS.

Все рассмотренные функции вызываются подсистемой VFS в контексте процесса.

Все они при необходимости могут блокироваться.

Некоторые из этих функций являются необязательными. Файловая система мо жет установить их значения в структуре операций суперблока равными NULL. Если соответствующий указатель равен NULL, то подсистема VFS или вызывает общий ва риант функции, или не происходит ничего, в зависимости от операции.

Объект inode Объект inode содержит всю информацию, которая необходима ядру для манипу ляций с файлами и каталогами. В файловых системах в стиле Unix вся информация просто считывается из дисковых индексов и помещается в объект inode подсисте мы VFS. Если файловые системы не имеют индексов, то эту информацию необходи мо получить из других дисковых структур4.

Объект индекса файла представляется с помощью структуры s t r u c t inode, ко торая определена в файле . Эта структура с комментариями, описыва ющими назначение каждого поля, имеет следующий вид.

struct inode { struct hlist_node i_hash;

/* хешированный список */ struct list_head i_list;

/* связанный список индексов */ struct list_head i_dentry;

/* связанный список объектов dentry */ unsigned long i_ino;

/* номер индекса */ /* счетчик ссылок */ atomic_t i_count;

umode_t i_mode;

/* права доступа */ unsigned int i_nlink;

/* количество жестких ссылок */ uid_t i_uid;

/* идентификатор пользователя-владельца */ gid_t i_gid;

/* идентификатор группы-владельца */ kdev_t i_rdev;

/* связанное устройство */ loff_t i_size;

/* размер файла в байтах */ struct timespec i_atime;

/* время последнего доступа к файлу */ struct timespec i_mtime;

/* время последнего изменения файла */ struct timespec i_ctime;

/* время изменения индекса */ /* размер блока в битах */ unsigned int i_blkbits;

Файловые системы, которые не имеют индексов, обычно хранят необходимую информацию как часть файла. Некоторые современные файловые системы также применяют базы данных для хра нения метаданных файла. В любом случае объект индекса создается тем способом, который под ходит для файловой системы.

274 Глава unsigned long i_blksize;

/* размер блока в байтах */ unsigned long i_version;

/* номер версии */ unsigned long i_blocks;

/* размер файла в блоках */ unsigned short i_bytes;

/* количество использованных байтов*/ spinlock_t i_lock;

/* блокировка для защиты полей */ struct rw_semaphore i_alloc_sem /* вложенные блокировки при захваченной i_sem */ struct semaphore i_sem;

/* семафор индекса */ struct inode_operations *i_op;

/* таблица операций с индексом */ struct file_operations *i_fop;

/* файловые операции */ struct super_block *i_sb;

/* связанный суперблок */ struct file_lock *i_flock;

/* список блокировок файлов */ struct address_space *i_mapping;

/* соответствующее адресное пространство */ struct address_space i_data;

/* адресное пространство устройства*/ struct dquot *i_dquot[MAXQUOTAS];

/* дисковые квоты для индекса*/ struct list_head i_devices;

/* список блочных устройств */ struct pipe_inode_info *i_pipe;

/* информация конвейера */ struct block_device *i_bdev;

/* драйвер блочного устройства */ unsigned long i_dnotify_mask;

/* события каталога */ struct dnotify_struct *i_dnotify;

/* информация о событиях каталога */ unsigned long i_state;

/* флаги состояния */ unsigned long dirtied_when /* время первого изменения */ unsigned int i_flags;

/* флаги файловой системы */ unsigned char i_sock;

/* сокет или нет? */ atomic_t i_writecount;

/* счетчик использования для записи*/ void *i_security;

/* модуль безопасности */ u32 i_generation;

/* номер версии индекса */ union { void *generic_ip;

/* специфическая информация файловой системы */ } u;

};

Для каждого файла в системе существует представляющий его индекс (хотя объ ект файлового индекса создается в памяти только тогда, когда к файлу осуществляет ся доступ). Это справедливо и для специальных файлов, таких как файлы устройств или конвейеры. Следовательно, некоторые из полей структуры s t r u c t inode отно сятся к этим специальным файлам. Например, поле i_pipe указывает на структуру данных именованного конвейера. Если индекс не относится к именованному конвей еру, то это поле просто содержит значение NULL. Другие поля, связанные со специ альными файлами, — это i_devices, i_bdev, i_cdev.

Может оказаться, что та или иная файловая система не поддерживает тех свойств, которые присутствуют в объекте inode. Например, некоторые файловые системы не поддерживают такого атрибута, как время создания файла. В этом случае файло вая система может реализовать это свойство как угодно. Например, поле i_ctime можно сделать нулевым или равным значению поля i_mtime.

Виртуальная файловая система Операции с файловыми индексами Так же как и в случае операций суперблока, важным является поле i n o d e _ o p e r a t i o n s, в котором описаны функции файловой системы, которые могут быть вы званы подсистемой VFS для объекта файлового индекса. Как и для суперблока, опе рации с файловыми индексами могут быть вызваны следующим образом.

i->i_op->truncate(i) где переменная i содержит указатель на определенный объект файлового индекса.

В данном случае для индекса I выполняется операция t r a n c a t e ( ), которая опреде лена для файловой системы, в которой находится указанный файловый индекс i.

Структура i n o d e _ o p e r a t i o n s определена в файле < l i n u x / f s. h >, как показано ниже.

struct inode_operations { int (*create) (struct inode *, struct dentry *,int);

struct dentry * (*lookup) (struct inode *, struct dentry * ) ;

int (*link) (struct dentry *, struct inode *, struct dentry * ) ;

int (*unlink) (struct inode *, struct dentry * ) ;

int (*symlink) (struct inode *, struct dentry *, const char * ) ;

int (*mkdir) (struct inode *, struct dentry *, int);

int (*rmdir) (struct inode *, struct dentry *);

int (*mknod) (struct inode *, struct dentry *, int, dev_t);

int (*rename) (struct inode *, struct dentry *, struct inode *, struct dentry * ) ;

int (*readlink) (struct dentry *, char *, int);

int (*follow_link) (struct dentry *, struct nameidata * ) ;

int (*put_link) (struct dentry *, struct nameidata * ) ;

void (*truncate) (struct inode * ) ;

int (*permission) (struct inode *, int);

int (*setattr) (struct dentry *, struct iattr * ) ;

int (*getattr) (struct vfsmount *, struct dentry *, struct kstat * ) ;

int (*setxattr) (struct dentry *, const char *, const void *, size_t, int);

ssize_t (*getxattr) (struct dentry *, const char *, void *, size_t);

ssize_t (*listxattr) (struct dentry *, char *, size_t);

int (*removexattr) (struct dentry *, const char * ) ;

};

Рассмотрим указанные операции более подробно.

• i n t c r e a t e ( s t r u c t inode * d i r, s t r u c t dentry *dentry, i n t mode) Эта функция вызывается подсистемой VFS из системных вызовов c r e a t () и open () для создания нового файлового индекса, который имеет указанный ре жим доступа (mode) и связан с указанным элементом каталога (dentry).

• s t r u c t dentry * lookup(struct inode * d i r, s t r u c t dentry *dentry) Эта функция производит поиск файлового индекса в указанном каталоге.

Файловый индекс должен соответствовать имени файла, хранящемуся в указан ном объекте элемента каталога.

276 Глава • i n t l i n k ( s t r u c t ctentry *old_dentry, s t r u c t inode * d i r, s t r u c t dentry *dentry) Эта функция вызывается из системного вызова l i n k () для создания жесткой ссылки (hard link) на файл, соответствующий элементу каталога old_dentry в каталоге d i r. Новая ссылка должна иметь имя, которое хранится в указанном элементе каталога dentry.

• i n t u n l i n k ( s t r u c t inode * d i r, s t r u c t dentry *dentry) Эта функция вызывается из системного вызова unlink () для удаления файло вого индекса, соответствующего элементу каталога dentry в каталоге d i r.

• int symlink(struct inode *dir, struct dentry *dentry, const char *symname) Эта функция вызывается из системного вызова symlink() для создания сим вольной ссылки с именем symnarne на файл, которому соответствует элемент каталога dentry в каталоге d i r.

• i n t m k d i r ( s t r u c t inode * d i r, s t r u c t dentry *dentry, i n t mode) Эта функция вызывается из системного вызова mkdir () для создания нового каталога с указанным режимом доступа (mode).

• i n t r m d i r ( s t r u c t inode * d i r, s t r u c t dentry *dentry) Эта функция вызывается из системного вызова rmdir () для удаления каталога на который указывает элемент каталога dentry из каталога d i r.

• int mknod (struct inode *dir, struct dentry *dentry, int mode, dev_t rdev) Эта функция вызывается из системного вызова mknod () для создания специаль ного файла (файла устройства, именованного конвейера или сокета), информа ция о котором хранится в параметре rdev. Файл должен быть создан в каталоге d i r с именем, указанным в параметре dentry, и режимом доступа mode.

• i n t rename(struct inode *old_dir, s t r u c t dentry *old_dentry, s t r u c t inode *new_dir, s t r u c t dentry *new_dentry) Эта функция вызывается подсистемой VFS для перемещения указанного эле мента каталога old_dentry из каталога o l d _ d i r в каталог new_dir с новым именем, указанным в параметре new_dentry.

• i n t r e a d l i n k ( s t r u c t dentry *dentry, char *buffer, i n t buflen) Эта функция вызывается из системного вызова r e a d l i n k () для копирования не более buflen байт полного пути, связанного с символьной ссылкой, соот ветствующей указанному элементу каталога, в указанный буфер.

• i n t f o l l o w _ l i n k ( s t r u c t dentry *dentry, s t r u c t nameidata *nd) Эта функция вызывается подсистемой VFS для трансляции символьной ссылки в индекс файла, на который эта ссылка указывает. На ссылку указывает указа тель dentry, а результат сохраняется в структуру nameidata, на которую ука зывает параметр nd.

• i n t p u t _ l i n k ( s t r u c t dentry *dentry, s t r u c t nameidata* nd) Эта функция вызывается подсистемой VFS после вызова функции followlink ().

• void truncate ( s t r u c t inode *inode). Эта функция вызывается подсистемой VFS для изменения размера заданного файла. Перед вызовом поле i _ s i z e ука занного индекса файла должно быть установлено в желаемое значение размера.

Виртуальная файловая система • i n t p e r m i s s i o n ( s t r u c t inode *inode, i n t mask) Эта функция проверяет, разрешен ли указанный режим доступа к файлу, на который ссылается объект inode. Функция должна возвращать нулевое значе ние, если доступ разрешен, и отрицательное значение кода ошибки в против ном случае. Для большинства файловых систем данное поле устанавливается в значение NULL, и при этом используется общий метод VFS, который просто сравнивает биты поля режима доступа файлового индекса с указанной маской.

Более сложные файловые системы, которые поддерживают списки контроля доступа (ACL), реализуют свой метод permission ().

• i n t s e t a t t r ( s t r u c t dentry *dentry, struct i a t t r *attr) Эта функция вызывается функцией notify_change () для уведомления о том, что произошло "событие изменения" ("change event") после модификации ин декса.

• i n t g e t a t t r ( s t r u c t vfsmount *mnt, s t r u c t dentry *dentry, struct kstat *stat) Эта функция вызывается подсистемой VFS при уведомлении, что индекс дол жен быть обновлен с диска.

• i n t s e t x a t t r ( s t r u c t dentry *dentry, const char *name, const void *value, s i z e _ t s i z e, i n t flags) Эта функция вызывается подсистемой VFS для установки одного из расширен ных атрибутов (extended attributes)5 с именем name в значение value для фай ла, соответствующего элементу каталога dentry.

• i n t g e t x a t t r ( s t r u c t dentry *dentry, const char *name, void *value, s i z e _ t size) Эта функция вызывается подсистемой VFS для копирования значения одного из расширенных атрибутов (extended attributes) с именем name в область памя ти с указателем value.

• ssize_t l i s t x a t t r ( s t r u c t dentry *dentry, char * l i s t, size_t size) Эта функция должна копировать список всех атрибутов для указанного файла в буфер, соответствующий параметру l i s t.

• i n t r e m o v e x a t t r ( s t r u c t dentry *dentry, const char *name) Эта функция удаляет указанный атрибут для указанного файла.

Объект dentry Как уже рассказывалось, подсистема VFS представляет каталоги так же, как и файлы. В имени пути / b i n / v i, и элемент b i n, и элемент vi — это файлы, только b i n — это специальный файл, который является каталогом, a vi — это обычный файл. Объекты файловых индексов служат для представления обоих этих компонен тов. Несмотря на такую полезную унификацию, подсистеме VFS также необходимо Расширенные атрибуты — это новая функциональность, которая появилась в ядре 2.6 для того, чтобы создавать параметры файлов в виде пар имя/значение по аналогии с базой данных. Эти па раметры поддерживаются не многими файловыми системами, и к тому же они еще используются не достаточно широко.

278 Глава выполнять операции, специфичные для каталогов, такие как поиск компонента пути по его имени, проверка того, что указанный элемент пути сущестпует, и переход на следующий компонент пути.

Для решения этой задачи в подсистеме VFS реализована концепция элемента ката лога (directory entry или dentry). Объект dentry — это определенный компонент пути.

В предыдущем примере компоненты /, bin и vi — это объекты элементов каталога.

Первые два— это каталоги, а последний— обычный файл. Важным моментом являет ся то, что все объекты dentry— это компоненты пути, включая и обычные файлы.

Элементы пути также могут включать в себя точки монтирования. В имени пути /mnt/cdrom/foo, компоненты /, mnt, cdrom и foo — это все объекты типа dentry.

Подсистема VFS при выполнении операций с каталогами по необходимости констру ирует объекты элементов каталога на лету.

Объекты типа dentry представлены с помощью структуры s t r u c t d e n t r y и определены в файле . Эта структура с комментариями, которые определяют назначение каждого поля, имеет следующий вид.

struct dentry { atomic_t d_count;

/* счетчик использования */ unsigned long d_vfs_flags;

/* флаги кэша объектов dentry */ spinlock_t d_lock;

/* блокировка данного объекта dentry */ struct inode *d_inode;

/* соответствующий файловый индекс */ struct list_head d_lru;

/* список неиспользованных объектов */ struct list_head d_child;

/* список объектов у родительского экземпляра */ struct list_head d_subdirs;

/* подкаталоги */ struct list_head d_alias;

/* список альтернативных (alias) индексов */ unsigned long d_time;

/* время проверки правильности */ struct dentry_operations *d_op;

/* таблица операций с элементом каталога */ struct super_block *d_sb;

/* связанный суперблок */ unsigned int d_flags;

/* флаги элемента каталога */ int d_mounted;

/* является ли объект точкой монтирования */ void *d_fsdata;

/* специфические данные файловой системы */ struct rcu_head d_rcu;

/* блокировки RCU (read-copy update) */ struct dcookie_struct *d_cookie;

/* cookie-идентификатор */ struct dentry *d_parent;

/* объект dentry родительского каталога */ struct qstr d_name;

/* имя dentry */ struct hlist_node d_hash;

/* список хеширования */ struct hlist_head *d_bucket;

/* сегмент хеш-таблицы */ unsigned char d_iname[DNAME_INLINE_LEN_MIN];

/*короткое имя файла */ };

В отличие от предыдущих двух объектов, объект dentry не соответствует какой бы то ни было структуре данных на жестком диске. Подсистема VSF создает эти объек ты на лету на основании строкового представления имени пути. Поскольку объекты элементов каталога не хранятся физически на дисках, то в структуре s t r u c t dentry нет никаких флагов, которые указывают на то, изменен ли объект (т.е. должен ли он быть записан назад на диск).

Виртуальная файловая система Состояние элементов каталога Действительный объект элемента каталога, может быть в одном из трех состоя ний: используемый fused), неиспользуемый (unused) и негативный (negative).

Используемый объект соответствует существующему файловому индексу (т.е. поле d_inode указывает на связанный объект типа mode) и используется один или более раз (т.е. значение поля d_count — положительное число). Используемый элемент ка талога используется подсистемой VFS, а также указывает на существующие данные, поэтому не может быть удален.

Неиспользуемый объект типа dentry соответствует существующему объекту inode (поле d_inode указывает на объект файлового индекса), но подсистема VFS в дан ный момент не использует этот элемент каталога (поле d_count содержит нулевое значение). Так как элемент каталога указывает на существующий объект, то он сохра няется на случай, если вдруг окажется нужным. Если объект не ликвидировать преж девременно, то его и не нужно будет создавать заново, если вдруг он понадобится в будущем, и поиск по имени пути пройдет быстрее. Когда же появляется необхо димость освободить память, то такой объект элемента каталога может быть удален, потому что он никем не используется.

Негативный объект dentry6 не связан с существующим файловым индексом (поле d_inode равно значению NULL), потому что или файловый индекс был удален, или соответствующий элемент пути никогда не существовал. Такие объекты элементов каталогов сохраняются, чтобы в будущем поиск по имени пути проходил быстрее.

Хотя такие объекты dentry и полезны, но они при необходимости могут уничтожать ся, поскольку никто их на самом деле не использует.

Объект dentry может быть освобожден, оставаясь в слябовом кэше объектов, как обсуждалось в предыдущей главе. В таком случае на этот объект нет ссылок ни в коде VFS, ни в коде файловых систем.

Кэш объектов dentry После того как подсистема VFS преодолела все трудности, связанные с переводом всех элементов пути в объекты элементов каталогов, и был достигнут конец пути, то было бы достаточно расточительным выбрасывать на ветер всю проделанную рабо ту. Ядро кэширует объекты в кэше элементов каталога, который называют dcache.

Кэш объектов dentry состоит из трех частей.

• Список "используемых" объектов dentry, которые связаны с определенным файловым индексом (поле i _ d e n t r y объекта inode). Поскольку указанный файловый индекс может иметь несколько ссылок, то ему может соответсвовать несколько объектов dentry, а следовательно используется связанный список.

• Двухсвязный список неиспользуемых и негативных объектов dentry "с наи более поздним использованием" (last recently used, LRU). Вставки элементов в этот список отсортированы по времени, поэтому элементы, которые нахо дятся в начале списка, — самые новые. Когда ядро должно удалить элементы каталогов для освобождения памяти, то эти элементы берутся из конца списка.

Это название несколько сбивает с толку. В таких объектах нет ничего негативного или отрица тельного. Более удачным было бы, наверное, название invalid denlry или несуществующий элемент каталога.

280 Глава потому что там находятся элементы, которые использовались наиболее давно и для которых меньше шансов, что они понадобятся в ближайшем будущем.

• Хеш-таблица и хеш-функция, которые позволяют быстро преобразовать задан ный путь в объект dentry.

Указанная хеш-таблица представлена с помощью массива d e n t r y _ h a s h t a b l e.

Каждый элемент массива — это указатель на список тех объектов dentry, которые соответствуют одному ключу. Размер этого массива зависит от объема физической памяти в системе.

Значение ключа определяется функцией d_hash ( ), что позволяет для каждой файловой системы реализовать свою хеш-функцию.

Поиск в хеш-таблице выполняется с помощью функции d_lookup (). Если в кэше dcache найден соответствующий объект, то это значение возвращается. В случае ошибки возвращается значение NULL.

В качестве примера рассмотрим редактирование файла исходного кода в вашем домашнем каталоге, / h o m e / d r a c u l a / s r c / f о о. с. Каждый раз, когда производится доступ к этому файлу (например, при первом открытии, при последующей записи, при компиляции и так далее), подсистема VFS должна пройти через псе элементы каталогов в соответствии с путем к файлу: /, home, d r a c u l a, s r e и, наконец, foo.с.

Для того чтобы каждый раз при доступе к этому (и любому другому) имени пути из бежать выполнения данной операции, которая требует довольно больших затрат времени, подсистема VFS вначале может попытаться найти это имя пути в dentry кэше. Если поиск проходит успешно, то необходимый конечный элемент каталога нужного пути получается без особых усилий. Если же данного элемента каталога нет в dentry-кэше, то подсистема VFS должна самостоятельно отследить путь. После за вершения поиска найденные объекты dentry помещаются в кэш dcache, чтобы уско рить поиск в будущем.

Кэш dcache также является интерфейсом к кэшу файлопых индексов icache.

Объекты inode связаны с объектами dentry, поскольку объект dentry поддерживает положительное значение счетчика использования для связанного с ним индекса.

Это в свою очередь позволяет объектам dentry удерживать связанные с ними объ екты mode в памяти. Иными словами, если закэширован элемент каталога, то соот ветственно оказывается закэшированным и соответствующий ему файловый индекс.

Следовательно, если поиск в кэше для некоторого имени пути прошел успешно, то соответствующие файловые индексы уже закэшированы в памяти.

Операции с элементами каталогов Структура d e n t r y _ o p e r a t i o n s содержит методы, которые подсистема VFS мо жет вызывать для элементов каталогов определенной файловой системы. Эта струк тура определена в файле следующим образом.

struct dentry_operations { int (*d_revalidate) (struct dentry *, int);

int (*d_hash) (struct dentry *, struct qstr * ) ;

int (*d_corapare) (struct dentry *, struct qstr *, struct qstr * ) ;

int (*d_delete) (struct dentry * ) ;

void (*d_release) (struct dentry * ) ;

void (*d_iput) (struct dentry *, struct inode * ) ;

};

Виртуальная файловая система Методы служат для следующих целей • i n t d _ r e v a l i d a t e ( s t r u c t dentry *dentry, i n t flags) Эта функция определяет, является ли указанный объект элемента каталога дей ствительным. Подсистема VFS вызывает эту функцию, когда она пытается ис пользовать объект dentry из кэша dcache. Для большинства файловых систем этот метод установлен в значение NULL, потому что объекты denry, которые находятся в кэше, всегда действительны.

• i n t d _ h a s h ( s t r u c t dentry *dentry, s t r u c t q s t r *name) Эта функция создает значение хеш-ключа на основании указанного объекта dentry. Подсистема VFS вызывает эту функцию всякий раз, когда добавляет объ ект элемента каталога в хеш-таблицу.

• i n t d_compare(struct dentry *dentry, s t r u c t q s t r *narael, s t r u c t q s t r *name2) Эта функция вызывается подсистемой VFS для сравнения двух имен файлов namel и name2. Большинство файловых систем используют умолчание VFS, которое соответствует простому сравнению двух строк. Для некоторых файло вых систем, таких как FAT, не достаточно простого сравнения строк. Файловая система FAT не чувствительна к регистру символов в именах файлов, поэтому появляется необходимость в реализации функции, которая при сравнении не учитывает регистр символов. Эта функция вызывается при захваченной блоки ровке dcache_lock 7.

• i n t d_delete ( s t r u c t dentry *dentry) Эта функция вызывается подсистемой VFS, когда количество ссылок d_count указанного объекта dentry становится равным пулю. Функция вызывается при захваченной блокировке dcache_lock.

• void d _ r e l e a s e ( s t r u c t dentry *dentry) Эта функция вызывается подсистемой VFS, когда она собирается освободить указанный объект dentry. По умолчанию данная функция не выполняет ника ких действий.

• void d _ i p u t ( s t r u c t dentry *dentry, s t r u c t inode *inode) Эта функция вызывается подсистемой VFS, когда элемент каталога теряет связь со своим файловым индексом (например, когда этот элемент каталога удаляет ся с диска). По умолчанию подсистема VFS просто вызывает функцию i p u t (), чтобы освободить соответствующий объект inode. Если файловая система пе реопределяет эту функцию, то она также должна вызывать функцию i p u t () в дополнение к специфичной для файловой системы работе.

А также при захваченной блокировке dentry->d_lock. — Примеч. перев.

282 Глава Объект file Последним из основных объектов подсистемы VFS рассмотрим объект файла.

Объект File используется для представления файлов, которые открыты процессом.

Когда мы думаем о подсистеме VFS с точки зрения пространства пользователя, то объект файла — это то, что первое приходит в голову. Процессы непосредственно работают с файлами, а не с суперблоками, индексами или элементами каталогов. Не удивительно, что информация, которая содержится в объекте file, наиболее привыч на (такие данные, как режим доступа или текущее смещение), а файловые операции очень похожи на знакомые системные вызовы, такие как read () и w r i t e ().

Объект файла — это представление открытого файла, которое хранится в опе ративной памяти. Объект (а не сам файл) создается в ответ на системный вызов open () и уничтожается в результате системного вызова c l o s e (). Все вызовы, свя занные с файлом, на самом деле являются методами, которые определены в таблице операций с файлом. Так как несколько процессов могут одновременно открыть и ис пользовать один и тот же файл, то для одного файла может существовать несколько объектов file. Файловый объект просто представляет открытый файл с точки зрения процесса. Этот объект содержит указатель на соответствующий элемент каталога (ко торый, в свою очередь, указывает на файловый индекс), представляющий открытый файл. Соответствующие объекты inode и dentry, конечно, являются уникальными.

Файловый объект представляется с помощью структуры s t r u c t f i l e, которая определена в файле < l i n u x / f s. h >. Рассмотрим поля этой структуры с комментари ями, которые описывают назначение каждого поля.

struct file { struct list_head f_list;

/* список объектов file*/ struct dentry *f_dentry;

/* связанный объект dentry */ struct vfsmount *f_vfsmnt;

/* связанная смонтированная файловая система */ struct file_operations *f_op;

/* таблица файловых операций */ atomic_t f_count;

/* счетчик ссылок на этот объект */ unsigned int f_flags;

/* флаги, указанные при вызове функции open */ mode_t f_mode;

/* режим доступа к файлу */ loff_t f_pos;

/* смещение в файле (file pointer, offset) */ struct fown_struct f_owner;

/* информация о владельце для обработки сигналов */ unsigned int f_uid;

/* идентификатор пользователя владельца, UID */ unsigned int f_gid;

/* идентификатор группы владельца, GID */ int f_error;

/* код ошибки */ struct file_ra_state f_ra;

/* состояние предварительного считывания */ unsigned long f_version;

/* номер версии */ void *f_security;

/* модуль безопасности */ void *private_data;

/* привязка для драйвера терминала */ struct list_head f_ep_links;

/* список ссылок eventpoll (опрос событий) */ spinlock_t f_ep_lock;

/* блокировка eventpoll */ struct address_space *f_mapping;

/* отображение в страничном кэше */ };

Виртуальная файловая система По аналогии с объектом элемента каталога объект файла на самом деле не соот ветствует никакой структуре, которая хранится на жестком диске. Поэтому в этой структуре нет никакого флага, который бы указывал, что объект изменен (dirty) и требует обратной записи на диск. Объект file указывает на связанный с ним объект dentry с помощью указателя f_dentry. Объект dentry в свою очередь содержит ука затель на связанный с ним индекс файла, который содержит информацию о том, изменен ли файл.

Файловые операции Как и для других объектоп подсистемы VFS, таблица файловых операций являет ся важной структурой. Операции, связанные со структурой s t r u c t f i l e, — это зна комые системные вызовы, составляющие основу системных вызовов ОС Unix.

Методы работы с файловым объектом хранятся в структуре file_operations и определены в файле следующим образом.

struct file_operations { struct module *owner;

loff_t (*llseek) (struct file *, loff_t, int);

ssize_t (*read) (struct file *, char *, size_t, loff_t *);

ssize_t (*aio_read) (struct kiocb *, char *, size_t, loff_t);

ssize_t (*write) (struct file *, const char *, size_t, loff_t * ) ;

ssize_t (*aio_write) (struct kiocb *, const char *, size_t, loff_t);

int (*readdir) (struct file *, void *, filldir_t);

unsigned int (*poll) (struct file *, struct poll_table_struct * ) ;

int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);

int (*mmap) (struct file *, struct vm_area_struct * ) ;

int (*open) (struct inode *, struct file * ) ;

int (*flush) (struct file * ) ;

int (*release) (struct inode *, struct file * ) ;

int (*fsync) (struct file *, struct dentry *, int);

int (*aio_fsync) (struct kiocb *, int);

int (*fasync) (int, struct file *, int);

int (*lock) (struct file *, int, struct file_lock * ) ;

ssize_t (*readv) (struct file *, const struct iovec *, unsigned long, loff_t * ) ;

ssize_t (*writev) (struct file *, const struct iovec *, unsigned long, loff_t * ) ;

ssize_t (*sendfile) (struct file *, loff_t *, size_t, read_actor_t, void * ) ;

ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);

unsigned long (*get_unmapped_area) (struct file *, unsigned long, unsigned long, unsigned long, unsigned long);

int (*check_flags) (int flags);

int (*dir_notify) (struct file *filp, unsigned long arg);

int (*flock) (struct file *filp, int cmd, struct file_lock *fl);

};

284 Глава Файловые системы могут реализовать уникальную функцию для каждой из этих операций или использовать общий существующий метод. Общие методы нормально работают для обычных Unix-подобных файловых систем. Разработчики файловых систем не обязаны реализовать все эти функции, хотя основные методы должны быть реализованы. Если какой-либо метод не представляет интереса, то его можно установить в значение NULL.

Рассмотрим каждую операцию подробнее.

• loff_t llseek(struct file *file, loff_t offset, int origin) Эта функция устанавливает значения указателя текущей позиции в файле (file pointer) в заданное значение параметра offset. Функция вызывается из си стемного вызова lseek().

• ssize_t r e a d ( s t r u c t f i l e * f i l e, char *buf, size_t count, loff_t *offset) Эта функция считывает count байт данных из указанного файла, начиная с позиции, заданной параметром offset, в буфер памяти, на который указы вает параметр buf. После этого значение указателя текущей позиции в файле должно быть обновлено. Данная функция вызывается из системного вызова read().

• ssize_t aio_read(struct kiocb *iocb, char *buf, size_t count, loff_t offset) Эта функция запускает асинхронную операцию считывания count байт данных из файла, который описывается параметром iocb, в буфер памяти, описанный параметром buf. Эта функция вызывается из системного вызова aio_read ().

• ssize_t w r i t e ( s t r u c t f i l e * f i l e, const char *buf, size_t count, loff_t *offset) Эта функция записывает count байт данных в указанный файл, начиная с по зиции offset. Данная функция вызывается из системного вызова write ().

• ssize_t aio_write(struct kiocb *iocb, const char *buf, size_t count, loff_t offset) Эта функция запускает асинхронную операцию записи count байт данных в файл, описываемый параметром iocb, из буфера памяти, на который указывает параметр buf. Данная функция вызывается из системного вызова aio_write.

• i n t r e a d d i r ( s t r u c t f i l e * f i l e, void * d i r e n t, f i l l d i r _ t f i l l d i r ) Эта функция возвращает следующий элемент из списка содержимого каталога.

Данная функция вызывается из системного вызова readdir ().

• unsigned i n t p o l l ( s t r u c t f i l e * f i l e, s t r u c t p o l l _ t a b l e _ s t r u c t *poll_table) Эта функция переводит вызывающий процесс в состояние ожидания для ожи дания действий, которые производятся с указанным файлом. Она вызывается из системного вызова p o l l ( ).

Виртуальная файловая система • int ioctl(struct inode *inode, struct file *file, unsigned int cmd, signed long arg) Эта функция используется для того, чтобы отправлять устройствам пары зна чений команда/аргумент. Функция используется, когда открытый файл— это специальный файл устройства. Данная функция вызывается из системного вы зова i o c t l ( ).

• i n t mmap(struct f i l e * f i l e, s t r u c t vra_area_struct *vma) Эта функция отображает указанный файл на область памяти в указанном адрес ном пространстве и вызывается из системного вызова mmap().

• i n t o p e n ( s t r u c t inode *inode, struct file *file) Эта функция создает новый файловый объект и связывает его с указанным файловым индексом. Она вызывается из системного вызова open ().

• i n t f l u s h ( s t r u c t f i l e *file) Эта функция вызывается подсистемой VFS, когда уменьшается счетчик ссылок на открытый файл. Назначение данной функции зависит от файловой системы.

• i n t r e l e a s e ( s t r u c t inode *inode, s t r u c t f i l e *file) Эта функция вызывается подсистемой VFS, когда исчезает последняя ссылка на файл, например, когда последний процесс, который использовал соответству ющий файловый дескриптор, вызывает функцию c l o s e () или завершается.

Назначение этой функции также зависит от файловой системы.

• int fsync(struct file *file, s t r u c t dentry *dentry, i n t datasync) Эта функция вызывается из системного вызова fsync() для записи на диск всех закэшированных данных файла.

• i n t a i o _ f s y n c ( s t r u c t kiocb *iocb, i n t datasync) Эта функция вызывается из системного вызова a i o f sync () для записи на диск всех закэшированных данных файла, связанного с параметром iocb.

• i n t fasyn (fint fd, s t r u c t f i l e * f i l e, i n t on) Эта функция разрешает или запрещает отправку сигнала для уведомлении о со бытиях при асинхронном вводе-выводе.

• int lock(struct file *file, i n t cmd, s t r u c t file_lock *lock) Эта функция управляет файловыми блокировками для данного файла.

• ssize_t readv(struct file * f i l e, const s t r u c t iovec *vector, unsigned long count, loff_t *offset) Эта функция вызывается из системного вызова readv () для считывания дан ных из указанного файла в count буферов, которые описываются параметром vector. После этого указатель текущей позиции файла должен быть соответ ственным образом увеличен.

286 Глава • ssize_t writev(struct file *file, const struct iovec *vector, unsigned long count, loff_t *offset) Эта функция вызывается из системного вызова writev () для записи в указан ный файл буферов, описанных параметром vector;

количество буферов равно count. После этого должно быть соответственным образом увеличено значе ние текущей позиции в файле.

• ssize_t sendfile(struct file *file, loff_t *offset, size_t size, read_actor_t actor, void *target) Эта функция вызывается из системного вызова s e n d f i l e () для копирования данных из одного файла в другой. Она выполняет операцию копирования ис ключительно в режиме ядра и позволяет избежать дополнительного копирова ния данных в пространство пользователя.

• s s i z e _ t sendpage(struct f i l e * f i l e, s t r u c t page *page, i n t offset, size_t size, loff_t *pos, i n t more) Эта функция используется для отправки данных из одного файла в другой.

• unsigned long get_unmapped_area(struct f i l e * f i l e, unsigned long addr, unsigned long len, unsigned long o f f s e t, unsigned long flags) Эта функция получает неиспользуемое пространство адресов для отображения данного файла.

• i n t check_flags(int flags) Эта функция используется для проверки корректности флагов, которые пере даются в системный вызов f c n t l (), при использовании команды SETFL. Как и в случае многих операций подсистемы VFS, для файловой системы нет необхо димости реализовать функцию check_flags (). Сейчас это сделано только для файловой системы NFS. Эта функция позволяет файловой системе ограничить некорректные значения флагов команды SETFL в обобщенном системном вы зове f c n t l ( ). Для файловой системы NFS не разрешается использовать ком бинацию флагов O_APPEND и O_DIRECT.

• i n t f l o c k ( s t r u c t f i l e * f i l p, i n t cmd, s t r u c t file_lock *fl) Эта функция используется для реализации системного вызова flock(), кото рый служит для выполнения рекомендованных блокировок.

Виртуальная файловая система Структуры данных, связанные с файловыми системами В дополнение к фундаментальным объектам подсистемы VFS, ядро использует и другие стандартные структуры данных для управления данными, связанными с фай ловыми системами. Первый объект используется для описания конкретного типа файловой системы, как, например, ext.3 или XFS. Вторая структура данных использу ется для описания каждого экземпляра смонтированной файловой системы.

Поскольку операционная система Linux поддерживает множество файловых си стем, то ядро должно иметь специальную структуру для описания возможностей и поведения каждой файловой системы.

struct file_system_type { const char *name;

/* название файловой системы */ struct subsystem subsys;

/* объект подсистемы sysfs */ int fs_flags;

/* флаги типа файловой системы */ /* следующая функция используется для считывания суперблока с диска */ struct super_block * (*get_sb) (struct file_system_type *, int, char*, void * ) ;

/* эта функция используется для прекращения доступа к суперблоку */ void (*kill_sb) (struct super_block * ) ;

struct module *owner;

/* соответствующий модуль (если есть) */ struct file_system_type *next;

/* следующая файловая система в списке */ struct list_head fs_supera;

/* список объектов типа суперблок */ };

Функция get_sb () служит для считывания суперблока с диска и заполнения объ екта суперблока соответствующими данными при монтировании файловой системы.

Остальные параметры описывают свойства файловой системы.

Для каждого типа файловой системы существует только одна структура f i l e _ s y s tem_type, независимо от того, сколько таких файловых систем смонтировано и смонтирован ли хотя бы один экземпляр соответствующей файловой системы.

Значительно интереснее становится, когда файловая система монтируется, при этом создается структура vfsmount. Эта структура используется для представления конкретного экземпляра файловой системы, или, другими словами, точки монтиро вания.

Структура vfsmount определена в файле следующим образом.

struct vfsmount { struct list_head mnt_hash;

/* список хеш-таблицы */ struct vfsmount *mnt_parent;

/* родительская файловая система */ struct dentry *mnt_mountpoint;

/* объект элемента каталога точки монтирования */ struct dentry *mnt_root;

/* объект элемента каталога корня данной файловой системы */ struct super_block *mnt_sb;

/* суперблок данной файловой системы */ struct list_head mnt_mounts;

/* список файловых систем, смонтированных к данной */ struct list_head mnt_child;

/* потомки, связанные с родителем */ 288 Глава atornic_t mnt count;

/* счетчик использования */ int mnt_flags;

/* флаги монтирования */ char *mnt_devname;

/* имя смонтированного устройства */ struct list_hcad mnt_list;

/* список дескрипторов */ struct listhead mnt_fslinkk;

/* истекший список, специфичный для файловой системы */ struct namespace *mnt_namespace;

/* связанное пространство имен */ };

Самая сложная задача — это поддержание списка всех точек монтирования и вза имоотношений между данной файловой системой и другими точками монтирования.

Эта информация хранится в различных связанных списках структуры vf smount.

Структура vfsmount также содержит иоле mnt_flags. В табл. 12.1 приведен спи сок стандартных флагов монтирования.

Таблица 1 2. 1. Список стандартных флагов монтирования Флаг Описание MNT_NOSUID Запрещает использование флагов setuid и s e t g i d для бинарных файлов на файловой системе MNT_NODEV Запрещает доступ к файлам устройств на файловой системе MNT_NOEXEC Запрещает выполнение программ на файловой системе Эти флаги полезны, в основном, для сменных носителей, которым администра тор не доверяет.

Структуры данных, связанные с процессом Каждый процесс в системе имеет свои открытые файлы, корневую файловую си стем);

текущий рабочий каталог, точки монтирования и т.д. Следующие три структу ры данных связывают вместе подсистему VFS и процессы, которые выполняются в системе. Это структуры f i l e s _ s t r u c t, fs_struct и namespace.

Структура f i l e s _ s t r u c t определена в файле . Адрес этой струк туры хранится в поле f i l e s дескриптора процесса. В данной структуре хранится вся информация процесса об открытых файлах и файловых дескрипторах. Эта структу ра, с комментариями, имеет следующий вид.

struct files_struct { atomic_t count;

/* счетчик ссылок на данную структуру */ spinlock_t file_lock;

/* блокировка для защиты данной структуры */ int max_fds;

/* максимальное количество файловых объектов */ int max_fdset;

/* максимальное количество файловых дескрипторов */ int next_fd;

/* номер следующего файлового дескриптора */ struct file **fd;

/* массив всех файловых объектов */ fd_set *close on exec;

/* файловые дескрипторы, которые должны закрываться при вызове ехес() */ fd_set *open_fds;

/* указатель на дескрипторы открытых файлов */ fd_set close_on_exec init;

/* первоначальные файлы для закрытия при вызове exec () */ fd_set open_fds_init;

/* первоначальный набор файловых дескрипторов */ struct file *fd_array[NR_OPEN_DEFAULT];

/* массив файловых объектов */ };

Виртуальная файловая система Массив fd указывает на список открытых файловых объектов. По умолчанию это массив fd_array. Так как по умолчанию значение константы NR_OPEN_DEFAULT равно 32, то это соответствует 32 файловым объектам. Если процесс открывает боль ше 32 файловых объектов, то ядро выделяет новый массив и присваивает полю fd указатель на него. При таком подходе доступ к небольшому количеству файловых объектов осуществляется быстро, потому что они хранятся в статическом массиве.

В случае, когда процесс открывает аномально большое количество файлов, ядро мо жет создать новый массив. Если большинство процессов в системе открывает боль ше 32 файлов, то для получения оптимальной производительности администратор может увеличить значение константы NR_OPEN_DE_FAULT с помощью директивы препроцессора. Следующая структура данных, связанная с процессом, — это струк тура f s _ s t r u c t, которая содержит информацию, связанную с процессом, и на ко торую указывает поле fs дескриптора процесса. Эта структура определена в файле < l i n u x / f s _ s t r u c t. h > и имеет следующий вид с поясняющими комментариями.

struct fs struct { atomic_t count;

/* счетчик ссылок на структуру */ rwlock_l lock;

/* блокировка для защиты структуры */ int umask;

/* права доступа к файлу, используемые по умолчанию */ struct dentry *root;

/* объект dentry корневого каталога */ struct dentry *pwd;

/* объект dentry текущего рабочего каталога */ struct dentry *allroot;

/* объект dentry альтернативного корня */ struct vfsmounL *rootmnt;

/* объект монтирования корневого каталога */ struct vfsmount *pwdmnt;

/* объект монтирования текущего рабочего каталога */ struct vfsmount *altrootrnnt;

/* объект монтирования альтернативного корня */ };

Эта структура содержит текущий рабочий каталог и корневой каталог данного процесса.

Третья, и последняя, структура— это структура namespace, которая определена в файле и на экземпляр которой указывает поле namespace дескриптора процесса. Пространства имен, индивидуальные для каждого процесса, были введены в ядрах Linux серии 2.4. Это позволило создать для каждого процес са уникальное представление о смонтированных файловых системах. Иными слова ми, процесс может иметь не только уникальный корневой каталог, но и полностью уникальную иерархию смонтированных файловых систем, если это необходимо. Как обычно, ниже приведена соответствующая структура данных с комментариями.

struct namespace { atomic_t count;

/* счетчик ссылок на структуру */ struct vfsmount *root;

/* объект монтирования корневого каталога */ struct list_head list;

/* список точек монтирования */ struct rw_semaphore sem;

/* семафор для защиты пространства имен */ };

Поле l i s t представляет собой двухсвязный список смонтированных файловых систем, которые составляют пространство имен.

Каждый дескриптор процесса имеет связанные с ним рассмотренные структу ры данных. Для большинства процессов их дескриптор процесса указывает на уни кальную структуру f i l e s _ s t r u c t и структуру f s _ s t r u c t. Однако для процессов, 290 Глава созданных с флагами CLONE_FILES и CLONE_FS, эти структуры являются совмест но используемыми. Отсюда следует, что несколько дескрипторов процессов могут указывать на одну и ту же структуру f i l e s _ s t r u c t, или структуру f s _ s t r u c t. Поле count каждой структуры содержит счетчик использования, что предотвращает уни чтожение структуры данных, когда ее использует хотя бы один процесс.

Структура n a m e s p a c e используется несколько по-другому. По умолчанию вес процессы совместно используют одно пространство имен (и соответственно одну иерархию файловых систем). Только когда для системного вызова c l o n e () указан флаг CLONE_NEWNS, для процесса создается уникальная копия пространства имен.

Поскольку для большинства процессов этот флаг не указывается, процессы обычно наследуют пространство имен родительского процесса. Следовательно, для большин ства систем существует только одно пространство имен.

Файловые системы в операционной системе Linux Операционная система Linux поддерживает большой набор файловых систем, от "родных" ext2 и ext3 до сетевых файловых систем, таких как NFS или Coda. Сейчас в официальном ядре ОС Linux поддерживается более 50 файловых систем. Уровень VFS обеспечивает все эти разнообразные файловые системы общей базой для их реа лизации и общим интерфейсом для работы со стандартными системными вызовами.

Следовательно, уровень виртуальной файловой системы позволяет четким образом реализовать поддержку новых файловых систем в операционной системе Linux, a также дает возможность работать с этими файловыми системами с помощью стан дартных системных вызовов Unix.

В этой главе было описано назначение подсистемы VFS и рассмотрены соответ ствующие структуры данных, включая такие важные объекты, как inode, dentry и superblock. В главе 12, "Виртуальная файловая система", будет рассказано о том, как данные физически поступают на файловые системы.

Для создания потоков обычно укапываются флаги CLONE_FILES и CLONB_FS, поэтому они совмест но используют структуры iles_struct и fs_struct. С другой стороны, для обычных процессов эти флаги не указываются, поэтому для каждого процесса существует своя информация о файло вой системе и своя таблица открытых файлов.

Виртуальная файловая система Уровень блочного ввода-вывода У стройства блочного ввода-вывода (блочные устройства, устройства ввода-вы вода блоками, block devices) — это аппаратные устройства, которые позволяют случайным образом (т.е. не обязательно последовательно) осуществлять доступ к фрагментам данных фиксированного размера, называемых блоками. Наиболее часто встречающееся устройство блочного впода-вывода — это жесткий диск, но существу ют и другие блочные устройства, например устройства работы с гибкими дисками, оптическими компакт-дисками (CD-ROM) и флеш-памятью. Следует обратить внима ние, что файловые системы монтируются с таких устройств. Именно таким образом обычно и осуществляется доступ к устройствам блочного ввода-вывода.

Другой фундаментальный тип устройства — это устройство посимвольного вво да-вывода (символьное устройство, character device, char device). Это— устройство, к которому можно обращаться, только как к последовательному потоку данных, т.е.

байт за байтом. Пример символьных устройств — это последовательный порт и кла виатура. Если же устройстпо позволяет обращаться к данным случайным образом (не последовательно), то это блочное устройство.

Существенное различие между этими типами устройств проявляется, в основном, в возможности случайного доступа к данным, т.е. в возможности производить поиск (seek) по устройству, перемещаясь из одной позиции в другую. Как пример, рассмо трим клавиатуру. Драйвер устройства клавиатуры на выходе выдает поток данных.

Когда печатают слово "fox", то драйвер клавиатуры возвращает поток данных, в ко тором три символа идут строго в указанном порядке. Считывание символов в другом порядке или считывание какого-нибудь другого символа, кроме следующего символа в потоке, имеет немного смысла. Поэтому драйвер клавиатуры — это устройство по символьного ввода-вывода, он позволяет на выходе получить поток символов, кото рые пользователь вводит на клавиатуре. Операция чтения данных с устройства воз вращает сначала символ "f", затем символ "о" и в конце символ "х". Когда нажатий клавиш нет, то поток— пустой. Жесткий диск же работает по-другому Драйвер жест кого диска может потребовать чтения содержимого определенного блока, а затем прочитать содержимое другого блока, и эти блоки не обязательно должны следовать друг за другом. Поэтому доступ к данным жесткого диска может выполняться случай ным образом, а не как к потоку данных, и поэтому жесткий диск— блочное устрой ство.

Управление блочными устройствами в ядре требует большего внимания, подго товки и работы, чем управление устройствами посимвольного ввода-вывода. Все это потому, что символьные устройства имеют всего одну позицию — текущую, в то вре мя как блочные устройства должны иметь возможность перемещаться туда и обрат но между любыми позициями на физическом носителе информации. Оказывается, что нет необходимости создавать в ядре целую подсистему для обслуживания сим вольных устройств, а для блочных устройств это необходимо. Такая подсистема не обходима отчасти из-за сложности блочных устройств. Однако основная причина та кой мощной поддержки в том, что блочные устройства достаточно чувствительны к производительности. Выжать максимум производительности из жесткого диска зна чительно важнее, чем получить некоторый прирост скорости при работе с клавиату рой. Более того, как будет видно дальше, сложность блочных устройств обеспечивает большой простор для таких оптимизаций. Предмет данной главы — как ядро управ ляет работой блочных устройств и запросами к этим устройствам. Рассматриваемая часть ядра называется уровнем, блочного ввода-вывода (block I/O layer). Интересно, что усовершенствование подсистемы блочного ввода-вывода было одной из целей раз рабатываемой серии ядра 2.5. В этой главе рассматриваются все новые особенности уровня блочного ввода-вывода, которые появились в ядрах серии 2.6.

Анатомия блочного устройства Наименьший адресуемый элемент блочного устройства называется сектором.

Размеры секторов — это числа, которые являются целыми степенями двойки, одна ко наиболее часто встречающийся размер — 512 байт. Размер сектора— это физи ческая характеристика устройства, а сектор — фундаментальный элемент блочного устройства. Устройства не могут адресовать или другим образом работать с элемен тами данных, размер которых меньше, чем один сектор, тем не менее многие блоч ные устройства могут передавать несколько секторов за один раз. Хотя большинство блочных устройств и имеет размер сектора, равный 512 байт, все же существуют и другие стандартные размеры сектора (например, большинство компакт-дисков CD-ROM имеют размер сектора, равный 2 Кбайт).

У программного обеспечения несколько другие цели, и поэтому там существует другая минимально адресуемая единица, которая называется блок. Блок— это аб стракция файловой системы, т.е. все обращения к файловым системам могут вы полняться только с данными, кратными размеру блока. Хотя физические устройства сами по себе адресуются на уровне секторов, ядро выполняет все дисковые опера ции в терминах блоков. Так как наименьший возможный адресуемый элемент —- это сектор, то размер блока не может быть меньше размера одного сектора и должен быть кратен размеру сектора. Более того, для ядра (так же как и для аппаратного обеспечения в случае секторов) необходимо, чтобы размер блока был целой степе нью двойки. Ядро также требует, чтобы блок имел размер, не больший, чем размер страницы памяти (см. главу 11, "Управление памятью" и главу 12, "Виртуальная фай ловая система") 1.

Это ограничение является искусственным и в будущем оно может быть отменено. Тем не менее требование, чтобы размер блока был меньше или равен размеру страницы памяти, позволяет зна чительно упростить ядро.

294 Глава Поэтому размер блока равен размеру сектора, умноженному на число, которое яв ляется целой степенью двойки. Наиболее часто встречающиеся размеры блока— это 512 байт, один килобайт и четыре килобайта.

Часто сбивает с толку то, что некоторые люди называют секторы и блоки по разному. Секторы, наименьшие адресуемые элементы устройства, иногда называют "аппаратными секторами" (hardware sector) или "блоками аппаратного устройства" (device block). В то время как блоки, наименьшие адресуемые единицы файловых систем, иногда называются "блоками файловой системы" (filesyst.em block) или "бло ками ввода-вывода" (I/O block). В этой главе будут использованы термины "сектор" (sector) и "блок" (block), однако следует помнить и о других возможных названиях.

На рис. 13.1 показана диаграмма соответствия между секторами и блоками.

Существует еще одна часто встречающаяся терминология. По крайней мере в отношении.к жестким дискам, используются термины кластеры, (clusters), цилиндры (cylinder, дорожка) и головки (head). Такие обозначения являются специфическими только для некоторых типов блочных устройств, они, в основном, невидимы для пользовательских программ и в этой главе рассматриваться не будут. Причина, по которой секторы являются важными для ядра, состоит в том, что все операции вво да-вывода должны выполняться в единицах секторов. Поэтому более высокоуров невые концепции ядра, которые основаны на блоках, являются надстройками над секторами.

Жесткий диск Блок Сектор Сектор Отображение секторов на блоки i Рис. 13.1. Связь между секторами и бликами Буферы и заголовки буферов Когда блок хранится в памяти (скажем, после считывания или в ожидании запи си), то он хранится в структуре данных, называемой буфером (buffer). Каждый буфер связан строго с одним блоком. Буфер играет роль объекта, который представляет блок в оперативной памяти. Вспомним, что блок состоит из одного или больше сек торов, но по размеру не может быть больше одной страницы памяти. Поэтому одна страница памяти может содержать один или больше блоков. Поскольку для ядра требуется некоторая управляющая информация, связанная с данными (например, какому блочному устройству и какому блоку соответствует буфер), то каждый буфер связан со своим дескриптором. Этот дескриптор называется заголовком, буфера (buffer head) и представляется с помощью структуры s t r u c t buffer_head.

Уровень блочного ввода-вывода Структура b u f f e r _ h e a d содержит информацию, которая необходима ядру для управления буферами и определена в файле .

Рассмотрим эту структуру с комментариями, которые описывают назначение каж дого поля.

. struct buffer_head { unsigned long b_state;

/* флаги состояния буфера */ atomic_t b_count;

/* счетчик использования буфера */ struct buffer_head *b_this_page;

/* список буферов в текущей странице памяти */ struct page *b_page;

/* соответствующая страница памяти */ sector_t b_blocknr;

/* логический номер блока */ u32 b_size;

/* размер блока (в байтах) */ char *b_data;

/*указатель на буфер в странице памяти */ struct block_device *b_bdev;

/* соответствующее блочное устройство */ bh_end_io_t *b_end_io;

/* метод завершения ввода-вывода */ void *b_private;

/* данные метода завершения */ struct list_head b_assoc_buffers;

/* список связанных отображений */ };

Поле b _ s t a t e содержит состояние определенного буфера. Это значение мо жет содержать один или несколько флагов, которые перечислены в табл. 13.1.

Возможные значения флагов описаны в виде перечисления b h _ s t a t e _ b i t s, кото рое описано в файле .

Таблица 1 3. 1. Значения флагов поля b h _ s t a t e Флаг состояния Назначение BH_Uptodate Буфер содержит действительные данные BH_Dirty Буфер изменен (содержимое буфера новее соответствующих данных на диске, и поэтому буфер должен быть записан на диск) BH_Lock Для буфера выполняется операция чтения-записи дисковых данных, и буфер заблокирован, чтобы предотвратить конкурентный доступ BH_Req Буфер включен в запрос BH_Mapped Буфер является действительным и отображается на дисковый блок BH_NEW Буфер только что выделен и к нему еще не было доступа BH_Async_ Read Для буфера выполняется асинхронная операция чтения вн_Азуnс W r i t e Для буфера выполняется асинхронная операция записи вн_Dеlау С буфером еще не связан дисковый блок BH_Boundary Буфер является последним в последовательности смежных блоков — следующий за ним блок не является смежным с этой серией Перечисление b h _ s t a t e _ b i t s также содержит в качестве последнего элемента флаг B H _ P r i v a t e S t a r t. Этот флаг не является разрешенным значением флага, а со ответствует первому биту, который можно использовать по усмотрению разработчи ков кода. Все биты, номер которых больше или равен значению B H _ P r i v a t e S t a r t ;

не используются подсистемой блочного ввода-вывода и безопасно могут использо ваться драйверами, которым необходимо хранить информацию в поле b _ s t a t e.

296 Глава Флаги, которые используются драйверами, могут быть определены на основании значения этого флага, что позволяет гарантированно избежать перекрытия с бита ми, которые официально используются уровнем блочного ввода-вывода.

Поле b _ c o u n t — это счетчик использования буфера. Значение этого поля увели чивается и уменьшается двумя функциями, которые определены в файле < l i n u x / b u f f e r _ h e a d. h > следующим образом.

static inline void get_bh (struct buffer_head *bh) { atomic_inc{&bh->b_count);

} static inline void put_bh (struct buffer_head *bh) { atomic_dec (&bh->b_count);

} Перед тем как манипулировать заголовком буфера, необходимо увеличить значе ние счетчика использования с помощью функции g e t _ b h ( ), что гарантирует, что во время работы буфер не будет освобожден. Когда работа с заголовком буфера за кончена, необходимо уменьшить значение счетчика, ссылок с помощью функции put_bh ( ).

Физический блок на жестком диске, которому соответствует буфер, — это блок с логическим номером b _ b l o c k n r, который находится на блочном устройстве b_bdev.

Физическая страница памяти, в которой хранятся данные буфера, соответствует значению поля b_page. Поле b _ d a t a — это указатель прямо на данные блока (ко торые хранятся где-то в странице памяти b_page), размер блока хранится в поле b _ s i z e. Следовательно, блок хранится в памяти, начиная с адреса b _ d a t a и закан чивая адресом (b_data + b _ s i z e ).

Назначение заголовка буфера— это описание отображения между блоком на дис ке и буфером в физической памяти (т.е. последовательностью байтов, которые хра нятся в указанной странице памяти). Выполнение роли дескриптора отображения буфер-блок —единственное назначение этой структуры данных ядра.

В ядрах до серии 2.6 заголовок буфера был значительно более важной структурой данных. По существу, это была единица ввода-вывода данных в ядре. Он не только выполнял роль дескриптора для отображения буфер-блок-страница физической памя ти, но и выступал контейнером для всех операций блочного ввода-вывода. Это при водило к двум проблемам. Первая проблема заключалась в том, что заголовок буфе ра был большой и громоздкой структурой данных (сегодня он несколько уменьшился в размерах), а кроме того, выполнение операций блочного ввода-вывода в терминах заголовков буферов было непростой и довольно непонятной задачей. Вместо этого, ядру лучше работать со страницами памяти, что одновременно и проще и позволяет получить большую производительность. Использовать большой заголовок буфера, описывающий отдельный буфер (который может быть размером со страницу памя ти), — неэффективно. В связи с этим в ядрах серии 2.6 было сделано много работы, чтобы дать возможность ядру выполнять операции непосредственно со страницами памяти и пространствами адресов, вместо операций с буферами. Некоторые из этих операций обсуждаются в главе 15, "Страничный кэш и обратная запись страниц", где также рассматривается структура a d d r e s s _ s p a c e и демоны p d f l u s h.

Уровень блочного ввода-вывода Вторая проблема, связанная с заголовками буферов, — это то, что они описывают только один буфер. Когда заголовок буфера используется в качестве контейнера для операций ввода-вывода, то это требует, чтобы ядро разбивало потенциально боль шую операцию блочного ввода-вывода на множество мелких структур buffer_head, что в свою очередь приводит к ненужным затратам памяти для храпения структур данных. В результате, основной целью при создании серии ядра 2.5 была разработка нового гибкого и быстрого контейнера для операций блочного ввода-вывода. В ре зультат появилась структура bio, которая будет рассмотрена в следующем разделе.

Структура b i o Основным контейнером для операций ввода-вывода в ядре является структура bio, которая определена в файле . Эта структура представляет ак тивные операции блочного ввода-вывода в виде списка сегментов (segment). Сегмент — это участок буфера, который является непрерывным в физической памяти, т.е. от дельные буферы не обязательно должны быть непрерывными в физической памяти.

Благодаря тому, что буфер может представляться в виде нескольких участков, струк тура bio даст возможность выполнять операции блочного ввода-вывода, даже если данные одного буфера хранятся в разных местах памяти. Ниже показана структура bio с комментариями, описывающими назначение каждого поля.

struct bio { sector_t bi_sector;

/* соответствующий сектор на диске */ struct bio *bi_next;

/* список запросов */ struct block_device *bi_bdev;

/* соответствующее блочное устройство */ unsigned long bi_flags;

/* состояние и флаги команды */ unsigned long bi_rw;

/* чтение или запись? */ unsigned short bi_vcnt;

/* количество структур bio vec в массиве bi_io_vec */ unsigned short bi_idx;

/* текущий индекс в массиве bi_io_vec */ unsigned short bi_phys_segments;

/*количество сегментов после объединения */ unsigned short bi_hw_segments;

/* количество сегментов после перестройки отображения */ unsigned int bi_size;

/* объем данных для ввода-вывода */ unsigned int bi_hw_front_size;

/* размер первого объединяемого сегмента */ unsigned int bi_hw_front_size;

/* размер последнего объединяемого сегмента */ unsigned int bi_max_vecs;

/* максимально возможное количество структур bio_vecs */ struct bio_vec *bi_io_vec;

/* массив структур bio_vec */ bio_end_io_t *bi_end_io;

/* метод завершения ввода-вывода */ atomic_t bi_cnb;

/* счетчик использования */ void *bi_private;

/* поле для информации создателя */ bio_destructor_t *bi_destructor;

/* деструктор */ };

Главное назначение структуры bio — это представление активной (выполняющей ся) операции блочного ввода-вывода. В связи с этим большинство полей этой струк туры являются служебными. Наиболее важные поля — это bi_io_vecs, bi_vcnt и bi_idx.

298 Глава Структура bio bi idx bi_io_vec Массив структур biovec, содержащий bio_vcnt элементов bio_vec bio vec bio_vec bio_vec Структура page Структура page Структура page Сгруктура Структуры page, задействованные page в операции блочного ввода-вывода Рис. 13.2. Связь между структурами struct bio, struct b,io_vec u struct page Поле bi_io_vecs указывает па начало массива структур bio_vec, Эти структуры используются в качестве списка отдельных сегментов в соответствующей операции блочного ввода-вывода. Каждый экземпляр структуры bio_vec представляет собой вектор следующего вида: <страница памяти, смещение, размер>, который опи сывает определенный сегмент, соответственно страницу памяти, где этот сегмент хранится, положение блока — смещение внутри страницы — и размер блока. Массив рассмотренных векторов описывает весь буфер полностью. Структура bio_vec опре делена в файле следующим образом.

struct bio_vec { /* указатель на страницу физической памяти, где находится этот буфер */ struct page *bv_page;

/* размер буфера в байтах */ Unsigned int bv_len;

/* смещение в байтах внутри страницы памяти, где находится буфер */ unsigned int bv_offset;

};

Для каждой операции блочного ввода-выпода создается массив из bi_vcnt эле ментов типа bio_vec, начало которого содержится в поле bi _io_vecs. В процессе выполнения операции блочного ввода-вывода поле bi_idx используется для указа ния па текущий элемент массива.

В общем, каждый запрос на выполнение блочного ввода-вывода представляется с помощью структуры bio. Каждый такой запрос состоит из одного или более бло ков, которые хранятся в массиве структур bio_vec. Каждая из этих структур пред ставляет собой вектор, который описывает положение в физической памяти каж дого сегмента запроса. На первый сегмент для операции ввода-вывода указывает поле bi_io_vec. Каждый следующий сегмент следует сразу за предыдущим. Всего Уровень блочного ввода-вывода в массиве b i _ v c n t сегментов. В процессе того, как уровень блочного ввода-вывода обрабатывает сегменты запроса, обновляется значение поля b i _ i d x, чтобы его зна чение соответствовало номеру текущего сегмента. На рис. 13.2 показана связь между структурами b i o, bio_vec и page.

Поле b i _ i d x указывает на текущую структуру b i o _ v e c в массиве, что позволя ет уровню блочного ввода-вывода поддерживать частично выполненные операции блочного ввода-вывода. Однако более важное использование состоит в том, что драйверы таких устройств, как RAID (Redundant Array of Inexpensive/Independent Disks, массив недорогих/независимых дисковых устройств с избыточностью — спе циальный способ использования жестких дисков, при котором один логический том может быть распределен но нескольким физическим дискам для увеличения надеж ности или производительности), могут одну структуру b i o, которая изначально была адресована одному устройству, разбивать на несколько частей, которые предназнача ются различным дискам RAID массива. Все, что необходимо сделать драйверу RAID, это создать необходимое количество копий структуры b i o, которая предназначалась одному устройству, и изменить для каждой копии значение поля b i _ i d x, чтобы оно указывало на ту часть массива, откуда каждый диск должен начать свою операцию ввода-вывода.

Структура b i o содержит счетчик использования, который хранится в поле b i _ c n t. Когда значение этого поля становится равным нулю, структура удаляется, и занятая память освобождается. Следующие две функции позволяют управлять счет чиком использования.

void bio_get(struct bio *bio) void bio_put(struct bio *bio) Первая увеличивает на единицу значение счетчика использования, а в т о р а я уменьшает значение этого счетчика на единицу и, если это значение становится равным нулю, уничтожает соответствующую структуру b i o. Перед тем как работать с активной структурой b i o, необходимо увеличить счетчик использования, чтобы гарантировать, что экземпляр структуры не будет удален во время работы. После окончания работы необходимо уменьшить счетчик использования.

И наконец, поле b i o _ p r i v a t e — это поле данных создателя (владельца) структу ры. Как правило, это поле необходимо считывать или записывать только тому, кто создал данный экземпляр структуры b i o.

Pages:     | 1 |   ...   | 4 | 5 || 7 | 8 |   ...   | 9 |



© 2011 www.dissers.ru - «Бесплатная электронная библиотека»

Материалы этого сайта размещены для ознакомления, все права принадлежат их авторам.
Если Вы не согласны с тем, что Ваш материал размещён на этом сайте, пожалуйста, напишите нам, мы в течении 1-2 рабочих дней удалим его.