Приветствую всех!
В этой небольшой статье речь пойдет об одном способе создания разделяемой памяти, к которой можно будет обращаться как из режима ядра, так и из пользовательского режима. Приведу примеры функций выделения и освобождения памяти, а также будут ссылки на исходники, чтобы можно было попробовать любому желающему.
Полностью описывать разработку драйвера, начиная с установки WDK(DDK), выбора инструментов разработки, написания стандартных функций драйвера и т.д., я не буду (при желании можно почитать вот и вот, хотя там тоже не много информации). Чтобы статья не получилась слишком раздутой, опишу только способ реализации разделяемой памяти.
Немного теории
Драйвер не создает специального программного потока для выполнения своего кода, а выполняется в контексте потока, активного на данный момент. Поэтому считается, что драйвер выполняется в контексте произвольного потока (Переключение контекста). Очень важно, чтобы при отображении выделенной памяти в пользовательское адресное пространство, мы находились в контексте потока приложения, которое будет управлять нашим драйвером. В данном случае это правило соблюдается, т.к. драйвер является одноуровневым и обращаемся мы к нему с помощью запроса IRP_MJ_DEVICE_CONTROL, следовательно контекст потока не будет переключаться и мы будем иметь доступ к адресному пространству нашего приложения.
Выделение памяти
#pragma LOCKEDCODE
NTSTATUS AllocateSharedMemory(PDEVICE_EXTENSION pdx, PIRP Irp)
{ // AllocateSharedMemory
KdPrint(("SharedMemory: Entering AllocateSharedMemoryn"));
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 %08Xn", 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 %08Xn", 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: Don't mapped memory into kernel spacen"));
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 %08Xn", 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 spacen"));
return STATUS_NONE_MAPPED;
}
}
else
{
KdPrint(("SharedMemory: Don't allocate memory for MDLn"));
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 ReleaseSharedMemoryn"));
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 freedn", 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(hAccessDriver, 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 и видим успешное обращение к разделяемой памяти.
При нажатии на кнопку происходит освобождение памяти и удаление созданной структуры 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
Автор: kulinich