内核线程
在驱动中生成的线程一般是系统线程。系统线程所在的进程名为“System”。
NTSTATUS
PsCreateSystemThread(
OUT PHANDLE ThreadHandle, //用来返回句柄,放入一个句柄指针即可
IN ULONG DesiredAccess, //一般总是填写0
IN POBJECT_ATTRIBUTES ObjectAttributes OPTIONAL, //NULL
IN HANDLE ProcessHandle OPTIONAL, //NULL
OUT PCLIENT_ID ClientId OPTIONAL, //NULL
IN PKSTART_ROUTINE StartRoutine, //用于该线程启动的时候执行的函数,传入函数名即可
IN PVOID StartContext); //用于传入该函数的参数
线程的结束应该在线程中自己调用PsTerminateSystemThread来完成。此外得到的句柄也必须要用ZwClose来关闭。关闭句柄并不结束线程。
例子:
VOID MyThreadProc(PVOID context) //我的线程函数,传入一个参数,这个参数是一个字符串
{
PUNICODE_STRING str = (PUNICODESTRING)context;
KdPrint(("PrintInMyThread:%wZ\r\n",str));
PsTerminateSystemThread(STATUS_SUCCESS); //终止一个线程
}
VOID MyFunction()
{
UNICODE_STRING str = RTL_CONSTANT_STRING(L"Hello!");
HANDLE thread = NULL;
status = PsCreateSystemThread(&thread,0L,NULL,NULL,NULL,MyThreadProc,(PVOID)&str); //开始一个线程
if(!NT_SUCCESS(status))
{
//错如处理
}
//如果成功了,可以继续做自己的事,之后得到的句柄要关闭
ZwClose(thread);
}
注意 :MyThreadProc执行的时候,MyFunction可能已经执行完毕了,str就会无效,再执行K的Print去打印str一定会蓝屏。解决方法是在队中分配str的空间,或者str必须在全局空间中。
睡眠
#define DELAY_ONE_MICROSECOND (-10) //10个100纳秒 = 1 微秒, -10表示相对时间
#define DELAY_ONE_MILLISECOND (DELAY_ONE_MICROSECOND*1000)
VOID MySleep(LONG msec)
{
LARGE_INTEGER my_interval;
my_interval.QuadPart = DELAY_ONE_MILLISECOND;
my_interval.QuadPart *= msec;
KeDelayExecutionThread(KernelMode, //表示在内核编程中使用
0, //是否允许线程报警(用于重新唤醒)
&my_interval); //表示要睡眠多久
}
事件
内核中的事件是一个数据结构。这个结构的指针可以当作一个参数传入一个等待函数中。如果这个事件不被“设置”,则这个等待函数不会返回,这个线程被阻塞。如果这个事件被“设置”,则等待结束,可以继续下去。
事件不需要销毁。
可以发现,关于事件的操作这一部分(如事件的重设,同步等),其原理和MFC中是很类似的,只是MFC中封封装了更好处理的API函数而已。
实际上等待线程结束并不一定要用事件。线程本身也可以当作一个事件来等待。
例子:
KEVENT event; //定义一个事件
KeInitializeEvent(&event,SyschronizationEvent,TRUE); //事件初始化,SynchoronizationEvent为“自动重设”事件:只有一个线程的wait可以通过,通过之后被自动重设,其他的线程就只能继续等待了;若为NotificationEvent,这个事件必须手动重设KeResetEvent(&event)之后才能使用,否则所有等待线程都通过了。
KeWaitForSingleObject(&event,Executive,KernelMode,0,0); //等待这个事件event,直到这个事件被人设置
KeSetEvent(&event); //在另一个地方,设置这个事件,前面等待的地方将继续执行
驱动与设备和请求处理
#include <ntddk.h>
NTSTATUS
DriverEntry (
IN PDRIVER_OBJECT DriverObject,
IN PUNICODE_STRING RegistryPath
)
{
NTSTATUS status = STATUS_UNSUCCESSFUL;
return status;
}
函数DriverEntry是每个驱动程序中必须的。如同Win32应用程序里的WinMain。
DriverEntry的第一个参数就是一个 DRIVER_OBJECT的指针。这个DRIVER_OBJECT结构就对应当前编写的驱动程序。其内存是Windows系统已经分配的。
第二个参数RegistryPath是一个字符串。代表一个注册表子键。这个子键是专门分配给这个驱动程序使用的。用于保存驱动配置信息到注册表中。
DRIVER_OBJECT中含有分发函数指针,分发函数指针的个数为IRP_MJ_MAXIMUM_FUNCTION,保存在一个数组中。这些函数用来处理发到这个驱动的各种请求。Windows总是自己调用DRIVER_OBJECT下的分发函数来处理这些请求。所以编写一个驱动程序,本质就是自己编写这些处理请求的分发函数。
NTSTATUS
DriverEntry (
IN PDRIVER_OBJECT DriverObject,
IN PUNICODE_STRING RegistryPath
)
{
ULONG i;
for(i=0;i<IRP_MJ_MAXIMUM_FUNCTION;++i)
{
DriverObject->MajorFunctions[i] = MyDispatchFunction; //分发函数定义,所有的分发函数指针都指向一个分发函数MyDispatchFunction中
}
…
}
NTSTATUS MyDispatchFunction(PDEVICE_OBJECT device,PIRP irp) //处理发送给device的irp请求
{
……
}
VOID MyDriverUnload(PDRIVER_OBJECT driver)
{
……
}
这个函数的地址设置到DriverObject->DriverUnload即可。
设备与符号链接
如果驱动程序要和应用程序之间通信,则应该生成设备。此外还必须为设备生成应用程序可以访问的符号链接(就像文件路径一样)。
“\\.\”意味后面是一个符号链接名。 目前生成设备,请总是生成在\Device\目录下。
例子:
#include <ntifs.h>
NTSTATUS DriverEntry(
PDRIVER_OBJECT driver,
PUNICODE_STRING reg_path)
{
NTSTATUS status;
PDEVICE_OBJECT device;
UNICODE_STRING device_name = RTL_CONSTANT_STRING("\\Device\\MyCDO"); //设备名
UNICODE_STRING symb_link = RTL_CONSTANT_STRING("\\DosDevices\\MyCDOSL"); //符号链接名
status = IoCreateDevice( //生成设备,这个设备必须用系统权限才能访问,IoCreateDeviceSecure可以产生用户权限可以访问的设备
driver, //生成设备的驱动对象
0, //当用户需要在设备上记录一些额外的信息,就要指定设备扩展区内存的大小,以后就可以从DeviceObject->DeviceExtension中来获取这些信息了。
device_name, //设备名字
FILE_DEVICE_UNKNOWN, //设备类型
0,
FALSE,
&device);
if(!NT_SUCCESS(status))
return status;
status = IoCreateSymbolicLink(
&symb_link,
&device_name);
if(!NT_SUCCESS(status))
{
IoDeleteDevice(device);
return status;
}
device->Flags &= ~DO_DEVICE_INITIALIZENG; //设备生成之后,打开初始化完成标记
return status;
}
符号链接与用户相关性
某个用户穿件的符号链接只能被该用户访问,系统创建的符号链接可以被所有用户访问。下面的例子生成的符号链接总是随时可以使用:
UNICODE_STRING device_name;
UNICODE_STRING symbl_name;
if(IoIsWdmVersionAvailable(1,0x10))
{
RtlInitUnicodeString(&symbl_name,L"\\DosDevices\\Global\\SymbolicLinkName");
//如果是支持符号链接用户相关性的版本的系统,一个用户产生的符号链接只能被这一个用户访问,其它用户则不能访问,所以必须产生全局符号
}
else
{
RtlInitUnicodeString(&symbl_name,L"\\DosDevices\\SymbolicLinkName"); //如果系统不支持符号链接的用户相关性,则直接创建符号链接即可,所有用户都可以访问
}
IoCreateSymbolicLink(&symbl_name,&device_name);
请求处理
应用程序为了和驱动通信,首先必须打开设备。然后发送或者接收信息。最后关闭它。这至少需要三个IRP:第一个是打开请求。第二个发送或者接收信息。第三个是关闭请求。
IRP的种类取决于主功能号。
应用层调用的API 驱动层收到的IRP主功能号-----即DRIVER_OBJECT中分发函数指针数组中的索引。
IRP的主功能号在IRP的当前栈空间中;IRP总是发送给一个设备栈,到每个设备上的时候拥有一个“当前栈空间”来保存在这个设备上的请求信息。
这些功能都有应用层API引发,对应关系如下:
CreateFile IRP_MJ_CREATE
CloseHandle IRP_MJ_CLOSE
DeviceIoControl IRP_MJ_DEVICE_CONTROL
ReadFile IRP_MJ_READ
WriteFile IRP_MJ_WRITE
返回时一个IRP成功是一个三部曲:
(1)设置irp->IoStatus.Information为0;
(2)设置irp->IoStatus.Status的状态;成功或者失败
(3)调用IoCompleteRequest(irp,IO_NO_INCREMENT),这个函数完成IRP;
最后返回irp->IoStatus.Status即可。
打开和关闭请求
下面的函数能够实现打开和关闭请求:
NTSTATUS
MyCreateClose(
IN PDEVICE_OBJECT device,
IN PIRP irp)
{
irp->IoStatus.Information = 0;
irp->IoStatus.Status = STATUS_SUCCESS;
IoCompleteRequest(irp,IO_NO_INCREMENT);
return irp->IoStatus.Status;
}
驱动的分发函数设置如下:
DriverObject->MajorFunctions[IRP_MJ_CREATE] = MyCreateClose;
DriverObject->MajorFunctions[IRP_MJ_CLOSE] = MyCreateClose;
应用层信息的传入
可以使用WriteFile或者DeviceIoControl(双向的);
DeviceIoControl:设备控制接口,可以发送一个带有特定控制码的IRP,同时提供输入和输出缓冲区;应用程序可以定义一个控制码,然后把相应的参数填写在输入缓冲区中,同时可以从输出缓冲区得到返回的更多信息。
驱动获得一个DeviceIoControl产生的IRP的时候,需要获得当前的控制码、输入输出缓冲区的位置和长度;控制码必须预先用一个宏定义:
#define MY_DVC_IN_CODE \
(ULONG)CTL_CODE(FILE_DEVICE_UNKNOWN, \
0xa01, \ //自定义的值,其它照抄即可
METHOD_BUFFERED, \
FILE_READ_DATA|FILE_WRITE_DATA)
可以通过设备的栈空间获得三个要素。
驱动层信息的传出
应用程序开启一个线程,通过调用DeviceIoControl(或者ReadFile)实现该功能,驱动没有消息的时候,则阻塞这个IRP的处理,等待有消息的时候返回。
具体实现见《天书夜读:从汇编语言到windows内核开发》第92-93页。
关于上述几项内容的专题论述,请参见相关文档。[5,6]
参考
[1] http://www.cnblogs.com/phinecos/archive/2009/02/19/1393803.html
[2] http://www.cnblogs.com/qsilence/archive/2009/06/11/1501511.html
[3 http://msdn.microsoft.com/en-us/library/ff557565%28VS.85%29.aspx
[4] http://www.cnblogs.com/wanghao111/archive/2009/05/25/1489041.html
[5] Windows驱动编程基础教程.doc
[6] 天书夜读-从汇编语言到windows内核编程(改)
[7] Windows DDK
[8] 天书夜读——从汇编语言到Windows内核编程
http://download.csdn.net/source/2754275
http://msdn.microsoft.com/en-us/library/ff557573%28VS.85%29.aspx