Второе издание
Скачать 3.09 Mb.
|
Ожидание в очереди wait queue в течение интервала времени В главе 4 рассматривалось, как контекст процесса в ядре может поместить себя в очередь ожидания для того, чтобы ждать наступления некоторого события, а затем вызвать планировщик, который выберет новое задание для выполнения. Если где-то в другом месте произойдет указанное событие, то вызывается функция wake_up () для всех заданий, которые ожидают в очереди. Эти задания возвращаются к выпол- нению и могут продолжать работу. Иногда желательно ожидать наступления некоторого события или пока не прой- дет определенный интервал времени, в зависимости от того, что наступит раньше, В этом случае код должен просто вызвать функцию s c h e d u l e _ t i m e o u t () вместо функции schedule () после того, как он поместил себя в очередь ожидания. Задание будет возвращено к выполнению, когда произойдет желаемое событие или пройдет указанный интервал времени. Код обязательно должен проверить, почему он возвра- тился к выполнению — это может произойти потому, что произошло событие, про- шел интервал времени или был получен сигнал — после этого необходимо соответ- ственным образом продолжить выполнение. Время вышло В этой главе были рассмотрены понятия, связанные с представлением о времени в ядре и с тем, как при этом происходит управление абсолютным и относительным ходом времени. Были показаны отличия абсолютного и относительного времени, а также периодических и относительных событий. Далее были рассмотрены прерыва- ния таймера, импульсы таймера, константа HZ и переменная j i f f i e s . После этого было рассказано о том, как реализованы таймеры ядра и как их мож- но использовать в собственном коде ядра. В конце главы были представлены другие методы, которые разработчики могут использовать для учета времени. Большая часть кода ядра, который вам придется писать, требует понимания того, как время течет в ядре и как его отслеживать. С очень большой вероятностью, осо- бенно при разработке драйверов, вам необходимо будет иметь дело с таймерами ядра. Материал этой главы принесет практическую пользу. 232 Глава 10 11 Управление памятью В ыделить память внутри ядра не так просто, как вне ядра. Это связано со мно- гими факторами. Главным образом, причина в том, что в ядре не доступны те элементы роскоши, которыми можно пользоваться в пространстве пользователя, В отличие от пространства пользователя, в ядре не всегда можно позволить себе лег- ко выделять память. Например, в режиме ядра часто нельзя переходить в состояние ожидания. Более того, в ядре не так просто справиться с ошибками, которые возни- кают при работе с памятью. Из-за этих ограничений и из-за необходимости, чтобы схема выделения памяти была быстрой, работа с памятью в режиме ядра становится более сложной, чем в режиме пользователя. Конечно, нельзя сказать, что выделение памяти в ядре — очень сложная процедура, однако скоро все будет ясно — просто это делается несколько по-другому. В этой главе рассматриваются средства, которые предназначены для выделения памяти внутри ядра. Перед изучением интерфейсов, предназначенных для выделе- ния памяти, необходимо рассмотреть, как ядро управляет памятью. Страницы памяти Ядро рассматривает страницы физической памяти как основные единицы управ- ления памятью. Хотя наименьшая единица памяти, которую может адресовать процессор, — это машинное слово, модуль управления памятью (MMU, Memory Management Unit) — аппаратное устройство, которое управляет памятью и отвечает за трансляцию виртуальных адресов в физические — обычно работает со страница- ми. Поэтому модуль MMU управляет таблицами страниц на уровне страничной дета- лизации (отсюда и название). С точки зрения виртуальной памяти, страница — это наименьшая значащая единица. Как будет показано в главе 19, "Переносимость", каждая аппаратная платформа поддерживает свой характерный размер страницы. Многие аппаратные платформы поддерживают даже несколько разных размеров страниц. Большинство 32-разряд- ных платформ имеют размер страницы, равный 4 Кбайт, а большинство 64-разряд- ных платформ— 8 Кбайт. Это значит, что на машине, размер страницы которой ра- вен 4 Кбайт, при объеме физической памяти, равном 1 Гбайт, эта физическая память разбивается на 262 144 страницы. Ядро сопоставляет каждой странице физической памяти в системе структуру struct page. Эта структура определена в файле struct page { page_flags_t flags; atomic_t _count; atornic_t _mapcount; unsigned long private; struct address_space *mapping; pgoff_t index; struct list_head lru; void *virtual; }; Рассмотрим самые важные поля этой структуры. Поле flags содержит состоя- ние страницы. Это поле включает следующую информацию: является ли страни- ца измененной (dirty) или заблокированной (locked) в памяти. Значение каждого флага представлено одним битом, поэтому всего может быть до 32 разных флагов. Значения флагов определены в файле Поле _count содержит счетчик использования страницы — т.е. сколько на эту страницу имеется ссылок. Когда это значение равно нулю, это значит, что никто не использует страницу, и она становится доступной для использования при новом вы- делении памяти. Код ядра не должен явно проверять значение этого поля, вместо этого необходимо использовать функцию page_count () , которая принимает ука- затель на структуру page в качестве единственного параметра. Хотя в случае неза- нятой страницы памяти значение счетчика _count может быть отрицательным (во внутреннем представлении), функция page_count () возвращает значение нуль для незанятой страницы памяти и положительное значение — для страницы, которая в данный момент используется. Страница может использоваться страничным кэшем (в таком случае ноле mapping указывает на объект типа address_space, который связан с данной страницей памяти), может использоваться в качестве частных дан- ных (на которые в таком случае указывает поле private) или отображаться в табли- цу страниц процесса. Поле v i r t u a l — это виртуальный адрес страницы. Обычно это просто адрес данной страницы в виртуальной памяти ядра. Некоторая часть памяти (называемая областью верхней памяти, high memory) не отображается в адресное пространство ядра (т.е. не входит в него постоянно). В этом случае значение данного поля равно NULL и страница при необходимости должна отображаться динамически. Верхняя память будет рассмотрена в одном из следующих разделов. Наиболее важный момент, который необходимо понять, это то, что структура page связана со страницами физической, а не виртуальной памяти. Поэтому то, чему соответствует экземпляр этой структуры, в лучшем случае, очень быстро изменяет- ся. Даже если данные, которые содержались в физической странице, продолжают существовать, то это не значит, что эти данные будут всегда соответствовать одной и той же физической странице памяти и соответственно одной и той же структу- ре раде, например из-за вытеснения страниц (swapping) или по другим причинам. Ядро использует эту структуру данных для описания всего того, что содержится в данный момент в странице физической памяти, соответствующей данной структуре. 234 Глава 11 Назначение этой структуры— описывать область физической памяти, а не данных, которые в ней содержатся. Ядро использует рассматриваемую структуру данных для отслеживания всех стра- ниц физической памяти в системе, так как ядру необходима информация о том, сво- бодна ли страница (т.е. соответствующая область физической памяти никому не вы- делена). Если страница не свободна, то ядро должно иметь информацию о том, чему принадлежит эта страница. Возможные обладатели: процесс пространства пользова- теля, данные в динамически выделенной памяти в пространстве ядра, статический код ядра, страничный кэш (page cache) и т.д. Разработчики часто удивляются, что для каждой физической страницы в системе создается экземпляр данной структуры. Они думают: "Как много для этого использу- ется памяти!" Давайте посмотрим, насколько плохо (или хорошо) расходуется адрес- ное пространство для хранения информации о страницах памяти. Размер структу- ры s t r u c t page равен 40 байт. Допустим, что система имеет страницы размером 1 Кбайт, а объем физической памяти равен 128 Мбайт. Тогда все структуры раде в системе займут немного больше 1 Мбайт памяти — не очень большая плата за воз- можность управления всеми страницами физической памяти. Зоны В связи с ограничениями аппаратного обеспечения, ядро не может рассматривать все страницы памяти как идентичные. Некоторые страницы, в связи со значениями их физических адресов памяти, не могут использоваться для некоторых типов задач. Из-за этого ограничения ядро делит физическую память на зоны. Ядро использует зоны, чтобы группировать страницы памяти с аналогичными свойствами. В частно- сти, операционная система Linux должна учитывать следующие недостатки аппарат- ного обеспечения, связанные с адресацией памяти. • Некоторые аппаратные устройства могут выполнять прямой доступ к памяти (ПДП, DMA, Direct Memory Access) только в определенную область адресов. • На некоторых аппаратных платформах для физической адресации доступны большие объемы памяти, чем для виртуальной адресации. Следовательно, часть памяти не может постоянно отображаться в адресное пространство ядра. В связи с этими ограничениями, в операционной системе Linux выделяют три зоны памяти. • Z0NE_DMA. Содержит страницы, которые совместимы с режимом DMA. • ZONE_NORMAL. Содержит страницы памяти, которые отображаются в адресные пространства обычным образом. • ZONE_HIGHMEM. Содержит "верхнюю память", состоящую из страниц, которые не могут постоянно отображаться в адресное пространство ядра. Эти зоны определяются в заголовочном файле То, как используется разделение памяти на зоны, зависит от аппаратной плат- формы. Например, для некоторых аппаратных платформ нет проблем с прямым до- ступом к памяти ни по какому адресу. Для таких платформ зона ZONE_DMA является пустой, и для всех типов выделения памяти используется зона ZONE_NORMAL. Управление памятью 235 Операционная система разделяет страницы системной памяти на зоны, чтобы иметь пулы страниц для удовлетворения требований выделения памяти. Например, пул зоны ZONE_DMA дает возможность ядру удовлетворить запрос на выделение па- мяти, которая необходима для операций DMA. Если нужна такая память, ядро может просто выделить необходимое количество страниц из зоны ZONE_DMA. Следует обра- тить внимание, что зоны не связаны с аппаратным обеспечением— это логическое группирование, которое позволяет ядру вести учет страниц; памяти. Хотя некоторые запросы на выделение памяти могут требовать страницы из определенной зоны, это требование не обязательно может быть жестким. Например, выделение памяти для ПДП требует страницы из зоны ZONE DMA, а для обычного выделения памяти могут подойти страницы как из зоны ZONE_NORMAL, так и из зоны ZONE_DMA. Конечно, для удовлетворения запросов по обычному выделению памяти ядро будет стараться выделять страницы из зоны ZONE_NORMAL, чтобы сохранить страницы в зоне ZONE_DMA для случая, когда эти страницы действительно нужны, Если же наступает решающий момент (становится недостаточно памяти), то ядро может обратиться к любой доступной и подходящей зоне. 1 Некоторые некачественные устройства PCI также могут выполнять прямой доступ к памяти толь- ко к 24-битовом адресном пространстве. Но эти устройства работают не правильно. 2 Это не имеет ничего общего с верхней памятью в операционной системе DOS. 2 3 6 Глава 11 Зона Описание физическая память ZONE_DMA Страницы памяти, совместимые с ПДП < 16 Мбайт ZONE_NORMAL Нормально адресуемые страницы 16 - 896 Мбайт Z O N E _ H I G H M E M Динамически отображаемые страницы > 896 Мбайт Как противоположный пример можно привести платформу х86, для которой устройства ISA 1 не могут выполнять операции DMA в полном 32-разрядном простран- стве адресов, так как устройства ISA могут обращаться только к первым 16 Мбайт физической памяти. Следовательно, зона ZONE_DMA для платформы х8б содержит только страницы памяти с физическими адресами в диапазоне 0-16 Мбайт. Аналогично используется и зона ZONE_HIGHMEM. To, что аппаратная платформа может отображать и чего она не может отображать в адресное пространство ядра, отличается для разных аппаратных платформ. Для платформы х86 зона ZONE_ HIGHMEM— это вся память, адреса которой лежат выше отметки 896 Мбайт. Для других аппаратных платформ зона ZONE_HIGHMEM пуста, так как вся память может непосредственно отображаться. Память, которая содержится в зоне ZONE_HIGHMEM, называется верхней памятью 2 (high memory). Вся остальная память в системе называет- ся нижней памятью (low memory). Зона ZONE_NORMAL обычно содержит все, что не попало в две предыдущие зоны памяти. Для аппаратной платформы х86, например, зона ZONE_NORMAL содержит всю физическую память от 16 до 896 Мбайт. Для других, более удачных аппаратных платформ, SONE_NORMAL— это вся доступная память. В табл. 11.1 приведен список зон для аппаратной платформы х86. Таблица 11.1. Зоны памяти для аппаратной платформы х86 Каждая зона представлена с помощью структуры s t r u c t zone, которая опреде- лена в файле struct zone { spinlock t lock; unsigned ]ong free_pages; unsigned long pages_min; unsigned long pages_low; unsigned long pages_high; unsigned long protection[MAX_NR_ZONES]; spinlock_t lru_lock; struct list_head active_list; struct list_head inactive_list; unsigned long nr_scan_active; unsigned long nr_scan_inactive; unsigned long nr_active; unsigned long nr_inactive; int all_unreclaimable; unsigned long pages_scanned; int temp_priority; int prev_priority; struct free_area free_area[MAX_ORDER]; wait_queue_head_t *wait_table; unsigned long wait_table_size; unsigned long wait_table_bits; struct per_cpu_pageset pageset[NR_CPUS]; struct pglist_data *zone_pgdat; struct page *zone_mem_map; unsigned long zone_start_pfn; char *name; unsigned long spanned_pages; unsigned long prcsent_pages; }; Эта структура большая, но в системе всего три зоны и соответственно три такие структуры. Рассмотрим наиболее важные поля данной структуры. Поле lock— это спин-блокировка, которая защищает структуру от параллельно- го доступа. Обратите внимание, что она защищает только структуру, а не страницы, которые принадлежат зоне. Для защиты отдельных страниц нет блокировок, хотя отдельные части кода могут блокировать данные, которые могут оказаться в указан- ных страницах. Поле free_pages — это количество свободных страниц в соответствующей зоне. Ядро старается поддерживать свободными хотя бы pages_min страниц зоны, если это возможно (например, с помощью вытеснения на диск). Поле name— это строка, оканчивающаяся нулем, которая содержит имя соответ- ствующей зоны (что не удивительно). Ядро инициализирует указанное поле при за- грузке системы с помощью кода, который описан п файле mm/page_alloc.с. Три зоны имеют имена "DMA", "Normal" и "HighMem". Управление памятью 237 Получение страниц памяти Теперь, имея некоторое понятие о том, как ядро упрапляет памятью с помощью страниц, зон и так далее, давайте рассмотрим интерфейсы, которые реализованы в ядре для того, чтобы выделять и освобождать память внутри ядра. Ядро предо- ставляет один низкоуровневый интерфейс для выделения памяти и несколько интер- фейсов для доступа к ней. Все эти интерфейсы выделяют память в объеме, кратном размеру страницы, и определены в файле < l i n u x / g f p . h > . Основная функция вы- деления памяти следующая. struct page * alloc_pages(unsigned int gfp_mask, unsigned int order) Данная функция позволяет выделить 2 o r d e r (т.е. 1 << o r d e r ) смежных страниц (один непрерывный участок) физической памяти и возвращает указатель на структу- ру page, которая соответствует первой выделенной странице памяти. В случае ошиб- ки возвращается значение NULL. Параметр gfp_mask будет рассмотрен несколько позже. Полученную страницу памяти можно конвертировать в ее логический адрес с помощью следующей функции. void * page_address(struct page *page) Эта функция возвращает указатель на логический адрес, которому в данный мо- мент соответствует начало указанной страницы физической памяти. Если нет необ- ходимости в соответствующей структуре s t r u c t page, то можно использовать сле- дующую функцию. unsigned long __get_free_pages(unsigned int gfp_mask, unsigned int order) Эта функция работает так же, как и функция a l l o c _ p a g e s ( ) , за исключением того, что она сразу возвращает логический адрес первой выделенной страницы па- мяти. Так как выделяются смежные страницы памяти, то другие страницы просто следуют за первой. Если необходима всего одна страница памяти, то для этой цели определены сле- дующие функции-оболочки, которые позволяют уменьшить количество работы по набору кода программы. struct page * alloc_page(unsigned int gfp_mask) unsigned long __get_free_page(unsigned int gfp_mask) Эти функции работают так же, как и ранее описанные, по для них в качестве па- раметра o r d e r передается нуль (2 0 = одна страница памяти). Получение страниц заполненных нулями Для того чтобы получаемые страницы памяти были заполнены нулями, необходи- мо использовать следующую функцию. unsigned long get_zeroed_page(unsigned int gfp_mask) Эта функция аналогична функции _ _ g e t _ f r e e _ p a g e () , за исключением того, что после выделения страницы памяти она заполняется нулями. Это полезно для страниц памяти, которые возвращаются в пространство пользователя, так как слу- 238 Глава 11 Освобождение страниц Для освобождения страниц, которые больше не нужны, можно использовать сле- дующие функции. void __free_pages(struct page *page, unsigned int order) void free_pages(unsigned long addr, unsigned int order) void free_page(unsigned long addr) Необходимо быть внимательными и освобождать только те страницы памяти, ко- торые вам выделены. Передача неправильного значения параметра page, addr или o r d e r может привести к порче данных. Следует помнить, что ядро доверяет себе. В отличие от пространства пользователя, ядро с удовольствием зависнет, если вы по- просите. Рассмотрим пример. Мы хотим выделить 8 страниц памяти. page = __get_free_pages(GFP_KERNEL, 3 ) ; if (!page) { /* недостаточно памяти: эту ошибку необходимо обработать самим! */ return -ENOMEM; } /* переменная 'page' теперь содержит адрес первой из восьми страниц памяти*/ free_pages(page, 3); /* * наши страницы памяти теперь освобождены и нам больше нельзя * обращаться по адресу, который хранится в переменной 'page' */ Управление памятью 239 чайный "мусор", который находится в страницах памяти, может оказаться не совсем случайным и случайно может содержать некоторые (например, секретные) данные. Все данные необходимо обнулить или очистить каким-либо другим образом перед тем, как возвращать информацию в пространство пользователя, чтобы при этом не пострадала безопасность системы. В табл. 11.2 приведен список всех низкоуровне- вых средств выделения памяти. |