Source code of Windows XP (NT5)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1464 lines
37 KiB

  1. /*++
  2. Copyright (C) Microsoft Corporation, 1997 - 1999
  3. Module Name:
  4. bcache.cxx
  5. Abstract:
  6. RPC's buffer cache implementation
  7. Author:
  8. Mario Goertzel [MarioGo]
  9. Revision History:
  10. MarioGo 9/7/1997 Bits 'n pieces
  11. KamenM 5/15/2001 Rewrite the paged bcache implementation
  12. --*/
  13. #include <precomp.hxx>
  14. ////////////////////////////////////////////////////////////
  15. // (Internal) Perf counters
  16. //#define BUFFER_CACHE_STATS
  17. #ifdef BUFFER_CACHE_STATS
  18. LONG cAllocs = 0;
  19. LONG cFrees = 0;
  20. LONG cAllocsMissed = 0;
  21. LONG cFreesBack = 0;
  22. #define INC_STAT(x) InterlockedIncrement(&x)
  23. #else
  24. #define INC_STAT(x)
  25. #endif
  26. ////////////////////////////////////////////////////////////
  27. typedef BCACHE_STATE *PBCTLS;
  28. ////////////////////////////////////////////////////////////
  29. // Default (non-Guard Page mode) hints
  30. CONST BUFFER_CACHE_HINTS gCacheHints[4] =
  31. {
  32. // 64 bits and WOW6432 use larger message size
  33. #if defined(_WIN64) || defined(USE_LPC6432)
  34. {1, 4, 512}, // LRPC message size and small calls
  35. #else
  36. {1, 4, 256}, // LRPC message size and small calls
  37. #endif
  38. {1, 3, 1024}, // Default CO receive size
  39. {1, 3, 4096+44}, // Default UDP receive size
  40. {1, 3, 5840} // Maximum CO fragment size
  41. };
  42. // Guard page mode hints. Note that the sizes are updated during
  43. // init to the real page size.
  44. BUFFER_CACHE_HINTS gPagedBCacheHints[4] =
  45. {
  46. {1, 4, 4096 - sizeof(BUFFER_HEAD)}, // Forced to 1 page - header during init
  47. {1, 3, 8192 - sizeof(BUFFER_HEAD)}, // Forced to 2 pages - header during init
  48. {0, 0, 0}, // Not used
  49. {0, 0, 0} // Not used
  50. };
  51. BUFFER_CACHE_HINTS *pHints = (BUFFER_CACHE_HINTS *)gCacheHints;
  52. BOOL fPagedBCacheMode = FALSE;
  53. BCACHE *gBufferCache;
  54. static const RPC_CHAR *PAGED_BCACHE_KEY = RPC_CONST_STRING("Software\\Microsoft\\Rpc\\PagedBuffers");
  55. const size_t PagedBCacheSectionSize = 64 * 1024; // 64K
  56. #if DBG
  57. #define ASSERT_VALID_PAGED_BCACHE (VerifyPagedBCacheState())
  58. #else // DBG
  59. #define ASSERT_VALID_PAGED_BCACHE
  60. #endif // DBG
  61. // uncomment this for full verification. Note that it generates many first
  62. // time AVs in the debugger (benign but annoying)
  63. //#define FULL_PAGED_BCACHE_VERIFY
  64. BCACHE::BCACHE( OUT RPC_STATUS &status)
  65. // The default process heap lock spin count. This lock is held only
  66. // for a very short time while pushing/poping into a singly linked list.
  67. // PERF: move to a user-mode slist implementation if available.
  68. : _csBufferCacheLock(&status, TRUE, 4000)
  69. {
  70. // Determine guard page mode (or not)
  71. HKEY h = 0;
  72. DWORD statusT = RegOpenKeyExW(HKEY_LOCAL_MACHINE,
  73. (PWSTR)PAGED_BCACHE_KEY,
  74. 0,
  75. KEY_READ,
  76. &h);
  77. if (statusT != ERROR_FILE_NOT_FOUND)
  78. {
  79. // paged bcache mode!
  80. fPagedBCacheMode = TRUE;
  81. gPagedBCacheHints[0].cSize = gPageSize - sizeof(BUFFER_HEAD);
  82. gPagedBCacheHints[1].cSize = 2 * gPageSize - sizeof(BUFFER_HEAD);
  83. pHints = gPagedBCacheHints;
  84. RpcpInitializeListHead(&Sections);
  85. if (h)
  86. {
  87. RegCloseKey(h);
  88. }
  89. }
  90. // Compute the per cache size default buffer cache cap.
  91. // This only matters for the default mode.
  92. UINT cCapBytes = 20 * 1024; // Start at 20KB for UP workstations.
  93. if (gfServerPlatform) cCapBytes *= 2; // *2 for servers
  94. if (gNumberOfProcessors > 1) cCapBytes *= 2; // *2 for MP boxes
  95. for (int i = 0; i < 4; i++)
  96. {
  97. _bcGlobalState[i].cBlocks= 0;
  98. _bcGlobalState[i].pList = 0;
  99. if (fPagedBCacheMode)
  100. {
  101. _bcGlobalStats[i].cBufferCacheCap = 0;
  102. _bcGlobalStats[i].cAllocationHits = 0;
  103. _bcGlobalStats[i].cAllocationMisses = 0;
  104. }
  105. else
  106. {
  107. _bcGlobalStats[i].cBufferCacheCap = cCapBytes / pHints[i].cSize;
  108. // We keeps stats on process wide cache hits and misses from the
  109. // cache. We initially give credit for 2x allocations required
  110. // to load the cache. Any adjustments to the cap, up only, occur
  111. // in ::FreeHelper.
  112. _bcGlobalStats[i].cAllocationHits = _bcGlobalStats[i].cBufferCacheCap * 2*8;
  113. _bcGlobalStats[i].cAllocationMisses = 0;
  114. }
  115. }
  116. return;
  117. }
  118. BCACHE::~BCACHE()
  119. {
  120. // There is only one and it lives forever.
  121. ASSERT(0);
  122. }
  123. PVOID
  124. BCACHE::Allocate(CONST size_t cSize)
  125. {
  126. PBUFFER pBuffer;
  127. int index;
  128. INC_STAT(cAllocs);
  129. // Find the right bucket, if any. Binary search.
  130. if (cSize <= pHints[1].cSize)
  131. {
  132. if (cSize <= pHints[0].cSize)
  133. {
  134. index = 0;
  135. }
  136. else
  137. {
  138. index = 1;
  139. }
  140. }
  141. else
  142. {
  143. if (cSize <= pHints[2].cSize)
  144. {
  145. index = 2;
  146. }
  147. else
  148. {
  149. if (cSize <= pHints[3].cSize)
  150. {
  151. index = 3;
  152. }
  153. else
  154. {
  155. return(AllocHelper(cSize,
  156. -1, // Index
  157. 0 // per thread cache
  158. ));
  159. }
  160. }
  161. }
  162. // Try the per-thread cache, this is the 90% case
  163. THREAD *pThread = RpcpGetThreadPointer();
  164. ASSERT(pThread);
  165. PBCTLS pbctls = pThread->BufferCache;
  166. if (pbctls[index].pList)
  167. {
  168. // we shouldn't have anything in the thread cache in paged bcache mode
  169. ASSERT(fPagedBCacheMode == FALSE);
  170. ASSERT(pbctls[index].cBlocks);
  171. pBuffer = pbctls[index].pList;
  172. pbctls[index].pList = pBuffer->pNext;
  173. pbctls[index].cBlocks--;
  174. pBuffer->index = index + 1;
  175. LogEvent(SU_BCACHE, EV_BUFFER_OUT, pBuffer, 0, index, 1, 2);
  176. return((PVOID)(pBuffer + 1));
  177. }
  178. // This is the 10% case
  179. INC_STAT(cAllocsMissed);
  180. return(AllocHelper(cSize, index, pbctls));
  181. }
  182. PVOID
  183. BCACHE::AllocHelper(
  184. IN size_t cSize,
  185. IN INT index,
  186. PBCTLS pbctls
  187. )
  188. /*++
  189. Routine Description:
  190. Called by BCACHE:Alloc on either large buffers (index == -1)
  191. or when the per-thread cache is empty.
  192. Arguments:
  193. cSize - Size of the block to allocate.
  194. index - The bucket index for this size of block
  195. pbctls - The per-thread cache, NULL iff index == -1.
  196. Return Value:
  197. 0 - out of memory
  198. non-zero - A pointer to a block at least 'cSize' bytes long. The returned
  199. pointer is to the user portion of the block.
  200. --*/
  201. {
  202. PBUFFER pBuffer = NULL;
  203. LIST_ENTRY *CurrentListEntry;
  204. PAGED_BCACHE_SECTION_MANAGER *CurrentSection;
  205. ULONG SegmentIndex;
  206. BOOL fFoundUncommittedSegment;
  207. ULONG TargetSegmentSize;
  208. PVOID SegmentStartAddress;
  209. PVOID pTemp;
  210. BOOL Result;
  211. // Large buffers are a special case
  212. if (index == -1)
  213. {
  214. pBuffer = AllocBigBlock(cSize);
  215. if (pBuffer)
  216. {
  217. LogEvent(SU_BCACHE, EV_BUFFER_OUT, pBuffer, 0, index, 1, 2);
  218. return((PVOID(pBuffer + 1)));
  219. }
  220. LogEvent(SU_BCACHE, EV_BUFFER_FAIL, 0, 0, index, 1);
  221. return(0);
  222. }
  223. // in paged bcache mode directly try to allocate. We favor
  224. // full release over speed in order to catch the offenders
  225. // who touch memory after releasing it.
  226. if (fPagedBCacheMode)
  227. {
  228. ASSERT_VALID_PAGED_BCACHE;
  229. // Guard page mode, take care of allocations.
  230. ASSERT(index == 0 || index == 1);
  231. // First, try to get an existing section to commit something more
  232. fFoundUncommittedSegment = FALSE;
  233. TargetSegmentSize = gPageSize * (index + 1);
  234. _csBufferCacheLock.Request();
  235. CurrentListEntry = Sections.Flink;
  236. while (CurrentListEntry != &Sections)
  237. {
  238. CurrentSection = CONTAINING_RECORD(CurrentListEntry,
  239. PAGED_BCACHE_SECTION_MANAGER,
  240. SectionList);
  241. // if the section is with the same segment size as the one we need
  242. // and it has free segments, use it
  243. if (
  244. (CurrentSection->SegmentSize == TargetSegmentSize)
  245. &&
  246. (CurrentSection->NumberOfUsedSegments < CurrentSection->NumberOfSegments)
  247. )
  248. {
  249. break;
  250. }
  251. CurrentListEntry = CurrentListEntry->Flink;
  252. }
  253. if (CurrentListEntry != &Sections)
  254. {
  255. // we found something. Grab a segment, mark it as busy and attempt
  256. // to commit it outside the critical section
  257. for (SegmentIndex = 0; SegmentIndex < CurrentSection->NumberOfSegments; SegmentIndex ++)
  258. {
  259. if (CurrentSection->SegmentBusy[SegmentIndex] == FALSE)
  260. break;
  261. }
  262. // we must have found something here. Otherwise the counters are off
  263. ASSERT(SegmentIndex < CurrentSection->NumberOfSegments);
  264. CurrentSection->SegmentBusy[SegmentIndex] = TRUE;
  265. CurrentSection->NumberOfUsedSegments ++;
  266. fFoundUncommittedSegment = TRUE;
  267. }
  268. _csBufferCacheLock.Clear();
  269. if (fFoundUncommittedSegment)
  270. {
  271. SegmentStartAddress = (PBYTE)CurrentSection->VirtualMemorySection
  272. + (CurrentSection->SegmentSize + gPageSize) * SegmentIndex;
  273. pBuffer = (PBUFFER)CommitSegment(SegmentStartAddress,
  274. CurrentSection->SegmentSize
  275. );
  276. if (pBuffer == NULL)
  277. {
  278. _csBufferCacheLock.Request();
  279. CurrentSection->SegmentBusy[SegmentIndex] = FALSE;
  280. CurrentSection->NumberOfUsedSegments --;
  281. _csBufferCacheLock.Clear();
  282. LogEvent(SU_BCACHE, EV_BUFFER_FAIL, 0, 0, index, 1);
  283. ASSERT_VALID_PAGED_BCACHE;
  284. return NULL;
  285. }
  286. pBuffer = (PBUFFER)PutBufferAtEndOfAllocation(
  287. pBuffer, // Allocation
  288. CurrentSection->SegmentSize, // AllocationSize
  289. cSize + sizeof(BUFFER_HEAD) // BufferSize
  290. );
  291. pBuffer->SectionManager = CurrentSection;
  292. pBuffer->index = index + 1;
  293. ASSERT_VALID_PAGED_BCACHE;
  294. LogEvent(SU_BCACHE, EV_BUFFER_OUT, pBuffer, 0, index, 1, 2);
  295. ASSERT(IsBufferAligned(pBuffer));
  296. return (PVOID)(pBuffer + 1);
  297. }
  298. // we didn't find any section with uncommitted segments. Try to create
  299. // new sections.
  300. pBuffer = AllocPagedBCacheSection(index + 1, cSize);
  301. if (!pBuffer)
  302. {
  303. LogEvent(SU_BCACHE, EV_BUFFER_FAIL, 0, 0, index, 1);
  304. return(0);
  305. }
  306. LogEvent(SU_BCACHE, EV_BUFFER_OUT, pBuffer, 0, index, 1, 2);
  307. ASSERT(IsBufferAligned(pBuffer));
  308. return (PVOID)(pBuffer + 1);
  309. }
  310. // non guard page case
  311. // Try to allocate a process cached buffer
  312. // loop to avoid taking the mutex in the empty list case.
  313. // This allows us to opportunistically take it in the
  314. // non-empty list case only.
  315. do
  316. {
  317. if (0 == _bcGlobalState[index].pList)
  318. {
  319. // Looks like there are no global buffer available, allocate
  320. // a new buffer.
  321. ASSERT(IsBufferSizeAligned(sizeof(BUFFER_HEAD)));
  322. cSize = pHints[index].cSize + sizeof(BUFFER_HEAD);
  323. pBuffer = (PBUFFER) new BYTE[cSize];
  324. if (!pBuffer)
  325. {
  326. LogEvent(SU_BCACHE, EV_BUFFER_FAIL, 0, 0, index, 1);
  327. return(0);
  328. }
  329. _bcGlobalStats[index].cAllocationMisses++;
  330. break;
  331. }
  332. _csBufferCacheLock.Request();
  333. if (_bcGlobalState[index].pList)
  334. {
  335. ASSERT(_bcGlobalState[index].cBlocks);
  336. pBuffer = _bcGlobalState[index].pList;
  337. _bcGlobalState[index].cBlocks--;
  338. _bcGlobalStats[index].cAllocationHits++;
  339. ASSERT(pbctls[index].pList == NULL);
  340. ASSERT(pbctls[index].cBlocks == 0);
  341. PBUFFER pkeep = pBuffer;
  342. UINT cBlocksMoved = 0;
  343. while (pkeep->pNext && cBlocksMoved < pHints[index].cLowWatermark)
  344. {
  345. pkeep = pkeep->pNext;
  346. cBlocksMoved++;
  347. }
  348. pbctls[index].cBlocks = cBlocksMoved;
  349. _bcGlobalState[index].cBlocks -= cBlocksMoved;
  350. _bcGlobalStats[index].cAllocationHits += cBlocksMoved;
  351. // Now we have the head of the list to move to this
  352. // thread (pBuffer->pNext) and the tail (pkeep).
  353. // Block counts in the global state and thread state have
  354. // already been updated.
  355. pbctls[index].pList = pBuffer->pNext;
  356. ASSERT(pkeep->pNext || _bcGlobalState[index].cBlocks == 0);
  357. _bcGlobalState[index].pList = pkeep->pNext;
  358. // Break the link (if any) between the new per thread list
  359. // and the blocks which will remain in the process list.
  360. pkeep->pNext = NULL;
  361. }
  362. _csBufferCacheLock.Clear();
  363. }
  364. while (NULL == pBuffer );
  365. ASSERT(pBuffer);
  366. ASSERT(IsBufferAligned(pBuffer));
  367. pBuffer->index = index + 1;
  368. LogEvent(SU_BCACHE, EV_BUFFER_OUT, pBuffer, 0, index, 1, 2);
  369. return((PVOID(pBuffer + 1)));
  370. }
  371. VOID
  372. BCACHE::Free(PVOID p)
  373. /*++
  374. Routine Description:
  375. The fast (common) free path. For large blocks it just deletes them. For
  376. small blocks that are inserted into the thread cache. If the thread
  377. cache is too large it calls FreeHelper().
  378. Arguments:
  379. p - The pointer to free.
  380. Return Value:
  381. None
  382. --*/
  383. {
  384. PBUFFER pBuffer = ((PBUFFER )p - 1);
  385. INT index;
  386. ASSERT(((pBuffer->index >= 1) && (pBuffer->index <= 4)) || (pBuffer->index == -1));
  387. index = pBuffer->index - 1;
  388. LogEvent(SU_BCACHE, EV_BUFFER_IN, pBuffer, 0, index, 1, 2);
  389. INC_STAT(cFrees);
  390. if (index >= 0)
  391. {
  392. if (fPagedBCacheMode)
  393. {
  394. ASSERT_VALID_PAGED_BCACHE;
  395. FreeBuffers((PBUFFER)pBuffer, index, 1);
  396. ASSERT_VALID_PAGED_BCACHE;
  397. return;
  398. }
  399. // Free to thread cache
  400. THREAD *pThread = RpcpGetThreadPointer();
  401. if (NULL == pThread)
  402. {
  403. // No thread cache available - free to process cache.
  404. FreeBuffers(pBuffer, index, 1);
  405. return;
  406. }
  407. PBCTLS pbctls = pThread->BufferCache;
  408. pBuffer->pNext = pbctls[index].pList;
  409. pbctls[index].pList = pBuffer;
  410. pbctls[index].cBlocks++;
  411. if (pbctls[index].cBlocks >= pHints[index].cHighWatermark)
  412. {
  413. // 10% case - Too many blocks in the thread cache, free to process cache
  414. FreeHelper(p, index, pbctls);
  415. }
  416. }
  417. else
  418. {
  419. FreeBigBlock(pBuffer);
  420. }
  421. return;
  422. }
  423. VOID
  424. BCACHE::FreeHelper(PVOID p, INT index, PBCTLS pbctls)
  425. /*++
  426. Routine Description:
  427. Called only by Free(). Separate code to avoid unneeds saves/
  428. restores in the Free() function. Called when too many
  429. blocks are in a thread cache bucket.
  430. Arguments:
  431. p - The pointer being freed, used if pbctls is NULL
  432. index - The bucket index of this block
  433. pbctls - A pointer to the thread cache structure. If
  434. NULL the this thread has no cache (yet) p should
  435. be directly freed.
  436. Return Value:
  437. None
  438. --*/
  439. {
  440. ASSERT(pbctls[index].cBlocks == pHints[index].cHighWatermark);
  441. INC_STAT(cFreesBack);
  442. // First, build the list to free from the TLS cache
  443. // Note: We free the buffers at the *end* of the per thread cache. This helps
  444. // keep a set of buffers near this thread and (with luck) associated processor.
  445. PBUFFER ptail = pbctls[index].pList;
  446. // pbctls[index].pList contains the new keep list. (aka pBuffer)
  447. // ptail is the pointer to the *end* of the keep list.
  448. // ptail->pNext will be the head of the list to free.
  449. // One element already in keep list.
  450. ASSERT(pHints[index].cLowWatermark >= 1);
  451. for (unsigned i = 1; i < pHints[index].cLowWatermark; i++)
  452. {
  453. ptail = ptail->pNext; // Move up in the free list
  454. ASSERT(ptail);
  455. }
  456. // Save the list to free and break the link between keep list and free list.
  457. PBUFFER pfree = ptail->pNext;
  458. ptail->pNext = NULL;
  459. // Thread cache now contains on low watermark elements.
  460. pbctls[index].cBlocks = pHints[index].cLowWatermark;
  461. // Now we need to free the extra buffers to the process cache
  462. FreeBuffers(pfree, index, pHints[index].cHighWatermark - pHints[index].cLowWatermark);
  463. return;
  464. }
  465. VOID
  466. BCACHE::FreeBuffers(PBUFFER pBuffers, INT index, UINT cBuffers)
  467. /*++
  468. Routine Description:
  469. Frees a set of buffers to the global (process) cache. Maybe called when a
  470. thread has exceeded the number of buffers is wants to cache or when a
  471. thread doesn't have a thread cache but we still need to free a buffer.
  472. Arguments:
  473. pBuffers - A linked list of buffers which need to be freed.
  474. cBuffers - A count of the buffers to be freed.
  475. Return Value:
  476. None
  477. --*/
  478. {
  479. PBUFFER pfree = pBuffers;
  480. ULONG SegmentIndex;
  481. BOOL Result;
  482. PAGED_BCACHE_SECTION_MANAGER *CurrentSection;
  483. PVOID Allocation;
  484. // Special case for the freeing without a TLS blob. We're freeing just
  485. // one buffer but it's next pointer may not be NULL.
  486. if (cBuffers == 1)
  487. {
  488. pfree->pNext = 0;
  489. }
  490. // Find the end of the to free list
  491. PBUFFER ptail = pfree;
  492. while(ptail->pNext)
  493. {
  494. ptail = ptail->pNext;
  495. }
  496. // In guard page mode we switch to the alternate buffer manager
  497. // objects and uncommit the pages when moving to the global cache.
  498. if (fPagedBCacheMode)
  499. {
  500. Allocation = ConvertBufferToAllocation(pfree,
  501. TRUE // IsBufferInitialized
  502. );
  503. SegmentIndex = GetSegmentIndexFromBuffer(pfree, Allocation);
  504. CurrentSection = pfree->SectionManager;
  505. ASSERT(CurrentSection->SegmentBusy[SegmentIndex] == TRUE);
  506. ASSERT(CurrentSection->NumberOfUsedSegments > 0);
  507. pfree = (PBUFFER)Allocation;
  508. // decommit the buffer and the guard page
  509. Result = VirtualFree(
  510. pfree,
  511. CurrentSection->SegmentSize + gPageSize,
  512. MEM_DECOMMIT
  513. );
  514. ASSERT(Result);
  515. _csBufferCacheLock.Request();
  516. CurrentSection->SegmentBusy[SegmentIndex] = FALSE;
  517. CurrentSection->NumberOfUsedSegments --;
  518. if (CurrentSection->NumberOfUsedSegments == 0)
  519. {
  520. RpcpfRemoveEntryList(&CurrentSection->SectionList);
  521. _csBufferCacheLock.Clear();
  522. // free the whole section and the manager
  523. Result = VirtualFree(CurrentSection->VirtualMemorySection,
  524. 0,
  525. MEM_RELEASE
  526. );
  527. ASSERT(Result);
  528. delete CurrentSection;
  529. }
  530. else
  531. {
  532. _csBufferCacheLock.Clear();
  533. }
  534. return;
  535. }
  536. // We have a set of cBuffers buffers starting with pfree and ending with
  537. // ptail that need to move into the process wide cache now.
  538. _csBufferCacheLock.Request();
  539. // If we have too many free buffers we'll throw away these extra buffers.
  540. if (_bcGlobalState[index].cBlocks >= _bcGlobalStats[index].cBufferCacheCap)
  541. {
  542. // It looks like we have too many buffers. We can either increase the buffer
  543. // cache cap or really free the buffers.
  544. if (_bcGlobalStats[index].cAllocationHits > _bcGlobalStats[index].cAllocationMisses * 8)
  545. {
  546. // Cache hit rate looks good, we're going to really free the buffers.
  547. // Don't hold the lock while actually freeing to the heap.
  548. _csBufferCacheLock.Clear();
  549. PBUFFER psave;
  550. while(pfree)
  551. {
  552. psave = pfree->pNext;
  553. delete pfree;
  554. pfree = psave;
  555. }
  556. return;
  557. }
  558. // Hit rate looks BAD. Time to bump up the buffer cache cap.
  559. UINT cNewCap = _bcGlobalStats[index].cBufferCacheCap;
  560. cNewCap = min(cNewCap + 32, cNewCap * 2);
  561. _bcGlobalStats[index].cBufferCacheCap = cNewCap;
  562. // Start keeping new stats, start with a balanced ratio of hits to misses.
  563. // We'll get at least (cBlocks + cfree) more hits before the next new miss.
  564. _bcGlobalStats[index].cAllocationHits = 8 * cNewCap;
  565. _bcGlobalStats[index].cAllocationMisses = 0;
  566. // Drop into regular free path, we're going to keep these buffers.
  567. }
  568. _csBufferCacheLock.VerifyOwned();
  569. ptail->pNext = _bcGlobalState[index].pList;
  570. _bcGlobalState[index].pList = pfree;
  571. _bcGlobalState[index].cBlocks += cBuffers;
  572. _csBufferCacheLock.Clear();
  573. return;
  574. }
  575. void
  576. BCACHE::ThreadDetach(THREAD *pThread)
  577. /*++
  578. Routine Description:
  579. Called when a thread dies. Moves any cached buffes into
  580. the process wide cache.
  581. Arguments:
  582. pThread - The thread object of the thread which is dying.
  583. Return Value:
  584. None
  585. --*/
  586. {
  587. PBCTLS pbctls = pThread->BufferCache;
  588. INT index;
  589. // Guard page mode only has no thread cache.
  590. if (fPagedBCacheMode)
  591. {
  592. ASSERT(pbctls[0].pList == 0);
  593. ASSERT(pbctls[1].pList == 0);
  594. ASSERT(pbctls[2].pList == 0);
  595. ASSERT(pbctls[3].pList == 0);
  596. }
  597. for (index = 0; index < 4; index++)
  598. {
  599. if (pbctls[index].pList)
  600. {
  601. ASSERT(pbctls[index].cBlocks);
  602. FreeBuffers(pbctls[index].pList, index, pbctls[index].cBlocks);
  603. pbctls[index].pList = 0;
  604. pbctls[index].cBlocks = 0;
  605. }
  606. ASSERT(pbctls[index].pList == 0);
  607. ASSERT(pbctls[index].cBlocks == 0);
  608. }
  609. }
  610. PBUFFER
  611. BCACHE::AllocBigBlock(
  612. IN size_t cBytes
  613. )
  614. /*++
  615. Routine Description:
  616. Allocates all buffers which are bigger then a cached block size.
  617. In guard page mode allocates buffers which are guaranteed to have a read-only
  618. page following them. This allows for safe unmarshaling of NDR
  619. data when combinded with /robust on midl.
  620. Notes:
  621. Designed with 4Kb and 8Kb pages in mind. Assumes address space
  622. is allocated 64Kb at a time.
  623. Arguments:
  624. cBytes - The size of allocation needed.
  625. Return Value:
  626. null - out of Vm
  627. non-null - a pointer to a buffer of cBytes rounded up to page size.
  628. --*/
  629. {
  630. PBUFFER p;
  631. size_t BytesToAllocate;
  632. size_t BytesToAllocateAndGuardPage;
  633. PVOID pT;
  634. ASSERT(IsBufferSizeAligned(sizeof(BUFFER_HEAD)));
  635. BytesToAllocate = cBytes + sizeof(BUFFER_HEAD);
  636. if (!fPagedBCacheMode)
  637. {
  638. p = (PBUFFER) new BYTE[BytesToAllocate];
  639. if (p)
  640. {
  641. p->index = -1;
  642. p->size = BytesToAllocate;
  643. }
  644. return (p);
  645. }
  646. // Round up to multiple of pages
  647. BytesToAllocate = (BytesToAllocate + (gPageSize - 1)) & ~((size_t)gPageSize - 1);
  648. // Add one for the guard
  649. BytesToAllocateAndGuardPage = BytesToAllocate + gPageSize;
  650. p = (PBUFFER) VirtualAlloc(0,
  651. BytesToAllocateAndGuardPage,
  652. MEM_RESERVE,
  653. PAGE_READWRITE);
  654. if (p)
  655. {
  656. pT = CommitSegment(p, BytesToAllocate);
  657. if (pT == 0)
  658. {
  659. // Failed to commit, release the address space.
  660. VirtualFree(p,
  661. BytesToAllocateAndGuardPage,
  662. MEM_RELEASE);
  663. p = NULL;
  664. }
  665. else
  666. {
  667. p = (PBUFFER)PutBufferAtEndOfAllocation(
  668. p, // Allocation
  669. BytesToAllocate, // AllocationSize
  670. cBytes + sizeof(BUFFER_HEAD) // BufferSize
  671. );
  672. p->index = -1;
  673. p->size = BytesToAllocate;
  674. }
  675. }
  676. return(p);
  677. }
  678. VOID
  679. BCACHE::FreeBigBlock(
  680. IN PBUFFER pBuffer
  681. )
  682. /*++
  683. Routine Description:
  684. Frees a buffer allocated by PageAlloc
  685. Arguments:
  686. ptr - The buffer to free
  687. Return Value:
  688. None
  689. --*/
  690. {
  691. if (!fPagedBCacheMode)
  692. {
  693. delete pBuffer;
  694. return;
  695. }
  696. // Guard page mode, large alloc
  697. pBuffer = (PBUFFER)ConvertBufferToAllocation(pBuffer,
  698. TRUE // IsBufferInitialized
  699. );
  700. BOOL f = VirtualFree(pBuffer,
  701. 0,
  702. MEM_RELEASE
  703. );
  704. #if DBG
  705. if (!f)
  706. {
  707. DbgPrint("RPCRT4: VirtualFree failed %d\n", GetLastError());
  708. }
  709. #endif
  710. }
  711. PBUFFER
  712. BCACHE::AllocPagedBCacheSection (
  713. IN UINT Size,
  714. IN ULONG OriginalSize
  715. )
  716. /*++
  717. Routine Description:
  718. Allocates a set of 1 or 2 page PBUFFER objects to refill the global cache.
  719. The virtual memory for the associated buffers is reserved but not
  720. committed here.
  721. Arguments:
  722. size - 1 : allocate one page cache blocks
  723. 2 : allocate two page cache blocks
  724. OriginalSize - the size the consumer originally asked for. We need this in
  725. order to put the buffer at the end of the allocation. This does not include
  726. the BUFFER_HEAD
  727. Return Value:
  728. NULL or the new PBUFFER objects (linked list, one alloc)
  729. --*/
  730. {
  731. UINT Pages = PagedBCacheSectionSize / gPageSize;
  732. UINT SegmentSize = gPageSize * Size;
  733. UINT Blocks;
  734. PBUFFER pBuffer;
  735. PVOID pReadOnlyPage;
  736. PAGED_BCACHE_SECTION_MANAGER *SectionManager;
  737. BOOL Result;
  738. ASSERT(Size == 1 || Size == 2);
  739. Blocks = Pages / (Size + 1); // size is pages, +1 for read only (guard) page
  740. SectionManager = (PAGED_BCACHE_SECTION_MANAGER *)
  741. new BYTE[sizeof(PAGED_BCACHE_SECTION_MANAGER) + (Blocks - 1)];
  742. if (SectionManager == NULL)
  743. return NULL;
  744. // reserve the address space
  745. pBuffer = (PBUFFER)VirtualAlloc(0,
  746. PagedBCacheSectionSize,
  747. MEM_RESERVE,
  748. PAGE_READWRITE);
  749. if (!pBuffer)
  750. {
  751. delete SectionManager;
  752. return NULL;
  753. }
  754. // we commit the first buffer only
  755. pBuffer = (PBUFFER)CommitSegment(pBuffer, SegmentSize);
  756. if (pBuffer == NULL)
  757. {
  758. delete SectionManager;
  759. Result = VirtualFree(pBuffer,
  760. 0,
  761. MEM_RELEASE);
  762. // This must succeed
  763. ASSERT(Result);
  764. return NULL;
  765. }
  766. SectionManager->NumberOfSegments = Blocks;
  767. SectionManager->NumberOfUsedSegments = 1;
  768. SectionManager->SegmentSize = SegmentSize;
  769. SectionManager->VirtualMemorySection = pBuffer;
  770. SectionManager->SegmentBusy[0] = TRUE;
  771. RpcpMemorySet(&SectionManager->SegmentBusy[1], 0, Blocks);
  772. _csBufferCacheLock.Request();
  773. RpcpfInsertTailList(&Sections, &SectionManager->SectionList);
  774. pBuffer = (PBUFFER)PutBufferAtEndOfAllocation(
  775. pBuffer, // Allocation
  776. SegmentSize, // AllocationSize
  777. OriginalSize + sizeof(BUFFER_HEAD) // BufferSize
  778. );
  779. pBuffer->SectionManager = SectionManager;
  780. pBuffer->index = Size;
  781. ASSERT_VALID_PAGED_BCACHE;
  782. _csBufferCacheLock.Clear();
  783. return(pBuffer);
  784. }
  785. ULONG
  786. BCACHE::GetSegmentIndexFromBuffer (
  787. IN PBUFFER pBuffer,
  788. IN PVOID Allocation
  789. )
  790. /*++
  791. Routine Description:
  792. Calculates the segment index for the given
  793. buffer.
  794. Arguments:
  795. pBuffer - the buffer whose index we want to get
  796. Allocation - the beginning of the allocation containing the given buffer
  797. Return Value:
  798. The segment index of the buffer
  799. --*/
  800. {
  801. PAGED_BCACHE_SECTION_MANAGER *CurrentSection;
  802. ULONG AddressDifference;
  803. ULONG SegmentIndex;
  804. CurrentSection = pBuffer->SectionManager;
  805. AddressDifference = (ULONG)((PBYTE)Allocation - (PBYTE)CurrentSection->VirtualMemorySection);
  806. SegmentIndex = AddressDifference / (CurrentSection->SegmentSize + gPageSize);
  807. // the division must have no remainder
  808. ASSERT(SegmentIndex * (CurrentSection->SegmentSize + gPageSize) == AddressDifference);
  809. return SegmentIndex;
  810. }
  811. #if DBG
  812. void
  813. BCACHE::VerifyPagedBCacheState (
  814. void
  815. )
  816. /*++
  817. Routine Description:
  818. Verifies the state of the paged bcache. If the state is not consistent,
  819. is ASSERTs. Note that this can tremendously slow down a machine.
  820. Arguments:
  821. Return Value:
  822. --*/
  823. {
  824. LIST_ENTRY *CurrentListEntry;
  825. PAGED_BCACHE_SECTION_MANAGER *CurrentSection;
  826. _csBufferCacheLock.Request();
  827. CurrentListEntry = Sections.Flink;
  828. while (CurrentListEntry != &Sections)
  829. {
  830. CurrentSection = CONTAINING_RECORD(CurrentListEntry,
  831. PAGED_BCACHE_SECTION_MANAGER,
  832. SectionList);
  833. VerifySectionState(CurrentSection);
  834. CurrentListEntry = CurrentListEntry->Flink;
  835. }
  836. _csBufferCacheLock.Clear();
  837. }
  838. void
  839. BCACHE::VerifySectionState (
  840. IN PAGED_BCACHE_SECTION_MANAGER *Section
  841. )
  842. /*++
  843. Routine Description:
  844. Verifies the state of the paged bcache. If the state is not consistent,
  845. is ASSERTs. Note that this can tremendously slow down a machine.
  846. Arguments:
  847. Section - the section whose state we want to verify
  848. Return Value:
  849. --*/
  850. {
  851. ULONG EstimatedNumberOfSegments;
  852. ULONG i;
  853. ULONG CountedBusySegments;
  854. PVOID CurrentSegment;
  855. _csBufferCacheLock.VerifyOwned();
  856. // we take advantage of some rounding here. It is possible that the section
  857. // has unused space at the end (e.g. if 2 page (8K segments) + 1 guard page
  858. // = 12K. 64K has 5 of those, and one page is left. Using integer division
  859. // below should lose the extra page and still make a valid calculation
  860. EstimatedNumberOfSegments = PagedBCacheSectionSize / (Section->SegmentSize + gPageSize);
  861. ASSERT(EstimatedNumberOfSegments == Section->NumberOfSegments);
  862. ASSERT(Section->NumberOfUsedSegments <= Section->NumberOfSegments);
  863. CountedBusySegments = 0;
  864. CurrentSegment = Section->VirtualMemorySection;
  865. for (i = 0; i < Section->NumberOfSegments; i ++)
  866. {
  867. if (Section->SegmentBusy[i])
  868. {
  869. CountedBusySegments ++;
  870. // verify the segment
  871. VerifySegmentState(CurrentSegment,
  872. TRUE, // IsSegmentBusy
  873. Section->SegmentSize,
  874. Section
  875. );
  876. }
  877. else
  878. {
  879. // verify the segment
  880. VerifySegmentState(CurrentSegment,
  881. FALSE, // IsSegmentBusy
  882. Section->SegmentSize,
  883. Section
  884. );
  885. }
  886. CurrentSegment = (PBYTE)CurrentSegment + Section->SegmentSize + gPageSize;
  887. }
  888. ASSERT(CountedBusySegments == Section->NumberOfUsedSegments);
  889. }
  890. void
  891. BCACHE::VerifySegmentState (
  892. IN PVOID Segment,
  893. IN BOOL IsSegmentBusy,
  894. IN ULONG SegmentSize,
  895. IN PAGED_BCACHE_SECTION_MANAGER *OwningSection
  896. )
  897. /*++
  898. Routine Description:
  899. Verifies the state of the paged bcache. If the state is not consistent,
  900. is ASSERTs. Note that this can tremendously slow down a machine.
  901. Arguments:
  902. Segment - the segment whose state we want to verify
  903. IsSegmentBusy - if non-zero, we'll verify that the passed segment is properly
  904. allocated. If zero, we will verify it is no committed
  905. SegmentSize - the size of the segment in bytes. This doesn't include the
  906. guard page.
  907. OwningSection - the section manager that owns the section and the segment
  908. Return Value:
  909. Note:
  910. The buffer index must be in its external value (i.e. + 1)
  911. --*/
  912. {
  913. PBYTE CurrentPage;
  914. _csBufferCacheLock.VerifyOwned();
  915. if (IsSegmentBusy)
  916. {
  917. // probe the segment and the guard page
  918. #if defined(FULL_PAGED_BCACHE_VERIFY)
  919. // we don't use this always, as it can result in false positives
  920. // The buffer is decommitted without holding the mutex
  921. // first, the segment must be good for writing
  922. ASSERT(IsBadWritePtr(Segment, SegmentSize) == FALSE);
  923. // second, the guard page must be bad for writing
  924. ASSERT(IsBadWritePtr((PBYTE)Segment + SegmentSize, gPageSize));
  925. // third, the guard page must be good for reading
  926. ASSERT(IsBadReadPtr((PBYTE)Segment + SegmentSize, gPageSize) == FALSE);
  927. // make sure the segment agrees it belongs to the same section manager
  928. ASSERT(((PBUFFER)Segment)->SectionManager == OwningSection);
  929. // can give false positives if on. The index is not manipulated inside
  930. // the mutex
  931. // finally, the buffer header contents must be consistent with the segment
  932. // size
  933. ASSERT((((PBUFFER)Segment)->index) * gPageSize == SegmentSize);
  934. #endif // FULL_PAGED_BCACHE_VERIFY
  935. }
  936. else
  937. {
  938. #if defined(FULL_PAGED_BCACHE_VERIFY)
  939. // since IsBadReadPtr bails out on first failure, we must probe each
  940. // page individually to make sure it is uncommitted
  941. CurrentPage = (PBYTE)Segment;
  942. while (CurrentPage <= (PBYTE)Segment + SegmentSize)
  943. {
  944. ASSERT(IsBadReadPtr(CurrentPage, gPageSize));
  945. CurrentPage += gPageSize;
  946. }
  947. #endif // FULL_PAGED_BCACHE_VERIFY
  948. }
  949. }
  950. #endif // DBG
  951. PVOID
  952. BCACHE::PutBufferAtEndOfAllocation (
  953. IN PVOID Allocation,
  954. IN ULONG AllocationSize,
  955. IN ULONG BufferSize
  956. )
  957. /*++
  958. Routine Description:
  959. Given an allocation, a size and a buffer of a given size, it chooses
  960. an address for the buffer such that the buffer is 8 bytes aligned
  961. and it is as close as possible to the end of the allocation
  962. Arguments:
  963. Allocation - the allocation with which we try to position the buffer.
  964. AllocationSize - the total size of the allocation
  965. BufferSize - the size of the buffer we're trying to a position
  966. Return Value:
  967. The new address for the buffer within the allocation.
  968. --*/
  969. {
  970. ULONG BufferOffset;
  971. PVOID Buffer;
  972. ASSERT(AllocationSize >= BufferSize);
  973. BufferOffset = AllocationSize - BufferSize;
  974. // in our allocator, we should never have more than a page
  975. // extra
  976. ASSERT(BufferOffset < gPageSize);
  977. #if defined(_WIN64)
  978. // zero out four bits at the end. This 16 byte aligns the buffer
  979. BufferOffset &= ~(ULONG)15;
  980. #else // _WIN64
  981. // zero out three bits at the end. This 8 byte aligns the buffer
  982. BufferOffset &= ~(ULONG)7;
  983. #endif // _WIN64
  984. Buffer = (PVOID)((PBYTE)Allocation + BufferOffset);
  985. // make sure that the reverse calculation will yield the same result
  986. ASSERT(ConvertBufferToAllocation((PBUFFER)Buffer, FALSE) == Allocation);
  987. return Buffer;
  988. }
  989. PVOID
  990. BCACHE::ConvertBufferToAllocation (
  991. IN PBUFFER Buffer,
  992. IN BOOL IsBufferInitialized
  993. )
  994. /*++
  995. Routine Description:
  996. Given a buffer, finds the allocation that contains it. Since we know
  997. that all of our allocations are page aligned, and the allocation is
  998. never a page or more larger than the buffer, we can simply page align
  999. the buffer and return that.
  1000. Arguments:
  1001. Buffer - the buffer for which we are trying to find the containing
  1002. allocation.
  1003. IsBufferInitialized - non-zero if the Buffer has valid index and
  1004. SectionManager. 0 otherwise.
  1005. Return Value:
  1006. The containing allocation
  1007. --*/
  1008. {
  1009. PVOID LastSectionSegment;
  1010. PAGED_BCACHE_SECTION_MANAGER *Section;
  1011. PVOID Allocation;
  1012. if (IsBufferInitialized)
  1013. {
  1014. ASSERT((Buffer->index == 0)
  1015. || (Buffer->index == 1)
  1016. || (Buffer->index == -1));
  1017. }
  1018. Allocation = (PVOID)((ULONG_PTR)Buffer & ~(ULONG_PTR)(gPageSize - 1));
  1019. if (IsBufferInitialized && (Buffer->index != -1))
  1020. {
  1021. Section = Buffer->SectionManager;
  1022. LastSectionSegment = (PBYTE)Section->VirtualMemorySection
  1023. + Section->NumberOfSegments * (Section->SegmentSize + gPageSize);
  1024. ASSERT(Allocation >= Section->VirtualMemorySection);
  1025. ASSERT(Allocation <= LastSectionSegment);
  1026. }
  1027. return Allocation;
  1028. }
  1029. const ULONG GuardPageFillPattern = 0xf;
  1030. PVOID
  1031. BCACHE::CommitSegment (
  1032. IN PVOID SegmentStart,
  1033. IN ULONG SegmentSize
  1034. )
  1035. /*++
  1036. Routine Description:
  1037. Commits a segment (usable part and guard page).
  1038. Arguments:
  1039. SegmentStart - the start of the segment.
  1040. SegmentSize - the size of the segment to be committed. This
  1041. does not include the guard page.
  1042. Return Value:
  1043. The pointer to the segment start if it succeeds or NULL if
  1044. it fails.
  1045. --*/
  1046. {
  1047. PVOID pTemp;
  1048. BOOL Result;
  1049. ULONG Ignored;
  1050. pTemp = VirtualAlloc(
  1051. SegmentStart,
  1052. SegmentSize + gPageSize,
  1053. MEM_COMMIT,
  1054. PAGE_READWRITE
  1055. );
  1056. if (pTemp)
  1057. {
  1058. // initialize the guard page with non-zero data
  1059. RtlFillMemoryUlong((PBYTE)SegmentStart + SegmentSize, gPageSize, GuardPageFillPattern);
  1060. // revert protections on the guard page to read only
  1061. Result = VirtualProtect((PBYTE)SegmentStart + SegmentSize,
  1062. gPageSize,
  1063. PAGE_READONLY,
  1064. &Ignored);
  1065. if (Result == FALSE)
  1066. {
  1067. Result = VirtualFree(SegmentStart,
  1068. SegmentSize + gPageSize,
  1069. MEM_DECOMMIT
  1070. );
  1071. // this must succeed
  1072. ASSERT(Result);
  1073. return NULL;
  1074. }
  1075. }
  1076. return pTemp;
  1077. }