Cache的四个接口
File Stream Manipulation Functions
Copy Interface
MDL Interface
Pinning Interface
注意:Cache管理器的Pin接口和另外两个接口:Copy接口以及MDL接口不能
同时使用。
重要:Cache管理器并解释被缓存的数据内容,它只是理解File Object以及
相关的Stream对象。
Cache管理器的客户类型:文件系统驱动、过滤器驱动、网络文件重
定向器、网络文件服务器。
HSM:Hierarchical Storage Management。
FILE_OBJECT中大部分字段是由IO管理器负责维护,但有几个例外:
FsContext指向一个CommonFCBHeader结构,简称FCB,也就是Unix系统
中的vnode结构,或许就是DOS下的FCB。注意FILE_OBJECT是Per
handle的,而FCB则是per file stream的。
一个重要的结构:
//
// The following structure is pointed to by the SectionObject
// pointer field
// of a file object, and is allocated by the various NT file systems.
//
typedef struct _SECTION_OBJECT_POINTERS {
PVOID DataSectionObject;
PVOID SharedCacheMap;
PVOID ImageSectionObject;
} SECTION_OBJECT_POINTERS; typedef SECTION_OBJECT_POINTERS
*PSECTION_OBJECT_POINTERS;
和FCB一样,这也是一个per file stream的对象,其三个成员都是
Cache管理器的私有结构,甚至在IFSKits中都没有公开。
Cache Map
一个复杂的对象,结构未知!
理解Cache子系统的关键在于了解Io管理器、文件系统、Cache管理器、
Memory管理器之间的关系,用户读写文件时首先经过Io管理器,Io管
理器视情况而定可能会直接向文件系统发出请求,也可能绕过文件系统
直接和Cache管理器打交道,对于后者来说,Cache管理器将利用Memory
管理器的功能实现数据的读写—-事实上只是一个简单的拷贝。
问题:如果Io管理器向文件系统发出请求,那么是不是文件系统(在做了必要
的初始化工作之后)一定会回头调用Cache管理器?
Cache管理器为每个File
Stream开了一个或者多个窗口,每个窗口的大小都是固定的,
记得是256KB。这些窗口用VACB来管理。
看上去Private Cache Map只是为了将同一个File Stream的多个File
Object串起来, 因为Cache管理器为了Unmap一个VACB,必须通知每个
File Object,所以这样一个 链表是有必要的。
Cache Manager的Fast IO看上去是NT的开发小组在最后时刻发现系统
性能问题后草草设计出来的,虽然现在这个接口已经发展的很成熟了,
但总的来说还是有些不好理解。
每个Handle都必须调用Cache管理器初始化缓存。
文件系统提供给Cache管理器的回调接口倒是很简单:
typedef struct _CACHE_MANAGER_CALLBACKS {
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_CALLBACKS
初始化CacheMap:
void CcInitializeCacheMap (
IN PFILE_OBJECT PtrFileObject,
IN PCC_FILE_SIZES FileSizes,
IN BOOLEAN PinAccess,
IN PCACHE_MANAGER_CALLBACKS CallBacks,
IN PVOID LazyWriterContext
);
LazyWrite和ReadAhead是Cache管理器完成的,但是Cache管理器在
执行这些任务时,必须获得文件系统的支持,上面的Callback参数
就是文件系统提供给Cache管理器的许可证,Cache管理器会在
LazyWrite和ReadAhead的时候调用这些回调函数。这些回调的主要
任务通常是同步。
LazyWriterContext这个名字取的不好,实际上这个参数同时用作
LazyWriter和ReadAhead的上下文参数,通常这个参数就是Context
Control Block,一个和File Object并列的对象,不过后者是Io
管理器创建的而CCB则是文件系统创建的,另外,通常这个指针会
保存在FileObject对象的Context2字段中,这应该算是一种文化
了吧我想。
注意这个函数没有返回值,调用者必须作好准备接受任何可能产生
的例外。
Copy接口
文件系统在处理Read请求时,可以调用Cache管理器的Copy接口,
如果要copy的数据不在物理内存中,则会产生页面错误,将由
Memory Manager调用文件系统将数据从外设(磁盘或者网络服
务器)拷贝到物理内存中,也就是说,文件系统将被递归的调用。
写数据的时候也可能会需要等待,如果写的内容不是硬件扇区
对齐的,则可能需要执行读操作,这样就有可能造成Blocking;
另外写数据需要空闲物理内存页面,如果没有的话则需要执行
LazyWrite并分配新的内存,这也会造成阻塞。
CcCanIWrite
这个函数很有意思,由于在Write接口中的函数有可能会由于空闲
页面不足(即Dirty Page过多)造成等待,所以可以用这个函数
测试一下当前能否执行不阻塞的写操作,这个测试是建议性的,
如果测试完了立刻有个别的家伙又写了一堆东西,则随后的写操作
可能还是会阻塞,不过由于这个竞争情况不会影响程序的正确性,
只是会导致阻塞,所以通常不需要关心。
CcDeferWrite
请求Cache管理器调度一个事件,在能够进行写操作的时候执行
一个回调,通常就是文件系统的写入口函数。
Pin接口
和Copy接口不同,Copy接口始终是使用FileObject和Offset来
寻址Cache管理器管理的内存,而不是直接用指针,而Pin接口
则提供了Pin功能,这样文件系统就可以把Cache管理器管理的
内存’钉’在内存里,随后就可以用指针来寻址了。
Pin需要经过两个步骤:Map、Pin。
CcMapData//
CcPinMappedData//
CcPinRead//
CcSetDirtyPinnedData//
CcPreparePinWrite//
提供这个函数可以认为是一种优化措施,和CcPinRead相比,如果
参数中指定的区域刚好在Page边界上,则Cache管理器不会从二级
存储器中读入该页,因此这个函数返回的缓冲区不能用来读取数据。
CcUnpinData/CcUnPinDataForThread。
注意在Pin期间,在调用者线程上下文中不会有IO发生,所有的IO
都是异步的。
/section{MDL接口}
CcMdlRead//
CcMdlReadComplete//
CcPrepareMdlWrite//
CcMdlWriteComplete//
CcMdlRead和CcPrepareMdlWrite会返回一个MDL链表,这个链表在
对应的Complete函数中释放,所以这两对函数必须成对调用,以
避免内存泄漏。
/section{Cache管理器和VMM的交互}
初始化CacheMap时调用MmCreateSection,扩展文件流时调用
MmExtendSection。
这里有很多在DDK中都没有公开的函数,倒是作为系统服务导出给
ntdll了,例如MmCreateSection大致等于ZwCreateSection。
从工作集管理上看,Cache管理器对VMM来说是一个普通的客户,
即它和普通进程一样,其Working Set要经过VMM的修剪。
预读取模块
后写模块
文件子系统
文件系统不能区分两种请求,一种是应用程序调用产生的读写
请求,一种是由于Cache管理器进行Copy时的页面故障导致VMM向
文件系统发出的读写请求。
几种重要的同步对象
FSD锁(MainResource)
PagingIoResource
和文件流相关的字节范围锁
机会锁(oplock)
[返回顶部]
——————————————————————————–
2004-04-22 20:21:59
主题: 内核对象和执行体对象
内核和执行体具有一些同名对象,例如Event、Mutant、Process、Thread等,
通常的说法是执行体对象是对零个或者多个内核对象的封装,这个说法不妨
可以再直白一些:
/begin{verbatim}
class EPROCESS :
public KPROCESS,
public OBJECT_HEADER
{
};
class KPROCESS :
public DISPATCHER_HEADER
{
};
/end{verbatim}
写成这样,为什么说Windows的进程对象(以及线程对象,道理类似)都是
可以等待的,应该就很清楚了吧:因为它继承了DISPATCH/_HEADER对象,而
后者正是实现可等待对象(用Windows自己的话,大概应该叫`可调度对象’吧)
的关键。
也就是说,在一个执行体进程对象中,包含了一个用来给内核管理用的
KPROCESS对象,一个用来使进程对象能够参与同步的DISPATCHER/_HEADER
头部对象,和一个给对象管理器用来实施管理的OBJECT/_HEADER结构。
其它执行体对象,例如Event、FileObject等都可以进行类似分析。