同步异步的两种用法
- FILE_FLAG_OVERLAPPED异步打开参数
在CreateFile打开设备对象时
1
2
3
4
5
6
7
8
9
|
HANDLE CreateFile(
LPCSTR lpFileName,
DWORD dwDesiredAccess,
DWORD dwShareMode,
LPSECURITY_ATTRIBUTES lpSecurityAttributes,
DWORD dwCreationDisposition,
DWORD dwFlagsAndAttributes, //FILE_FLAG_OVERLAPPED
HANDLE hTemplateFile
);
|
加入FILE_FLAG_OVERLAPPED参数就是以异步的方式打开驱动的设备对象
与驱动通讯时如下的调用界面及异步结构,需要设置一个OVERLAPPED的参数,参数中的hEvent事件需要初始化一个事件传入内核层,应用层等待事件完成做为通知。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
BOOL ReadFile(
HANDLE hFile,
LPVOID lpBuffer,
DWORD nNumberOfBytesToRead,
LPDWORD lpNumberOfBytesRead,
LPOVERLAPPED lpOverlapped //异步处理事件
);
BOOL WINAPI DeviceIoControl(
_In_ HANDLE hDevice,
_In_ DWORD dwIoControlCode,
_In_opt_ LPVOID lpInBuffer,
_In_ DWORD nInBufferSize,
_Out_opt_ LPVOID lpOutBuffer,
_In_ DWORD nOutBufferSize,
_Out_opt_ LPDWORD lpBytesReturned,
_Inout_opt_ LPOVERLAPPED lpOverlapped //异步处理事件
);
typedef struct _OVERLAPPED {
ULONG_PTR Internal;
ULONG_PTR InternalHigh;
union {
struct {
DWORD Offset;
DWORD OffsetHigh;
} DUMMYSTRUCTNAME;
PVOID Pointer;
} DUMMYUNIONNAME;
HANDLE hEvent;
} OVERLAPPED, *LPOVERLAPPED;
|
总结如下:
- 应用层以FILE_FLAG_OVERLAPPED方式打开驱动设备对象
- 应用层创建事件,传入到Overlapped参数结构中
- 等待事件句柄至signaled状态
伪码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
HANDEL hDriver = CreateFile("\\.\\\\DeviceSymbolName",...,FILE_FLAG_OVERLAPPED,...);
..
OVERLAPPED overlap;
AsyncEvent = CreateEvent(NULL, TRUE, TRUE, "");
..
overlap.hEvent = AsyncEvent;
overlap.Offset = 0;
overlap.OffsetHigh = 0;
..
DeviceIoControl (hDriver, ... ,&overlap);
..
if (GetLastError() == ERROR_IO_PENDING)
{
WaitForSingleObject(AsyncEvent, INFINTE);
}
|
- 驱动层在IRP例程处理中返回STATUS_PENDING,标记IoMarkIrpPending(pIrp);
- 异步处理完成后才最终调用IoCompleteRequest完成驱动处理
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
pIrp->IoStatus.Information = 0;
pIrp->IoStatus.Status = STATUS_SUCCESS;
pIrpStack = IoGetCurrentIrpStackLocation(pIrp);
IoCode = pIrpStack->Parameters.DeviceIoControl.IoControlCode;
switch (IoCode)
{
case ...
status = STATUS_PENDING;
IoMarkIrpPending(pIrp);
pIrp->IoStatus.Status = status;
return status;
break;
}
..
|
- 另一种方式ReadFileEx及WriteFileEx
1
2
3
4
5
6
7
|
BOOL ReadFileEx(
HANDLE hFile,
LPVOID lpBuffer,
DWORD nNumberOfBytesToRead,
LPOVERLAPPED lpOverlapped,
LPOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine
);
|
- ReadFileEx的LPOVERLAPPED不需要应用层提供并初始化Event
- lpCompletionRoutine参数为驱动IRP结束处理完后的回调处理
- 回调的原理就是插入的APC丢给应用层处理
- APC会在线程进入警惕模式后执行
- 线程在调用SleepEx,WaitForSingleObject等API时会进入警惕模式
- 驱动层在IRP_MJ_CREATE中处理,处理跟第一种方式类似,都是返回STATUS_PENDING,标记IoMarkIrpPending(pIrp)
IoCompleteRequest结束处理,并返回到应用层,这里可以看一下WRK的代码处理
处理流程如下
IoCompleteRequest->IopCompleteRequest->IopfCompleteRequest
在结束的代码中第一种方式用了事件置位通知,第二种方式用了APC回调
异步调用过程APC
APC是一种软中断过程,用于异步处理一些不是特别紧急的任务
调用接口界面如下
应用层
1
2
3
4
5
6
7
8
9
|
DWORD WINAPI QueueUserAPC(
_In_ PAPCFUNC pfnAPC,
_In_ HANDLE hThread,
_In_ ULONG_PTR dwData
);
VOID CALLBACK APCProc(
_In_ ULONG_PTR dwParam
);
|
内核层
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
NTKERNELAPI
VOID
KeInitializeApc (
__out PRKAPC Apc,
__in PRKTHREAD Thread,
__in KAPC_ENVIRONMENT Environment,
__in PKKERNEL_ROUTINE KernelRoutine,
__in_opt PKRUNDOWN_ROUTINE RundownRoutine,
__in_opt PKNORMAL_ROUTINE NormalRoutine,
__in_opt KPROCESSOR_MODE ProcessorMode,
__in_opt PVOID NormalContext
);
NTKERNELAPI
BOOLEAN
KeInsertQueueApc (
__inout PRKAPC Apc,
__in_opt PVOID SystemArgument1,
__in_opt PVOID SystemArgument2,
__in KPRIORITY Increment
);
|
KTHREAD的结构里保存了APC的一些关键结构
1
2
3
4
5
6
7
8
9
10
|
dt nt!_KTHREAD
...
+0x034 ApcState : _KAPC_STATE
...
+0x138 ApcStatePointer : [2] Ptr32 _KAPC_STATE
...
+0x14c SavedApcState : _KAPC_STATE
+0x164 Alertable : UChar
+0x165 ApcStateIndex : UChar
...
|
- ApcState 当前需要执行的APC就放在ApcState这里,当Attach到其他进程的APC也保存在这里,表示的就是当前的APC处理队列
- SavedApcState Attached后保存的当前线程的ApcState(处理代码在KeAttachProcess->KiAttachProcess)
- ApcStatePointer[2] 两个ApcState的指针
- ApcStateIndex 表示一个状态当前是挂靠进程还是正常的进程,一般只用到正常状态及另一个其他进程的挂靠状态,靠这个值才能区分开APCState这个值当前队列是否为本进程
再看一下_KAPC_STATE
1
2
3
4
5
6
7
8
|
typedef struct _KAPC_STATE
{
LIST_ENTRY ApcListHead[2];
struct _KPROCESS *Process;
BOOLEAN KernelApcInProgress;
BOOLEAN KernelApcPending;
BOOLEAN UserApcPending;
} KAPC_STATE, *PKAPC_STATE, *RESTRICTED_POINTER PRKAPC_STATE;
|
头部包含了两个APC链表,分别表示了内核及用户层的两种APC
APC调用时机
- 过程KiDeliverApc在_KiServiceExit时被调用,也就是调用时机是在从Ring0退回到Ring3的过程中
KiDeliverApc处理的流程得参照代码来讲,过程如下
- 从内核APC队列循环拿出APC,先处理只有KernelRoutine的APC,再处理普通的APC,包含了KernelRoutine及NormalRoutine,循环处理全部的内核APC
- 再处理用户队列,只处理第一个,先执行KernelRoutine,NormalRoutine是要放在用户空间执行的,需要调用一个API处理KiInitializeUserApc()
另外在警惕模式下才会执行用户态的APC,具体是线程在调用特定API时会设置Thread->ApcState.UserApcPending这个判断条件