驱动程序中文件,定时器,IO_WORKITEM 的使用

内容要点展示:

内核中文件的使用

内核定时器的使用

IO_WORKITEM 的使用

文章概要

最近一个项目呢,是做一个基于 TDI 的防火墙,

而在该防火墙的实现过程中呢,有对文件的处理,

因为这个防火墙中涉及到日志文件,黑名单文件,白名单文件的处理,

所以整个的 TDI 防火墙中对于文件处理这一块,

就涉及到文件的创建,打开,读取,写入等等文件操作。

而在内核中处理文件呢,自然有其特殊的一面,所以不太好操作。

同时还需要有定时器的处理以及 IRQL 的控制等细节。

文件操作概要

Windows 操作系统的内核环境中,对于文件的路径和在应用层中的表示是不同的,

在应用层中,我们对于文件的路径很简单,比如:E:\Driver\CodeProject\Sample.txt

但是在内核环境下,则必须在前面的路径中再加上一点点:\DosDevices\E:\Driver\CodeProject\Sample.txt 。

其实这一点呢,估计写过一点点驱动程序的都很熟悉了,因为创建符号链接名的时候都是这样来实现的,

还有值得一提的是,在 Windows 2000 以及以后的 Windows 操作系统中,可以通过 ?? 来代替 DosDevices 了,

也就是说内核中文件的路径又可以写成:\??\E:\Driver\CodeProject\Sample.txt 。

然后呢,既然有了文件的路径,那么同样可以通过 内核  API(不是 Win32  API) 来打开文件从而获得句柄。

获得句柄以后呢,就可以通过 内核  API  来完成读写等等操作了。

文件操作涉及的内核 API

首先,来看一下将要使用的针对于文件处理的 内核 API

image

image

image

image

image

image

对于上面的这些 API 呢,MSND 上明确记载:

must be running at IRQL = PASSIVE_LEVEL and with APCs enabled.

也就是说,它们必须运行在 IRQL PASSIVE_LEVEL 层,

众所周知的是,PASSIVE_LEVEL 是所有中断请求级别 (IRQL) 中最低的,

也就是说,工作在这个中断请求级别下的任务相比于工作在其他 IRQL 层(比如 DISPATCH_LEVEL)的任务来说,

它们获得 CPU 处理的时间会更少,至于为什么对于文件的处理的时候 IRQL 必须为最低呢,

这里其实稍动脑子想想就能够明白,对于文件的处理向来都是很慢的,需要消耗很多的 CPU 时间,

如果你在内核中进行文件操作,而且这些操作不是工作在 PASSIVE_LEVEL 层的话,

那么这些文件操作就有更多的机会获取到 CPU 时间,

从而会消耗很多的 CPU 时间,从而造成系统性能的大幅度下降。                 

然后的话,上面的这些 API 如果你不是在 PASSIVE_LEVEL 下调用的话,

那么 Windows 会很坦率的给你一个蓝屏,从而很显示的提示您:非法了。         

再来看看这些 API 的原型,至于这些 API 的具体信息,MSDN 中有很完整的解释,这里就不给出了:

NTSTATUS  
ZwCreateFile(
             OUT PHANDLE  FileHandle,
             IN ACCESS_MASK  DesiredAccess,
             IN POBJECT_ATTRIBUTES  ObjectAttributes,
             OUT PIO_STATUS_BLOCK  IoStatusBlock,
             IN PLARGE_INTEGER  AllocationSize  OPTIONAL,
             IN ULONG  FileAttributes,
             IN ULONG  ShareAccess,
             IN ULONG  CreateDisposition,
             IN ULONG  CreateOptions,
             IN PVOID  EaBuffer  OPTIONAL,
             IN ULONG  EaLength
             );
NTSTATUS
ZwOpenFile(
           OUT PHANDLE  FileHandle,
           IN ACCESS_MASK  DesiredAccess,
           IN POBJECT_ATTRIBUTES  ObjectAttributes,
           OUT PIO_STATUS_BLOCK  IoStatusBlock,
           IN ULONG  ShareAccess,
           IN ULONG  OpenOptions
           );
NTSTATUS 
ZwClose(
        IN HANDLE  Handle
        );

        

NTSTATUS 
ZwReadFile(
           IN HANDLE  FileHandle,
           IN HANDLE  Event  OPTIONAL,
           IN PIO_APC_ROUTINE  ApcRoutine  OPTIONAL,
           IN PVOID  ApcContext  OPTIONAL,
           OUT PIO_STATUS_BLOCK  IoStatusBlock,
           OUT PVOID  Buffer,
           IN ULONG  Length,
           IN PLARGE_INTEGER  ByteOffset  OPTIONAL,
           IN PULONG  Key  OPTIONAL
           );
NTSTATUS 
ZwWriteFile(
            IN HANDLE  FileHandle,
            IN HANDLE  Event  OPTIONAL,
            IN PIO_APC_ROUTINE  ApcRoutine  OPTIONAL,
            IN PVOID  ApcContext  OPTIONAL,
            OUT PIO_STATUS_BLOCK  IoStatusBlock,
            IN PVOID  Buffer,
            IN ULONG  Length,
            IN PLARGE_INTEGER  ByteOffset  OPTIONAL,
            IN PULONG  Key  OPTIONAL
            );
           
NTSTATUS 
ZwQueryInformationFile(
                       IN HANDLE  FileHandle,
                       OUT PIO_STATUS_BLOCK  IoStatusBlock,
                       OUT PVOID  FileInformation,
                       IN ULONG  Length,
                       IN FILE_INFORMATION_CLASS  FileInformationClass
                       );

定时器涉及的内核 API

在内核中使用定时器呢,有好几种方式,

我这里只介绍了我在这个 TDI 防火墙项目中所使用的定时器,

至于其他几种定时器的使用呢,我给出几篇来自博客园和看雪的博文的链接,

这几篇博文对定时器的介绍都是非常贴切详细的,有想了解定时器的这里荣老衲推荐一下。

定时器:http://www.cnblogs.com/mydomain/archive/2010/11/14/1877125.html

高手进阶windows内核定时器之一:http://bbs.pediy.com/showthread.php?t=60474

高手进阶windows内核定时器之二:http://bbs.pediy.com/showthread.php?t=60579

VOID 
KeInitializeTimer(
                  IN PKTIMER  Timer
                  );
              
VOID 
KeInitializeDpc(
                IN PRKDPC  Dpc,
                IN PKDEFERRED_ROUTINE  DeferredRoutine,
                IN PVOID  DeferredContext
                );
BOOLEAN 
KeSetTimer(
           IN PKTIMER  Timer,
           IN LARGE_INTEGER  DueTime,
           IN PKDPC  Dpc  OPTIONAL
           );

// DPC 回调函数

VOID
CustomDpc(
          IN struct _KDPC  *Dpc,
          IN PVOID  DeferredContext,
          IN PVOID  SystemArgument1,
          IN PVOID  SystemArgument2
          );

对于上面这些 API 的使用呢,就是先调用 KeInitializeTimer 来初始化一个内核定时器,

然后调用 KeInitializeDpc 来初始化一个 DPC ,在该 DPC 中需要指定回调函数 CustomDpc ,

然后再调用 KeSetTimer 来将定时器和 DPC  关联起来,同时也在这个 API 中指定定时器触发的时间间隔。

需要注意的是,使用 KeSetTimer 设置好定时器后,定时器只会触发一次,

如果要持续的触发定时器,则必须在 CustomDpc 这个回调函数中重新调用 KeSetTimer 来重新设置定时器触发。

还有需要注意的一点是在 CustomDpc 这个回调函数其是工作在 DISPATCH_LEVEL 这个 IRQL 层而非 PASSIVE_LEVEL 。

IO_WORKITEM 概要

对于这个 TDI 防火墙项目呢,我要实现的是如下这种方式的文件处理操作,

1. 每隔 10s 即需要重新读取黑名单和白名单中的 IP 地址。

2. 每隔 1h 即需要重新创建日志文件。

所以,我的思路就是通过两个内核定时器来实现,

然后在内核定时器中来处理上面的这两个针对文件的操作,

但是上面也提到了,在定时器所绑定的 DPC 的回调函数 CustomDpc 是工作在 DISPATCH_LEVEL 下,

而我们的 ZwCreateFile 之类的函数均必须工作在 PASSIVE_LEVEL 下,

如果是直接在 CustomDpc 下直接进行 ZwCreateFile 这些函数的话,

乖乖,直接给个很耀眼的提示 - 蓝屏。

要解决上面的这个问题呢,就必须要用到 IO_WORKITEM

因为 IO_WORKITEM 这个东西呢,与其绑定的回调函数是工作在 PASSIVE_LEVEL 下的,

IO_WORKITEM 其实就是一个 IO 工作项,在操作系统中,由系统自行维护了一个 IO_WORKITEM 队列,

而每一个 IO_WORKITEM 呢均有其对应的一个回调函数,众所周知的是,

操作系统有一个系统线程池,所以系统会自动的使用这个系统线程池中的某一个空闲线程,

然后这个空闲线程从 IO_WORKITEM 队列中取出一个 IO_WORKITEM

然后这个线程便调用这个 IO_WORKITEM 的回调函数进行处理,

这样这个 IO_WORKITEM 也就得到了处理,关键是它的处理还是在 IRQL PASSIVE_LEVEL 时进行的。

而这刚好适应了我前面的需求。

IO_WORKITEM 涉及的内核 API

只列出我所使用的,关于其他的可以查看 MSDN

PIO_WORKITEM
IoAllocateWorkItem(
                   IN PDEVICE_OBJECT  DeviceObject
                   );
VOID
IoQueueWorkItem(
                IN PIO_WORKITEM  IoWorkItem,
                IN PIO_WORKITEM_ROUTINE  WorkerRoutine,
                IN WORK_QUEUE_TYPE  QueueType,
                IN PVOID  Context
                );

// IO_WORKITEM 回调函数   

VOID
WorkItem (
          IN PDEVICE_OBJECT  DeviceObject,
          IN PVOID  Context 
          );

不完整代码展示:

基本定义:

 
//定义读取黑白文件的时间间隔为 10 秒
#define READ_FILE_INTERVAL    -10000 * 1000 * 10
//定义创建新的日志文件间隔为 1 小时
#define CREATE_LOGFILE_INTERVAL -10000 * 1000 * 60 * 60
 
HANDLE            hBlackFile; 
typedef struct _KTIMER_EXT 
{    
    KDPC                kDpc;                //存储 DPC 对象
    KTIMER                kTimer;                //存储计时器对象
    LARGE_INTEGER        dueInterval;        //记录计时器间隔时间 
 
} KTIMER_EXT, * PKTIMER_EXT;
 
 
//两个内核定时器
KTIMER_EXT queryFileInfoKTimerExt;
KTIMER_EXT createLogKTimerExt;
 
 
//保存设备句柄
PDEVICE_OBJECT        pDeviceObj;
 
//从黑名单文件中读取出所有的黑名单记录
VOID ReadBlackFile();
 
//查询文件信息的 Work Item 的回调处理函数
VOID WorkerItemQueryFileInfoRoutine(PDEVICE_OBJECT  DeviceObject, PVOID  Context);
 
//查询文件信息定时器的 DPC 回调函数
VOID QueryFileInfoTimerDpcRoutine(PKDPC pDpc, PVOID pContext, PVOID SysArg1, PVOID SysArg2);
               

打开或者创建文件:

    WCHAR fileName2[] = L"\\??\\C:\\NdisBlack.txt";
    WCHAR fileName3[] = L"\\??\\C:\\NdisWhite.txt";
    NTSTATUS status;
 
    UNICODE_STRING unifilename2;
    UNICODE_STRING unifilename3;
 
    OBJECT_ATTRIBUTES oa;
 
    IO_STATUS_BLOCK iostatus;
 
    RtlInitUnicodeString(&unifilename2,fileName2);
    RtlInitUnicodeString(&unifilename3,fileName3);

                  

    InitializeObjectAttributes(&oa,&unifilename1, OBJ_CASE_INSENSITIVE|OBJ_KERNEL_HANDLE, NULL,NULL);
    status = ZwCreateFile(&hBlackFile, FILE_READ_DATA|FILE_READ_ATTRIBUTES , &oa, &iostatus, NULL,
        FILE_ATTRIBUTE_NORMAL, FILE_SHARE_READ|FILE_SHARE_WRITE, FILE_OPEN_IF, 0, 0, 0);
    if(!NT_SUCCESS(status))
    {
        DbgPrint("ZwCreateFile(BlackFile) Failed %8x",status);
        return status;
    }

            

读取文件:

    ULONG length;
    PCHAR pBuffer;
    NTSTATUS status;
    LARGE_INTEGER offset = {0};
    IO_STATUS_BLOCK ioStatus;
 
    pBuffer = (PCHAR)ExAllocatePool(NonPagedPool, ROWLENGTH);
 
    while(1)
    {
        RtlZeroMemory(pBuffer, ROWLENGTH);
 
        status = ZwReadFile(hBlackFile, NULL, NULL, NULL, &ioStatus, pBuffer, ROWLENGTH, &offset, NULL);
        if(!NT_SUCCESS(status))
        {
            if(STATUS_END_OF_FILE == status)
            {
                DbgPrint("Read All File Contents ! \n"); 
                break;
            }
        }
 
        DbgPrint("Current    ID: %d \n",lTotalBlackRecordCount);
        DbgPrint("Current Data: %s \n",chArrayBlack[lTotalBlackRecordCount]);
 
        //包括一个 ; 以及每一行的结束符 \t\r 所以总共是 3 个字符
        length = i + 3;
        offset.QuadPart += length;
    }

写入文件:

    NTSTATUS            ntstatus;
    IO_STATUS_BLOCK        iostatus;
    LARGE_INTEGER        ByteOffset = { 0 } ;
 
    ntstatus = ZwWriteFile(hLogFile, NULL, NULL, NULL, &iostatus, pszDest,pszDestLen, &ByteOffset, NULL);

         

定时器设置(在其中绑定了 DPC 回调函数):

pDeviceObj = theDriverObject->DeviceObject;
 
queryFileInfoKTimerExt.dueInterval.QuadPart = READ_FILE_INTERVAL;
KeInitializeTimer(&queryFileInfoKTimerExt.kTimer);
KeInitializeDpc(&queryFileInfoKTimerExt.kDpc, QueryFileInfoTimerDpcRoutine, (PVOID)&queryFileInfoKTimerExt);
KeSetTimer(&queryFileInfoKTimerExt.kTimer, queryFileInfoKTimerExt.dueInterval, &queryFileInfoKTimerExt.kDpc);
 
DbgPrint("读文件定时器设置成功 ! \n");
 
 

定时器之 DPC 回调函数(在其中创建了 IO_WORKITEM):

//在该定时器的回调函数中重新读取黑白文件
//定时器处理函数运行在 DISPATCH_LEVEL
VOID QueryFileInfoTimerDpcRoutine(PKDPC pDpc, PVOID pContext, PVOID SysArg1, PVOID SysArg2)
{
    PKTIMER_EXT                    pTmpKTimerExt;
    PIO_WORKITEM                pIoWorkItem;
 
    //由于 ZwQueryInformationFile 必须运行在 PASSIVE_LEVEL 
    //而当前的这个函数作为内核定时器的处理函数,其工作在 DISPATCH_LEVEL 
    //所以如果在该函数中直接调用 ZwQueryInformationFile 会由于 IRQL 的不符合而导致蓝屏
    //所以在这里使用一个 IO_WORKITEM 来解决
    //因为 IO_WORKITEM 的回调函数是在 PASSIVE_LEVEL 中处理的
    //所以可以在 IO_WORKITEM 的回调函数中来调用 ZwQueryInformationFile 
    pIoWorkItem = IoAllocateWorkItem(pDeviceObj);
    if(pIoWorkItem)
    {
        IoQueueWorkItem(pIoWorkItem, WorkerItemQueryFileInfoRoutine, DelayedWorkQueue, NULL);
    }
 
    //#if DBG
    //    _asm int 3
    //#endif
 
    pTmpKTimerExt = (PKTIMER_EXT)pContext;
 
    KeSetTimer(&pTmpKTimerExt->kTimer, pTmpKTimerExt->dueInterval, &pTmpKTimerExt->kDpc);
 
    DbgPrint("读文件定时器再次设置成功 ! \n");
}

IO_WORKITEM 回调函数:

//Work Item 回调函数,用来查询文件属性信息从而判断是否需要重新读入黑白名单
//WorkItem 处理函数运行在 PASSIVE_LEVEL
VOID WorkerItemQueryFileInfoRoutine(PDEVICE_OBJECT  DeviceObject, PVOID  Context )
{    
    NTSTATUS                    status;
    LARGE_INTEGER                localTime;
 
    IO_STATUS_BLOCK                ioStatus;
    FILE_BASIC_INFORMATION        flBscInfo;
 
    //查询黑名单文件属性信息
    status = ZwQueryInformationFile(hBlackFile, &ioStatus, &flBscInfo, sizeof(FILE_BASIC_INFORMATION), FileBasicInformation);
    if(NT_SUCCESS(status))
    {
        ExSystemTimeToLocalTime(&flBscInfo.ChangeTime, &localTime);
        RtlTimeToTimeFields(&localTime, &timeFields);
 
        if(prevReadBlackTime.QuadPart < localTime.QuadPart)
        {
            DbgPrint("上一次读取的时间小于黑名单文件的最新修改时间,所以需要重新读取黑名单 \n");
 
            //重新读取黑名单
            ReadBlackFile();
 
            //重新设置上一次读取黑名单文件的时间
            prevReadBlackTime.QuadPart = localTime.QuadPart;
        }
        else
        {
            DbgPrint("不需要重新读取黑名单 \n");
        }
    }
}

版权所有,迎转载,但转载请注明: 转载自 Zachary.XiaoZhen - 梦想的天空

posted @ 2011-04-09 14:01 小宝马的爸爸 阅读(...) 评论(...) 编辑 收藏