|
|
//+---------------------------------------------------------------------------
//
// Microsoft Windows
// Copyright (C) Microsoft Corporation, 1992 - 1992.
//
// File: cache.cxx
//
// Contents: Stream cache code
//
// Classes:
//
// Functions:
//
// History: 26-May-93 PhilipLa Created
//
//----------------------------------------------------------------------------
#include "msfhead.cxx"
#pragma hdrstop
#include <sstream.hxx>
#include <cache.hxx>
#include <mread.hxx>
#if DBG == 1
static ULONG csTotalWalked = 0; static ULONG csSeqWalked = 0; static ULONG csRealWalked = 0; static ULONG cTotalCalls = 0; #endif
inline SECT CacheStart(SCacheEntry cache) { return cache.sect; }
inline SECT CacheEnd(SCacheEntry cache) { return cache.sect + cache.ulRunLength - 1; }
inline ULONG CacheLength(SCacheEntry cache) { return cache.ulRunLength; }
inline ULONG CacheStartOffset(SCacheEntry cache) { return cache.ulOffset; }
inline ULONG CacheEndOffset(SCacheEntry cache) { return cache.ulOffset + cache.ulRunLength - 1; }
//+---------------------------------------------------------------------------
//
// Member: CStreamCache::CheckSegment, private
//
// Synopsis: Given a bunch of information, determine if there is
// a cache hit on a given segment and set return variables
// to represent best hit.
//
// Arguments: [ulOffset] -- Offset being sought.
// [sce] -- Stream cache entry being checked
// [pulCount] -- Pointer to count of best hit so far
// [psectCache] -- Pointer to sect of best hit so far
// [pulCacheOffset] -- Pointer to offset of best hit so far
//
// Returns: TRUE if this is the best hit, FALSE otherwise.
//
// History: 11-Jul-94 PhilipLa Created
//
// Notes: This function has mega side effects. Think of it as
// a typesafe #define.
//
//----------------------------------------------------------------------------
inline BOOL CStreamCache::CheckSegment(ULONG ulOffset, SCacheEntry sce, ULONG *pulCount, SECT *psectCache, ULONG *pulCacheOffset) { if (CacheStartOffset(sce) <= ulOffset) { //We have a potential cache hit. Check the runlength to
// get the best fit.
if (ulOffset <= CacheEndOffset(sce)) { //Direct hit.
*pulCount = 0; *pulCacheOffset = ulOffset; *psectCache = CacheStart(sce) + (ulOffset - CacheStartOffset(sce)); } else { if (*pulCount > ulOffset - CacheEndOffset(sce)) { //The requested sector is past the end of the cached
// segment. Use the endpoint as the closest hit.
*pulCount = ulOffset - CacheEndOffset(sce); *psectCache = CacheEnd(sce); *pulCacheOffset = CacheEndOffset(sce); } else { return FALSE; } } msfAssert(*pulCacheOffset <= ulOffset); return TRUE; } return FALSE; }
inline CDirectory * CStreamCache::GetDir(void) { return _pmsParent->GetDir(); }
inline CFat * CStreamCache::GetFat(void) { return _pmsParent->GetFat(); }
inline CFat * CStreamCache::GetMiniFat(void) { return _pmsParent->GetMiniFat(); }
#ifdef LARGE_STREAMS
inline ULONGLONG CStreamCache::GetSize(void) #else
inline ULONG CStreamCache::GetSize(void) #endif
{ #ifdef LARGE_STREAMS
ULONGLONG ulSize = 0; #else
ULONG ulSize = 0; #endif
if (_pds != NULL) { _pds->CDirectStream::GetSize(&ulSize); } else { _pmsParent->GetSize(_sid, &ulSize); } return ulSize; }
inline SID CStreamCache::GetSid(void) { return _sid; }
//+---------------------------------------------------------------------------
//
// Member: CStreamCache::SelectFat, private
//
// Synopsis: Returns the appropriate CFat object for the cache.
// If we are a control structure, then the real fat is
// always the right one. Otherwise (we are a real stream)
// key off of size to determine whether the minifat or
// the real fat is appropriate.
//
// Arguments: None.
//
// Returns: Appropriate CFat pointer
//
// History: 16-Jun-94 PhilipLa Created
//----------------------------------------------------------------------------
inline CFat * CStreamCache::SelectFat(void) { return ((_pds == NULL) || (GetSize() >= MINISTREAMSIZE) || (GetSid() == SIDMINISTREAM)) ? GetFat() : GetMiniFat(); }
//+---------------------------------------------------------------------------
//
// Member: CStreamCache::CacheSegment, private
//
// Synopsis: Store a segment in the cache.
//
// Arguments: [pseg] -- Pointer to segment to store
//
// Returns: void
//
// History: 19-Oct-94 PhilipLa Created
//
//----------------------------------------------------------------------------
void CStreamCache::CacheSegment(SSegment *pseg) { USHORT iCache; if (_uNextCacheIndex >= CACHESIZE) { _uNextCacheIndex = 0; }
iCache = _uNextCacheIndex; _ase[iCache].ulOffset = SegStartOffset(*pseg); _ase[iCache].ulRunLength = SegLength(*pseg); _ase[iCache].sect = SegStart(*pseg); _uNextCacheIndex++; _uHighCacheIndex = (USHORT) max(_uHighCacheIndex, iCache + 1);
//_uCacheState can be used to determine if the cache has changed.
_uCacheState++; }
//+---------------------------------------------------------------------------
//
// Member: CStreamCache::GetStart, private
//
// Synopsis: Get start sector for this chain
//
// Arguments: [psectStart] -- Return location
//
// Returns: Appropriate status code
//
// History: 01-Jun-94 PhilipLa Created
//
//----------------------------------------------------------------------------
SCODE CStreamCache::GetStart(SECT *psectStart) { SCODE sc = S_OK;
if (_pds != NULL) { //We're a normal stream, so get the start sect from the
// directory.
sc = GetDir()->GetStart(_sid, psectStart); } else { //We're a control stream, so get the start sect from the
// multistream.
*psectStart = _pmsParent->GetStart(_sid); }
return sc; }
//+---------------------------------------------------------------------------
//
// Member: CStreamCache::CStreamCache, public
//
// Synopsis: CStreamCache constructor
//
// History: 14-Dec-92 PhilipLa Created
//
//----------------------------------------------------------------------------
CStreamCache::CStreamCache() { _sid = NOSTREAM; _pmsParent = NULL; _pds = NULL; _uHighCacheIndex = 0; _uNextCacheIndex = 0; _uCacheState = 0; }
CStreamCache::~CStreamCache() { #if DBG == 1
msfDebugOut((DEB_ITRACE, "Cache stats: Total = %lu Seq = %lu Real = %lu Calls = %lu\n", csTotalWalked, csSeqWalked, csRealWalked, cTotalCalls)); #endif
}
void CStreamCache::Init(CMStream *pmsParent, SID sid, CDirectStream *pds) { _pmsParent = P_TO_BP(CBasedMStreamPtr, pmsParent); _sid = sid; _pds = P_TO_BP(CBasedDirectStreamPtr, pds); Empty(); }
void CStreamCache::Empty(void) { for (USHORT uIndex = 0; uIndex < CACHESIZE; uIndex++) { _ase[uIndex].ulOffset = MAX_ULONG; _ase[uIndex].sect = ENDOFCHAIN; _ase[uIndex].ulRunLength = 0; }
_uHighCacheIndex = 0; _uNextCacheIndex = 0; _uCacheState++; }
//+---------------------------------------------------------------------------
//
// Member: CStreamCache::GetSect, public
//
// Synopsis: Retrieve a SECT from the cache given an offset
//
// Arguments: [ulOffset] -- Offset to look up.
// [psect] -- Location for return value
//
// Returns: Appropriate status code
//
// History: 26-May-93 PhilipLa Created
//
//----------------------------------------------------------------------------
SCODE CStreamCache::GetSect(ULONG ulOffset, SECT *psect) { SCODE sc = S_OK; CFat *pfat; USHORT iCache = 0; ULONG ulCount = MAX_ULONG; SECT sectCache = ENDOFCHAIN; ULONG ulCacheOffset = MAX_ULONG;
*psect = ENDOFCHAIN;
pfat = SelectFat();
for (USHORT iCacheLoop = 0; iCacheLoop < _uHighCacheIndex; iCacheLoop++) { if (CheckSegment(ulOffset, _ase[iCacheLoop], &ulCount, §Cache, &ulCacheOffset)) { //Cache hit.
} }
//We now have the best hit from the cache. If it is exact, return
// now.
if (ulCount == 0) { *psect = sectCache; return S_OK; }
if (ulCacheOffset == MAX_ULONG) { //No cache hit.
msfChk(GetStart(§Cache)); ulCacheOffset = 0; }
//Otherwise, go to the fat and get the real thing.
#if DBG == 1 && defined(CHECKLENGTH)
SECT sectStart; ULONG ulLengthOld, ulLengthNew; GetStart(§Start); pfat->GetLength(sectStart, &ulLengthOld); #endif
SSegment segtab[CSEG + 1]; ULONG ulSegCount; while (TRUE) { msfChk(pfat->Contig( segtab, FALSE, sectCache, ulOffset - ulCacheOffset + 1, &ulSegCount));
if (ulSegCount <= CSEG) { //We're done.
break; }
//We need to call Contig again. Update ulCacheOffset and
//sectCache to be the last sector in the current table.
ulCacheOffset = ulCacheOffset + SegEndOffset(segtab[CSEG - 1]); sectCache = SegEnd(segtab[CSEG - 1]); }
//Last segment is in segtab[ulSegCount - 1].
//ulSegOffset is the absolute offset within the stream of the first
//sector in the last segment.
ULONG ulSegOffset; ulSegOffset = ulCacheOffset + SegStartOffset(segtab[ulSegCount - 1]);
msfAssert(ulSegOffset <= ulOffset);
msfAssert(ulOffset < ulSegOffset + SegLength(segtab[ulSegCount - 1])); *psect = SegStart(segtab[ulSegCount - 1]) + (ulOffset - ulSegOffset);
//Now, stick the last segment into our cache. We need to update
// the ulOffset field to be the absolute offset (i.e. ulSegOffset)
// before calling CacheSegment().
segtab[ulSegCount - 1].ulOffset = ulSegOffset; CacheSegment(&(segtab[ulSegCount - 1])); #if DBG == 1 && defined(CHECKLENGTH)
//Confirm that the chain hasn't grown.
pfat->GetLength(sectStart, &ulLengthNew); msfAssert(ulLengthOld == ulLengthNew); //Confirm that we're getting the right sector.
SECT sectCheck; pfat->GetSect(sectStart, ulOffset, §Check);
msfAssert(*psect == sectCheck); #endif
Err: return sc; }
//+---------------------------------------------------------------------------
//
// Member: CStreamCache::GetESect, public
//
// Synopsis: Retrieve a SECT from the cache given an offset, extending
// the stream if necessary.
//
// Arguments: [ulOffset] -- Offset to look up.
// [psect] -- Location for return value
//
// Returns: Appropriate status code
//
// History: 26-May-93 PhilipLa Created
//
//----------------------------------------------------------------------------
SCODE CStreamCache::GetESect(ULONG ulOffset, SECT *psect) { SCODE sc = S_OK; CFat *pfat; USHORT iCache = 0; ULONG ulCount = MAX_ULONG; SECT sectCache = ENDOFCHAIN; ULONG ulCacheOffset = MAX_ULONG; USHORT uCacheHit = CACHESIZE + 1; *psect = ENDOFCHAIN;
pfat = SelectFat();
for (USHORT iCacheLoop = 0; iCacheLoop < _uHighCacheIndex; iCacheLoop++) { if (CheckSegment(ulOffset, _ase[iCacheLoop], &ulCount, §Cache, &ulCacheOffset)) { uCacheHit = iCacheLoop; //Cache hit.
} }
//We now have the best hit from the cache. If it is exact, return
// now.
if (ulCount == 0) { *psect = sectCache; return S_OK; }
if (ulCacheOffset == MAX_ULONG) { //No cache hit.
msfChk(GetStart(§Cache)); ulCacheOffset = 0; } //Otherwise, go to the fat and get the real thing.
SSegment segtab[CSEG + 1]; ULONG ulSegCount; while (TRUE) { msfChk(pfat->Contig( segtab, TRUE, sectCache, ulOffset - ulCacheOffset + 1, &ulSegCount));
if (ulSegCount <= CSEG) { //We're done.
break; }
//We need to call Contig again. Update ulCacheOffset and
//sectCache to be the last sector in the current table.
ulCacheOffset = ulCacheOffset + SegEndOffset(segtab[CSEG - 1]); sectCache = SegEnd(segtab[CSEG - 1]); }
//Last segment is in segtab[ulSegCount - 1].
//ulSegOffset is the absolute offset within the stream of the first
//sector in the last segment.
ULONG ulSegOffset; ulSegOffset = ulCacheOffset + SegStartOffset(segtab[ulSegCount - 1]);
msfAssert(ulSegOffset <= ulOffset);
msfAssert(ulOffset < ulSegOffset + SegLength(segtab[ulSegCount - 1])); *psect = SegStart(segtab[ulSegCount - 1]) + (ulOffset - ulSegOffset); segtab[ulSegCount - 1].ulOffset = ulSegOffset; //If we grew the chain with this call, we may need to merge the
// new segment with the previous best-hit in our cache.
// Otherwise, we end up with excessive fragmentation.
if ((uCacheHit != CACHESIZE + 1) && (SegStart(segtab[ulSegCount - 1]) <= CacheEnd(_ase[uCacheHit]) + 1) && (SegStart(segtab[ulSegCount - 1]) > CacheStart(_ase[uCacheHit])) && (SegStartOffset(segtab[ulSegCount - 1]) <= CacheEndOffset(_ase[uCacheHit]) + 1)) { //We can merge the two.
_ase[uCacheHit].ulRunLength += (SegLength(segtab[ulSegCount - 1]) - (CacheEnd(_ase[uCacheHit]) + 1 - SegStart(segtab[ulSegCount - 1])));
_uCacheState++; } else { //Now, stick the last segment into our cache. We need to update
// the ulOffset field to be the absolute offset (i.e. ulSegOffset)
// before calling CacheSegment().
CacheSegment(&(segtab[ulSegCount - 1])); } #if DBG == 1
//Confirm that we're getting the right sector.
SECT sectCheck; SECT sectStart; msfChk(GetStart(§Start));
pfat->GetESect(sectStart, ulOffset, §Check);
msfAssert(*psect == sectCheck); #endif
Err: return sc; }
//+---------------------------------------------------------------------------
//
// Member: CStreamCache::EmptyRegion, public
//
// Synopsis: Invalidate cached values for a segment that has been
// remapped
//
// Arguments: [oStart] -- Offset marking first remapped sector
// [oEnd] -- Offset marking last remapped sector
//
// Returns: Appropriate status code
//
// History: 26-May-93 PhilipLa Created
//
//----------------------------------------------------------------------------
SCODE CStreamCache::EmptyRegion(ULONG oStart, ULONG oEnd) { for (USHORT i = 0; i < CACHESIZE; i ++) { ULONG ulStart = CacheStartOffset(_ase[i]); ULONG ulEnd = CacheEndOffset(_ase[i]); if ((ulStart <= oEnd) && (ulEnd >= oStart)) { //There are 3 possible cases:
// 1) The cache entry is completely contained in the
// region being invalidated.
// 2) The front part of the cache entry is contained in
// the region being invalidated.
// 3) The tail part of the cache entry is contained in
// the region being validated.
if ((oStart <= ulStart) && (oEnd >= ulEnd)) { //Invalidate the entire thing.
_ase[i].ulOffset = MAX_ULONG; _ase[i].sect = ENDOFCHAIN; _ase[i].ulRunLength = 0; } else if (oStart <= ulStart) { #if DBG == 1
ULONG ulCacheStart = _ase[i].ulOffset; SECT sectCache = _ase[i].sect; ULONG ulCacheLength = _ase[i].ulRunLength; #endif
//Invalidate the front of the cache entry
ULONG ulInvalid; ulInvalid = oEnd - ulStart + 1;
_ase[i].ulOffset += ulInvalid; msfAssert(_ase[i].ulRunLength > ulInvalid); _ase[i].sect += ulInvalid; _ase[i].ulRunLength -= ulInvalid; #if DBG == 1
//Make sure our cache is still within the old valid range.
msfAssert((_ase[i].ulOffset >= ulCacheStart) && (_ase[i].ulOffset <= ulCacheStart + ulCacheLength - 1)); msfAssert(_ase[i].ulRunLength <= ulCacheLength); msfAssert(_ase[i].ulRunLength > 0); msfAssert((_ase[i].sect >= sectCache) && (_ase[i].sect <= sectCache + ulCacheLength - 1)); #endif
} else { #if DBG == 1
ULONG ulCacheStart = _ase[i].ulOffset; SECT sectCache = _ase[i].sect; ULONG ulCacheLength = _ase[i].ulRunLength; #endif
//Invalidate the tail of the cache entry
ULONG ulInvalid; ulInvalid = ulEnd - oStart + 1; msfAssert(_ase[i].ulRunLength > ulInvalid); _ase[i].ulRunLength -= ulInvalid; #if DBG == 1
//Make sure our cache is still within the old valid range.
msfAssert((_ase[i].ulOffset >= ulCacheStart) && (_ase[i].ulOffset <= ulCacheStart + ulCacheLength - 1)); msfAssert(_ase[i].ulRunLength <= ulCacheLength); msfAssert(_ase[i].ulRunLength > 0); msfAssert((_ase[i].sect >= sectCache) && (_ase[i].sect <= sectCache + ulCacheLength - 1)); #endif
} _uCacheState++; } }
return S_OK; }
//+---------------------------------------------------------------------------
//
// Member: CStreamCache::Contig, public
//
// Synopsis: Return a contig table from a given offset and runlength
//
// Arguments: [ulOffset] -- Offset to begin contiguity check at
// [fWrite] -- TRUE if segment is to be written to.
// [aseg] -- Pointer to SSegment array
// [ulLength] -- Run length of sectors to return table for
//
// Returns: Appropriate status code
//
// History: 21-Apr-94 PhilipLa Created
//
//----------------------------------------------------------------------------
SCODE CStreamCache::Contig( ULONG ulOffset, BOOL fWrite, SSegment STACKBASED *aseg, ULONG ulLength, ULONG *pcSeg) { SCODE sc; msfDebugOut((DEB_ITRACE, "In CStreamCache::Contig:%p()\n", this)); SECT sect; USHORT uCacheState; CFat *pfat;
for (USHORT iCache = 0; iCache < _uHighCacheIndex; iCache++) { //Look for direct hit.
if ((ulOffset >= _ase[iCache].ulOffset) && (ulOffset < _ase[iCache].ulOffset + _ase[iCache].ulRunLength)) { //Direct hit. Return this segment.
ULONG ulCacheOffset = ulOffset - _ase[iCache].ulOffset; aseg[0].ulOffset = ulOffset; aseg[0].sectStart = _ase[iCache].sect + ulCacheOffset; aseg[0].cSect = _ase[iCache].ulRunLength - ulCacheOffset; *pcSeg = 1; return S_OK; } }
uCacheState = _uCacheState; if (fWrite) { //This can grow the chain, so get the whole thing at once
// instead of one sector at a time. Chances are good that
// the second GetESect call will be fed from the cache, so
// this won't be too expensive in the common case.
//Ideally, we'd like to make this first call only when we
// know the stream is growing. There isn't a convenient
// way to detect that here, though.
msfChk(GetESect(ulOffset + ulLength - 1, §)); msfChk(GetESect(ulOffset, §)); } else { msfChk(GetSect(ulOffset, §)); }
//The GetSect() or GetESect() call may have actually snagged the
// segment we need, so check the cache again. If _uCacheState
// changed in the duration of the call, we know that something
// new is in the cache, so we go look again.
if (uCacheState != _uCacheState) { for (USHORT iCache = 0; iCache < _uHighCacheIndex; iCache++) { //Look for direct hit.
if ((ulOffset >= _ase[iCache].ulOffset) && (ulOffset < _ase[iCache].ulOffset + _ase[iCache].ulRunLength)) { //Direct hit. Return this segment.
ULONG ulCacheOffset = ulOffset - _ase[iCache].ulOffset; aseg[0].ulOffset = ulOffset; aseg[0].sectStart = _ase[iCache].sect + ulCacheOffset; aseg[0].cSect = _ase[iCache].ulRunLength - ulCacheOffset; *pcSeg = 1; return S_OK; } } }
pfat = SelectFat(); msfChk(pfat->Contig(aseg, fWrite, sect, ulLength, pcSeg));
//At this point, we can peek at the contig table and pick out
// the choice entries to put in the cache.
//For the first pass, we just grab the last thing in the Contig
//table and cache it.
aseg[*pcSeg - 1].ulOffset += ulOffset; CacheSegment(&(aseg[*pcSeg - 1])); Err: return sc; }
//+---------------------------------------------------------------------------
//
// Member: CStreamCache::Allocate, public
//
// Synopsis: Allocate a new chain for a stream, returning the start
// sector and caching the appropriate amount of contig
// information.
//
// Arguments: [pfat] -- Pointer to fat to allocate in
// [cSect] -- Number of sectors to allocate
// [psectStart] -- Returns starts sector for chain
//
// Returns: Appropriate status code
//
// History: 19-Oct-94 PhilipLa Created
//
//----------------------------------------------------------------------------
SCODE CStreamCache::Allocate(CFat *pfat, ULONG cSect, SECT *psectStart) { SCODE sc;
msfAssert((_uHighCacheIndex == 0) && aMsg("Called Allocate with non-empty buffer"));
#ifndef CACHE_ALLOCATE_OPTIMIZATION
//This will allocate the complete requested chain. We'll then
// walk over that chain again in the Contig() call, which isn't
// optimal. Ideally, we'd like GetFree() to return us the
// contiguity information, but that's a fairly major change.
// Consider it for future optimization work.
msfChk(pfat->GetFree(cSect, psectStart, GF_WRITE)); #else
//Get the first sector (to simplify Contig code)
//First reserve enough free sectors for the whole thing.
msfChk(pfat->ReserveSects(cSect)); msfChk(pfat->GetFree(1, psectStart, GF_WRITE)); #endif
SSegment segtab[CSEG + 1]; ULONG ulSegCount; ULONG ulSegStart; SECT sectSegStart;
sectSegStart = *psectStart; ulSegStart = 0; while (TRUE) { msfChk(pfat->Contig( segtab, TRUE, sectSegStart, cSect - ulSegStart, &ulSegCount));
if (ulSegCount <= CSEG) { //We're done.
break; }
//We need to call Contig again. Update ulSegStart and
//sectSegStart to be the last sector in the current table.
ulSegStart = ulSegStart + SegEndOffset(segtab[CSEG - 1]); sectSegStart = SegEnd(segtab[CSEG - 1]); }
//Last segment is in segtab[ulSegCount - 1].
//ulSegOffset is the absolute offset within the stream of the first
//sector in the last segment.
ULONG ulSegOffset; ulSegOffset = ulSegStart + SegStartOffset(segtab[ulSegCount - 1]);
//Now, stick the last segment into our cache. We need to update
// the ulOffset field to be the absolute offset (i.e. ulSegOffset)
// before calling CacheSegment().
segtab[ulSegCount - 1].ulOffset = ulSegOffset; CacheSegment(&(segtab[ulSegCount - 1]));
#if DBG == 1 && defined(CHECKLENGTH)
ULONG ulLength;
pfat->GetLength(*psectStart, &ulLength); msfAssert(ulLength == cSect); #endif
Err: return sc; }
|