首页
论坛
专栏
课程

[分享]缓存管理器

2009-7-31 21:48 9638

[分享]缓存管理器

2009-7-31 21:48
9638
简言之,就是会预先读入文件和延迟写入文件。当ReadFile时,会调用NtReadFile()系统调用,它会构造一个IRP下发到FSD,FSD会检查这个IRP看是不是可以缓存 的,是的话,如果还没有为此文件建立缓存的话,就会调用 CcInitializeCacheMap()函数建立缓存,它里面会调用内存管理器(VMM)函数建立一个节对象 。当用到时,会把这个节对象(和文件关联)映射到内核空间。如果IRP是可缓存 的,则调用CcCopyRead()函数进行从缓存中读入文件。

如果此文件还没有在内存中,则会产生页面错误,交给MmAccessFault()函数处理,它会调用IoPageRead()分配一个不缓存 的IRP,但是它会走FSD,不会调用缓存的函数,而是最终调用磁盘驱动进行真实的磁盘读写读入到内存。之后CcCopyRead()再不会产生错误了,会从缓存复制到用户Buffer中

NtReadFile (
    __in HANDLE FileHandle,
    __in_opt HANDLE Event,
    __in_opt PIO_APC_ROUTINE ApcRoutine,
    __in_opt PVOID ApcContext,
    __out PIO_STATUS_BLOCK IoStatusBlock,
    __out_bcount(Length) PVOID Buffer,
    __in ULONG Length,
    __in_opt PLARGE_INTEGER ByteOffset,
    __in_opt PULONG Key
    )

status = ObReferenceObjectByHandle( FileHandle,
                                        FILE_READ_DATA,
                                        IoFileObjectType,
                                        requestorMode,
                                        (PVOID *) &fileObject,
                                        NULL );//得到文件对象

deviceObject = IoGetRelatedDeviceObject( fileObject );//得到设备对象

// 如果文件已经有缓存了,直接调用

if (fileObject->PrivateCacheMap) {

            IO_STATUS_BLOCK localIoStatus;

            ASSERT(fastIoDispatch && fastIoDispatch->FastIoRead);

            //
            // Negative file offsets are illegal.
            //

            if (fileOffset.HighPart < 0) {
                if (eventObject) {
                    ObDereferenceObject( eventObject );
                }
                IopReleaseFileObjectLock( fileObject );
                ObDereferenceObject( fileObject );
                return STATUS_INVALID_PARAMETER;
            }

            if (fastIoDispatch->FastIoRead( fileObject,
                                            &fileOffset,
                                            Length,
                                            TRUE,
                                            keyValue,
                                            Buffer,
                                            &localIoStatus,
                                            deviceObject )

否则的话还要分配IRP,下发到文件系统驱动 ,(注意有三种处理用户Buffer的方法,因为有可能FSD驱动不是在本用户进程的地址空间中执行的,则访问Buffer(尽管虚拟地址相同,但是一般会被映射到不同的物理地址),所以要做如下处理Buffer。

1,是调用irp->AssociatedIrp.SystemBuffer =
                    ExAllocatePoolWithQuota( NonPagedPoolCacheAligned, Length );在非分页内存中分配内存,因为都是在内核空间,所以就算另一个进程也能访问。

2,是调用mdl = IoAllocateMdl( Buffer, Length, FALSE, TRUE, irp );分配一个内存描述符,再调用 MmProbeAndLockPages( mdl, requestorMode, IoWriteAccess );
把此Buffer所在的物理页锁定在内存中,防止换出去。

3,直接就是那个 irp->Flags = 0; irp->UserBuffer = Buffer;

调用 这个IopSynchronousServiceTail()函数下发到FSD

注意先是IopfCallDriver()调用fltMgr.sys驱动的分派函数,最后它也调用IofCallDriver()函数下发IRP到下层驱动(既ntfs.sys的NtfsFsdRead()函数)

NTSTATUS
NtfsFsdRead (
    IN PVOLUME_DEVICE_OBJECT VolumeDeviceObject,
    IN PIRP Irp
    )
最后调用NtfsCommonRead()这个函数里面会做好多判断,之后会建立该文件的Cache

if (FileObject->PrivateCacheMap == NULL) {

                    DebugTrace( 0, Dbg, ("Initialize cache mapping.\n") );

                    //
                    // Now initialize the cache map.
                    //
                    // Make sure we are serialized with the FileSizes, and
                    // will remove this condition if we abort.
                    //

                    if (!DoingIoAtEof) {
                        FsRtlLockFsRtlHeader( Header );
                        IrpContext->FcbWithPagingExclusive = (PFCB)Scb;
                    }

CcInitializeCacheMap( FileObject,
                                          (PCC_FILE_SIZES)&Header->AllocationSize,
                                          FALSE,
                                          &NtfsData.CacheManagerCallbacks,
                                          Scb );

它里面会分配SharedCacheMap = ExAllocatePoolWithTag( NonPagedPool, sizeof(SHARED_CACHE_MAP), 'cScC' );

初始化这个结构,最后调用内存管理器(VMM) 函数SharedCacheMap->Status = MmCreateSection( &SharedCacheMap->Section,
                                                      SECTION_MAP_READ
                                                        | SECTION_MAP_WRITE
                                                        | SECTION_QUERY,
                                                      NULL,
                                                      &LocalSizes.AllocationSize,
                                                      PAGE_READWRITE,
                                                      SEC_COMMIT,
                                                      NULL,
                                                      FileObject );
建立一个共享节对象

之后FSD调用 if (!CcCopyRead( FileObject,
                                     (PLARGE_INTEGER)&StartingVbo,
                                     (ULONG)ByteCount,
                                     Wait,
                                     SystemBuffer,
                                     &Irp->IoStatus ))进行从缓存中读入数据

如果缓存没有这个要读文件的页面,则会产生页面异常,最终进入MmAccessFault()处理,它会调用IoPageRead()分配一个IRP_PAGING_IO | IRP _NOCACHE (没有缓存的IRP)再次调用IoCallDriver调用FSD的函数,这里和上面一样,同样进入FSD的NtfsFsdRead()-》NtfsNonCachedIo()进行没有缓存的IRP请求。-》NtfsSingleAsync()它里面先调用IoSetCompletionRoutine()设置一个FSD回调函数,然后调用    IoCallDriver( DeviceObject, Irp );调用1,volsnap!VolSnapRead------->2,ftdisk!FtDiskReadWrite------>3,PartMgr!PmReadWrite------->4,CLASSPNP!ClassReadWrite----->5,SCSIPORT!ScsiPortGlobalDispatch()等等会进行真正磁盘读写文件内容

当读写磁盘完成了后会产生中断之后进入KiDispatchInterrupt()

ScsiPortCompletionDpc()一层一层的调用SCSIPORT!SpCompleteRequest()完成回调函数,最后会调用到FSD先前建立的Ntfs!NtfsSingleSyncCompletionRoutine()也算是完成了磁盘读写。

2020安全开发者峰会(2020 SDC)议题征集 中国.北京 7月!

最新回复 (3)
techie 2009-7-31 23:12
2
0
补充一下,文件的缓存不仅在READ中可以生成,在WRITE和SET_INFORMATION中也是可以进行初始化的,就是简单的调用缓存初始化函数CcInitializeCacheMap(),其他步骤以及FastIO的处理都是类似的。
天涯一鸿 2009-8-1 11:56
3
0
第一次文件数据接收到读写操作指令时,文件系统会负责决定文件的哪一部分被映射到系统缓存,如果不这样,文件系统必须调用CcInitializeCacheMap函数在前面章节中设置该文件的数据类型描述。
   一旦一个文件在存取缓存中被设置,文件系统会调用几个函数中的一个去接收文件里的数据。这里有三种接收主要的方法,每一种都为不同的情景准备。
   
   拷贝的方法从系统的缓冲区和用户态进程的缓冲中复制用户数据

   映射和锁定的方法使用虚拟内存地址去读写数据到缓存区
   
   物理内存存储的方法使用物理内存地址去读写数据到缓存区

文件系统驱动必须提供两两种形式的文件读取操作——可缓存和不可缓存——去预防一个当内存管理器产生一个页故障时发生的无限循环。当内存管理器在调用文件系统从文件中检索数据时接收到一个页故障。它必须指定这个读操作的IRP被设定了"不缓存"标志。

图标11-13 阐述了缓存管理器,内存管理器,文件系统之间由于用户读写用户态文件I/O所作出的反应。文件系统通过复制接口来引用缓存管理器(通过CcCopyRead 和CcCopyWrite 的途径)。但是对于进程来说就是一个CcFastCopyRead或者CcCopyRead读取操作,举个例子,缓存管理器在缓存中创建一个视图去映射文件正在被读取的一部分而用从缓存视图中复制数据到用户态缓冲区的方法来读取文件。复制操作在接收一个视图中的无效内存页时会产生到一个页故障,然后答复内存管理器让它加入一个不缓存I/O端口到文件系统驱动去接收文件数据相当于把内存页故障那部分文件重新映射。

下面三个章节解释了缓存的存储系统,它们的用途以及如何使用它们。

从缓存中复制数据

因为系统缓存在系统区域,所以它被映射到每一个进程的空间。正如所有的系统空间页一样,然而,缓存页并不容易从用户态的模式中取得因为那将成为一个潜在的安全漏洞。(打个比方,一个进程可能没有用正确的方法读取一个当前在系统缓存区域中的文件数据)这样,用户态的应用文件读写缓存中的文件必须通过内核模式中的服务例程把数据拷贝到系统空间缓存区和用户态应用程序的缓冲区驻留在进程的寻址空间。文件系统驱动能使用实现上述效果的函数如Table 11-4

Table 11-4.内核模式用来从缓存中复制数据的函数

CcCopyRead
从系统缓冲中复制一个指定的字节到用户态缓冲区

CcFastCopyRead
比CcCopyRead更快,但是仅限在32位系统文件和同时读取文件时使用。

CcCopyWrite
从用户态缓冲区复制一个指定的字节到系统缓存

CcFastCopyWrite

随便找到的……
第八个门 1 2009-8-1 20:04
4
0
嘿,不知道楼主也是用那个ntfs的工程源码级调试出来的么
游客
登录 | 注册 方可回帖
返回