Implementing shared memory between driver and application
- Tutorial

Greetings to all!
This short article will focus on one way to create shared memory, which can be accessed from both kernel mode and user mode. I will give examples of the functions of allocating and freeing memory, and there will also be links to the sources so that anyone can try it.
The driver was built for 32-bit Windows XP (it was also tested
in 32-bit Windows 7).
I will not fully describe the development of the driver, starting with installing WDK (DDK), choosing development tools, writing standard driver functions, etc. (if you wish, you can read this and that , although there is also a lot of information there). To prevent the article from getting too bloated, I will only describe how to implement shared memory.
Bit of theory
The driver does not create a special program thread for executing its code, but executes in the context of the currently active thread. Therefore, it is believed that the driver is executed in the context of an arbitrary thread ( Context Switching ). It is very important that when mapping the allocated memory to the user address space, we are in the context of the application flow that will manage our driver. In this case, this rule is respected, because the driver is single-level and we access it using the IRP_MJ_DEVICE_CONTROL request , therefore the flow context will not switch and we will have access to the address space of our application.
Memory allocation
#pragma LOCKEDCODENTSTATUS 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 = (unsignedshort **) 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 = (unsignedshort*) 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 = (unsignedshort*) 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;
} // AllocateSharedMemoryanalysis of the function in parts: We
save the pointer, with which we pass the pointer to the allocated memory to our application:
pdx->vaReturned = (unsignedshort **) GenericGetSystemAddressForMdl(Irp->MdlAddress);
The next step is the allocation of non-relocatable physical memory of size memory_size and the construction of an MDL ( Memory Descriptor List ) structure based on it , the pointer to which is stored in the variable pdx-> mdl:
pdx->mdl = MmAllocatePagesForMdl(pstart, pstop, pskip, memory_size);

As you can see from the image, we need the MDL structure to describe fixed physical pages.
Then we get the range of virtual addresses for MDL in the system address space and save the pointer to these addresses in the variable pdx-> kernel_va:
pdx->kernel_va = (unsignedshort*) MmGetSystemAddressForMdlSafe(pdx->mdl, NormalPagePriority);
This function will return a pointer by which we can access the allocated memory in the driver (moreover, regardless of the current context of the stream, because addresses are received from the system address space).
In the loop, write the first 10 memory cells with numbers from 10 to 1, so that you can check the availability of the allocated memory from user mode:
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
}
Now you need to map the allocated memory to the address space of the application that accessed the driver:
pdx->user_va = (unsignedshort*) MmMapLockedPagesSpecifyCache(pdx->mdl, UserMode, MmCached, NULL, FALSE, NormalPagePriority);
The variable pdx-> vaReturned is a pointer to a pointer and is declared in the pdx structure (see driver.h in the source_driver folder). Using it, we will pass the pdx-> user_va pointer to the application:
*pdx->vaReturned = pdx->user_va;
Free memory
#pragma LOCKEDCODENTSTATUS 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;
} // ReleaseSharedMemoryHere the application address space is freed up:
MmUnmapLockedPages(pdx->user_va, pdx->mdl);
system address space:
MmUnmapLockedPages(pdx->kernel_va, pdx->mdl);
then the physical pages are freed:
MmFreePagesFromMdl(pdx->mdl);
and "kill" MDL:
IoFreeMdl(pdx->mdl);
We appeal to the driver from user mode
(All application code see in the attached materials)
The first thing to do is get the device manipulator (handle) using the CreateFile () function :
hDevice = CreateFile(L"\\\\.\\SharedMemory", GENERIC_READ|GENERIC_WRITE,
FILE_SHARE_READ|FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
Then you need to send an I / O request to the driver using the DeviceIoControl () function :
unsignedshort** vaReturned = newunsignedshort*();
ioCtlCode = IOCTL_ALLOCATE_SHARED_MEMORY;
checker = DeviceIoControl(hDevice, ioCtlCode, NULL, 0,
vaReturned, sizeof(int), &bytesReturned, NULL);
The function call is converted into an IRP packet, which will be processed in the dispatch function of the driver (see DispatchControl () in the driver control.cpp file). Those. when calling DeviceIoControl (), control will be transferred to the driver function, the code of which was described above. Also, when calling the DeviceIoControl () function in the DebugView program (you need to check the box so that it catches kernel mode events), we will see the following:

Upon returning control to the application, the vaReturned variable will point to shared memory (more precisely, it will point to a pointer that will already point to memory). Let's make a little simplification to get a regular pointer to memory:
unsignedshort* data = *vaReturned;
Now, through the data pointer, we have access to shared memory from the application:

When you click on the "Allocate memory" button, the application transfers control to the driver, which performs all the steps described above, and returns a pointer to the allocated memory, which will be accessed from the application through the pointer data . Using the “Fill TextEdit” button, we display the contents of the first 10 elements that were filled in the driver in QTextEdit and we see a successful call to shared memory.
When you click on the button “Release memory”, the memory is freed and the created MDL structure is deleted.
Source code
1. source_driver.rar .
2. source_app.rar .
3. source_generic_oney .
For the basis of the driver (source_driver), I took one of the examples from Walter Oni (examples are attached to his book “Using the Microsoft Windows Driver Model”). It is also necessary to download the Generic core library, as This library is needed both during assembly and during driver operation.
Those who want to try it yourself
Create a directory (e.g. C: \ Drivers) and unpack the sources (source_driver, source_generic_oney and source_app) there. If you will not rebuild the driver, then it is enough to install new equipment manually (by specifying the inf-file: sharedmemory.inf) through the Control Panel — install new equipment (for Windows XP). Then you need to run habr_app.exe (source_app / release).
If you decide to rebuild, then:
1. You need to install WDK .
2. First you need to rebuild the Generic library, because Depending on the OS version, folders with output files can be called differently (eg, for XP - objchk_wxp_x86, for Win7 - objchk_win7_x86).
3. After points 1 and 2, you can try to build the driver with the “build” command using the x86 Checked Build Environment included in the WDK.
Sources
- Articles from wasm.ru
- Walter They are “Using the Microsoft Windows Driver Model” (ISBN 978-5-91180-057-4, 0735618038).
- Jeffrey Richter Windows for Professionals. Creation of effective Win32 applications ”(ISBN 5-272-00384-5, 1-57231-996-8).
- msdn
UPD: jon habrayuzer brought the link the shared memory means Qt .