Второе издание
Скачать 3.09 Mb.
|
Буферы и заголовки буферов Когда блок хранится в памяти (скажем, после считывания или в ожидании запи- си), то он хранится в структуре данных, называемой буфером (buffer). Каждый буфер связан строго с одним блоком. Буфер играет роль объекта, который представляет блок в оперативной памяти. Вспомним, что блок состоит из одного или больше сек- торов, но по размеру не может быть больше одной страницы памяти. Поэтому одна страница памяти может содержать один или больше блоков. Поскольку для ядра требуется некоторая управляющая информация, связанная с данными (например, какому блочному устройству и какому блоку соответствует буфер), то каждый буфер связан со своим дескриптором. Этот дескриптор называется заголовком, буфера (buffer head) и представляется с помощью структуры s t r u c t buffer_head. Уровень блочного ввода-вывода 295 Сектор Сектор 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 также содержит в качестве последнего элемента флаг BH_PrivateStart. Этот флаг не является разрешенным значением флага, а со- ответствует первому биту, который можно использовать по усмотрению разработчи- ков кода. Все биты, номер которых больше или равен значению B H _ P r i v a t e S t a r t ; не используются подсистемой блочного ввода-вывода и безопасно могут использо- ваться драйверами, которым необходимо хранить информацию в поле b _ s t a t e . 296 Глава 13 Флаг состояния Назначение Структура 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 , кото- рое описано в файле Таблица 13.1. Значения флагов поля b h _ s t a t e Флаги, которые используются драйверами, могут быть определены на основании значения этого флага, что позволяет гарантированно избежать перекрытия с бита- ми, которые официально используются уровнем блочного ввода-вывода. Поле b_count — это счетчик использования буфера. Значение этого поля увели- чивается и уменьшается двумя функциями, которые определены в файле < l i n u x / 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); } Перед тем как манипулировать заголовком буфера, необходимо увеличить значе- ние счетчика использования с помощью функции 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 и демоны pdflush. Уровень блочного ввода-вывода 297 Вторая проблема, связанная с заголовками буферов, — это то, что они описывают только один буфер. Когда заголовок буфера используется в качестве контейнера для операций ввода-вывода, то это требует, чтобы ядро разбивало потенциально боль- шую операцию блочного ввода-вывода на множество мелких структур buffer_head, что в свою очередь приводит к ненужным затратам памяти для храпения структур данных. В результате, основной целью при создании серии ядра 2.5 была разработка нового гибкого и быстрого контейнера для операций блочного ввода-вывода. В ре- зультат появилась структура bio, которая будет рассмотрена в следующем разделе. Структура b i o Основным контейнером для операций ввода-вывода в ядре является структура bio, которая определена в файле это участок буфера, который является непрерывным в физической памяти, т.е. от- дельные буферы не обязательно должны быть непрерывными в физической памяти. Благодаря тому, что буфер может представляться в виде нескольких участков, струк- тура 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 Глава 13 Массив структур biovec, содержащий bio_vcnt элементов Структуры 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. Каждый следующий сегмент следует сразу за предыдущим. Всего Уровень блочного ввода-вывода 299 Структура bio bi_io_vec bi idx Структура page Структура page Структура page Сгруктура page bio_vec bio vec bio_vec bio_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 . Сравнение старой и новой реализаций Между заголовками буферов и новой структурой b i o существуют важные отли- чия. Структура b i o представляет операцию ввода-вывода, которая может включать одну или больше страниц в физической памяти. С другой стороны, заголовок буфера связан с одним дисковым блоком, который занимает не более одной страницы па- мяти. Поэтому использование заголовков буферов приводит к ненужному делению запроса ввода-вывода на части, размером в один блок, только для того, чтобы их по- том снова объединить. Работа со структурами b i o выполняется быстрее, эта струк- тура может описывать несмежные блоки и не требует без необходимости разбивать операции ввода-вывода на части. Переход от структуры s t r u c t buffer_head к структурам s t r u c t b i o позволяет получить также и другие преимущества. 300 Глава 13 • Структура bio может легко представлять верхнюю память (см. главу 11), так как структура s t r u c t bio работает только со страницами физической памяти, а не с указателями. • Структура bio может представлять как обычные страничные операции ввода- вывода, так и операции непосредственного (direct) ввода-вывода (т.е. те, кото- рые не проходят через страничный кэш; страничный кэш обсуждается в гла- ве 15 ). • Структура bio позволяет легко выполнять операции блочного ввода-вывода типа распределения-аккумуляции (scatter-gather), в которых данные находятся в нескольких страницах физической памяти. • Структура bio значительно проще заголовка буфера, потому что она содержит только минимум информации, необходимой для представления операции блоч- ного ввода-вывода, а не информацию, которая связана с самим буфером. Тем не менее заголовки буферов все еще необходимы для функций, которые вы- полняют отображение дисковых блоков на страницы физической памяти. Структура bio не содержит никакой информации о состоянии буфера, это просто массив век- торов, которые описывают один или более сегментов данных одной операции блоч- ного ввода-вывода, плюс соответствующая дополнительная информация. Структура buffer_head необходима для хранения информации о буферах. Применение двух отдельных структур позволяет сделать размер обеих этих структур минимальным. Очереди запросов Для блочных устройств поддерживаются очереди запросов (request queue), в которых хранятся ожидающие запросы на выполнение операций блочного ввода-вывода. Очередь запросов представляется с помощью структуры request_queue, которая определена в файле Пока очередь запросов не пуста, драйвер блочного устройства, связанный с оче- редью, извлекает запросы из головы очереди и отправляет их на соответствующее блочное устройство. Каждый элемент списка запросов очереди— это один запрос, представленный с помощью структуры s t r u c t request. Запросы Отдельные запросы представляются с помощью структуры s t r u c t request, ко- торая тоже определена в файле Уровень блочного ввода-вывода 301 Планировщики ввода-вывода Простая отправка запросов на устройство ввода-вывода в том же порядке, в ко- тором эти запросы направляет ядро, приводит к очень плохой производительности. Одна из наиболее медленных операций, которые вообще могут быть в компьютере,— это поиск по жесткому диску. Операция поиска — это позиционирование головки жесткого диска на определенный блок, которое может запять много миллисекунд. Минимизация количества операций поиска чрезвычайно критична для производи- тельности всей системы. Поэтому ядро не отправляет все запросы на выполнение операций блочного вво- да-вывода жесткому диску в том же порядке, в котором они были получены, или сра- зу же, как только они были получены. Вместо этого, оно выполняет так называемые операции слияния (объединения, merging) и сортировка (sorting), которые позволяют зна- чительно увеличить производительность всей системы 2 . Подсистема ядра, которая выполняет эти операции называется планировщиком ввода-вывода (J/0 scheduler). Планировщик ввода-вывода разделяет дисковые ресурсы между ожидающими в очереди запросами впода-вывода. Это выполняется путем объединения и сортировки запросов ввода-вывода, которые находятся в очереди. Планировщик ввода-вывода не нужно путать с планировщиком выполнения процессов (см. главу 4, "Планирование выполнения процессов"), который делит ресурсы процессора между процессами си- стемы. Эти две подсистемы похожи, но это— не одно и то же. Как планировщик выполнения процессов, так и планировщик ввода-вывода выполняют виртуализацию ресурсов для нескольких объектов. В случае планировщика процессов выполняется виртуализация процессора, который совместно используется процессами системы. Это обеспечивает иллюзию того, что процессы выполняются одновременно, и яв- ляется основой многозадачных операционных систем с разделением времени, таких как Unix. С другой стороны, планировщики ввода-вывода выполняют виртуализацию блочных устройств для ожидающих выполнения запросов блочного ввода-вывода. Это делается для минимизации количества операций поиска по жесткому диску и для получения оптимальной производительности дисковых операций. Задачи планировщика ввода-вывода Планировщик ввода-вывода работает управляя очередью запросов блочного устройства. Он определяет, в каком порядке должны следовать запросы в очере- ди и в какое время каждый запрос должен передаваться на блочное устройство. Управление производится с целью уменьшения количества операций поиска по жесткому диску, что значительно повышает общее быстродействие. Определение "об- щее" здесь существенно. Планировщик ввода-вывода ведет себя "нечестно" по отно- шению к некоторым запросам и за счет этого повышает производительность систе- мы в целом. 2 Это необходимо подчеркнуть особо. Системы, не имеющие таких функций или в которых эти функции плохо реализованы, будут иметь очень плохую производительность даже при небольшом количестве операций блочного ввода-вывода. 302 Глава 13 Планировщики ввода-вывода выполняют две основные задачи: объединение и со- ртировку. Объединение — это слияние нескольких запросов в один. В качестве при- мера рассмотрим запрос, который поставлен в очередь кодом файловой системы, например чтобы прочитать порцию данных из файла (конечно, на данном этапе все уже выполняется на уровне секторов и блоков, а не на уровне файлов). Если в очере- ди уже есть запрос на чтение из соседнего сектора диска (например, другой участок того же файла), то два запроса могут быть объединены в один запрос для работы с одним или несколькими, расположенными рядом, секторами диска. Путем слия- ния запросов планировщик ввода-вывода уменьятает затраты ресурсов, связанные с обработкой нескольких запросов, до уровня необходимого на обработку одного за- проса. Наиболее важно здесь то, что диск выполняет всего одну команду и обслужи- вание нескольких запросов может быть выполнено вообще без применения поиска. Следовательно, слияние запросов уменьшает накладные расходы и минимизирует количество операций поиска. Теперь предположим, что наш запрос на чтение помещается в очередь запросов, но там нет других запросов на чтение соседних секторов. Поэтому нет возможности выполнить объединение этого запроса с другими запросами, находящимися в очере- ди. Можно просто поместить запрос в конец очереди. Но что если в очереди есть запросы к близкорасположенным секторам диска? Не лучше ли будет поместить но- вый запрос в очередь где-то рядом с запросами к физически близко расположенным секторам диска. На самом деле планировщики ввода-вывода именно так и делают, Вся очередь запросов поддерживается в отсортированном состоянии по секторам, чтобы последовательность запросов в очереди (насколько это возможно) соответ- ствовала линейному движению по секторам жесткого диска. Цель состоит в том, что- бы не только уменьшить количество перемещений в каждом индивидуальном случае, но и минимизировать общее количество операций поиска таким образом, чтобы головка двигалась по прямой линии. Это аналогично алгоритму, который использу- ется в лифте (elevator) — лифт не прыгает между этажами. Вместо этого он плавно пытается двигаться в одном направлении. Когда лифт доходит до последнего этажа в одном направлении, он начинает двигаться в другую сторону. Из-за такой аналогии планировщик ввода-вывода (а иногда только алгоритм сортировки) называют лифто- вым планировщиком (алгоритмом лифта, elevator). Лифтовой алгоритм Линуса Рассмотрим некоторые планировщики ввода-вывода, применяемые в реальной жизни. Первый планировщик ввода-вывода, который мы рассмотрим, называется Linus Elevator (лифтовой алгоритм Линуса). Это не опечатка, действительно существу- ет лифтовой планировщик, разработанный Лисусом Торвальдсом и названный в его честь! Это основной планировщик ввода-вывода в ядре 2.4. В ядре 2.6 его заменили другими планировщиками, которые мы ниже рассмотрим. Однако поскольку этот ал- горитм значительно проще новых и в то же время позволяет выполнять почти те же функции, то он заслуживает внимания. Лифтовой алгоритм Линуса позволяет выполнять как объединение, так и со- ртировку запросов. Когда запрос добавляется в очередь, вначале он сравнивается со всеми ожидающими запросами, чтобы обнаружить все возможные кандидаты па объединение. Алгоритм Линуса выполняет два типа объединения: добавление в начало Уровень блочного ввода-вывода 303 запроса (front merging) и добавление в конец запроса (back merging). Тип объединения со- ответствует тому, с какой стороны найдено соседство. Если новый запрос следует перед существующим, то выполняется вставка в начало запроса. Если новый запрос следует сразу за существующим — добавление выполняется в конец очереди. В связи с тем, что секторы, в которых хранится файл, расположены по мере увеличения но- мера сектора и операции ввода-вывода чаще всего выполняются от начала файла до конца, а не наоборот, то при обычной работе вставка в начало запроса встречается значительно реже, чем вставка в конец. Тем не менее алгоритм Линуса проверяет и выполняет оба типа объединения, Если попытка объединения была неудачной, то определяется возможное место вставки запроса в очередь (положение в очереди, в котором новый запрос наилуч- шим образом вписывается по номеру сектора между окружающими запросами). Если такое положение находится, то новый запрос помещается туда. Если подходящего места не найдено, то запрос помещается в конец очереди. В дополнение к этому, если в очереди найден запрос, который является достаточно старым, то новый за- прос также добавляется в конец очереди. Это предотвращает ситуацию, в которой наличие большого количества запросов к близко расположенным секторам при- водит к недостатку обслуживания других запросов. К сожалению, такая проверка "на старость" не очень эффективна. В рассмотренном алгоритме не предпринимает- ся никаких попыток обслуживания запросов в заданных временных рамках, а просто прекращается процесс сортировки-вставки при наличии определенной задержки. Это в свою очередь приводит к задержке в обслуживании, что было веской причи- ной для доработки планировщика ввода-пывода ядра 2.4. Итак, когда запрос добавляется в очередь возможны четыре типа действий. Вот эти действия в необходимой последовательности. • Если запрос к соседнему сектору находится в очереди, то существующий за- прос и новый объединяются в один. • Если в очереди существует достаточно старый запрос, то новый запрос поме- щается в конец очереди, чтобы предотвратить отказ обслуживания для других запросов, которые долгое время находятся в очереди. • Если для секторов данного запроса в очереди существует позиция, которая со- ответствует рациональному перемещению между секторами, то данный запрос помещается в эту позицию, что позволяет поддерживать очередь в отсортиро- ванном состоянии. • И наконец, если такая позиция не найдена, то запрос помещается в конец оче- реди. Планировщик ввода-вывода с лимитом по времени Планировщик ввода-выпода с лимитом по времени (Deadline I/O scheduler, dead- line-планировщик ввода-вывода) разработан с целью предотвращения задержек об- служивания, которые могут возникать для алгоритма Линуса. Если задаться целью только минимизировать количество операций поиска, то при большом количестве операций ввода-вывода из одной области диска могут возникать задержки обслужи- вания для операций с другими областями диска, причем на неопределенное время. Более того, поток запросов к одной и той же области диска может привести к тому, Глава 13 что запросы к области диска, которая находится далеко от первой, никогда не будут обработаны. Такой алгоритм не может обеспечить равнодоступность ресурсов. Хуже того, общая проблема задержки обслуживания запросов приводит к частной проблеме задержки обслуживания чтения при обслуживании записи (writes-starving-reads). Обычно операции записи могут быть отправлены на обработку диском в любой мо- мент, когда ядру это необходимо, причем это выполняется полностью асинхронно по отношению к пользовательской программе, которая сгенерировала запрос запи- си. Обработка же операций чтения достаточно сильно отличается. Обычно, когда пользовательское приложение отправляет запрос на чтение, это приложение бло- кируется до тех пор, пока запрос не будет выполнен, т.е. запросы чтения возникают синхронно по отношению к приложению, которое эти запросы генерирует. В связи с этим время реакции системы, в основном, не зависит от латентности записи (вре- мени задержки, которое необходимо на выполнение запроса записи), а задержки чтения (время, которое необходимо на выполнение операции чтения) очень важно минимизировать. Латентность записи мало влияет на производительность пользова- тельских программ 3 , но эти программы должны "с дрожащими руками" ждать завер- шение каждого запроса чтения. Следовательно, задержки чтения очень важны для производительности системы. Проблему усугубляет то, что запросы чтения обычно зависят друг от друга. Например, рассмотрим чтение большого количества файлов. Каждая операция чтения выполняется небольшими порциями, которые соответствуют размеру буфе- ра. Приложение не станет считывать следующую порцию данных (или следующий файл), пока предыдущая порция данных не будет считана с диска и возвращена при- ложению. Следовательно, если существует задержка в обслуживании одного запро- са чтения, то для программы эти задержки складываются, и общая задержка может стать недопустимой. Принимая во внимание, что синхронность и взаимозависимость запросов чтения приводят к большим задержкам обработки этих запросов (что в свою очередь сильно влияет на производительность системы), в планировщике вво- да-вывода с лимитом по времени были добавлены некоторые функции, которые по- зволяют гарантированно минимизировать задержки в обработке запросов вообще и в обработке запросов чтения в частности. Следует обратить внимание, что уменьшение времени задержки в обслуживании может быть выполнено только за счет уменьшения общего быстродействия системы. Даже для алгоритма Линуса такой компромисс существует, хотя и в более мягкой форме. Алгоритм Линуса мог бы обеспечить и большую общую пропускную способ- ность (путем уменьшения количества операций поиска), если бы запросы всегда по- мещались в очередь в соответствии с номерами секторов и не выполнялась проверка на наличие старых запросов и вставка в конец очереди. Хотя минимизация количе- ства операций поиска и важна, тем не менее неопределенное время задержки тоже не очень хорошая вещь. Поэтому deadline-планировщик и выполняет много работы для уменьшения задержек в обслуживании. Достаточно сложно одновременно обе- спечить равнодоступность и максимизировать общую пропускную способность. 3 Однако все же не желательно задерживать операции записи на неопределенное время. .Запросы записи также должны немедленно отправляться на диск, но это не так критично, как в случае за- просов чтения. Уровень блочного ввода-вывода 305 4 Для deadline-планировщика операция вставки в начало запроса выполняется опционально. Обычно невыполнение вставки в начало запроса не приводит к проблемам, так как в большинстве случаев количество запросов, которые могут быть добавлены в начало, очень незначительно. 306 Глава 13 Очередь FIFO запросов чтения Очередь FIFO запросов записи Отсортированная очередь Очередь диспетчеризации Диск В планировщике ввода-вывода, с лимитом по времени с запросом связано предель- ное время ожидания (expiration time). По умолчанию этот момент времени равен 500 миллисекунд в будущем для запросов чтения и 5 секунд в будущем для запросов записи. Планировщик ввода-вывода с лимитом по времени работает аналогично планировщику Линуса — он также поддерживает очередь запросов в отсортирован- ном состоянии в соответствии с физическим расположением сектора на диске. Эта очередь называется отсортированной (sorted queue). Когда запрос помещается в от- сортированную очередь, то deadlme-планировщик ввода-вывода выполняет объеди- нение и вставку запросов так же, как это делается в лифтовом алгоритме Линуса 4 Кроме того, планировщик с лимитом по времени помещает каждый запрос и во вто- рую очередь, в зависимости от типа запроса. Запросы чтения помещаются в специ- альную очередь FIFO запросов чтения, а запросы записи— в очередь FIFO запросов записи. В то время как обычная очередь отсортирована по номеру сектора на диске, две очереди FIFO (first-in first-out— первым поступил, первым обслужен) сортируют- ся по времени поступления запроса, так как новые запросы всегда добавляются в ко- нец очереди. При нормальной работе deadline-планировщик ввода-вывода получает запросы из головы отсортированной очереди и помещает их в очередь диспетчери- зации. Очередь диспетчеризации отправляет запросы жесткому диску. Это приводит к минимизации количества операций поиска. Если же для запроса, который находится в голове FIFO-очереди записи или FIFO- очереди чтения, истекает период ожидания (т.е. текущий момент времени становит- ся большим, чем момент времени, когда истекает период ожидания, связанный с запросом), то deadline-планировщик начинает обрабатывать запросы из соответству- ющей очереди FIFO. Таким образом планировщик с лимитом по времени пытается гарантировать, что запросы не будут ожидать дольше максимального периода ожида- ния (рис. 13.3). |