Роберт Лав - Разработка ядра Linux
У программного обеспечения несколько другие цели, и поэтому там существует другая минимально адресуемая единица, которая называется блок. Блок— это абстракция файловой системы, т.е. все обращения к файловым системам могут выполняться только с данными, кратными размеру блока. Хотя физические устройства сами по себе адресуются на уровне секторов, ядро выполняет все дисковые операции в терминах блоков. Так как наименьший возможный адресуемый элемент —- это сектор, то размер блока не может быть меньше размера одного сектора и должен быть кратен размеру сектора. Более того, для ядра (так же как и для аппаратного обеспечения в случае секторов) необходимо, чтобы размер блока был целой степенью двойки. Ядро также требует, чтобы блок имел размер, не больший, чем размер страницы памяти (см. главу 11, "Управление памятью" и главу 12, "Виртуальная файловая система")[75].
Поэтому размер блока равен размеру сектора, умноженному на число, которое является целой степенью двойки. Наиболее часто встречающиеся размеры блока — это 512 байт, один килобайт и четыре килобайта.
Часто сбивает с толку то, что некоторые люди называют секторы и блоки по- разному. Секторы, наименьшие адресуемые элементы устройства, иногда называют "аппаратными секторами" (hardware sector) или "блоками аппаратного устройства" (device block). В то время как блоки, наименьшие адресуемые единицы файловых систем, иногда называются "блоками файловой системы" (filesystem block) или "блоками ввода-вывода" (I/O block). В этой главе будут использованы термины "сектор" (sector) и "блок" (block), однако следует помнить и о других возможных названиях. На рис. 13.1 показана диаграмма соответствия между секторами и блоками.
Рис. 13.1. Связь между секторами и бликами
Существует еще одна часто встречающаяся терминология. По крайней мере в отношении к жестким дискам, используются термины кластеры (clusters), цилиндры (cylinder, дорожка) и головки (head). Такие обозначения являются специфическими только для некоторых типов блочных устройств, они, в основном, невидимы для пользовательских программ и в этой главе рассматриваться не будут. Причина, по которой секторы являются важными для ядра, состоит в том, что все операции ввода-вывода должны выполняться в единицах секторов. Поэтому более высокоуровневые концепции ядра, которые основаны на блоках, являются надстройками над секторами.
Буферы и заголовки буферов
Когда блок хранится в памяти (скажем, после считывания или в ожидании записи), то он хранится в структуре данных, называемой буфером (buffer). Каждый буфер связан строго с одним блоком. Буфер играет роль объекта, который представляет блок в оперативной памяти. Вспомним, что блок состоит из одного или больше секторов, но по размеру не может быть больше одной страницы памяти. Поэтому одна страница памяти может содержать один или больше блоков. Поскольку для ядра требуется некоторая управляющая информация, связанная с данными (например, какому блочному устройству и какому блоку соответствует буфер), то каждый буфер связан со своим дескриптором. Этот дескриптор называется заголовком буфера (buffer head) и представляется с помощью структуры struct buffer_head.
Структура buffer_head содержит информацию, которая необходима ядру для управления буферами и определена в файле <linux/buffer_head.h>.
Рассмотрим эту структуру с комментариями, которые описывают назначение каждого поля.
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_state содержит состояние определенного буфера. Это значение может содержать один или несколько флагов, которые перечислены в табл. 13.1. Возможные значения флагов описаны в виде перечисления bh_state_bits, которое описано в файле <linux/buffer_head.h>.
Таблица 13.1. Значения флагов поля bh_state
Флаг состояния Назначение BH_Uptodate Буфер содержит действительные данные BH_Dirty Буфер изменен (содержимое буфера новее соответствующих данных на диске, и поэтому буфер должен быть записан на диск) BH_Lock Для буфера выполняется операция чтения-записи дисковых данных, и буфер заблокирован, чтобы предотвратить конкурентный доступ BH_Req Буфер включен в запрос BH_Mapped Буфер является действительным и отображается на дисковый блок BH_New Буфер только что выделен и к нему еще не было доступа BH_Async_Read Для буфера выполняется асинхронная операция чтения BH_Async_Write Для буфера выполняется асинхронная операция записи BH_Delay С буфером еще не связан дисковый блок BH_Boundary Буфер является последним в последовательности смежных блоков — следующий за ним блок не является смежным с этой сериейПеречисление bh_state_bits также содержит в качестве последнего элемента флаг BH_PrivateStart. Этот флаг не является разрешенным значением флага, а соответствует первому биту, который можно использовать по усмотрению разработчиков кода. Все биты, номер которых больше или равен значению BH_PrivateStart, не используются подсистемой блочного ввода-вывода и безопасно могут использоваться драйверами, которым необходимо хранить информацию в поле b_state.
Флаги, которые используются драйверами, могут быть определены на основании значения этого флага, что позволяет гарантированно избежать перекрытия с битами, которые официально используются уровнем блочного ввода-вывода.
Поле b_count — это счетчик использования буфера. Значение этого поля увеличивается и уменьшается двумя функциями, которые определены в файле <linux/buffer_head.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);
}
Перед тем как манипулировать заголовком буфера, необходимо увеличить значение счетчика использования с помощью функции get_bh(), что гарантирует, что во время работы буфер не будет освобожден. Когда работа с заголовком буфера закончена, необходимо уменьшить значение счетчика, ссылок с помощью функции put_bh().
Физический блок на жестком диске, которому соответствует буфер, — это блок с логическим номером b_blocknr, который находится на блочном устройстве b_bdev.
Физическая страница памяти, в которой хранятся данные буфера, соответствует значению поля b_page. Поле b_data — это указатель прямо на данные блока (которые хранятся где-то в странице памяти b_page), размер блока хранится в поле b_size. Следовательно, блок хранится в памяти, начиная с адреса b_data и заканчивая адресом (b_data + b_size).
Назначение заголовка буфера — это описание отображения между блоком на диске и буфером в физической памяти (т.е. последовательностью байтов, которые хранятся в указанной странице памяти). Выполнение роли дескриптора отображения буфер-блок — единственное назначение этой структуры данных ядра.
В ядрах до серии 2.6 заголовок буфера был значительно более важной структурой данных. По существу, это была единица ввода-вывода данных в ядре. Он не только выполнял роль дескриптора для отображения буфер-блок-страница физической памяти, но и выступал контейнером для всех операций блочного ввода-вывода. Это приводило к двум проблемам. Первая проблема заключалась в том, что заголовок буфера был большой и громоздкой структурой данных (сегодня он несколько уменьшился в размерах), а кроме того, выполнение операций блочного ввода-вывода в терминах заголовков буферов было непростой и довольно непонятной задачей. Вместо этого, ядру лучше работать со страницами памяти, что одновременно и проще и позволяет получить большую производительность. Использовать большой заголовок буфера, описывающий отдельный буфер (который может быть размером со страницу памяти), — неэффективно. В связи с этим в ядрах серии 2.6 было сделано много работы, чтобы дать возможность ядру выполнять операции непосредственно со страницами памяти и пространствами адресов, вместо операций с буферами. Некоторые из этих операций обсуждаются в главе 15, "Страничный кэш и обратная запись страниц", где также рассматривается структура address_space и демоны pdflush.