Блокировка страничного кода или данных
Некоторые драйверы режима ядра, такие как последовательные и параллельные драйверы, не должны быть резидентами памяти, если управляемые ими устройства не открыты. Однако при наличии активного подключения или порта часть кода драйвера, которая управляет этим портом, должна быть резидентной для обслуживания устройства. Если порт или подключение не используются, код драйвера не требуется. В отличие от этого, драйвер для диска, который содержит системный код, код приложения или системный файл подкачки, всегда должен быть резидентом памяти, так как драйвер постоянно передает данные между устройством и системой.
Драйвер для нерегулярно используемого устройства (например, модема) может освободить системное пространство, если управляемое им устройство неактивно. Если вы помещаете в один раздел код, который должен быть резидентом для обслуживания активного устройства, и если ваш драйвер блокирует код в памяти во время использования устройства, этот раздел можно назначить страничной. Когда устройство драйвера открыто, операционная система переносит доступный для страницы раздел в память, и драйвер блокирует его до тех пор, пока он больше не понадобится.
Этот метод используется в коде драйвера аудиосистемы CD. Код драйвера сгруппирован в разделы, доступные для страниц, в соответствии с изготовителем устройства КОМПАКТ-диска. Некоторые бренды могут никогда не присутствовать в данной системе. Кроме того, даже если компакт-диск существует в системе, к нему может обращаться редко, поэтому группирование кода в разделы, доступные для страниц, по типу компакт-диска гарантирует, что код для устройств, которые не существуют на определенном компьютере, никогда не будет загружен. Однако при доступе к устройству система загружает код для соответствующего устройства компакт-диска. Затем драйвер вызывает подпрограмму MmLockPagableCodeSection , как описано ниже, чтобы заблокировать код в памяти во время использования устройства.
Чтобы изолировать страничный код в именованном разделе, пометьте его следующей директивой компилятора:
#pragma alloc_text(PAGE*Xxx, *RoutineName)
Имя раздела кода, доступного для страницы, должно начинаться с четырех букв "PAGE" и может следовать до четырех символов (представленных здесь как Xxx) для уникальной идентификации раздела. Первые четыре буквы имени раздела (т. е. "СТРАНИЦА") должны быть прописными. RoutineName определяет точку входа, которая будет включена в раздел, доступный для страницы.
Самое короткое допустимое имя для раздела кода, доступного для страницы, в файле драйвера — это просто PAGE. Например, директива pragma в следующем примере кода идентифицирует RdrCreateConnection
как точку входа в разделе страничного кода с именем PAGE.
#ifdef ALLOC_PRAGMA
#pragma alloc_text(PAGE, RdrCreateConnection)
#endif
Чтобы сделать код драйвера, доступный для страниц, резидентным и заблокированным в памяти, драйвер вызывает MmLockPagableCodeSection, передавая адрес (обычно это точка входа в подпрограмму драйвера), который находится в разделе кода для страничного файла. MmLockPagableCodeSection блокирует все содержимое раздела, содержащего подпрограмму, на которую ссылается вызов. Другими словами, этот вызов делает каждую подпрограмму, связанную с тем же идентификатором PAGEXxx , резидентом и заблокированным в памяти.
MmLockPagableCodeSection возвращает дескриптор, используемый при разблокировке раздела (путем вызова подпрограммы MmUnlockPagableImageSection ) или когда драйвер должен заблокировать раздел из дополнительных расположений в коде.
Драйвер также может обрабатывать редко используемые данные как страничные, чтобы их также можно было выгруждать до тех пор, пока устройство, которое он поддерживает, не станет активным. Например, драйвер системного микшера использует страничные данные. Устройство микшера не имеет связанных с ним асинхронных операций ввода-вывода, поэтому этот драйвер может сделать свои данные страничными.
Имя раздела данных, доступного для страницы, должно начинаться с четырех букв "PAGE" и может содержать до четырех символов для уникальной идентификации раздела. Первые четыре буквы имени раздела (т. е. "СТРАНИЦА") должны быть прописными.
Избегайте назначения идентичных имен разделам кода и данных. Чтобы сделать исходный код более удобочитаемым, разработчики драйверов обычно назначают имя PAGE разделу страничного кода, так как это имя короткое и может отображаться в многочисленных директивах alloc_text директивы pragma. Затем более длинные имена назначаются всем разделам данных с возможностью страницы (например, PAGEDATA для data_seg, PAGEBSS для bss_seg и т. д.), которые могут потребоваться драйверу.
Например, первые две директивы pragma в следующем примере кода определяют два раздела страничных данных: PAGEDATA и PAGEBSS. PAGEDATA объявляется с помощью директивы data_seg pragma и содержит инициализированные данные. PAGEBSS объявляется с помощью директивы bss_seg pragma и содержит неинициализированные данные.
#pragma data_seg("PAGEDATA")
#pragma bss_seg("PAGEBSS")
INT Variable1 = 1;
INT Variable2;
CHAR Array1[64*1024] = { 0 };
CHAR Array2[64*1024];
#pragma data_seg()
#pragma bss_seg()
В этом примере Variable1
кода и Array1
явно инициализируются и поэтому помещаются в раздел PAGEDATA.
Variable2
и Array2
неявно инициализируются с нуля и помещаются в раздел PAGEBSS.
Неявная инициализация глобальных переменных до нуля уменьшает размер исполняемого файла на диске и предпочтительнее явной инициализации до нуля. Следует избегать явной нулевой инициализации, за исключением случаев, когда это необходимо для размещения переменной в определенном разделе данных.
Чтобы сделать раздел данных резидентным и заблокировать его в памяти, драйвер вызывает MmLockPagableDataSection, передавая элемент данных, который отображается в разделе страничных данных. MmLockPagableDataSection возвращает дескриптор, который будет использоваться в последующих запросах блокировки или разблокировки.
Чтобы восстановить состояние страницы заблокированного раздела, вызовите MmUnlockPagableImageSection, передавая значение дескриптора, возвращенное MmLockPagableCodeSection или MmLockPagableDataSection, соответствующим образом. Подпрограмма unload драйвера должна вызывать MmUnlockPagableImageSection , чтобы освободить каждый полученный дескриптор для блокируемых разделов кода и данных.
Блокировка раздела является дорогостоящей операцией, так как диспетчер памяти должен выполнить поиск в списке загруженных модулей перед блокировкой страниц в памяти. Если драйвер блокирует раздел из нескольких расположений в коде, он должен использовать более эффективный mmLockPagableSectionByHandle после первоначального вызова MmLockPagableXxxSection.
Дескриптор, передаваемый в MmLockPagableSectionByHandle , — это дескриптор, возвращенный предыдущим вызовом MmLockPagableCodeSection или MmLockPagableDataSection.
Диспетчер памяти поддерживает количество для каждого дескриптора раздела и увеличивает это число каждый раз, когда драйвер вызывает MmLockPagableXxx для этого раздела. Вызов MmUnlockPagableImageSection уменьшает счетчик. Хотя счетчик для любого дескриптора раздела не имеет нуля, этот раздел остается заблокированным в памяти.
Дескриптор раздела действителен до тех пор, пока загружен его драйвер. Поэтому драйвер должен вызывать MmLockPagableXxxSection только один раз. Если драйвер требует дополнительных вызовов блокировки, он должен использовать MmLockPagableSectionByHandle.
Если драйвер вызывает любую подпрограмму MmLockPagableXxx для раздела, который уже заблокирован, диспетчер памяти увеличивает количество ссылок для раздела. Если раздел выстраивался при вызове процедуры блокировки, диспетчер памяти страницы в разделе и задает для него значение 1.
Использование этого метода сводит к минимуму влияние драйвера на системные ресурсы. При запуске драйвер может заблокировать в памяти код и данные, которые должны быть резидентами. Если для устройства отсутствуют незавершенные запросы ввода-вывода (т. е. при закрытии устройства или если устройство никогда не открывалось), драйвер может разблокировать тот же код или данные, что делает их доступными для вывода.
Однако после того, как драйвер подключил прерывания, любой код драйвера, который можно вызвать во время обработки прерываний, всегда должен быть резидентом памяти. Хотя некоторые драйверы устройств могут быть доступны для страниц или заблокированы в памяти по требованию, некоторые основные наборы кода и данных такого драйвера должны постоянно находиться в системном пространстве.
Рассмотрите следующие рекомендации по реализации блокировки кода или раздела данных.
Основное использование подпрограмм Mm(Un)LockXxx заключается в том, чтобы сделать обычный непагружируемый код или данные доступными для страниц и переносить в виде непагированного кода или данных. Хорошими примерами являются такие драйверы, как последовательный драйвер и параллельный драйвер: если нет открытых дескрипторов для устройства, которым управляет такой драйвер, части кода не требуются и могут оставаться на страницы. Перенаправление и сервер также являются хорошими примерами драйверов, которые могут использовать этот метод. Если нет активных подключений, оба этих компонента можно вывести на страницу.
Весь раздел, доступный для страницы, заблокирован в памяти.
Один раздел для кода и один для данных для каждого драйвера является эффективным. Многие именованные разделы, доступные для страниц, как правило, неэффективны.
Разделы, доступные для страниц, и разделы с развинутыми, но заблокированными по запросу, следует разделять отдельно.
Помните, что mmLockPagableCodeSection и MmLockPagableDataSection не следует вызывать часто. Эти подпрограммы могут привести к интенсивному выполнению операций ввода-вывода, когда диспетчер памяти загружает раздел. Если драйвер должен заблокировать раздел из нескольких расположений в коде, он должен использовать MmLockPagableSectionByHandle.