缓冲管理器回调
文件系统和内存管理器之间的互动是通过一系列的回调实现的.这些回调函数被注册给Cache管理器之后被Cahce管理器用来确保数据结构在一个文件系统操作之前被"锁定".
Windows NT假设资源如何被文件系统,Cahce管理器和内存管理器使用有一个严格的顺序.这个顺序确保死缩不会发生.反之则可能死锁.一般来说,文件系统首先得到资源.然后是Cache管理器,最后是内存管理器.
因此,这些回调被内存管理器用来维护它的层级:这些回调有:
typedef BOOLEAN (*PACQUIRE_FOR_LAZY_WRITE) ( IN PVOID Context, IN BOOLEAN Wait ); typedef VOID (*PRELEASE_FROM_LAZY_WRITE) ( IN PVOID Context ); typedef BOOLEAN (*PACQUIRE_FOR_READ_AHEAD) ( IN PVOID Context, IN BOOLEAN Wait, ); typedef VOID (*PRELEASE_FROM_READ_AHEAD ( IN PVOID Context ); typedef struct _CAHHE_MANAGER_CLALLBACKES { PACQUIRE_FOR_LAZY_WRITE AcquireForLazyWrite; PRELEASE_FROM_LAZY_WRITE ReleaseFromLazyWrite; PACQUIRE_FOR_READ_AHEAD AcquireForReadAhead; PRELEASE_FROM_READ_AHEAD ReleaseFromReadAhead; } CACHE_MANAGER_CALLBACKS,*PCACHE_MANAGER_CLALLBACKS;
注意这些回调分别被缓冲管理器的两个部分使用.首先是延迟写,负责把脏的(修改过的)缓冲数据写回文件系统.另一种是预读,预先的读取文件,给用户调用的读返回信息.
首先,在设计上很重要的是要理解你的文件系统应该避免那些情况,而哪些是不需要避免的.比如说,没有理由把用户应用的缓冲读操作和缓冲管理器的延迟写序列化.它们互无影响.但是,你需要避免用户调用的非缓冲写操作改变文件的长度和缓冲管理器延迟写冲突.
NT的文件系统使用两个资源结构来实现这个.所有这些资源可以被不同的操作系统组件来使用,通过使用共同的头,铁别是头中的Resource域和PageingIoResource域.缓冲管理器并不直接去获取这些资源,它通过调用文件系统中的回调函数来获取这些必要的资源.
请注意这些回调例程必须由你的文件系统提供,他们不是可选的.如果你不能提供,那么系统会崩溃.
微软的IFSKit中每个文件系统的例子都含有这些例程.比如延迟写的位置入下:
文件系统 文件名 例程 FAT resrcsup.c FatAcquireFcbForLazyWrite CDFS resrcsup.c CdAcquireForCache RDR2 rxce\resrcsup.c RxAcquireFcbForLazyWrite
其他的例程基本上可以在同一个文件中找到.
下面的代码是一个回调例程的例子:
static BOOLEAN OwAcquireForLazyWrite(PVOID Conetext,BOOLEAN Wait) { POW_FCB fcb = (POW_FCB)Context; BOOLEAN result;
// 打开文件上的锁 result = OwAcquireResourceExclusiveExp(&fcb->Resource,Wait); if(!result) { // 我们没有能获得资源 return result; }
// 我们得到了资源,我们必须: // (1)保存当前线程的线程id(用于释放) // (2)设置顶级irp一个假的值. // 无论如何,当前线程id在设置之前应该为0(必须是没有使用的)
OwAssert(!fcb->ResourceThread); fcb->ResourceThread = OwGetCurrentResourceThread(); return (TRUE); }
CcCanIWrite
因为一个应用程序可能修改内存中的数据的速度超过写磁盘到数据上的能力,导致虚拟内存系统可能被数据所"饱和".这可能导致虚拟系统发生内存枯竭的情况.为了避免这点,文件系统必须和虚拟系统合作探测这种条件.缓冲管理器提供的一个关键操作之一就是CcCanIWrite,原型如下:
NTKERNELAPI BOOLEAN CcCanIWrite ( IN PFILE_OBJECT FileObject, IN ULONG BytesToWrite, IN BOOLEAN Wait, IN BOOLEAN Retrying );
如果返回了FALSE,那么文件系统必须延迟脏页面的写入来避免内存枯竭.典型的内存枯竭症状是一些调用返回NO_PAGES_AVAILABLE.
文件系统必须排列这些写操作的重试并发送.文件系统可以通过内部的发送机制来发送,也可以使用这个例程.
CcDeferWrite
你的文件系统可以调用FsRtlCopyWirte,这样就不必直接调缓冲管理器了.这样内部的延迟写实际上是用这个例程实现延迟写的.
CcCopyRead
只要一个文件系统建立了一个缓冲(通过CcInitializeCacheMap调用),它就可以使用FsRtl例程(比如FsRtlCopyRead)或者这个例程.一般FsRtlCopyRead用来实现Fast IO的读,而这个例程一般用来实现IRP_MJ_READ,原型如下:
NTKERNELAPI BOOLEAN CcCopyRead ( IN PFILE_OBJECT FileObject, IN PLARGE_INTERGER FileOffset, IN ULONG Length, IN BOOLEAN Wait, OUT PVOID Buffer, OUT PIO_STATUS_BLOCK IoStatus );
FileObject含有一个指向SectionObjectPointer的指针.当数据从缓冲拷贝到用户缓冲区间(也就是这里的Buffer所指的区域),SectionObjectPointer是被缓冲管理器所使用.当然这里假设缓冲已经实现初始化过了.
Wait说明调用这是否希望阻塞一段不定长的时间.比如可能要等待获取一个锁.这个参数应该被看成一个"提示"而不是一个"确保".比如说,如果一个磁盘io有必要结束这个操作,这个操作可能继续运行.即使Wait的值是FALSE.
Buffer是调用者提供的缓冲.它不一定是一个有效的缓冲.失败的情况下,这个调用会抛出一个异常.一个文件系统驱动应该捕获这个异常并返回一个错误给应用程序.
IoStatus用来收集操作的完成状态,以及有多少个字节被成功的读取了.
请注意缓冲管理器的这个调用可能导致分页交换.这可能导致文件系统驱动处理实际的分页交换读写的操作的例程重入.
(译者注:假设文件系统中的读处理例程中调用了CcCopyRead从缓冲中读,这可能导致缓冲进行页面交换来读取硬盘.而读取硬盘的操作又是文件系统的读处理例程进行的,因此这个读处理例程是可能重入的.).
[上一页] [1] [2] [3] [下一页]
|