驱动分为两类,一类是支持即插即用的WDM驱动程序
一类是不支持即插即用的NT式驱动程序
#define PAGEDCODE code_seg("PAGE")
#define LOCKEDCODE code_seg()
#define INITCODE code_seg("INIT")
定义了分页标记,非分页标记和初始化内存
windows驱动开发中,所有程序的函数和变量都要被指明加载到分页内存还是非分页内存中
另DriverEntry需放在INIT标志的内存中
INIT指明该函数只是在加载时需要载入内存,当驱动成功加载后,该函数可以从内存中卸载掉
默认情况下,内核加载器会加载所有的代码部分和全局数据到非分页内存中。而且,加载器是一次加载整个驱动的可执行文件,包括相关的DLL。加载后,内核加载器关闭驱动程序文件,甚至你可以删除当前正在执行的驱动文件
但是,你可以告诉加载器你希望驱动的哪部分是可分页,所谓可分页,就是可能会被页换出内存(Page out)
window驱动的入口是一个叫DriverEntry的函数,DriverEntry函数是由内核中的I/O管理器负责调用,
extern "C" NTSTATUS DriverEntry (
IN PDRIVER_OBJECT pDriverObject,
IN PUNICODE_STRING pRegistryPath )
这个函数有两个参数:pDriverObject和pRegistryPath
从字面上理解,一个是I/O管理器传递进来的驱动对象,一个是指向该驱动负责的注册表
我们必须在DriverEntry函数前加上extern“c”, 如下:
extern "C" NTSTATUS DriverEntry (
否则会报错:(因为编译器会自动按C++的符号名编译)
error LNK2019: unresolved external symbol _DriverEntry@8 referenced in function _GsDriverEntry@8
//注册其他驱动调用函数入口
pDriverObject->DriverUnload = HelloDDKUnload;
pDriverObject->MajorFunction[IRP_MJ_CREATE] = HelloDDKDispatchRoutine;
pDriverObject->MajorFunction[IRP_MJ_CLOSE] = HelloDDKDispatchRoutine;
pDriverObject->MajorFunction[IRP_MJ_WRITE] = HelloDDKDispatchRoutine;
pDriverObject->MajorFunction[IRP_MJ_READ] = HelloDDKDispatchRoutine;
以上可以通过名字来判断,就是驱动程序向windows的I/O管理器注册一些回调函数,
但是这些函数不是由驱动程序本身负责调用,而是由操作系统负责调用,我们只需要把这些函数的入口地址告诉操作系统,操作系统会在适当的时候调用它们
比如,当驱动被卸载时,调用HelloDDkUnload,当驱动处理创建,关闭,读写相关的I/O请求包(IRP)时,都调用HelloDDKDispatchRoutine(这里只是简单起见)
UNICODE_STRING devName;
RtlInitUnicodeString(&devName,L"\Device\MyDDKDevice");
这个是构造一个unicode字符串,此字符串是用来做存储此设备对象的名称
//创建设备
status = IoCreateDevice( pDriverObject,
sizeof(DEVICE_EXTENSION),
&(UNICODE_STRING)devName,
FILE_DEVICE_UNKNOWN,
0, TRUE,
&pDevObj );
NTSTATUS status = IoCreateDevice(DriverObject,
sizeof(DEVICE_EXTENSION),
NULL,
FILE_DEVICE_UNKNOWN,
FILE_DEVICE_SECURE_OPEN,
FALSE,
&fdo);
第一个DriverObject是由I/O管理器传递进来的驱动对象,也就是DriverEntry里传过来的
第二个参数是设备扩展结构的大小,IO管理器自动分配这个内存,并把设备对象中的DeviceExtension指针指向这块内存。
第三个参数(可选的参数)指向一个以零结尾的包含Unicode字符串的缓冲区,那是这个设备的名称,该字符串必须是一个完整的设备路径名
第四个参数是表示这个设备的类型
第五个参数表示提供有关驱动程序的设备其他信息
第六个参数(FALSE)
指出设备是否是排斥的。通常,对于排斥设备,I/O管理器仅允许打开该设备的一个句柄
第七个参数是一个指向DEVICE_OBJECT结构体指针的指针,这是一个指针的指针,指向的指针用来接收DEVICE_OBJECT结构体的指针.
pDevObj->Flags |= DO_BUFFERED_IO;
表明此设备为BUFFERED_IO设备
DO_DIRECT_IO常用于传输大块的讲求速度的数据。DO_BUFFERED_IO常用于传输小块的,慢速的数据。DO_DIRECT_IO的效率较高(只需内存锁定即可),代价是牺牲物理内存。DO_BUFFERED_IO的内存效率高,但速度差(它要分配内存,复制数据)
pDevExt = (PDEVICE_EXTENSION)pDevObj->DeviceExtension;
pDevExt->pDevice = pDevObj;
pDevExt->ustrDeviceName = devName;
pDevExt指向pDevObj的扩展,(注意看IoCreateDevice的第二个参数,传的就是它,按解释是O管理器自动分配这个内存,并把设备对象中的DeviceExtension指针指向这块内存。)仔细看上面的代码,我们可以通过pDevObj的PVOID
DeviceExtension字段找到pDevExt,从而得到它的一些自定义属性
//创建符号链接
UNICODE_STRING symLinkName;
RtlInitUnicodeString(&symLinkName,L"\??\HelloDDK");
pDevExt->ustrSymLinkName = symLinkName;
status = IoCreateSymbolicLink( &symLinkName,&devName );
NTSTATUS IoCreateSymbolicLink(
_In_ PUNICODE_STRING SymbolicLinkName,
_In_ PUNICODE_STRING DeviceName
);
Pointer to a buffered Unicode string that is the user-visible name.
Pointer to a buffered Unicode string that is the name of the driver-created device object
WDM drivers do not name device objects and therefore should not use this routine. Instead, a WDM driver should call IoRegisterDeviceInterface to set up a symbolic link.(WDM驱动没有设备对象名称,所以不能用这个设定符号链接)上面的目的是,因为驱动程序虽然有了设备名称,但这种设备名称只在内核中可见,所以驱动需要暴露一个符号链接,该链接指向真正的设置名称
if (!NT_SUCCESS(status))
{
IoDeleteDevice( pDevObj );
return status;
}
如果创建设备失败,就删除设备对象
下面看看卸载驱动设备的操作:由I/O管理器负责调用此回调函数
PDEVICE_OBJECT pNextObj; KdPrint(("Enter DriverUnload ")); pNextObj = pDriverObject->DeviceObject;第一个设备对象的地址存在于驱动对象的DeviceObject域中,每个设备对象的NextDevice域又记录了下个设备对象的地址
卸载的主要目的就是遍历所有的此类设备对象,然后删除设备对象以及符号链接
while (pNextObj != NULL) { PDEVICE_EXTENSION pDevExt = (PDEVICE_EXTENSION) pNextObj->DeviceExtension; //删除符号链接 UNICODE_STRING pLinkName = pDevExt->ustrSymLinkName; IoDeleteSymbolicLink(&pLinkName); pNextObj = pNextObj->NextDevice; IoDeleteDevice( pDevExt->pDevice ); }
这个主要是pDevExt存储了符号名
PS:
示例代码有问题,强制用monitor卸载会崩掉,抓了个图:
跟了下,CreateDevice使用的是INIT标志的内存中,意味着加载完后就可以卸载。导致其中的变量就不存在了。把INIT换成PAGE即可解决了