TCP/IP 动手实践2-windows驱动编程入门2

                                                   TCP/IP 动手实践2-windows驱动编程入门2


本文相关代码下载地址为http://download.csdn.net/source/2670262

 

用户程序与驱动进行异步的数据读写。


大家都知道,文件是可以异步读写的,同样,用户程序和驱动也能进行异步的数据读取操作。很多时候用户程序想从驱动读取数据,但是驱动此时还没有合适的数据可以提供给用户程序,这时它可以通知用户程序此时没有生意可做,让用户程序先做其他的事情,等到驱动有数据了后再通知程序,到时候程序再来取就行了。现在我们就来实现程序驱动异步数据读取这一功能,详细代码参见HelloWorldDriver2.1/HelloWorldDriver/MyHelloWorldDriver.c。

 

首先我们先声明一个全局的IRP指针,用于记录用户程序调用ReadFile而产生的IRP


 

PIRP g_pReadFileIRP = NULL;

 

我们先看看驱动里DispatchRead的相应实现:

 

 

 

这里我们涉及到一个新的内核对象,就是定时器(KTIMER), 设置定时器的触发事件段为3秒, DpcTimerProcessRoutine 是定时器触发时

要执行的函数, 在该函数中,我们将处理当前挂起的IRP. 我们再看看DpcTimerProcessRoutine 是如何处理被挂起的IRP的

 

 

在DpcTimerProcessRoutine 中, 先将读请求的IRP中的缓冲区用0xAA填充,然后调用IoCompleteRequest

完成该IRP, 这样系统就知道该IRP已经被处理,于是他可以通知应用程序处理该IRP缓冲区内的内容。

 

接下来我们再看看应用程序如何发起异步读写请求, 应用程序想与底层驱动进行异步数据交互的时候,在调用

CreateFile 打开驱动时,输入参数需要做一些修改,即要添加FILE_FLAG_OVERLAPPED标志位,具体实施如下

 

handle = CreateFile(MyDriverName, GENERIC_READ | GENERIC_WRITE, 0 , NULL , OPEN_EXISTING,
        FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED //此处必须加入FILE_FLAG_OVERLAPPED表示异步读写
        , 0);
    if(handle == INVALID_HANDLE_VALUE)
    {
        printf("Open MyHelloWorldDriver fail!/n");
        return;
    }

 

由于是异步过程,所以程序需要一个OVERLAPPED 结构来获取读写完成等相应事件的通知,因此在应用程序的启动阶段,我们先

初始化一个 OVERLAPPED 结构

 

//异步读取的话需要OVERLAP结构
    OVERLAPPED overlapped = { 0 };
    overlapped.hEvent = CreateEvent( NULL, FALSE, FALSE, NULL);

 

当驱动完成读请求后,overlapped.hEvent 所代表的 事件对象会转变为有信号状态,因此我们可以监控该事件对象,当这个事件

对象变为有信号时,表明驱动已经完成了我们的异步读请求。发出异步读请求的方法如下:

 

 //ReadFile执行后,驱动的DispatchRead将被调用,由于驱动中通过IoMarkIrpPending
    //将ReadFile发出的Irp设置为Pending,所以该函数返回值为FALSE,表明读请求未完成
    //GetLastError返回为ERROR_IO_PENDING进一步表明该请求被延迟处理
    BOOL bRead = ReadFile( handle, buffer, 10, &ulBytesRead, &overlapped );
    if( !bRead && GetLastError() == ERROR_IO_PENDING )
    {
        printf("The read operation is pending/n");
    }
    //在等待overlapped,当读取操作完成后,overlapped将会变为有信号状态
    WaitForSingleObject( overlapped.hEvent, INFINITE );

 

应用程序的完整代码如下:

 

 

 

程序执行结果如下:

 

 

加载驱动后 ,运行客户程序。可以看到,程序打印出" The read operation is pending" 这表明,程序

对驱动的读请求被延迟了,等了大概6秒后,一串"AA"打印出来。也就是说6秒后,驱动完成了程序的读请求。客户程序的WaitForSingleObject 等待函数得以返回。

 

 

驱动开发中的多线程控制

 

我们在windows应用程序的开发中,对多线程的控制往往需要用到CMutext, CEvent等对象。在驱动开发中,对多线程并发的控制异常重要,一旦线程控制失误,造成共享资源被破坏,引起程序出错,从而会导致蓝屏。在这一小节,我们着重关注驱动中是如何控制并发线程或是如何应对函数重入。该小节的代码可参见HelloWorldDriver2.2/HelloWorldDriver/MyHelloWorldDriver.c.

 

驱动开发中,线程并发控制最常用的手段是使用KSPIN_LOCK 对象。这个对象是一种自旋锁,当两个并发线程同时读写关键资源时,为了保证同时只有一个线程能对资源进行读写,在读写资源前,先获取自旋锁。得到锁的线程可以访问修改资源,而没有获取锁的线程则等待,直到获取到锁后才可以对资源进行访问。我们通过具体代码看看具体流程。

 

首先声明一个全局变量,作为两个并发线程竞争访问的资源


//全局计数
ULONG g_GlobalCounter = 0;

我们将启动两个线程,其中一个对g_GlobalCounter进行++操作。另一个进行--操作。

当然我们需要一个线程控制所需要的自旋锁:


//访问全局计数的自旋锁
KSPIN_LOCK  g_SpinLock;


最后我们需要内核事件对象,用于线程间的通信:


//内核事件对象,用于通知 计数增加线程 计数减少线程已经启动了
KEVENT      g_Event;

 

接下来我们看看增加全局计数线程的实现


 

在线程函数中先通过KeWaitForSingleObject等待 一个事件对象,这主要是为了等待另个线程的启动。

因为存在这样的可能:计数增加线程先启动,等到他执行完了,计数减少线程才被系统运行,这样一来,

我们就得不到两个线程竞争的结果。因此增加线程启动后,等待g_Event事件对象。计数减少线程启动后。通过KeSetEvent 函数设置该事件对象,这样计数增加线程就能继续往下走,从而形成两个线程

同时运行的结果,两个线程能同时运行才会有对全局计数变量的竞争访问,这样自旋锁才有用武之地。

 

计数增加线程等待到事件对象后,就知道计数减少线程也启动了。于是通过KeAcquireSpinLock获取

自旋锁,如果能获取到锁,则对全局计数进行++操作,获取不到则会一直等待,直到获取到为止。

当结束对全局计数的访问后要通过KeReleaseSpinLock释放锁,结束自己。内核线程要结束自己的

时候需呀调用PsTerminateSystemThread来了断自己。

 

计数递减线程函数的实现如下,道理相同:



 

 

DriverEntry 中,我们要做的就是初始化自旋锁,以及启动两个线程:

 

 

KeDelayExecutionThread 相当于应用层程序的Sleep.此处在计数增加进程生成后延迟30微秒再

启动计数减少线程是为了保证计数增加线程先得到执行。

 

程序运行后结果如下

 

 

两个线程同时竞争对g_GlobalCounter的操作权,由于DecreaseCounterThread先获取了自旋锁,所以它在没有任何干扰的情况下对g_GlobalCounter进行了--操作。等到它释放自旋锁后,增加进程才有

机会对g_GlobalCounter进行增加操作。

你可能感兴趣的:(TCP/IP 动手实践2-windows驱动编程入门2)