
Приветствую всех!
В этой небольшой статье речь пойдет об одном способе создания разделяемой памяти, к которой можно будет обращаться как из режима ядра, так и из пользовательского режима. Приведу примеры функций выделения и освобождения памяти, а также будут ссылки на исходники, чтобы можно было попробовать любому желающему.
Драйвер собирался под 32-битную ОС Windows XP (так же проверял его работу и
в 32-битной Windows 7).
Полностью описывать разработку драйвера, начиная с установки WDK(DDK), выбора инструментов разработки, написания стандартных функций драйвера и т.д., я не буду (при желании можно почитать вот и вот, хотя там тоже не много информации). Чтобы статья не получилась слишком раздутой, опишу только способ реализации разделяемой памяти.
Немного теории
Драйвер не создает специального программного потока для выполнения своего кода, а выполняется в контексте потока, активного на данный момент. Поэтому считается, что драйвер выполняется в контексте произвольного потока (Переключение контекста). Очень важно, чтобы при отображении выделенной памяти в пользовательское адресное пространство, мы находились в контексте потока приложения, которое будет управлять нашим драйвером. В данном случае это правило соблюдается, т.к. драйвер является одноуровневым и обращаемся мы к нему с помощью запроса IRP_MJ_DEVICE_CONTROL, следовательно контекст потока не будет переключаться и мы будем иметь доступ к адресному пространству нашего приложения.
Выделение памяти
#pragma LOCKEDCODE NTSTATUS AllocateSharedMemory(PDEVICE_EXTENSION pdx, PIRP Irp) { // AllocateSharedMemory KdPrint(("SharedMemory: Entering AllocateSharedMemory\n")); int memory_size = 262144000; // 250 Mb PHYSICAL_ADDRESS pstart = { 0x0, 0x00000000 }; PHYSICAL_ADDRESS pstop = { 0x3, 0xffffffff }; PHYSICAL_ADDRESS pskip = { 0x0, 0x00000000 }; // pointer to the output memory => pdx->vaReturned pdx->vaReturned = (unsigned short **) GenericGetSystemAddressForMdl(Irp->MdlAddress); // create MDL structure (pointer on MDL => pdx->mdl) pdx->mdl = MmAllocatePagesForMdl(pstart, pstop, pskip, memory_size); if (pdx->mdl != NULL) { KdPrint(("SharedMemory: MDL allocated at address %08X\n", pdx->mdl)); // get kernel space virtual address pdx->kernel_va = (unsigned short*) MmGetSystemAddressForMdlSafe(pdx->mdl, NormalPagePriority); if (pdx->kernel_va != NULL) { KdPrint(("SharedMemory: pdx->kernel_va allocated at address %08X\n", pdx->kernel_va)); for (int i = 0; i < 10; ++i) { pdx->kernel_va[i] = 10 - i; // write in memory: 10-9-8-7-6-5-4-3-2-1 } } else { KdPrint(("SharedMemory: Not mapped memory into kernel space\n")); return STATUS_NONE_MAPPED; } // get user space virtual address pdx->user_va = (unsigned short*) MmMapLockedPagesSpecifyCache(pdx->mdl, UserMode, MmCached, NULL, FALSE, NormalPagePriority); if (pdx->user_va != NULL) { KdPrint(("SharedMemory: pdx->user_va allocated at address %08X\n", pdx->user_va)); // return pointer on sharing memory into user space *pdx->vaReturned = pdx->user_va; } else { KdPrint(("SharedMemory: Don't mapped memory into user space\n")); return STATUS_NONE_MAPPED; } } else { KdPrint(("SharedMemory: Don't allocate memory for MDL\n")); return STATUS_MEMORY_NOT_ALLOCATED; } return STATUS_SUCCESS; } // AllocateSharedMemory
разбор функции по частям:
Сохраняем указатель, с помощью которого передадим указатель на выделенную память нашему приложению:
pdx->vaReturned = (unsigned short **) GenericGetSystemAddressForMdl(Irp->MdlAddress);
Следующий шаг — выделение неперемещаемой физической памяти размером memory_size и построение на ее основе структуру MDL (Memory Descriptor List), указатель на которую сохраняем в переменной pdx->mdl:
pdx->mdl = MmAllocatePagesForMdl(pstart, pstop, pskip, memory_size);
Как видно из изображения, структура MDL нам нужна для описания зафиксированных физических страниц.
Затем получаем диапазон виртуальных адресов для MDL в системном адресном пространстве и сохраняем указатель на эти адреса в переменной pdx->kernel_va:
pdx->kernel_va = (unsigned short*) MmGetSystemAddressForMdlSafe(pdx->mdl, NormalPagePriority);
Эта функция возвратит указатель, по которому мы сможем обращаться к выделенной памяти в драйвере (причем независимо от текущего контекста потока, т.к. адреса получены из системного адресного пространства).
В цикле запишем первые 10 ячеек памяти числами от 10 до 1, чтобы можно было проверить доступность выделенной памяти из пользовательского режима:
for (int i = 0; i < 10; ++i) { pdx->kernel_va[i] = 10 - i; // write in memory: 10-9-8-7-6-5-4-3-2-1 }
Теперь необходимо отобразить выделенную память в адресное пространство приложения, которое обратилось к драйверу:
pdx->user_va = (unsigned short*) MmMapLockedPagesSpecifyCache(pdx->mdl, UserMode, MmCached, NULL, FALSE, NormalPagePriority);
Переменная pdx->vaReturned является указателем на указатель и объявляется в структуре pdx (см. driver.h в папке source_driver). С помощью нее передадим указатель pdx->user_va в приложение:
*pdx->vaReturned = pdx->user_va;
Освобождение памяти
#pragma LOCKEDCODE NTSTATUS ReleaseSharedMemory(PDEVICE_EXTENSION pdx, PIRP Irp) { // ReleaseSharedMemory KdPrint(("SharedMemory: Entering ReleaseSharedMemory\n")); if (pdx->mdl != NULL) { MmUnmapLockedPages(pdx->user_va, pdx->mdl); MmUnmapLockedPages(pdx->kernel_va, pdx->mdl); MmFreePagesFromMdl(pdx->mdl); IoFreeMdl(pdx->mdl); KdPrint(("SharedMemory: MDL at address %08X freed\n", pdx->mdl)); } return STATUS_SUCCESS; } // ReleaseSharedMemory
Здесь происходит освобождение адресного пространства приложения:
MmUnmapLockedPages(pdx->user_va, pdx->mdl);
ситемного адресного пространства:
MmUnmapLockedPages(pdx->kernel_va, pdx->mdl);
затем освобождаются физические страницы:
MmFreePagesFromMdl(pdx->mdl);
и «убиваем» MDL:
IoFreeMdl(pdx->mdl);
Обращаемся к драйверу из пользовательского режима
(Весь код приложения смотрите в прилагаемых материалах)
Первое, что необходимо сделать, это получить манипулятор устройства (handle) с помощью функции CreateFile():
hDevice = CreateFile(L"\\\\.\\SharedMemory", GENERIC_READ|GENERIC_WRITE, FILE_SHARE_READ|FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
Затем необходимо отправить запрос ввода/вывода драйверу с помощью функции DeviceIoControl():
unsigned short** vaReturned = new unsigned short*(); ioCtlCode = IOCTL_ALLOCATE_SHARED_MEMORY; checker = DeviceIoControl(hDevice, ioCtlCode, NULL, 0, vaReturned, sizeof(int), &bytesReturned, NULL);
Вызов функции преобразуется в IRP пакет, который будет обрабатываться в диспетчерской функции драйвера (см. DispatchControl() в файле control.cpp драйвера). Т.е. при вызове DeviceIoControl() управление передастся функции драйвера, код которой выше был описан. Так же, при вызове функции DeviceIoControl() в программе DebugView (надо галочку поставить, чтобы она отлавливала события режима ядра) увидим следующее:

По возвращению управления приложению переменная vaReturned будет указывать на разделяемую память (точнее будет указывать на указатель, который уже будет указывать на память). Сделаем небольшое упрощение, чтобы получить обычный указатель на память:
unsigned short* data = *vaReturned;
Теперь по указателю data мы имеем доступ к разделяемой памяти из приложения:

При нажатии на кнопку «Allocate memory» приложение передает управление драйверу, который выполняет все действия, описанные выше, и возвращает указатель на выделенную память, доступ к которой из приложения будет осуществляться через указатель data. Кнопкой «Fill TextEdit» выводим содержимое первых 10-и элементов, которые были заполнены в драйвере, в QTextEdit и видим успешное обращение к разделяемой памяти.
При нажатии на кнопку «Release memory» происходит освобождение памяти и удаление созданной структуры MDL.
Исходники
1. source_driver.rar.
2. source_app.rar.
3. source_generic_oney.
За основу драйвера (source_driver) я взял один из примеров у Уолтера Они (примеры прилагаются к его книге «Использование Microsoft Windows Driver Model»). Так же необходимо скачать библиотеку ядра Generic, т.к. эта библиотека нужна как при сборке, так и при работе драйвера.
Тем, кто хочет попробовать сам
Создаем директорию (н-р, C:\Drivers) и распаковываем туда исходники (source_driver, source_generic_oney и source_app). Если не будете пересобирать драйвер, то достаточно установить новое оборудование вручную (указав inf-файл: sharedmemory.inf) через Панель управления-установка нового оборудования (для Windows XP). Затем надо запустить habr_app.exe (source_app/release).
Если решите пересобирать, то:
1. Необходимо установить WDK.
2. Сначала нужно будет пересобрать библиотеку Generic, т.к. в зависимости от версии ОС папки с выходными файлами могут по разному называться (н-р, для XP — objchk_wxp_x86, для Win7 — objchk_win7_x86).
3. После 1 и 2 пункта можно пробовать собрать драйвер командой «build» с помощью x86 Checked Build Environment, входящую в WDK.
Источники
- Статьи с wasm.ru
- Уолтер Они “Использование Microsoft Windows Driver Model” (ISBN 978-5-91180-057-4, 0735618038).
- Джеффри Рихтер «Windows для профессионалов. Создание эффективных Win32-приложений» (ISBN 5-272-00384-5, 1-57231-996-8).
- msdn
UPD: хабраюзер DZhon привел ссылку разделяемая память средствами Qt.
