Второе издание
Скачать 3.09 Mb.
|
Буферный кэш В операционной системе Linux больше нет отдельного буферного кэша. В ядрах серии 2.2 существовало два отдельных кэша: страничный и буферный. В первом кэ- шировалисы страницы памяти, а в другом — буферы. Эти два кэша не были объедине- ны между собой. Дисковый блок мог находиться в обоих кэшах одновременно. Это требовало больших усилий по синхронизации двух кэшированных копий, не говоря уже о напрасной трате памяти. Так было в ядрах серии 2.2 и более ранних, но начиная с ядер Linux серии 2.4 оба кэша объединили вместе. Сегодня существует только один дисковый кэш — стра- ничный кэш. Ядру все еще необходимо использовать буферы для того, чтобы представлять дис- ковые блоки в памяти. К счастью, буферы описывают отображение блоков на стра- ницы памяти, которые в свою очередь находятся в страничном кэше. 336 Глава 15 Демон pdflush Измененные (dirty, "грязные") страницы памяти когда-нибудь должны быть запи- саны на диск. Обратная запись страниц памяти выполняется в следующих двух слу- чаях. • Когда объем свободной памяти становится меньше определенного порога, ядро должно записать измененные данные обратно на диск, чтобы освободить память. • Когда несохраненные данные хранятся в памяти достаточно долго, то ядро должно их записать на диск, чтобы гарантировать, что эти данные не будут на- ходиться в Несохраненном состоянии неопределенное время. Эти два типа записи имеют разные цели. В более старых ядрах они выполнялись двумя разными потоками пространства ядра (см. следующий раздел). Однако в ядре 2.6 эту работу выполняет группа (gang 4 ) потоков ядра pdflush, которые называются демонами фоновой обратной записи (или просто потоками pdflush). Ходят слухи, что название pdflush — это сокращение от "dirty page flush" ("очистка грязных стра- ниц"). Не обращайте внимание на это сомнительное название, давайте лучше более детально рассмотрим, для чего нужны эти процессы. Во-первых, потоки pdflush служат для записи измененных страниц на диск, ког- да объем свободной памяти в системе уменьшается до определенного уровня. Цель такой фоновой записи— освобождение памяти, которую занимают незаписанные страницы, в случае недостатка физических страниц памяти. Уровень, когда начи- нается обратная запись, может быть сконфигурирован с помощью параметра d i r - ty_background_ratio утилиты s y s c t l . Когда объем свободной памяти становится меньше этого порога, ядро вызывает функцию wakeup_bdf lush () 5 для перевода в состояние выполнения потока pdflush, который пыполняет функцию обратной за- писи измененных страниц памяти background_writeout (). Эта функция получает один параметр, равный количеству страниц, которые функция должна попытаться записать на диск. Функция продолжает запись до тех пор, пока не выполнятся два следующих условия. • Указанное минимальное количество страниц записано на диск. • Объем свободной памяти превышает соответствующее значение параметра dirty_background_ratio. Выполнение этих условий гарантирует, что демон pdf lush выполнил свою рабо- ту по предотвращению нехватки памяти. Если эти условия не выполняются, то об- ратная запись может остановиться только тогда, когда демон p d f l u s h запишет на диск все несохраненные страницы и для него больше не будет работы. Во-вторых, назначение демона pdflush — периодически переходить в состояние выполнения (независимо от состояния нехватки памяти) и записывать на диск очень 4 Слово "gang" не является жаргонным. Этот термин часто используется в компьютерных науках, чтобы указать группу чего-либо, что может выполняться параллельно. 5 Да, название функции не совсем верное. Должно было бы быть wakeup_pdflush (). В следующем разделе рассказано, откуда произошло это название. Страничный кэш и обратная запись страниц 337 давно измененные страницы памяти. Это гарантирует, что измененные страницы не будут находиться в памяти неопределенное время. При сбоях системы будут потеря- ны те страницы памяти, которые не были сохранены на диске, так как содержимое памяти после перегрузки не сохраняется. Следовательно, периодическая синхрони- зация страничного кэша с данными на диске является важным делом. При загрузке системы инициализируется таймер, периодически возвращающий к выполнению по- ток pdflush, который выполняет функцию wb_kupdate (). Эта функция выполняет обратную запись данных, которые были изменены более чем d i r t y _ e x p i r e _ c e n t i - seсs сотых секунды тому назад. После этого таймер снова инициализируется, чтобы сработать через d i r t y _ e x p i r e _ c e n t i s e c s сотых секунды. Таким образом потоки p d f l u s h периодически возвращаются к выполнению и записывают на диск все из- мененные страницы, данные в которых старше, чем указанный лимит. Системный администратор может установить эти значения с помощью каталога /proc/sys/vrn и утилиты s y s c t l . Втабл. 15.1 приведен список всех соответствую- щих переменных. Таблица 15.1. Параметры для настройки демона p d f l u s h Переменная Описание dirty_background_ratio dirty_expire_centisecs dirty_ratio dirty_writeback_centisecs laptop_mode Код потока p d f l u s h находится в файлах m m / p a g e - w r i t e b a c k . c и f s / f s - w r i t e b a c k . с . Режим ноутбука Режим ноутбука — это специальная политика обратной записи страниц с целью оптимизации использования батареи и продления срока ее работы. Это делается путем минимизации активности жестких дисков, чтобы они оставались в останов- ленном состоянии по возможности долго. Конфигурировать этот режим можно с по- мощью файла /proc/sys/vm/laptop_mode. По умолчанию в этом файле записано значение 0 и режим ноутбука выключен. Запись значения 1 в этот файл позволяет включить режим ноутбука. В режиме ноутбука существует всего одно изменение в выполнении обратной записи страниц. В дополнение к обратной записи измененных страниц; памяти, когда они становятся достаточно старыми, демон p d f l u s h также выполняет и все остальные операции дискового ввода-вывода, записывая все дисковые буферы на 338 Глава 15 Объем свободной оперативной памяти, при котором демон p d f l u s h начинает обратную запись незаписанных данных Время, в сотых долях секунды, в течение которого неза- писанные данные могут оставаться в памяти, перед тем как демон p d f l u s h не запишет их на диск при следующем периоде обратной записи Процент от общей оперативной памяти, соответствующий страницам памяти одного процесса, при котором начинает- ся обратная запись незаписанных данных • • i Насколько часто, в сотых долях секунды, процесс b d f l u s h возвращается к выполнению для обратной записи данных Переменная булевого типа, которая включает или выключает режим ноутбука (см. следующий раздел) диск. Таким образом демон p d f l u s h пользуется тем преимуществом, что диск уже запущен, а также он гарантирует, что в ближайшем будущем диск снова запущен не будет. Такое поведение имеет смысл, когда параметры d i r t y _ e x p i r e _ c e n t i s e c s и d i r t y _ w r i t e b a c k _ c e n t i s e c s установлены в большие значения, скажем 10 минут. При таких задержках обратной записи диск запускается не часто, а когда он все-таки запускается, то работа в режиме ноутбука гарантирует, что этот момент будет ис- пользован с максимальной эффективностью. Во многих поставках ОС Linux режим ноутбука автоматически включается и вы- ключается, при этом также могут изменяться и другие параметры демона pbflush, когда заряд батареи уменьшается. Такое поведение позволяет получать преимуще- ства от режима ноутбука при работе от батареи и автоматически возвращаться к нормальному поведению, когда машина включается в электрическую сеть. Демоны bdflush и kupdated В ядрах серий до 2.6 работа потоков p d f l u s h выполнялась двумя другими пото- ками ядра b d f l u s h и kupdated. Поток пространства ядра b d f l u s h выполнял фоновую обратную запись изменен- ных страниц, когда количество доступной памяти становилось достаточно малым. Также был определен ряд пороговых значений, аналогично тому как это делается для демона pdflush. Демон b d f l u s h возвращался к выполнению с помощью функ- ции wakeup_bdflush () , когда количество свободной памяти становилось меньше этих пороговых значений. Между демонами b d f l u s h и p d f l u s h существует два главных отличия. Первое состоит в том, что демон b d f l u s h был всего один, а количество потоков p d f l u s h может меняться динамически. Об этом более подробно будет рассказано в следую- щем разделе. Второе отличие состоит в том, что демон bdflush работал с буферами, он записывал на диск измененные буферы. Демон p d f l u s h работает со страница- ми, он записывает на диск целые измененные страницы памяти. Конечно, страницы памяти могут соответствовать буферам, но единицей ввода-вывода является целая страница памяти, а не один буфер. Это дает преимущество, поскольку работать со страницами памяти проще, чем с буферами, так как страница памяти — более общий и более часто используемый объект. Так как демон b d f l u s h выполнял обратную запись, только когда количество сво- бодной памяти очень сильно уменьшалось или количество буферов было очень боль- шим, то был введен поток ядра kupdated, который периодически выполнял обрат- ную запись измененных страниц памяти. Он использовался для целей, аналогичных функции wb_kupdate () демона pdflush. Потоки b d f l u s h и kupdated и их функциональность сейчас заменены потоками pdflush. Предотвращение перегруженности: для чего нужны несколько потоков Один из главных недостатков решения на основе демона b d f l u s h состоит в том, что демон b d f l u s h имел всего один поток выполнения. Это приводило к возмож- ности зависания демона при большом количестве операций обратной записи, когда Страничный кэш и обратная запись страниц 339 один поток демона b d f l u s h блокировался на очереди запросов ввода-вывода пере- груженного устройства, в то время как очереди запросов других устройств могли быть в этот момент сравнительно свободными. Если система имеет несколько дис- ков и соответствующую процессорную мощность, то ядро должно иметь возмож- ность загрузить работой все диски. К сожалению, даже при большом количестве данных, для которых необходима обратная запись, демон b d f l u s h может оказаться загруженным работой с одной очередью и не сможет поддерживать все диски в на- груженном состоянии. Это происходит потому, что пропускная способность диском конечна и, к несчастью, очень низкая. Если только один поток выполняет обратную запись страниц, то он может проводить много времени в ожидании одного диска, так как пропускная способность диска ограничена. Для облегчения этой ситуации ядру необходима многопоточная обратная запись. В таком случае ни одна очередь запросов не может стать узким местом. В ядрах серии 2.6 эта проблема решается путем введения нескольких потоков pdflush. Каждый поток самостоятельно выполняет обратную запись страниц памя- ти на диск, что позволяет различным потокам p d f l u s h работать с разными очередя- ми запросов устройств. Количество потоков изменяется в процессе работы системы в соответствии с простым алгоритмом. Если все существующие потоки p d f l u s h оказываются заняты- ми в течение одной секунды, то создается новый поток pdflush. Общее количество потоков не может превышать значения константы MAX_PDFLUSH_THREADS, которая по умолчанию равна 8. И наоборот, если поток p d f l u s h находился в состоянии ожи- дания больше одной секунды, то он уничтожается. Минимальное количество потоков равно, по крайней мере, значению константы MIN_PDFLUSH_THREADS, что по умол- чанию соответствует 2. Таким образом, количество потоков p d f l u s h изменяется ди- намически в зависимости от количества страниц, для которых необходима обратная запись, и загруженности этих потоков. Если все потоки p d f l u s h заняты обратной записью, то создается новый поток. Это гарантирует, что ни одна из очередей запро- сов устройств не будет перегружена, в то время как другие очереди устройств не так загружены и в них тоже можно выполнять обратную запись. Если перегрузка предот- вращается, то количество потоков p d f l u s h уменьшается, чтобы освободить память. Вce это хорошо, но что если все потоки p d f l u s h зависнут в ожидании записи в одну и ту же перегруженную очередь? В этом случае производительность нескольких потоков p d f l u s h не будет выше производительности одного потока, а количество занятой памяти станет значительно большим. Чтобы уменьшить такой эффект, для потоков p d f l u s h реализован алгоритм предотвращения зависания (congestion avoi- dance). Потоки активно начинают обратную запись страниц для тех очередей, ко- торые не перегружены. В результате потоки p d f l u s h распределяют свою работу по разным очередям и воздерживаются от записи в перегруженную очередь. Когда все потоки p d f l u s h заняты работой и запускается новый поток, то это означает, что они действительно заняты. В связи с усовершенствованием алгоритмов обратной записи страниц, включая введение демона b d f l u s h , ядро серии 2.6 позволяет поддерживать в загруженном состоянии значительно большее количество дисков, чем в более старых версиях ядер. При активной работе потоки p d f l u s h могут обеспечить большую пропускную способность сразу для большого количества дисковых устройств. 340 Глава 15 Коротко о главном В этой главе был рассмотрен страничный кэш и обратная запись страниц. Было показано, как ядро выполняет все операции страничного ввода-вывода, как опера- ции записи откладываются с помощью дискового кэша и как данные записываются на диск с помощью группы потоков пространства ядра pdf lush. На основании материала последних нескольких глав вы получили устойчивое представление о том, как выполняется управление памятью и файловыми система- ми. Теперь давайте перейдем к теме модулей и посмотрим, ядро Linux обеспечивает модульную и динамическую инфраструктуру для загрузки кода ядра во время работы системы. Страничный кэш и обратная запись страниц 341 16 Модули Н есмотря на то что ядро является монолитным, в том смысле что все ядро вы- полняется в общем защищенном адресном домене, ядро Linux также является модульным, что позволяет выполнять динамическую вставку и удаление кода ядра в процессе работы системы. Соответствующие подпрограммы, данные, а также точки входа и выхода группируются в общий бинарный образ, загружаемый объект ядра, который называется модулем. Поддержка модулей позволяет системам иметь мини- мальное базовое ядро с опциональными возможностями и драйверами, которые компилируются в качестве модулей. Модули также позволяют просто удалять и пере- гружать код ядра, что помогает при отладке, а также дает возможность загружать драйверы по необходимости в ответ на появление новых устройств с функциями го- рячего подключения. В этой главе рассказывается о хитростях, которые стоят за поддержкой модулей в ядре, и о том, как написать свой собственный модуль. Модуль "Hello,World!" В отличие от разработки основных подсистем ядра, большинство из которых были уже рассмотрено, разработка модулей подобна созданию новой прикладной программы, по крайней мере в том, что модули имеют точку входа, точку выхода и находятся каждый в своем бинарном файле. Может показаться банальным, но иметь возможность написать программу, кото- рая выводит сообщение "Hello World!", и не сделать этого- просто смешно. Итак, леди и джентльмены, модуль "Hello, World!". /* * hello.с - модуль ядра Hello, World! /* #include #include #include /* * hello_init - функция инициализации, вызывается при загрузке модуля, * В случае успешной загрузки модуля возвращает значение нуль, * и ненулевое значение в противном случае. */ static int hello_init(void) { printk(KERN_ALERT "I bear a charmed life.\n"); return 0; } /* * hello_exit - функция завершения, вызывается при выгрузке модуля. */ static void hello_exit (void) { printk(KERN_ALERT "Out, out, brief candle!\n"); } module_init(hello_init); module_exit(hello_exit); MODULE_LICENSE{"GPL"); MODULE_AUTHOR("Shakespeare"); Это самый простой модуль ядра, который только может быть. Функция h e l l o _ i n i t () регистрируется с помощью макроса module_init () в качестве точки входа в модуль. Она вызывается ядром при загрузке модуля. Вызов m o d u l e _ i n i t () — это не вызов функции, а макрос, который устанавливает значение своего параметра в ка- честве функции инициализации. Все функции инициализации должны соотпетство- вать следующему прототипу. int my_init(void); Так как функция инициализации редко вызывается за пределами модуля, ее обыч- но не нужно экспортировать и можно объявить с ключевым словом s t a t i c . Функции инициализации возвращают значение тина i n t . Если инициализация (или то, что делает функция инициализации) прошла успешно, то функция должна возвратить значение нуль. В случае ошибки возвращается ненулевое значение. В данном случае эта функция просто печатает сообщение и возвращает значе- ние нуль. В настоящих модулях функция инициализации регистрирует ресурсы, вы- деляет структуры данных и т.д. Даже если рассматриваемый файл будет статически скомпилирован с ядром, то функция инициализации останется и будет вызвана при загрузке ядра. Функция h e l l o _ e x i t () регистрируется в качестве точки выхода из модуля с помощью макроса module_exit (). Ядро вызывает функцию h e l l o _ e x i t (), когда модуль удаляется из памяти. Завершающая функция должна выполнить очистку ре- сурсов, гарантировать, что аппаратное обеспечение находится в непротиворечивом состоянии, и т.д. После того как эта функция завершается, модуль выгружается. Завершающая функция должна соответствовать следующему прототипу. void my_exit(void); Так же как и в случае функции инициализации, ее можно объявить как s t a t i c . 344 Глава 16 Если этот файл будет статически скомпилирован с образом ядра, то данная функ- ция не будет включена в образ и никогда не будет вызвана (так как если нет модуля, то код никогда не может быть удален из памяти). Макрос MODULE_LICENSE () позволяет указать лицензию на право копирования модуля. Загрузка в память модуля, для которого лицензия не соответствует GPL, при- ведет к установке в ядре флага t a i n t e d (буквально, испорченное). Этот флаг служит для информационных целей, кроме того, многие разработчики уделяют меньше вни- мания сообщениям об ошибках, в которых указан этот флаг. Более того, модули, у которых лицензия не соответствует GPL, не могут использовать символы, которые служат "только для GPL" (см. раздел "Экспортируемые символы" ниже в этой главе). Наконец, макрос MODULE_AUTHOR () позволяет указать автора модуля. Значение этого макроса служит только для информационных целей. Сборка модулей Благодаря новой системе сборки "kbuild", в ядрах серии 2.6 сборка модулей вы- полняется значительно проще, чем в старых сериях. Первое, что нужно сделать при сборке модулей, — это решить, где будет находиться исходный код модуля. Исходный код модуля необходимо правильно объединить с деревом исходных кодов ядра. Это можно сделать в виде заплаты или путем добавления в официальное дерево исходно- го кода ядра. Кроме этого, можно компилировать исходный код модуля отдельно от исходных кодов ядра. |