您的位置:首页 > 其它

应用程序对设备 + IRP 的同步异步学习

2013-09-21 23:45 543 查看
对设备的的操作转换为IRP请求,而一般IRP都是由操作系统异步发送的。

异步处理IRP有助于提高效率,但有时会导致逻辑错误,需要将异步的IRP进行同步化

StartIOl例程,使用中断服务例程等。

应用程序对设备的同步+异步操作

大部分IRP是由应用=程序的WIN32 API 函数发起的。这些API EG: ReadFile,WriteFile,DeviceIOControl 等 都有同步异步操作

同步操作(等待继续·····)

HANDLE CreateFile(
LPCTSTR lpFileName,                         // file name
DWORD dwDesiredAccess,                      // access mode
DWORD dwShareMode,                          // share mode
LPSECURITY_ATTRIBUTES lpSecurityAttributes, // SD
DWORD dwCreationDisposition,                // how to create
DWORD dwFlagsAndAttributes,                 // file attributes ////////下面那个参数///////////////////////
HANDLE hTemplateFile                        // handle to template file
);

FILE_FLAG_OVERLAPPED为异步标志

BOOL ReadFile(
HANDLE hFile,                // handle to file
LPVOID lpBuffer,             // data buffer
DWORD nNumberOfBytesToRead,  // number of bytes to read
LPDWORD lpNumberOfBytesRead, // number of bytes read
LPOVERLAPPED lpOverlapped    // overlapped buffer
);

If hFile was not opened with FILE_FLAG_OVERLAPPED and lpOverlapped is NULL 同步操作

演示代码:

HANDLE hDevice =
CreateFile("test.dat",
GENERIC_READ | GENERIC_WRITE,
0,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,//此处没有设置FILE_FLAG_OVERLAPPED
NULL );

if (hDevice == INVALID_HANDLE_VALUE)
{
printf("Read Error\n");
return 1;
}

UCHAR buffer[BUFFER_SIZE];
DWORD dwRead;
ReadFile(hDevice,buffer,BUFFER_SIZE,&dwRead,NULL);//这里没有设置OVERLAP参数

CloseHandle(hDevice);


异步操作设备1(不等待 继续运行·····)

typedef struct _OVERLAPPED {
ULONG_PTR  Internal;
ULONG_PTR  InternalHigh;
DWORD  Offset;            //指定一个偏移量,设备的偏移量开始读取  64位整形表示,参数是该参数的32位整形
DWORD  OffsetHigh;        //偏移量的高32位
HANDLE hEvent;            //这个事件用于该操作完成后通知应用程序,可以初始化改事件为未激发,当操作设备结束后,驱动调用IoCompleteRequest后,设置该设备为激发态
} OVERLAPPED;

演示代码:

HANDLE hDevice =
CreateFile("test.dat",
GENERIC_READ | GENERIC_WRITE,
0,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL|FILE_FLAG_OVERLAPPED,//此处设置FILE_FLAG_OVERLAPPED
NULL );

if (hDevice == INVALID_HANDLE_VALUE)
{
printf("Read Error\n");
return 1;
}

UCHAR buffer[BUFFER_SIZE];
DWORD dwRead;

//初始化overlap使其内部全部为零
OVERLAPPED overlap={0};

//创建overlap事件   自动事件
overlap.hEvent = CreateEvent(NULL,FALSE,FALSE,NULL);

//这里没有设置OVERLAP参数,因此是异步操作
ReadFile(hDevice,buffer,BUFFER_SIZE,&dwRead,&overlap);

//做一些其他操作,这些操作会与读设备并行执行

//等待读设备结束
WaitForSingleObject(overlap.hEvent,INFINITE);


异步操作设备2(不等待 继续运行·····)

除了 ReadFile WriteFile 外,还有 ReadFileEx + WriteFileEx 函数
不同的是 Ex只支持 异步读写操作的

BOOL ReadFileEx(
HANDLE hFile,                                       // handle to file
LPVOID lpBuffer,                                    // data buffer
DWORD nNumberOfBytesToRead,                         // number of bytes to read
LPOVERLAPPED lpOverlapped,                          // offset                   //指针   !!不需要提供事件句柄
LPOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine // completion routine       //完成例程
);

驱动程序在读操作后,会通过调用 上面那个回调例程。 类似一个软中断

WINDOWS将这种机制称为 异步过程调用 APC asynchronousProcedure

回调函数被调用条件 只有先成功处于警惕(alert) 状态,回调函数才有可能被调用。有多个API可以使系统进程警惕状态 SleepEx,WaitForSingleObjectEx,WaitForMultipleObjectsEx函数等

当系统进入警惕模式后,操作系统会枚举当前线程的APC队列。驱动程序一旦结束读写操作,就会把ReadFileEx提供的的完成例程插入到APC队列

回调例程会报告本次操作的完成状况 + 本次读取操作实际读取的字节数等。

VOID CALLBACK FileIOCompletionRoutine(                             //一般回调例程的声明
DWORD dwErrorCode,                // completion code             //读取错误,会返回错误码
DWORD dwNumberOfBytesTransfered,  // number of bytes transferred //返回实际读取操作的字节数
LPOVERLAPPED lpOverlapped         // I/O information buffer      //OVERLAP参数,指定读取的偏移量等信息
);

下面是演示代码:

#define DEVICE_NAME	"test.dat"
#define BUFFER_SIZE	512
//假设该文件大于或等于BUFFER_SIZE
VOID CALLBACK MyFileIOCompletionRoutine(
DWORD dwErrorCode,                // 对于此次操作返回的状态
DWORD dwNumberOfBytesTransfered,  // 告诉已经操作了多少字节,也就是在IRP里的Infomation
LPOVERLAPPED lpOverlapped         // 这个数据结构
)
{
printf("IO operation end!\n");
}
HANDLE hDevice =
CreateFile("test.dat",
GENERIC_READ | GENERIC_WRITE,
0,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL|FILE_FLAG_OVERLAPPED,//此处设置FILE_FLAG_OVERLAPPED
NULL );
if (hDevice == INVALID_HANDLE_VALUE)
{
printf("Read Error\n");
return 1;
}
UCHAR buffer[BUFFER_SIZE];
//初始化overlap使其内部全部为零
//不用初始化事件!!
OVERLAPPED overlap={0};
//这里设置了OVERLAP参数,因此是异步操作
ReadFileEx(hDevice, buffer, BUFFER_SIZE,&overlap,MyFileIOCompletionRoutine);

//做一些其他操作,这些操作会与读设备并行执行
//进入alterable
SleepEx(0,TRUE);
CloseHandle(hDevice);


IRP的同步完成 + 异步完成

上面的这些操作必须得到驱动程序的支持,有两种方式处理IRP请求:

1)在派遣函数中直接结束IRP请求,可以认为是一种同步处理

2)不结束IRP请求,让派遣函数直接返回, IRP在以后的某个时候再进行处理。

对设备读取3个方法:

1)ReadFIle 如果同步读取时,创建一个事件(IRP 的 UserEvent),派遣函数调用IoCompleteRequest时内部会设置IRP的UserEvent

如果派遣函数没有调用IoCompleteRequest函数,那么ReadFile一直等待

2)ReadFile 异步处理,内部不会创建事件,但ReadFile函数会接受 overlap参数。参数会提供一个事件,被用作 同步处理

IoCompleteRequest内部设置overlap 提供的事件

在ReadFile函数退出之前不会检测该事件是否被设置,因此可以不等待操作是否真的被完成

当IRP操作被完成后,overlap提供的事件被设置,通知应用程序IRP请求被完成

3)ReadFileEx 异步读取时,不提供事件,但提供一个回调函数,这个回调函数的地址会作为IRP的参数传递给派遣函数

IoCompleteRequest会将这个完成函数插入APC队列

应用程序只要进入警惕模式,APC队列会自动出队列,完成函数会被执行,这相当于通知应用程序操作已经完成

前几次所有例子都是 同步读取,IRP的同步处理在派遣函数中,将IRP处理完毕,这里指的完毕就是调用IoCompleteRequrst函数

IRP的异步完成:

指的就是不在派遣函数中调用IoCompleteRequest内核函数,调用IoCompleteRquest函数意味着IRP请求的结束,也标志着本次对设备操作的结束

借用上面的例子~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

1)没有调用IoCompleteRequest,IRP请求没有被结束。ReadFIle会一直等待,知道操作被结束

2)没有调用IoCompleteRequest,IRP请求没有被结束。ReadFile会立刻返回失败,GetLastError函数得知 ERROR_IO_PENDING 不是真正的操作错误

3)没有调用IoCompleteRequest,IRP请求没有被结束。立刻返回FALSE,跟(2)一样,表明当前操作被“挂起”。

下面介绍如何将“挂起”的IRP插入队列,并在关闭设备的时候将“挂起”的IRP结束:

如果不调用 IoC~R~函数,则需要告诉操作系统处于“挂起”状态。

VOID
IoMarkIrpPending(   //告诉操作系统处于挂起状态 同时返回STATUS_PENDING
IN OUT PIRP  Irp
);


NTSTATUS HelloDDKRead(IN PDEV0CE_OBJECT pDevObj,
IN PIRP pIrp)
{
IoMarkIrkPending(pIrp);
return   STATUS_PENDING;
}

队列的数据结构:

typedef struct _MY_IRP_ENTRY
{
PIRP pIrp;				//记录IRP指针
LIST_ENTRY ListEntry;
}MY_IRP_ENTRY,*PMY_IRP_ENTRY;

在设备扩展力加入 “队列” 这个变量。所有派遣函数都可以使用这个队列

DriverEntry中初始化 该队列 DriverUnload回收该队列

在IRP_MJ_READ的派遣函数中,将IRP插入堆栈,然后返回“挂起”状态:

异步处理IRP的派遣函数:

NTSTATUS HelloDDKRead(IN PDEVICE_OBJECT pDevObj,
IN PIRP pIrp)
{
KdPrint(("Enter HelloDDKRead\n"));

PDEVICE_EXTENSION pDevExt = (PDEVICE_EXTENSION)
pDevObj->DeviceExtension;

PMY_IRP_ENTRY pIrp_entry = (PMY_IRP_ENTRY)ExAllocatePool(PagedPool,sizeof(MY_IRP_ENTRY));

pIrp_entry->pIRP = pIrp;

//插入队列
InsertHeadList(pDevExt->pIRPLinkListHead,&pIrp_entry->ListEntry);

//将IRP设置为挂起
IoMarkIrpPending(pIrp);

KdPrint(("Leave HelloDDKRead\n"));

//返回pending状态
return STATUS_PENDING;
}


在关闭设备时,产生IRP_MJ_CLEANUP 的IRP,其派遣函数抽取队列中每一个“挂起” 的IRP,并调用IoCompleteRequest设置完成

NTSTATUS HelloDDKCleanUp(IN PDEVICE_OBJECT pDevObj,
IN PIRP pIrp)
{
KdPrint(("Enter HelloDDKCleanUp\n"));

PDEVICE_EXTENSION pDevExt = (PDEVICE_EXTENSION)
pDevObj->DeviceExtension;

//(1)将存在队列中的IRP逐个出队列,并处理

PMY_IRP_ENTRY my_irp_entry;
while(!IsListEmpty(pDevExt->pIRPLinkListHead))
{
PLIST_ENTRY pEntry = RemoveHeadList(pDevExt->pIRPLinkListHead);
my_irp_entry = CONTAINING_RECORD(pEntry,
MY_IRP_ENTRY,
ListEntry);
my_irp_entry->pIRP->IoStatus.Status = STATUS_SUCCESS;
my_irp_entry->pIRP->IoStatus.Information = 0;	// bytes xfered
IoCompleteRequest( my_irp_entry->pIRP, IO_NO_INCREMENT );

ExFreePool(my_irp_entry);
}

//(2)处理IRP_MJ_CLEANUP的IRP
NTSTATUS status = STATUS_SUCCESS;
// 完成IRP
pIrp->IoStatus.Status = status;
pIrp->IoStatus.Information = 0;	// bytes xfered
IoCompleteRequest( pIrp, IO_NO_INCREMENT );

KdPrint(("Leave HelloDDKCleanUp\n"));
return STATUS_SUCCESS;
}


应用程序代码:

HANDLE hDevice =
CreateFile("\\\\.\\HelloDDK",
GENERIC_READ | GENERIC_WRITE,
0,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL|FILE_FLAG_OVERLAPPED,//此处设置FILE_FLAG_OVERLAPPED
NULL );

if (hDevice == INVALID_HANDLE_VALUE)
{
printf("Open Device failed!");
return 1;
}

OVERLAPPED overlap1={0};
OVERLAPPED overlap2={0};

UCHAR buffer[10];
ULONG ulRead;

BOOL bRead = ReadFile(hDevice,buffer,10,&ulRead,&overlap1);
if (!bRead && GetLastError()==ERROR_IO_PENDING)
{
printf("The operation is pending\n");
}
bRead = ReadFile(hDevice,buffer,10,&ulRead,&overlap2);
if (!bRead && GetLastError()==ERROR_IO_PENDING)
{
printf("The operation is pending\n");
}

//迫使程序中止2秒
Sleep(2000);

//创建IRP_MJ_CLEANUP IRP
CloseHandle(hDevice);


所以 当应用程序 异步读设备时,创建两个IRP_MJ_READ,这两个IRP被插入队列,显示 Pending
停顿2秒 关闭设备的时候,导致驱动程序调用IRP_MG_CLEANUP的派遣函数 显示 Completed







下面介绍将“挂起”的IRP逐个结束,取消IRP请求:

PDRIVER_CANCEL
IoSetCancelRoutine(                 //可以设置取消IRP请求的回调函数
IN PIRP  Irp,                     //需要取消的IRP请求的回调函数
IN PDRIVER_CANCEL  CancelRoutine  //取消函数的函数指针,一旦IRP请求被取消的时候,操作系统会调用这个取消函数
);

可以将一个取消例程与该IRP关联,一旦取消IRP请求的时候,这个取消例程会被执行

这个函数也可以用来删除取消例程,当输入的 第二指针为空, 则删除原来设置的取消例程

BOOLEAN
IoCancelIrp(    //指针取消IRP请求,zai IoCancelIrp内部,需要进行同步。 DDK在IoCancelIrp内部使用一个叫做cancel的自旋锁用来进行同步
IN PIRP  Irp
);


在内部首先得到该自旋锁,IoCancelIrp会调用 取消回调 例程,因此,释放该自旋锁的任务就留给了取消回调例程。

VOID
IoAcquireCancelSpinLock( //获得取消自旋锁
OUT PKIRQL  Irql
);

VOID
IoReleaseCancelSpinLock( //释放取消自旋锁
IN KIRQL  Irql
);

BOOL CancelIo(
HANDLE hFile  // handle to file // 这个是WIN32 API 取消IRP请求 内部会调用所有没有被完成的IRP,然后依次调用IoCancelIrp.如果没有调用它,                                       在关闭设备同意自动调用CancelIo
);


应用程序代码:

HANDLE hDevice =
CreateFile("\\\\.\\HelloDDK",
GENERIC_READ | GENERIC_WRITE,
0,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL|FILE_FLAG_OVERLAPPED,
//此处设置FILE_FLAG_OVERLAPPED
NULL );

if (hDevice == INVALID_HANDLE_VALUE)
{
printf("Open Device failed!");
return 1;
}

OVERLAPPED overlap1={0};
OVERLAPPED overlap2={0};

UCHAR buffer[10];
ULONG ulRead;

BOOL bRead = ReadFile(hDevice,buffer,10,&ulRead,&overlap1);
if (!bRead && GetLastError()==ERROR_IO_PENDING)
{
printf("The operation is pending\n");
}
bRead = ReadFile(hDevice,buffer,10,&ulRead,&overlap2);
if (!bRead && GetLastError()==ERROR_IO_PENDING)
{
printf("The operation is pending\n");
}

//迫使程序中止2秒
Sleep(2000);

//显式的调用CancelIo,其实在关闭设备时会自动运行CancelIo
CancelIo(hDevice);

//创建IRP_MJ_CLEANUP IRP
CloseHandle(hDevice);

驱动代码(省略):

NTSTATUS HelloDDKRead(IN PDEVICE_OBJECT pDevObj,
IN PIRP pIrp)
{
KdPrint(("Enter HelloDDKRead\n"));

PDEVICE_EXTENSION pDevExt = (PDEVICE_EXTENSION)
pDevObj->DeviceExtension;

IoSetCancelRoutine(pIrp,CancelReadIRP); //设置取消IRP请求的回调函数

//将IRP设置为挂起
IoMarkIrpPending(pIrp);

KdPrint(("Leave HelloDDKRead\n"));

//返回pending状态
return STATUS_PENDING;
}
VOID
CancelReadIRP(
IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp
)
{
KdPrint(("Enter CancelReadIRP\n"));

PDEVICE_EXTENSION pDevExt = (PDEVICE_EXTENSION)
DeviceObject->DeviceExtension;

//设置完成状态为STATUS_CANCELLED
Irp->IoStatus.Status = STATUS_CANCELLED;
Irp->IoStatus.Information = 0;	// bytes xfered
IoCompleteRequest( Irp, IO_NO_INCREMENT );

//释放Cancel自旋锁
IoReleaseCancelSpinLock(Irp->CancelIrql); //一定要释放cancel自旋锁!!否则系统崩溃,另外cancel自旋锁是全局自旋锁,                                                           所有驱动程序都会使用这个自旋锁,因此自旋锁时间不宜过长
KdPrint(("Leave CancelReadIRP\n"));
}

运行结果相似

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐