2025-04-27 07:49:33 -04:00

566 lines
16 KiB
C++

/*==========================================================================*\
Module: smblockh.cpp
Copyright Microsoft Corporation 1996, All Rights Reserved.
Author: mikepurt
Descriptions:
\*==========================================================================*/
#include "_shmem.h"
/*$--CSharedMemoryBlockHeap::CSharedMemoryBlockHeap=========================*\
\*==========================================================================*/
CSharedMemoryBlockHeap::CSharedMemoryBlockHeap()
{
m_cbBlockSize = 0;
m_iSMSFirstFree = 0;
m_cSMSMac = 0;
m_cSMS = 0;
m_rgpSMS = NULL;
m_wszInstanceName[0] = L'\0';
m_hmtxGrow = NULL;
}
/*$--CSharedMemoryBlockHeap::~CSharedMemoryBlockHeap========================*\
\*==========================================================================*/
CSharedMemoryBlockHeap::~CSharedMemoryBlockHeap()
{
Deinitialize();
}
/*$--CSharedMemoryBlockHeap::FInitialize====================================*\
pwszInstanceName: this is used as the root of the shared memory segment names
cbBlockSize: this is the size of the memory blocks that this block heap
will be serving up.
returns: TRUE on success, FALSE on error. Use GetLastError for
more information in case of failure.
\*==========================================================================*/
BOOL
CSharedMemoryBlockHeap::FInitialize(IN LPCWSTR pwszInstanceName,
IN DWORD cbBlockSize)
{
BOOL fSuccess = FALSE;
WCHAR wszBindingExtension[MAX_PATH];
Assert(cbBlockSize);
Assert(wcslen(pwszInstanceName) < MAX_PATH);
m_cbBlockSize = cbBlockSize;
m_iSMSFirstFree = 0;
m_cSMSMac = 0;
m_cSMS = 0;
m_rgpSMS = NULL;
m_wszInstanceName[0] = L'\0';
if (wcslen(pwszInstanceName) >= MAX_PATH)
{
SetLastError(ERROR_INVALID_PARAMETER);
goto Exit;
}
else
lstrcpyW(m_wszInstanceName, pwszInstanceName);
_ultow(m_cbBlockSize, wszBindingExtension, 16 /* numeric base */);
lstrcatW(wszBindingExtension, L"_GrowMutex");
//
// Now that we have a binding extension, let's create the mutex.
//
m_hmtxGrow = BindToMutex(pwszInstanceName,
wszBindingExtension,
FALSE,
NULL);
if (NULL == m_hmtxGrow)
goto Exit;
if (!m_rwl.FInitialize())
goto Exit;
fSuccess = TRUE;
Exit:
return fSuccess;
}
/*$--CSharedMemoryBlockHeap::Deinitialize===================================*\
Call Reset and zero some things out.
\*==========================================================================*/
void
CSharedMemoryBlockHeap::Deinitialize()
{
if (m_cSMS)
{
Assert(m_cSMS <= m_cSMSMac);
Assert(m_rgpSMS);
while(m_cSMS)
{
Assert(m_rgpSMS[m_cSMS-1]);
delete m_rgpSMS[--m_cSMS];
}
SharedMemory::Free(m_rgpSMS);
m_rgpSMS = NULL;
}
m_iSMSFirstFree = 0;
m_cSMSMac = 0;
m_cSMS = 0;
m_cbBlockSize = 0;
m_wszInstanceName[0] = L'\0';
if (m_hmtxGrow)
CloseHandle(m_hmtxGrow);
m_hmtxGrow = NULL;
}
/*$--CSharedMemoryBlockHeap::FreeListFromSegmentId==========================*\
FreeLists are stored in the first block of the first SMS. There are as many
FreeLists in this first block as space for them permits. If there's room
for 8 FreeLists, then the FreeLists for SMSs 0 through 7 will be stored
here. The Freelists for SMSs 8 through 15 will then be stored in the first
block on SMS 8. And so on. This keeps the spaced used for the FreeLists to
a minimum.
This method returns the virtual address of the FreeList for a given Segment.
It's necessary to pass in a pointer to that shared memory so that in the case
that the FreeList is on the same Segment we have a pointer to calculate
the location of the FreeList from. This is because this SMS hasn't
been registered with CSharedMemoryBlockHeap yet (it's initializing right
now).
\*==========================================================================*/
FreeList *
CSharedMemoryBlockHeap::FreeListFromSegmentId(IN DWORD dwSegmentId,
IN BYTE * pbMappedView)
{
FreeList * pfl = NULL;
DWORD dwFreeListSegment = (DWORD)(dwSegmentId / FreeListsPerSegment(m_cbBlockSize)) * FreeListsPerSegment(m_cbBlockSize);
DWORD iFreeList = dwSegmentId % FreeListsPerSegment(m_cbBlockSize);
Assert(pbMappedView);
//
// We must get a read lock to access the m_rgpSMS array.
//
m_rwl.ReadLock();
Assert((0 == iFreeList) || (iFreeList && (dwFreeListSegment < m_cSMS)));
//
// If we're creating the segment that the FreeList is on, it won't be
// in the segment array yet, so we'll use the pointer that was passed
// in to this method. Otherwise we calculate the location of the
// FreeList.
//
if (0 == iFreeList)
//
// In this case it's the first FreeList in the block
//
pfl = (FreeList *)pbMappedView;
else
pfl = (FreeList *)(m_rgpSMS[dwFreeListSegment]->PbGetMappedView() +
sizeof(FreeList) * iFreeList);
m_rwl.ReadUnlock();
return pfl;
}
/*$--CSharedMemoryBlockHeap::FGrowHeap======================================*\
This is called from several other place to request that another SMS be
added. The cCurrentSegments count is the value of m_cSMS that the decision
to call FGrowHeap was based on. So if (m_cSMS > cCurrentSegments) any longer
then we just return TRUE to indicate success without adding another SMS.
This prevent too many SMSs from being added because of race condition.
If the caller still needs another SMS, the caller will call again.
FALSE is only returned if there's an error in attempting to add another SMS.
GetLastError() can be used to get more information about the failure.
\*==========================================================================*/
BOOL
CSharedMemoryBlockHeap::FGrowHeap(IN DWORD cCurrentSegments)
{
BOOL fSuccess = FALSE;
BOOL fMutexAcquired = FALSE;
DWORD dwLastError = NO_ERROR;
PVOID pvTemp = NULL;
DWORD cSMSTemp = 0;
CSharedMemorySegment * pSMS = NULL;
//
// We need to get a write lock in order to modify m_rgpSMS and friends
//
m_rwl.WriteLock();
//
// Now we have exclusive access to m_rgpSMS, m_cSMS, and m_cSMSMac
//
//
// Check to see if what they thought was a current count of the segments
// changed because someone else just grew the heap.
//
if (cCurrentSegments < m_cSMS)
{
//
// This is a special case of success.
// We don't want to Grow the heap if someone else just did.
// If we still need to grow, they'll just call us again.
//
fSuccess = TRUE;
goto Exit;
}
//
// Does the array need to be grown?
//
if (m_cSMS + 1 > m_cSMSMac)
{
//
// Double the array size if it's already been allocated, otherwise
// try the initial array size.
//
cSMSTemp = (m_cSMSMac ? (m_cSMSMac * 2) : INITIAL_SMS_ARRAY_SIZE);
pvTemp = SharedMemory::PvReAllocate(m_rgpSMS,
cSMSTemp * sizeof(CSharedMemorySegment *));
if (!pvTemp)
{
SetLastError(ERROR_NOT_ENOUGH_MEMORY);
goto Exit;
}
//
// Now commit the new values to the class member vars
//
m_rgpSMS = (CSharedMemorySegment **)pvTemp;
m_cSMSMac = cSMSTemp;
}
//
// Create a new SMS and tell it who owns it so it can find its FreeList.
//
pSMS = new CSharedMemorySegment(this);
if (NULL == pSMS)
{
SetLastError(ERROR_NOT_ENOUGH_MEMORY);
goto Exit;
}
//
// We must now acquire the grow mutex so that any other processes that might
// be growing this block heap in their process won't collide with us when
// we initialize the shared memory (if we're the creators of the mapping).
//
if (!FAcquireMutex(m_hmtxGrow))
goto Exit;
fMutexAcquired = TRUE;
if (!pSMS->FInitialize(m_wszInstanceName,
m_cbBlockSize,
m_cSMS))
goto Exit;
//
// It worked, so now add it to the SMS array.
//
m_rgpSMS[m_cSMS++] = pSMS;
pSMS = NULL;
Assert(m_cSMS <= m_cSMSMac);
fSuccess = TRUE;
Exit:
dwLastError = GetLastError(); // Preserve LastError
if (fMutexAcquired)
FReleaseMutex(m_hmtxGrow);
m_rwl.WriteUnlock();
//
// If the SMS initialization failed this will be non-NULL, delete the SMS.
//
if (pSMS)
delete pSMS;
SetLastError(dwLastError); // Restore LastError
return fSuccess;
}
/*$--CSharedMemoryBlockHeap::Free===========================================*\
For this we essentially identify which SMS the block was allocated on and
forward the call to that SMS.
We may need to grow the heap to get the SMS opened that the block was
allocated from.
We also update a hint we use to find free blocks in the lowest numbered
SMSs. With high allocation turnover, this will help maintain a good
locallity of access amoung allocated blocks and help to minimize our
working set.
\*==========================================================================*/
void
CSharedMemoryBlockHeap::Free(IN SHMEMHANDLE hSMBA)
{
DWORD iFirstFree = 0;
DWORD dwSegmentId = SegmentIdFromSMBA(hSMBA);
DWORD cSMS = m_cSMS;
//
// Make sure we've got this segment opened.
// If it was allocated on the heap from within another process, we may not
// have open this segment yet.
//
while(dwSegmentId >= cSMS)
{
//
// cSMS is the SMS count that we based our decision upon to request that
// the heap be grown.
//
if (!FGrowHeap(cSMS))
goto Exit;
cSMS = m_cSMS;
}
//
// Get a read lock so that m_rgpSMS doesn't changed underneath us.
//
m_rwl.ReadLock();
//
// Free the block from it's segment.
//
m_rgpSMS[dwSegmentId]->Free(hSMBA);
//
// Now let's update our hint
//
iFirstFree = m_iSMSFirstFree;
if (dwSegmentId < iFirstFree)
//
// If someone else hasn't changed it, let's update it.
//
InterlockedCompareExchange((LONG *)&m_iSMSFirstFree,
dwSegmentId,
iFirstFree);
//
// We won't spin on this. If someone else modified it before we did, that's
// fine. We don't need to be perfect about this.
//
m_rwl.ReadUnlock();
Exit:
return;
}
/*$--CSharedMemoryBlockHeap::PvAlloc========================================*\
Find an SMS to allocate a block from. On error, NULL is returned and
GetLastError() can be used to get more information.
\*==========================================================================*/
PVOID
CSharedMemoryBlockHeap::PvAlloc(OUT SHMEMHANDLE * phSMBA)
{
PVOID pvReturn = NULL;
BOOL fReadLockEntered = FALSE;
DWORD iSMS = 0;
DWORD iFirstFree = 0;
DWORD cSMS = 0;
Assert(phSMBA);
*phSMBA = NULL;
//
// prime the pump if it needs priming...
//
if (0 == m_cSMS)
{
if (!FGrowHeap(0))
{
Assert(FALSE);
goto Exit;
}
}
while(NULL == pvReturn)
{
//
// Get a read lock so that m_rgpSMS doesn't change beneath us.
//
m_rwl.ReadLock();
fReadLockEntered = TRUE;
//
// We'll start looking in the lowest segment that was recently freed
//
iFirstFree = m_iSMSFirstFree;
//
// Now try an allocation on that SMS
//
pvReturn = m_rgpSMS[iFirstFree]->PvAlloc(phSMBA);
if (pvReturn)
break;
//
// That didn't work, so let's start there and work our way up the
// segment numbers and then start at 0 up to our hinted SMS.
//
for (iSMS = (iFirstFree + 1) % m_cSMS;
iSMS != iFirstFree;
iSMS = (iSMS + 1) % m_cSMS)
{
pvReturn = m_rgpSMS[iSMS]->PvAlloc(phSMBA);
if (pvReturn)
break;
}
//
// We successfully allocate something, so break out of here.
//
if (pvReturn)
break;
//
// We've not had any success, so let's try to grow the heap.
// First make a copy of m_cSMS so FGrowHeap knows what value of m_cSMS
// we were working from.
//
cSMS = m_cSMS;
//
// Now temporarily leave the read lock so we can attempt to grow the heap
//
m_rwl.ReadUnlock();
fReadLockEntered = FALSE;
//
// It won't actually grow if (cSMS < m_cSMS)
//
if (!FGrowHeap(cSMS))
{
pvReturn = NULL;
goto Exit;
}
//
// Ok, so let's try this again. We'll re-acquire the read lock at the
// top of the loop.
//
}
//
// Since m_iSMSFirstFree is only a hint, it's not critical that we're
// exact about doing this.
//
if (iSMS > iFirstFree)
//
// If someone else hasn't changed it, let's update it.
//
InterlockedCompareExchange((LONG *)&m_iSMSFirstFree,
iSMS,
iFirstFree);
//
// We won't spin on this. If someone else modified it before we did, that's
// fine. We don't need to be perfect about this.
//
Exit:
if (fReadLockEntered)
m_rwl.ReadUnlock();
return pvReturn;
}
/*$--CSharedMemoryBlockHeap::PvFromSMBA=====================================*\
This returns the virtual address of a shared memory block.
It may need to grow the heap, since the referred to block may have been
allocated in another process.
The growing of the heap may result in an error and a NULL value being returned.
If the case of an error, GetLastError() can be used to get more information.
\*==========================================================================*/
PVOID
CSharedMemoryBlockHeap::PvFromSMBA(IN SHMEMHANDLE hSMBA)
{
PVOID pvReturn = NULL;
DWORD dwSegmentId = SegmentIdFromSMBA(hSMBA);
DWORD cSMS = m_cSMS;
//
// Make sure we've got this segment opened.
// If it was allocated on the heap from within another process, we may not
// have open this segment yet.
//
while(dwSegmentId >= cSMS)
{
//
// cSMS is the SMS count that we based our decision upon to request that
// the heap be grown.
//
if (!FGrowHeap(cSMS))
goto Exit;
cSMS = m_cSMS;
}
//
// Get a read lock so that m_rgpSMS doesn't changed underneath us.
//
m_rwl.ReadLock();
//
// Forward the call to the SMS that owns the block.
//
pvReturn = m_rgpSMS[dwSegmentId]->PvFromSMBA(hSMBA);
m_rwl.ReadUnlock();
Exit:
return pvReturn;
}