lufu

lufu

0个粉丝

17

问答

0

专栏

5

资料

lufu  发布于  2008-08-23 16:14:41
采纳率 0%
17个问答
6393

怎样在内核驱动程序和用户应用程序的消息通讯机制

驱动程序与应用程序运行与不同的环境又紧密合作,但是应用程序通知驱动程序易(IOCTL等),驱动程序通知应用程序却不易。一般的方法是单纯通过EVENT来进行,但是这种方法有其缺点:

1、EVENT只有信号态和非信号态两种区别,不能有附带的参数,因此一个EVENT只能对应一种事件,同时很多时候EVENT并不是在信号态,此时就需要应用程序在线程中等待,一旦事件较多那么线程就会较多,不但线程同步困难,程序也不易读写。

2、Windows 98对EVENT操作没有完全支持,像IoCreateXxxEvent并不被支持,因此要获得应用程序创建的事件句柄并不简单,这样驱动程序的通用性也被破坏了。

基于以上原因,单纯使用EVENT并不好,那么应该怎么做呢?经过实践,我总结出了一个较好的方法,原理是利用OVERLAPPED的异步调用、同时它又可以带参数的特性。具体做法如下:

1、在驱动程序的设备扩展中定义一个IRP变量,并在适当时候(调用IoCreateDevice初始化设备扩展后)初始化其为NULL。如 PIRP UserMessageIrp;

2、定义一个IOCTL,如:

#define IOCTL_DRIVER_USERMESSAGE CTL_CODE(FILE_DEVICE_UNKNOWN, \
0x801,\
METHOD_BUFFERED, \
FILE_ANY_ACCESS)

3、应用程序在启动或要监控驱动程序的状态时发送该IOCTL到驱动程序,注意应用程序在用CreateFile打开设备连接时一定要带FILE_FLAG_OVERLAPPED参数。

HANDLE FileIOWaiter = CreateEvent( NULL, TRUE, FALSE, NULL);
if( FileIOWaiter==NULL)
return GetLastError();
OVERLAPPED ol;
ol.Offset = 0;
ol.OffsetHigh = 0;
ol.hEvent = FileIOWaiter;

ULONG PnpMessage,nBytes;
if(!DeviceIoControl(hDevice,//设备句柄
IOCTL_DRIVER_USERMESSAGE
NULL,
0,
&PnpMessage,
sizeof(PnpMessage),
&nBytes,
&ol))
{
if(GetLastError()==ERROR_IO_PENDING)
{
while(WaitForSingleObject(FileIOWaiter, 100)==WAIT_TIMEOUT)
{//驱动程序没有消息传过来,循环等待
if(bInstanceisExit == TRUE)//应用程序退出标志
{
CancelIo(hDevice);//见5
}
}
GetOverlappedResult(hDevice, &ol, &nBytes, FALSE);//
//驱动程序有消息传过来,见6,得到数据。
}
}
//处理得到的数据,接7。

4、驱动程序在接到此IOCTL时如下处理:

ioControlCode = irpStack->Parameters.DeviceIoControl.IoControlCode;
switch( ioControlCode)
{
...
case IOCTL_DRIVER_USERMESSAGE
ntStatus =
Irp->IoStatus.Status = STATUS_PENDING;
Irp->IoStatus.Information = 0;
IoMarkIrpPending(Irp);
IoSetCancelRoutine(Irp,DriverUserMessageCancelIrp);//见5
deviceExtension->UserMessageIrp = Irp;
return STATUS_PENDING;
...
}

驱动程序与应用程序运行与不同的环境又紧密合作,但是应用程序通知驱动程序易(IOCTL等),驱动程序通知应用程序却不易。一般的方法是单纯通过EVENT来进行,但是这种方法有其缺点:

1、EVENT只有信号态和非信号态两种区别,不能有附带的参数,因此一个EVENT只能对应一种事件,同时很多时候EVENT并不是在信号态,此时就需要应用程序在线程中等待,一旦事件较多那么线程就会较多,不但线程同步困难,程序也不易读写。

2、Windows 98对EVENT操作没有完全支持,像IoCreateXxxEvent并不被支持,因此要获得应用程序创建的事件句柄并不简单,这样驱动程序的通用性也被破坏了。

基于以上原因,单纯使用EVENT并不好,那么应该怎么做呢?经过实践,我总结出了一个较好的方法,原理是利用OVERLAPPED的异步调用、同时它又可以带参数的特性。具体做法如下:

1、在驱动程序的设备扩展中定义一个IRP变量,并在适当时候(调用IoCreateDevice初始化设备扩展后)初始化其为NULL。如 PIRP UserMessageIrp;

2、定义一个IOCTL,如:

#define IOCTL_DRIVER_USERMESSAGE CTL_CODE(FILE_DEVICE_UNKNOWN, \
0x801,\
METHOD_BUFFERED, \
FILE_ANY_ACCESS)

3、应用程序在启动或要监控驱动程序的状态时发送该IOCTL到驱动程序,注意应用程序在用CreateFile打开设备连接时一定要带FILE_FLAG_OVERLAPPED参数。

HANDLE FileIOWaiter = CreateEvent( NULL, TRUE, FALSE, NULL);
if( FileIOWaiter==NULL)
return GetLastError();
OVERLAPPED ol;
ol.Offset = 0;
ol.OffsetHigh = 0;
ol.hEvent = FileIOWaiter;

ULONG PnpMessage,nBytes;
if(!DeviceIoControl(hDevice,//设备句柄
IOCTL_DRIVER_USERMESSAGE
NULL,
0,
&PnpMessage,
sizeof(PnpMessage),
&nBytes,
&ol))
{
if(GetLastError()==ERROR_IO_PENDING)
{
while(WaitForSingleObject(FileIOWaiter, 100)==WAIT_TIMEOUT)
{//驱动程序没有消息传过来,循环等待
if(bInstanceisExit == TRUE)//应用程序退出标志
{
CancelIo(hDevice);//见5
}
}
GetOverlappedResult(hDevice, &ol, &nBytes, FALSE);//
//驱动程序有消息传过来,见6,得到数据。
}
}
//处理得到的数据,接7。

4、驱动程序在接到此IOCTL时如下处理:

ioControlCode = irpStack->Parameters.DeviceIoControl.IoControlCode;
switch( ioControlCode)
{
...
case IOCTL_DRIVER_USERMESSAGE
ntStatus =
Irp->IoStatus.Status = STATUS_PENDING;
Irp->IoStatus.Information = 0;
IoMarkIrpPending(Irp);
IoSetCancelRoutine(Irp,DriverUserMessageCancelIrp);//见5
deviceExtension->UserMessageIrp = Irp;
return STATUS_PENDING;
...
}

5、定义IRP的Cancel例程,这是在应用程序要退出而驱动程序并没有完成该IRP时调用。

VOID DriverUserMessageCancelIrp(
IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp)
{
PDEVICE_EXTENSION deviceExtension;

deviceExtension = (PDEVICE_EXTENSION)
DeviceObject->DeviceExtension;

IoReleaseCancelSpinLock(Irp->CancelIrql);

// If this is our queued read, then unqueue it
if( Irp==deviceExtension->UserMessageIrp)
{
deviceExtension->UserMessageIrp = NULL;
}
// Whatever Irp it is, just cancel it
Irp->IoStatus.Status = STATUS_CANCELLED;
Irp->IoStatus.Information = 0;
IoCompleteRequest(Irp,IO_NO_INCREMENT);
}

6、在驱动程序需要通知应用程序时,举个例子,设备拔出时在处理IRP_MN_REMOVE_DEVICE时,在调用IoDetachDevice之前: (#define PNP_REMOVE_DEVICE 0 //驱动程序和应用程序共同定义的消息类型)

...
ntStatus = DriverProcessUserMessageIrp(DeviceObject,PNP_REMOVE_DEVICE);
...
//DriverProcessUserPnpIrp的定义如下:
NTSTATUS
DriverProcessUserMessageIrp(
IN PDEVICE_OBJECT DeviceObject,
ULONG ProcessReason)
{
NTSTATUS ntStatus;
PVOID ioBuffer;
ULONG outputBufferLength;
PIO_STACK_LOCATION irpStack;
PIRP Irp;
PDEVICE_EXTENSION deviceExtension;

deviceExtension = (PDEVICE_EXTENSION)
DeviceObject->DeviceExtension;

Irp = deviceExtension->UserMessageIrp;
if(Irp == NULL)
return STATUS_SUCCESS;//这种情况是在设备启动后,应用程序并没有发送
//过该IRP,设备又要卸载时出现。

irpStack = IoGetCurrentIrpStackLocation (Irp);

// get pointers and lengths of the caller's (user's) IO buffer
ioBuffer = Irp->AssociatedIrp.SystemBuffer;
outputBufferLength = irpStack->Parameters.DeviceIoControl.OutputBufferLength;

if(ioBuffer!=NULL && sizeof(ProcessReason)<=outputBufferLength)
{
RtlCopyMemory(ioBuffer,
&ProcessReason,
sizeof(ProcessReason));
}
Irp->IoStatus.Status = STATUS_SUCCESS;
Irp->IoStatus.Information = sizeof(ProcessReason);

IoSetCancelRoutine(Irp,NULL);//取消Cancel例程。
IoCompleteRequest(Irp,IO_NO_INCREMENT);
return STATUS_SUCCESS;
}
7、此时应用程序的OVERLAPPED的EVENT会置位,WaitForSingleObject结束等待,应用程序应如此处理:
switch(PnpMessage)//定义见3,是不是很像Windows消息处理例程?:)
{
...
case PNP_REMOVE_DEVICE:
//处理过程就不必写了吧。
break;
...
}

至此驱动程序和应用程序就完成了一次消息传递,其余可类似循环。

以上程序段在98和2000下都经过测试。

TigerZD .2002.7.11.(完)

以下是在sfilter的框架上实现上面的思路:

一、驱动程序: 1、定义消息类型、全局变量

#define SFILTER_DRIVER_USERMESSAGE \
    (ULONG) CTL_CODE(FILE_DEVICE_DISK_FILE_SYSTEM,  0x999, METHOD_BUFFERED, FILE_ANY_ACCESS )

PIRP gUserMessageIrp = NULL;
typedef enum _USER_MSG_TYPES {

    USER_MSG_1            = 0x00000001L,
    USER_MSG_2            = 0x00000002L,
    USER_MSG_3            = 0x00000003L

} USER_MSG_TYPES;

2、处理IRP_MJ_DEVICE_CONTROL分派例程SfDeviceIoControl

NTSTATUS
SfDeviceIoControl( IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp )
{
    NTSTATUS Status = STATUS_SUCCESS;
    PIO_STACK_LOCATION IrpSp;

    PAGED_CODE();

    if (IS_MY_CONTROL_DEVICE_OBJECT(DeviceObject)) {

        //
        //  A request is being made on our control device object
        //

        Irp->IoStatus.Information = 0;

        IrpSp = IoGetCurrentIrpStackLocation( Irp );

        if (IrpSp->Parameters.DeviceIoControl.IoControlCode ==
            SFILTER_DRIVER_USERMESSAGE) {

            KdPrint(("SFilter: SfDeviceIoControl SFCONTROL_DRIVER_USERMESSAGE!\n"));

            if (gUserMessageIrp != NULL) {

                KdPrint( ("Sfilter: gUserMessageIrp != NULL, so cancel the Irp!\n"));

                Irp->IoStatus.Status = STATUS_CANCELLED; 
                IoCompleteRequest(Irp, IO_NO_INCREMENT);
                return STATUS_CANCELLED;
            }

            Irp->IoStatus.Status = STATUS_PENDING;
            Irp->IoStatus.Information = 0;
            IoMarkIrpPending(Irp);
            IoSetCancelRoutine(Irp,SfUserMessageCancelIrp);
            gUserMessageIrp = Irp;
            return STATUS_PENDING;
        }

        Status = SfCommonDeviceIoControl( Irp->AssociatedIrp.SystemBuffer,
            IrpSp->Parameters.DeviceIoControl.InputBufferLength,
            Irp->AssociatedIrp.SystemBuffer,
            IrpSp->Parameters.DeviceIoControl.OutputBufferLength,
            IrpSp->Parameters.DeviceIoControl.IoControlCode,
            &Irp->IoStatus );

        Irp->IoStatus.Status = Status;

        IoCompleteRequest( Irp, IO_NO_INCREMENT );
        return Status;
    }

    IoSkipCurrentIrpStackLocation(Irp);
    return IoCallDriver(((PDEVICE_EXTENSION)DeviceObject->DeviceExtension)->AttachedToDeviceObject, Irp);
}

其中SfCommonDeviceIoControl函数是参照FileSpy写的其他的消息处理过程。

VOID
SfUserMessageCancelIrp(
    IN PDEVICE_OBJECT DeviceObject,
    IN PIRP Irp
    )
{
    UNREFERENCED_PARAMETER(DeviceObject);

    KdPrint(("FileEncrypt! Enter SfUserMessageCancelIrp....\n"));

    IoReleaseCancelSpinLock(Irp->CancelIrql);

    // If this is our queued read, then unqueue it
    if ( Irp == gUserMessageIrp) {

        gUserMessageIrp = NULL;
    }

    // Whatever Irp it is, just cancel it
    Irp->IoStatus.Status = STATUS_CANCELLED;
    Irp->IoStatus.Information = 0;
    IoCompleteRequest(Irp,IO_NO_INCREMENT);
}

3、处理FastIoDeviceControl

BOOLEAN
SfFastIoDeviceControl (
    IN PFILE_OBJECT FileObject,
    IN BOOLEAN Wait,
    IN PVOID InputBuffer OPTIONAL,
    IN ULONG InputBufferLength,
    OUT PVOID OutputBuffer OPTIONAL,
    IN ULONG OutputBufferLength,
    IN ULONG IoControlCode,
    OUT PIO_STATUS_BLOCK IoStatus,
    IN PDEVICE_OBJECT DeviceObject
)
{
……
    if (DeviceObject == gEncryptControlDeviceObject) {

        if (IoControlCode == SFILTER_DRIVER_USERMESSAGE) {

            return FALSE;
          }
        ……
    }
……
}

4、编写消息处理函数SfProcessUserMessage

NTSTATUS
SfProcessUserMessage(
    IN PDEVICE_OBJECT DeviceObject,
    IN USER_MSG_TYPES UserMsg
    )
{
    PIO_STACK_LOCATION          IrpSp;
    PIRP                        Irp;
    PVOID                      IoBuffer;
    ULONG                      OutputBufferLength;   

    UNREFERENCED_PARAMETER(DeviceObject);

    KdPrint(("Sfilter: Enter SfProcessUserMessage....\n"));

    __try {

        Irp = gUserMessageIrp;

        //这种情况是在设备启动后,应用程序并没有发送过该IRP,设备又要卸载时出现。
        if(Irp == NULL)
            return STATUS_SUCCESS;

        IrpSp = IoGetCurrentIrpStackLocation (Irp);

        // Get pointers and lengths of the caller's (user's) IO buffer
        IoBuffer = Irp->AssociatedIrp.SystemBuffer;
        OutputBufferLength = IrpSp->Parameters.DeviceIoControl.OutputBufferLength;

        if (IoBuffer != NULL && sizeof(UserMsg) <= OutputBufferLength) {
            RtlCopyMemory(IoBuffer, &UserMsg, sizeof(UserMsg));
        }

        Irp->IoStatus.Status = STATUS_SUCCESS;
        Irp->IoStatus.Information = sizeof(UserMsg);

        IoSetCancelRoutine(Irp,NULL);// 取消Cancel例程。
        IoCompleteRequest(Irp,IO_NO_INCREMENT);
        gUserMessageIrp = NULL;

    } __except (EXCEPTION_EXECUTE_HANDLER) {

        KdPrint("Sfilter! SfProcessUserMessage exception happen, exception code (0x%x)\n", GetExceptionCode()));
    }

    return STATUS_SUCCESS; 
}

5、现在在驱动任何一个地方调用SfProcessUserMessage即可

二、应用程序: 1、定义和驱动程序相同的消息类型:

#define SFILTER_DRIVER_USERMESSAGE \
    (ULONG) CTL_CODE(FILE_DEVICE_DISK_FILE_SYSTEM,  0x999, METHOD_BUFFERED, FILE_ANY_ACCESS )

typedef enum _USER_MSG_TYPES {

    USER_MSG_1            = 0x00000001L,
    USER_MSG_2            = 0x00000002L,
    USER_MSG_3            = 0x00000003L

} USER_MSG_TYPES;

2、编写消息循环函数MessageLoop

BOOL MessageLoop(HANDLE hDevice, HANDLE hEvent)
{
    ULONG uMsg, byReturn;   
    OVERLAPPED ol;
    ol.Offset = 0;
    ol.OffsetHigh = 0;
    ol.hEvent = hEvent;

    if(!DeviceIoControl( hDevice, SFCONTROL_DRIVER_USERMESSAGE,
        NULL, 0, &uMsg, sizeof(uMsg), &byReturn, &ol)) {

        DWORD dwError = GetLastError();
        if (dwError == ERROR_IO_PENDING) {

            // 驱动程序没有消息传过来,循环等待
            while (WaitForSingleObject(hEvent, 100) == WAIT_TIMEOUT)  {

                //应用程序退出标志
                if(gbInstanceIsExit == TRUE) {

                    CancelIo(hDevice);
                }
            }

            //驱动程序有消息传过来,得到数据。
            GetOverlappedResult(hDevice, &ol, &byReturn, TRUE);

            // Process message
            switch (uMsg) {

            case USER_MSG_1:
                ::MessageBox(NULL, TEXT("USER_MSG_1"), NULL, MB_OK);                     
                break;

            case USER_MSG_2:
                ::MessageBox(NULL, TEXT("USER_MSG_2"), NULL, MB_OK);             
                break;

            case USER_MSG_3:
                ::MessageBox(NULL, TEXT("USER_MSG_3"), NULL, MB_OK);             
                break;

            default:
                break;
            }

        } else if (dwError != ERROR_OPERATION_ABORTED) { // Error 995

            TCHAR bufMsg[80];
            wsprintf(bufMsg, TEXT("MessageLoop call DeviceIoControl failed 0x%x(%d)"), dwError, dwError);
            WriteToLog(bufMsg);
            return FALSE;

        }

        return TRUE;

    }

    return FALSE;
}

注:当应用程序退出是设置gbInstanceIsExit为true,则 驱动程序中的SfUserMessageCancelIrp将调用。 函数WriteToLog只是简单做些的日志记录。

3、调用MessageLoop如下:

HANDLE hEventMsg = NULL;
HANDLE hDeviceSys = INVALID_HANDLE_VALUE;

hEventMsg = CreateEvent( NULL, TRUE, FALSE, NULL);
if( pInitParam->hEvent == NULL) {

        DWORD dwError = GetLastError();
        TCHAR bufMsg[80];
        wsprintf(bufMsg, TEXT("call CreateEvent failed! 0x%x(%d)"), dwError, dwError);
        WriteToLog(bufMsg);
        return dwError;
  }

hDeviceSys = CreateFile(SYSSERVICENAME, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING,  FILE_FLAG_OVERLAPPED, NULL);

    if (pInitParam->hDevice == INVALID_HANDLE_VALUE || pInitParam->hDevice == NULL) {

        DWORD dwError = GetLastError();
        TCHAR bufMsg[80];
        wsprintf(bufMsg, TEXT("call CreateFile failed! 0x%x(%d)"), dwError, dwError);
        WriteToLog(bufMsg);
        return dwError;
    }

while (MessageLoop(hDeviceSys, hEventMsg))
    ; // do nothing

CloseHandle(hDeviceSys);
CloseHandle(hEventMsg);

注:CreateFile函数必须添加FILE_FLAG_OVERLAPPED标志。

4、现在整个框架已经建立,只有处理MessageLoop的switch语句块相应的消息即可。

我来回答
回答2个
时间排序
认可量排序

papa123

0个粉丝

41

问答

0

专栏

11

资料

papa123 2009-11-06 20:17:11
认可0
方法还是很多的。。
看怎么用了。。
命名事件 , 消息队列, IOCTL,readfile等。。。
或将文件直接拖到这里
悬赏:
E币
网盘
* 网盘链接:
* 提取码:
悬赏:
E币

Markdown 语法

  • 加粗**内容**
  • 斜体*内容*
  • 删除线~~内容~~
  • 引用> 引用内容
  • 代码`代码`
  • 代码块```编程语言↵代码```
  • 链接[链接标题](url)
  • 无序列表- 内容
  • 有序列表1. 内容
  • 缩进内容
  • 图片![alt](url)
+ 添加网盘链接/附件

Markdown 语法

  • 加粗**内容**
  • 斜体*内容*
  • 删除线~~内容~~
  • 引用> 引用内容
  • 代码`代码`
  • 代码块```编程语言↵代码```
  • 链接[链接标题](url)
  • 无序列表- 内容
  • 有序列表1. 内容
  • 缩进内容
  • 图片![alt](url)
相关问答
无更多相似问答 去提问
举报反馈

举报类型

  • 内容涉黄/赌/毒
  • 内容侵权/抄袭
  • 政治相关
  • 涉嫌广告
  • 侮辱谩骂
  • 其他

详细说明

易百纳技术社区