小弟试图在驱动程序申请一段公用缓存区连续内存供DMA操作使用,然后在IO响应程序中将这个连续内存的首地址映射给用户程序访问,已降低数据搬移的时间消耗。
在内存开辟(申请)上没有问题,可以满足我的需要,但在映射超过一定大小的公用缓冲区的过程中就会蓝屏。
1.当DMA_SIZE约大于800k时MmMapLockedPagesSpecifyCache会导致蓝屏,当DMA_SIZE在800k以下基本可以正常映射.我也尝试过用
ExAllocatePoolWithTag 和 MmAllocateContiguousMemorySpecifyCache 来申请内存去映射到用户进程空间,也出现这个映射失败的蓝屏情况.
看到看雪中这篇帖子
http://bbs.pediy.com/showthread.php?p=818239 中提到映射1M到16M都没有问题,我映射800K以上就蓝屏,不知道代码有没有什么问题,出错函数应该是
MmMapLockedPagesSpecifyCache,但我不清楚问题出在哪里~
2.目标及调试环境:win7 64bit RAM:4G
开发环境:win7 64bit VS2013 WDK8.1
3.驱动上下文结构体定义(Device.h)
typedef struct _DEVICE_CONTEXT{
struct _tag_DrvMapApp_Reg{
PHYSICAL_ADDRESS DevBaseAddr; //物理地址或者逻辑地址(设备访问需要)
PVOID DrvBaseAddr; //内核态虚拟地址(驱动程序访问需要)
PVOID AppBaseAddr; //用户态虚拟地址(应用程序访问需要)
unsigned long int length;
int isBusy;
PMDL pMdl;
WDFDMAENABLER dmaEnable;
WDFCOMMONBUFFER commonBuffer;
WDFDMATRANSACTION DmaTransaction;
WDFINTERRUPT Interrupt;
WDFREQUEST Request;
}DrvMapAppReg1;
ULONG Counter_i; // 资源计数器,记录WDF框架分配给设备的资源个数,counter for WdfCmResourceListGetCount
(ResourceListTranslated)
ULONG OffsetAddressFromApp; // get offset address that is given by application 偏移地址(由应用程序传递过来)
} DEVICE_CONTEXT, *PDEVICE_CONTEXT;
4.EvtDeviceAdd函数中
#define DMA_SIZE (900*1024)
PVOID DrvUseAddr1 = NULL;
PHYSICAL_ADDRESS DevUseAddr1;
WDF_DMA_ENABLER_CONFIG dmaConfig;
DevUseAddr1.QuadPart = 0;
WdfDeviceSetAlignmentRequirement(device, FILE_OCTA_ALIGNMENT); //16字节对齐
//创建一个DMA适配器
WDF_DMA_ENABLER_CONFIG_INIT( &dmaConfig,
WdfDmaProfileScatterGather64Duplex, //通道特性
DMA_SIZE/20 //单个传输的最大长度,小于65536
(64K)
);
status = WdfDmaEnablerCreate( device,
&dmaConfig,
WDF_NO_OBJECT_ATTRIBUTES,
&pDevContext->DrvMapAppReg1.dmaEnable
);
if (!NT_SUCCESS(status)){
return status;
}
//创建一个DMA传输
status = WdfDmaTransactionCreate( pDevContext->DrvMapAppReg1.dmaEnable,
WDF_NO_OBJECT_ATTRIBUTES,
&pDevContext->DrvMapAppReg1.DmaTransaction
);
//分配DMA公用缓冲区
status = WdfCommonBufferCreate( pDevContext->DrvMapAppReg1.dmaEnable,
DMA_SIZE,
WDF_NO_OBJECT_ATTRIBUTES,
&pDevContext->DrvMapAppReg1.commonBuffer
);
if (status == STATUS_SUCCESS){ //能够正常获取
DrvUseAddr1 = WdfCommonBufferGetAlignedVirtualAddress(pDevContext->DrvMapAppReg1.commonBuffer);
DevUseAddr1 = WdfCommonBufferGetAlignedLogicalAddress(pDevContext->DrvMapAppReg1.commonBuffer);
pDevContext->DrvMapAppReg1.DrvBaseAddr = DrvUseAddr1;
pDevContext->DrvMapAppReg1.DevBaseAddr = DevUseAddr1;
pDevContext->DrvMapAppReg1.AppBaseAddr = NULL;
pDevContext->DrvMapAppReg1.isBusy = 0;
pDevContext->DrvMapAppReg1.length = DMA_SIZE;
RtlZeroMemory(pDevContext->DrvMapAppReg1.DrvBaseAddr, pDevContext->DrvMapAppReg1.length);
KdrPrint("DMA_SIZE = %d KB", WdfCommonBufferGetLength(pDevContext->DrvMapAppReg1.commonBuffer) / 1024);
}
else{
return status;
}
5.在EvtIoDeviceControl例程中:
类型定义
typedef struct _tag_DMA_MEMORY{
void * addr; //注意环境切换 win32 or x64 指针变量的长度不一样
unsigned long length;
}DMA_MEMORY_Header, *PDMA_MEMORY_Header;
case PCIeCardDrv_IOCTL_GET_THREAD_ADDR:
//outBuffer返回进程需要使用DMA空间的首地址和长度,在switch之前已经获取
KdrPrint("EvtIoDeviceControl: I am GET_THREAD_ADDR!\n");
MapSharedMemory2App(pDevContext, (PDMA_MEMORY_Header)outBuffer);
WdfRequestCompleteWithInformation(Request, status, OutputBufferLength);
if (!NT_SUCCESS(status)){
goto Exit;
}
break;
6.其中MapSharedMemory2App的具体实现如下:
NTSTATUS MapSharedMemory2App(PDEVICE_CONTEXT pDevContext,PDMA_MEMORY_Header pMemoryHeader){
PMDL pMdl = NULL;
PVOID AppBaseAddr = NULL;
pMemoryHeader->addr = NULL;
pMemoryHeader->length = 0;
pMdl = IoAllocateMdl(pDevContext->DrvMapAppReg1.DrvBaseAddr, DMA_SIZE, FALSE, FALSE, NULL);
pMdl->MdlFlags |= MDL_MAPPED_TO_SYSTEM_VA; // 改变MDL的flags为可读状态
MmBuildMdlForNonPagedPool(pMdl); //非页面缓冲池调用这个
AppBaseAddr = MmMapLockedPagesSpecifyCache(pMdl, //
UserMode,
MmNonCached,
NULL,
FALSE,
NormalPagePriority// | MdlMappingNoWrite
);
pDevContext->DrvMapAppReg1.AppBaseAddr = AppBaseAddr; //保存信息到
pDevContext->DrvMapAppReg1.pMdl = pMdl;
pDevContext->DrvMapAppReg1.length = DMA_SIZE;
pMemoryHeader->addr = AppBaseAddr;
pMemoryHeader->length = DMA_SIZE;
KdrPrint("2:pMdl = 0x%x\n", pDevContext->DrvMapAppReg1.pMdl);
KdrPrint("I am MapSharedMemory2App Return\n");
return STATUS_SUCCESS;
}
希望各位能指点一下原因和怎么解决这个问题,谢谢~
[课程]Linux pwn 探索篇!