Поделиться через


Блокировка страничного кода или данных

Некоторые драйверы в режиме ядра, такие как последовательные и параллельные драйверы, не должны быть резидентами памяти, если устройства, которыми они управляют, не открыты. Однако до тех пор, пока есть активное подключение или порт, часть кода драйвера, управляющая этим портом, должна оставаться резидентной для обслуживания устройства. Если порт или подключение не используется, код драйвера не требуется. В отличие от этого, драйвер для диска, содержащего системный код, код приложения или файл системного разбиения на страницы, всегда должен быть резидентом памяти, так как драйвер постоянно передает данные между устройством и системой.

Драйвер для нерегулярного используемого устройства (например, модема) может освободить место в системе, когда устройство, которое оно управляет, неактивно. Если вы размещаете в одном разделе код, который должен быть резидентным для обслуживания активного устройства, и если драйвер блокирует код в памяти во время использования устройства, этот раздел можно отметить как страничный. Когда устройство драйвера открывается, операционная система переносит раздел страницы в память, и драйвер блокирует его до тех пор, пока не не потребуется.

В коде звукового драйвера системы CD используется этот метод. Код драйвера сгруппирован на отдельные секции в соответствии с производителем CD-устройства. Некоторые бренды никогда не могут присутствовать в данной системе. Кроме того, даже если CD-ROM существует в системе, он может быть доступен редко, поэтому группирование кода в страничные секции по типу CD гарантирует, что код для устройств, которые не существуют на определенном компьютере, никогда не будет загружен. Однако при доступе к устройству система загружает код для соответствующего cd-устройства. Затем драйвер вызывает подпрограмму MmLockPagableCodeSection , как описано ниже, чтобы заблокировать код в памяти во время использования устройства.

Чтобы изолировать страничный код в именованном разделе, отметьте его с помощью следующей директивы компилятора:

#pragma alloc_text(PAGE*XXX, *RoutineName)

Имя раздела кода, который может быть размещён на страницах, должно начинаться с четырех букв "PAGE", за которыми могут следовать до четырех символов (представленных здесь как Xxx), чтобы однозначно определить раздел. Первые четыре буквы имени раздела (т. е. page) должны быть прописными буквами. Имя подпрограммы идентифицирует точку входа, которая будет включена в раздел, доступный для страниц.

Самое короткое допустимое имя для раздела кода с возможностью разбиения на страницы в файле драйвера — это просто PAGE. Например, директива pragma в следующем примере кода идентифицирует RdrCreateConnection как точку входа в разделяемом на страницы разделе кода под названием PAGE.

#ifdef  ALLOC_PRAGMA 
#pragma alloc_text(PAGE, RdrCreateConnection) 
#endif 

Чтобы сделать код драйвера страницы резидентом и заблокированным в памяти, драйвер вызывает MmLockPagableCodeSection, передав адрес (обычно точка входа подпрограммы драйвера), который находится в разделе кода, доступном для страниц. MmLockPagableCodeSection блокирует все содержимое раздела, содержащего подпрограмму, указанную в вызове. Другими словами, этот вызов делает каждую подпрограмму, связанную с тем же идентификатором PAGEXxx, резидентной и заблокированной в памяти.

MmLockPagableCodeSection возвращает дескриптор, который используется при разблокировке раздела (вызовом подпрограммы MmUnlockPagableImageSection) или когда драйвер должен заблокировать раздел на различных участках своего кода.

Драйвер также может рассматривать редко используемые данные как поддающиеся выгрузке в файл подкачки, чтобы их можно было выгрузить, пока поддерживаемое устройство не активно. Например, драйвер системного миксера использует страничные данные. Устройство миксера не имеет с ним связанного асинхронного ввода-вывода, поэтому этот драйвер может сделать свои данные размещаемыми на страницах.

Имя раздела данных, который может использоваться по страницам, должно начинаться с четырех букв "PAGE", за которыми могут следовать до четырех символов для уникальной идентификации раздела. Первые четыре буквы имени раздела (т. е. page) должны быть прописными буквами.

Избегайте назначения идентичных имен разделам кода и данных. Чтобы сделать исходный код более читаемым, разработчики драйверов обычно назначают имя PAGE разделу кода с возможностью страницы, так как это имя короткое, и оно может отображаться в многочисленных директивах pragma alloc_text. Затем более длинные имена назначаются любым разделам данных, которые могут размещаться на страницах (например, PAGEDATA для data_seg, PAGEBSS для bss_seg и т. д.), которые могут понадобиться драйверу.

Например, первые две директивы pragma в следующем примере кода определяют два раздела данных, PAGEDATA и PAGEBSS. PAGEDATA объявляется с помощью директивы pragma data_seg и содержит инициализированные данные. PAGEBSS объявляется с помощью директивы pragma bss_seg и содержит неинициализированные данные.

#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, как это необходимо. Подпрограмма выгрузки драйвера должна вызывать MmUnlockPagableImageSection, чтобы освободить каждый хендл, полученный для блокируемых разделов кода и данных.

Блокировка раздела является дорогой операцией, так как диспетчер памяти должен искать свой загруженный список модулей, прежде чем блокировать страницы в память. Если драйвер блокирует секцию из многих мест в коде, он должен использовать более эффективную функцию MmLockPagableSectionByHandle после первоначального вызова MmLockPagableXxxSection.

Дескриптор, переданный в MmLockPagableSectionByHandle, является тем, который был возвращен вызовом MmLockPagableCodeSection или MmLockPagableDataSection ранее.

Диспетчер памяти сохраняет количество для каждого дескриптора раздела и увеличивает это число каждый раз, когда драйвер вызывает MmLockPagableXxx для этого раздела. Вызов MmUnlockPagableImageSection уменьшает количество. Хотя счетчик для любого дескриптора секции ненулевой, данный раздел остается заблокированным в памяти.

Дескриптор раздела действителен до загрузки драйвера. Поэтому драйвер должен вызывать функцию MmLockPagableXxxSection только один раз. Если драйверу необходимы дополнительные вызовы для блокировки, он должен использовать MmLockPagableSectionByHandle.

Если драйвер вызывает любую подпрограмму MmLockPagableXxx для раздела, который уже заблокирован, диспетчер памяти увеличивает число ссылок для раздела. Если раздел выгружен из памяти при вызове подпрограммы блокировки, менеджер памяти подгружает этот раздел и устанавливает его счетчик ссылок на единицу.

Использование этого метода сводит к минимуму влияние драйвера на системные ресурсы. При запуске драйвера он может заблокировать в памяти код и данные, которые должны быть резидентами. Если для устройства отсутствуют незавершенные запросы ввода-вывода (то есть, когда устройство закрыто или если устройство никогда не было открыто), драйвер может разблокировать тот же код или данные, что делает его доступным для вывода.

Однако после того, как драйвер подключил прерывания, любой код драйвера, который может быть вызван во время обработки прерываний, всегда должен быть резидентом памяти. Хотя некоторые драйверы устройств могут быть странично или загружены в память по требованию, некоторые основные части кода и данных такого драйвера должны быть постоянно резидентными в системном пространстве.

Рассмотрим следующие рекомендации по реализации для блокировки кода или раздела данных.

  • Основное использование подпрограмм Mm(Un)LockXxx заключается в том, чтобы позволить обычно незагружаемому в память коду или данным стать подлежащими обмену с диском и в загружаемом в память виде. Драйверы, такие как серийный драйвер и параллельный драйвер, являются хорошими примерами: если на устройстве нет открытых дескрипторов, управляемых драйвером, части кода не требуются и могут оставаться на странице. Средство перенаправления и сервер также являются хорошими примерами драйверов, которые могут использовать этот метод. Если активные подключения отсутствуют, оба этих компонента могут быть выгружены из памяти.

  • Весь раздел, доступный для страниц, заблокирован в память.

  • Один раздел кода и один для данных для каждого драйвера является эффективным. Многие именованные разделы, подлежащие выгрузке в память, обычно неэффективны.

  • Держите отдельными полностью выгружаемые разделы и выгружаемые, но блокируемые по требованию разделы.

  • Помните, что mmLockPagableCodeSection и MmLockPagableDataSection не должны вызываться часто. Эти подпрограммы могут привести к интенсивному выполнению операций ввода-вывода при загрузке раздела диспетчером памяти. Если драйвер должен заблокировать раздел из нескольких расположений в коде, он должен использовать MmLockPagableSectionByHandle.