Leaked source code of windows server 2003
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.

788 lines
26 KiB

  1. /*++
  2. Copyright (c) 1999-2000 Microsoft Corporation
  3. Module Name:
  4. fsbpool.c
  5. Abstract:
  6. This file contains the implementation of fixed-size block pool.
  7. Author:
  8. Shaun Cox (shaunco) 10-Dec-1999
  9. --*/
  10. #include "precomp.h"
  11. #include <align.h> // Macros: ROUND_UP_POINTER, POINTER_IS_ALIGNED
  12. #define FSB_SCAVENGE_PERIOD_IN_SECONDS 30
  13. #define FSB_MINIMUM_PAGE_LIFETIME_IN_SECONDS 20
  14. #if defined (_WIN64)
  15. #define APPROX_L2_CACHE_LINE_SIZE 128
  16. #define BLOCK_TYPE SLIST_HEADER
  17. #else
  18. #define APPROX_L2_CACHE_LINE_SIZE 64
  19. #define BLOCK_TYPE PVOID
  20. #endif
  21. // The following structures are used in the single allocation that
  22. // a pool handle points to.
  23. // PoolHandle ---> [FSB_POOL_HEADER + FSB_CPU_POOL_HEADER for cpu 0 +
  24. // FSB_CPU_POOL_HEADER for cpu 1 + ...
  25. // FSB_CPU_POOL_HEADER for cpu N]
  26. //
  27. // FSB_POOL_HEADER is the data common to all CPUs for a given pool.
  28. //
  29. typedef struct _FSB_POOL_HEADER
  30. {
  31. // cache-line -----
  32. struct _FSB_POOL_HEADER_BASE
  33. {
  34. ULONG Tag;
  35. USHORT CallerBlockSize; // caller's requested block size
  36. USHORT AlignedBlockSize; // ALIGN_UP(CallerBlockSize, PVOID)
  37. USHORT BlocksPerPage;
  38. USHORT FreeBlockLinkOffset;
  39. NDIS_BLOCK_INITIALIZER BuildFunction;
  40. PVOID Allocation;
  41. KSPIN_LOCK Interlock;
  42. };
  43. UCHAR Alignment[APPROX_L2_CACHE_LINE_SIZE
  44. - (sizeof(struct _FSB_POOL_HEADER_BASE) % APPROX_L2_CACHE_LINE_SIZE)];
  45. } FSB_POOL_HEADER, *PFSB_POOL_HEADER;
  46. C_ASSERT(sizeof(FSB_POOL_HEADER) % APPROX_L2_CACHE_LINE_SIZE == 0);
  47. // FSB_CPU_POOL_HEADER is the data specific to a CPU for a given pool.
  48. //
  49. typedef struct _FSB_CPU_POOL_HEADER
  50. {
  51. // cache-line -----
  52. struct _FSB_CPU_POOL_HEADER_BASE
  53. {
  54. // The doubly-linked list of pages that make up this processor's pool.
  55. // These pages have one or more free blocks available.
  56. //
  57. LIST_ENTRY PageList;
  58. // The doubly-linked list of pages that are fully in use. This list
  59. // is separate from the above list so that we do not spend time walking
  60. // a very long list during FsbAllocate when many pages are fully used.
  61. //
  62. LIST_ENTRY UsedPageList;
  63. // The next scheduled time (in units of KeQueryTickCount()) for
  64. // scavenging this pool. The next scavenge will happen no earlier
  65. // than this.
  66. //
  67. LARGE_INTEGER NextScavengeTick;
  68. // The number of the processor that owns this pool.
  69. //
  70. ULONG OwnerCpu;
  71. ULONG TotalBlocksAllocated;
  72. ULONG TotalBlocksFreed;
  73. ULONG PeakBlocksInUse;
  74. ULONG TotalPagesAllocated;
  75. ULONG TotalPagesFreed;
  76. ULONG PeakPagesInUse;
  77. };
  78. UCHAR Alignment[APPROX_L2_CACHE_LINE_SIZE
  79. - (sizeof(struct _FSB_CPU_POOL_HEADER_BASE) % APPROX_L2_CACHE_LINE_SIZE)];
  80. } FSB_CPU_POOL_HEADER, *PFSB_CPU_POOL_HEADER;
  81. C_ASSERT(sizeof(FSB_CPU_POOL_HEADER) % APPROX_L2_CACHE_LINE_SIZE == 0);
  82. // FSB_PAGE_HEADER is the data at the beginning of each allocated pool page
  83. // that describes the current state of the blocks on the page.
  84. //
  85. typedef struct _FSB_PAGE_HEADER
  86. {
  87. // cache-line -----
  88. // Back pointer to the owning cpu pool.
  89. //
  90. PFSB_CPU_POOL_HEADER Pool;
  91. // Linkage entry for the list of pages managed by the cpu pool.
  92. //
  93. LIST_ENTRY PageLink;
  94. // Number of blocks built so far on this page. Blocks are built on
  95. // demand. When this number reaches Pool->BlocksPerPage, all blocks on
  96. // this page have been built.
  97. //
  98. USHORT BlocksBuilt;
  99. // Boolean indicator of whether or not this page is on the cpu pool's
  100. // used-page list. This is checked during FsbFree to see if the page
  101. // should be moved back to the normal page list.
  102. // (it is a USHORT, instead of BOOLEAN, for proper padding)
  103. //
  104. USHORT OnUsedPageList;
  105. // List of free blocks on this page.
  106. //
  107. SLIST_HEADER FreeList;
  108. // The value of KeQueryTickCount (normalized to units of seconds)
  109. // which represents the time after which this page can be freed back
  110. // to the system's pool. This time is only used once the depth of
  111. // FreeList is Pool->BlocksPerPage. (i.e. this time is only used if
  112. // the page is completely unused.)
  113. //
  114. LARGE_INTEGER LastUsedTick;
  115. } FSB_PAGE_HEADER, *PFSB_PAGE_HEADER;
  116. // Get a pointer to the overall pool given a pointer to one of
  117. // the per-processor pools within it.
  118. //
  119. __inline
  120. PFSB_POOL_HEADER
  121. PoolFromCpuPool(
  122. IN PFSB_CPU_POOL_HEADER CpuPool
  123. )
  124. {
  125. return (PFSB_POOL_HEADER)(CpuPool - CpuPool->OwnerCpu) - 1;
  126. }
  127. __inline
  128. VOID
  129. ConvertSecondsToTicks(
  130. IN ULONG Seconds,
  131. OUT PLARGE_INTEGER Ticks
  132. )
  133. {
  134. // If the following assert fires, you need to cast Seconds below to
  135. // ULONGLONG so that 64 bit multiplication and division are used.
  136. // The current code assumes less than 430 seconds so that the
  137. // 32 multiplication below won't overflow.
  138. //
  139. ASSERT(Seconds < 430);
  140. Ticks->HighPart = 0;
  141. Ticks->LowPart = (Seconds * 10*1000*1000) / KeQueryTimeIncrement();
  142. }
  143. // Build the next block on the specified pool page.
  144. // This can only be called if not all of the blocks have been built yet.
  145. //
  146. PUCHAR
  147. FsbpBuildNextBlock(
  148. IN const FSB_POOL_HEADER* Pool,
  149. IN OUT PFSB_PAGE_HEADER Page
  150. )
  151. {
  152. PUCHAR Block;
  153. ASSERT(Page->BlocksBuilt < Pool->BlocksPerPage);
  154. ASSERT((PAGE_SIZE - sizeof(FSB_PAGE_HEADER)) / Pool->AlignedBlockSize
  155. == Pool->BlocksPerPage);
  156. ASSERT(Pool->CallerBlockSize <= Pool->AlignedBlockSize);
  157. Block = (PUCHAR)(Page + 1) + (Page->BlocksBuilt * Pool->AlignedBlockSize);
  158. ASSERT(PAGE_ALIGN(Block) == Page);
  159. if (Pool->BuildFunction) {
  160. Pool->BuildFunction(Block, Pool->CallerBlockSize);
  161. }
  162. Page->BlocksBuilt++;
  163. return Block;
  164. }
  165. // Allocate a new pool page and insert it at the head of the specified
  166. // CPU pool. Build the first block on the new page and return a pointer
  167. // to it.
  168. //
  169. PUCHAR
  170. FsbpAllocateNewPageAndBuildOneBlock(
  171. IN const FSB_POOL_HEADER* Pool,
  172. IN PFSB_CPU_POOL_HEADER CpuPool
  173. )
  174. {
  175. PFSB_PAGE_HEADER Page;
  176. PUCHAR Block = NULL;
  177. ULONG PagesInUse;
  178. ASSERT(Pool);
  179. Page = ExAllocatePoolWithTag(NonPagedPool, PAGE_SIZE, Pool->Tag);
  180. if (Page)
  181. {
  182. ASSERT(Page == PAGE_ALIGN(Page));
  183. RtlZeroMemory(Page, sizeof(FSB_PAGE_HEADER));
  184. Page->Pool = CpuPool;
  185. ExInitializeSListHead(&Page->FreeList);
  186. // Insert the page at the head of the cpu's pool.
  187. //
  188. InsertHeadList(&CpuPool->PageList, &Page->PageLink);
  189. CpuPool->TotalPagesAllocated++;
  190. // Update the pool's statistics.
  191. //
  192. PagesInUse = CpuPool->TotalPagesAllocated - CpuPool->TotalPagesFreed;
  193. if (PagesInUse > CpuPool->PeakPagesInUse)
  194. {
  195. CpuPool->PeakPagesInUse = PagesInUse;
  196. }
  197. Block = FsbpBuildNextBlock(Pool, Page);
  198. ASSERT(Block);
  199. }
  200. return Block;
  201. }
  202. // Free the specified pool page back to the system's pool.
  203. //
  204. VOID
  205. FsbpFreePage(
  206. IN PFSB_CPU_POOL_HEADER CpuPool,
  207. IN PFSB_PAGE_HEADER Page
  208. )
  209. {
  210. ASSERT(Page == PAGE_ALIGN(Page));
  211. ASSERT(Page->Pool == CpuPool);
  212. ExFreePool(Page);
  213. CpuPool->TotalPagesFreed++;
  214. ASSERT(CpuPool->TotalPagesFreed <= CpuPool->TotalPagesAllocated);
  215. }
  216. // Free the specified pool page list back to the system's pool.
  217. //
  218. VOID
  219. FsbpFreeList(
  220. IN PFSB_CPU_POOL_HEADER CpuPool,
  221. IN PLIST_ENTRY Head
  222. )
  223. {
  224. PFSB_POOL_HEADER Pool;
  225. PFSB_PAGE_HEADER Page;
  226. PLIST_ENTRY Scan;
  227. PLIST_ENTRY Next;
  228. BOOLEAN UsedPageList;
  229. Pool = PoolFromCpuPool(CpuPool);
  230. UsedPageList = (Head == &CpuPool->UsedPageList);
  231. for (Scan = Head->Flink; Scan != Head; Scan = Next)
  232. {
  233. Page = CONTAINING_RECORD(Scan, FSB_PAGE_HEADER, PageLink);
  234. ASSERT(Page == PAGE_ALIGN(Page));
  235. ASSERT(CpuPool == Page->Pool);
  236. ASSERT(UsedPageList ? Page->OnUsedPageList : !Page->OnUsedPageList);
  237. ASSERT(Page->BlocksBuilt <= Pool->BlocksPerPage);
  238. ASSERT(Page->BlocksBuilt == ExQueryDepthSList(&Page->FreeList));
  239. // Step to the next link before we free this page.
  240. //
  241. Next = Scan->Flink;
  242. RemoveEntryList(Scan);
  243. FsbpFreePage(CpuPool, Page);
  244. }
  245. }
  246. // Reclaim the memory consumed by completely unused pool pages belonging
  247. // to the specified per-processor pool.
  248. //
  249. // Caller IRQL: [DISPATCH_LEVEL]
  250. //
  251. VOID
  252. FsbpScavengePool(
  253. IN OUT PFSB_CPU_POOL_HEADER CpuPool
  254. )
  255. {
  256. PFSB_POOL_HEADER Pool;
  257. PFSB_PAGE_HEADER Page;
  258. PLIST_ENTRY Scan;
  259. PLIST_ENTRY Next;
  260. LARGE_INTEGER Ticks;
  261. LARGE_INTEGER TicksDelta;
  262. // We must not only be at DISPATCH_LEVEL (or higher), we must also
  263. // be called on the processor that owns the specified pool.
  264. //
  265. ASSERT(KeGetCurrentIrql() >= DISPATCH_LEVEL);
  266. ASSERT((ULONG)KeGetCurrentProcessorNumber() == CpuPool->OwnerCpu);
  267. Pool = PoolFromCpuPool(CpuPool);
  268. KeQueryTickCount(&Ticks);
  269. // Compute the next tick value which represents the earliest time
  270. // that we will scavenge this pool again.
  271. //
  272. ConvertSecondsToTicks(FSB_SCAVENGE_PERIOD_IN_SECONDS, &TicksDelta);
  273. CpuPool->NextScavengeTick.QuadPart = Ticks.QuadPart + TicksDelta.QuadPart;
  274. // Compute the tick value which represents the last point at which
  275. // its okay to free a page.
  276. //
  277. ConvertSecondsToTicks(FSB_MINIMUM_PAGE_LIFETIME_IN_SECONDS, &TicksDelta);
  278. Ticks.QuadPart = Ticks.QuadPart - TicksDelta.QuadPart;
  279. for (Scan = CpuPool->PageList.Flink;
  280. Scan != &CpuPool->PageList;
  281. Scan = Next)
  282. {
  283. Page = CONTAINING_RECORD(Scan, FSB_PAGE_HEADER, PageLink);
  284. ASSERT(Page == PAGE_ALIGN(Page));
  285. ASSERT(CpuPool == Page->Pool);
  286. ASSERT(!Page->OnUsedPageList);
  287. // Step to the next link before we possibly unlink this page.
  288. //
  289. Next = Scan->Flink;
  290. if ((Pool->BlocksPerPage == ExQueryDepthSList(&Page->FreeList)) &&
  291. (Ticks.QuadPart > Page->LastUsedTick.QuadPart))
  292. {
  293. RemoveEntryList(Scan);
  294. FsbpFreePage(CpuPool, Page);
  295. }
  296. }
  297. // Scan the used pages to see if they can be moved back to the normal
  298. // list. This can happen if too many frees by non-owning processors
  299. // are done. In that case, the pages get orphaned on the used-page
  300. // list after all of their blocks have been freed to the page. Un-orphan
  301. // them here.
  302. //
  303. for (Scan = CpuPool->UsedPageList.Flink;
  304. Scan != &CpuPool->UsedPageList;
  305. Scan = Next)
  306. {
  307. Page = CONTAINING_RECORD(Scan, FSB_PAGE_HEADER, PageLink);
  308. ASSERT(Page == PAGE_ALIGN(Page));
  309. ASSERT(CpuPool == Page->Pool);
  310. ASSERT(Page->OnUsedPageList);
  311. // Step to the next link before we possibly unlink this page.
  312. Next = Scan->Flink;
  313. if (0 != ExQueryDepthSList(&Page->FreeList))
  314. {
  315. RemoveEntryList(Scan);
  316. Page->OnUsedPageList = FALSE;
  317. InsertTailList(&CpuPool->PageList, Scan);
  318. }
  319. }
  320. }
  321. // Creates a pool of fixed-size blocks built over non-paged pool. Each
  322. // block is BlockSize bytes long. If NULL is not returned,
  323. // FsbDestroyPool should be called at a later time to reclaim the
  324. // resources used by the pool.
  325. //
  326. // Arguments:
  327. // BlockSize - The size, in bytes, of each block.
  328. // FreeBlockLinkOffset - The offset, in bytes, from the beginning of a block
  329. // that represenets a pointer-sized storage location that the pool can
  330. // use to chain free blocks together. Most often this will be zero
  331. // (meaning use the first pointer-size bytes of the block.)
  332. // Tag - The pool tag to be used internally for calls to
  333. // ExAllocatePoolWithTag. This allows callers to track
  334. // memory consumption for different pools.
  335. // BuildFunction - An optional pointer to a function which initializes
  336. // blocks when they are first allocated by the pool. This allows the
  337. // caller to perform custom, on-demand initialization of each block.
  338. //
  339. // Returns the handle used to identify the pool.
  340. //
  341. // Caller IRQL: [PASSIVE_LEVEL, DISPATCH_LEVEL]
  342. //
  343. HANDLE
  344. FsbCreatePool(
  345. IN USHORT BlockSize,
  346. IN USHORT FreeBlockLinkOffset,
  347. IN ULONG Tag,
  348. IN NDIS_BLOCK_INITIALIZER BuildFunction OPTIONAL
  349. )
  350. {
  351. SIZE_T Size;
  352. PVOID Allocation;
  353. PFSB_POOL_HEADER Pool = NULL;
  354. PFSB_CPU_POOL_HEADER CpuPool;
  355. CCHAR NumberCpus = KeNumberProcessors;
  356. CCHAR i;
  357. // We are going to ensure that all blocks returned by FsbAllocate are
  358. // properly aligned (4-byte on x86 and 16-byte on 64-bit), but we also
  359. // need to make sure that users are giving us a free block link offset
  360. // that is a multiple of the base alignment, to ensure proper alignment
  361. // for the free block SLIST operations.
  362. //
  363. ASSERT(FreeBlockLinkOffset % sizeof(BLOCK_TYPE) == 0);
  364. // We need at least a pointer size worth of space to manage free
  365. // blocks and we don't impose any per-block overhead, so we borrow it
  366. // from the block itself.
  367. //
  368. ASSERT(BlockSize >= FreeBlockLinkOffset + sizeof(BLOCK_TYPE));
  369. // This implementation shouldn't be used if we are not going to get more
  370. // than about 8 blocks per page. Blocks bigger than this should probably
  371. // be allocated with multiple pages at a time.
  372. //
  373. ASSERT(BlockSize < PAGE_SIZE / 8);
  374. // Compute the size of our pool header allocation.
  375. // Add padding to ensure that the pool header can begin on a cache line.
  376. //
  377. Size = sizeof(FSB_POOL_HEADER) + (sizeof(FSB_CPU_POOL_HEADER) * NumberCpus)
  378. + (APPROX_L2_CACHE_LINE_SIZE - MEMORY_ALLOCATION_ALIGNMENT);
  379. // Allocate the pool header.
  380. //
  381. Allocation = ExAllocatePoolWithTag(NonPagedPool, Size, ' bsF');
  382. if (Allocation)
  383. {
  384. ASSERT(POINTER_IS_ALIGNED(Allocation, MEMORY_ALLOCATION_ALIGNMENT));
  385. RtlZeroMemory(Allocation, Size);
  386. Pool = ROUND_UP_POINTER(Allocation, APPROX_L2_CACHE_LINE_SIZE);
  387. // Initialize the pool header fields.
  388. //
  389. Pool->Tag = Tag;
  390. Pool->CallerBlockSize = BlockSize;
  391. Pool->AlignedBlockSize = (USHORT)ALIGN_UP(BlockSize, BLOCK_TYPE);
  392. Pool->BlocksPerPage = (PAGE_SIZE - sizeof(FSB_PAGE_HEADER))
  393. / Pool->AlignedBlockSize;
  394. Pool->FreeBlockLinkOffset = FreeBlockLinkOffset;
  395. Pool->BuildFunction = BuildFunction;
  396. Pool->Allocation = Allocation;
  397. KeInitializeSpinLock(&Pool->Interlock);
  398. // Initialize the per-cpu pool headers.
  399. //
  400. CpuPool = (PFSB_CPU_POOL_HEADER)(Pool + 1);
  401. for (i = 0; i < NumberCpus; i++)
  402. {
  403. InitializeListHead(&CpuPool[i].PageList);
  404. InitializeListHead(&CpuPool[i].UsedPageList);
  405. CpuPool[i].OwnerCpu = i;
  406. }
  407. }
  408. return Pool;
  409. }
  410. // Destroys a pool of fixed-size blocks previously created by a call to
  411. // FsbCreatePool.
  412. //
  413. // Arguments:
  414. // PoolHandle - Handle which identifies the pool being destroyed.
  415. //
  416. // Caller IRQL: [PASSIVE_LEVEL, DISPATCH_LEVEL]
  417. //
  418. VOID
  419. FsbDestroyPool(
  420. IN HANDLE PoolHandle
  421. )
  422. {
  423. PFSB_POOL_HEADER Pool;
  424. PFSB_CPU_POOL_HEADER CpuPool;
  425. CCHAR NumberCpus = KeNumberProcessors;
  426. CCHAR i;
  427. Pool = (PFSB_POOL_HEADER)PoolHandle;
  428. if (!Pool)
  429. {
  430. return;
  431. }
  432. for (i = 0, CpuPool = (PFSB_CPU_POOL_HEADER)(Pool + 1);
  433. i < NumberCpus;
  434. i++, CpuPool++)
  435. {
  436. ASSERT(CpuPool->OwnerCpu == (ULONG)i);
  437. FsbpFreeList(CpuPool, &CpuPool->PageList);
  438. FsbpFreeList(CpuPool, &CpuPool->UsedPageList);
  439. ASSERT(CpuPool->TotalPagesAllocated == CpuPool->TotalPagesFreed);
  440. ASSERT(CpuPool->TotalBlocksAllocated == CpuPool->TotalBlocksFreed);
  441. }
  442. ASSERT(Pool ==
  443. ROUND_UP_POINTER(Pool->Allocation, APPROX_L2_CACHE_LINE_SIZE));
  444. ExFreePool(Pool->Allocation);
  445. }
  446. // Returns a pointer to a block allocated from a pool. NULL is returned if
  447. // the request could not be granted. The returned pointer is guaranteed to
  448. // have 8 byte alignment.
  449. //
  450. // Arguments:
  451. // PoolHandle - Handle which identifies the pool being allocated from.
  452. //
  453. // Caller IRQL: [PASSIVE_LEVEL, DISPATCH_LEVEL]
  454. //
  455. PUCHAR
  456. FsbAllocate(
  457. IN HANDLE PoolHandle
  458. )
  459. {
  460. PFSB_POOL_HEADER Pool;
  461. PFSB_CPU_POOL_HEADER CpuPool;
  462. PFSB_PAGE_HEADER Page;
  463. PSLIST_ENTRY BlockLink;
  464. PUCHAR Block = NULL;
  465. KIRQL OldIrql;
  466. ULONG Cpu;
  467. LARGE_INTEGER Ticks;
  468. ASSERT(PoolHandle);
  469. Pool = (PFSB_POOL_HEADER)PoolHandle;
  470. // Raise IRQL before saving the processor number since there is chance
  471. // it could have changed if we saved it while at passive.
  472. //
  473. OldIrql = KeRaiseIrqlToDpcLevel();
  474. Cpu = KeGetCurrentProcessorNumber();
  475. CpuPool = (PFSB_CPU_POOL_HEADER)(Pool + 1) + Cpu;
  476. // See if the minimum time has passed since we last scavenged
  477. // the pool. If it has, we'll scavenge again. Normally, scavenging
  478. // should only be performed when we free. However, for the case when
  479. // the caller constantly frees on a non-owning processor, we'll
  480. // take this chance to do the scavenging.
  481. //
  482. KeQueryTickCount(&Ticks);
  483. if (Ticks.QuadPart > CpuPool->NextScavengeTick.QuadPart)
  484. {
  485. FsbpScavengePool(CpuPool);
  486. }
  487. if (!IsListEmpty(&CpuPool->PageList))
  488. {
  489. Page = CONTAINING_RECORD(CpuPool->PageList.Flink, FSB_PAGE_HEADER, PageLink);
  490. ASSERT(Page == PAGE_ALIGN(Page));
  491. ASSERT(CpuPool == Page->Pool);
  492. ASSERT(!Page->OnUsedPageList);
  493. BlockLink = ExInterlockedPopEntrySList(&Page->FreeList, &Pool->Interlock);
  494. if (BlockLink)
  495. {
  496. Block = (PUCHAR)BlockLink - Pool->FreeBlockLinkOffset;
  497. }
  498. else
  499. {
  500. // If there were no blocks on this page's free list, it had better
  501. // mean we haven't yet built all of the blocks on the page.
  502. // (Otherwise, what is a fully used page doing on the page list
  503. // and not on the used-page list?)
  504. //
  505. ASSERT(Page->BlocksBuilt < Pool->BlocksPerPage);
  506. Block = FsbpBuildNextBlock(Pool, Page);
  507. ASSERT(Block);
  508. }
  509. // Got a block. Now check to see if it was the last one on a fully
  510. // built page. If so, move the page to the used-page list.
  511. //
  512. if ((0 == ExQueryDepthSList(&Page->FreeList)) &&
  513. (Page->BlocksBuilt == Pool->BlocksPerPage))
  514. {
  515. PLIST_ENTRY PageLink;
  516. PageLink = RemoveHeadList(&CpuPool->PageList);
  517. InsertTailList(&CpuPool->UsedPageList, PageLink);
  518. Page->OnUsedPageList = TRUE;
  519. ASSERT(Page == CONTAINING_RECORD(PageLink, FSB_PAGE_HEADER, PageLink));
  520. }
  521. ASSERT(Block);
  522. goto GotABlock;
  523. }
  524. else
  525. {
  526. // The page list is empty so we have to allocate and add a new page.
  527. //
  528. Block = FsbpAllocateNewPageAndBuildOneBlock(Pool, CpuPool);
  529. }
  530. // If we are returning an block, update the statistics.
  531. //
  532. if (Block)
  533. {
  534. ULONG BlocksInUse;
  535. GotABlock:
  536. CpuPool->TotalBlocksAllocated++;
  537. BlocksInUse = CpuPool->TotalBlocksAllocated - CpuPool->TotalBlocksFreed;
  538. if (BlocksInUse > CpuPool->PeakBlocksInUse)
  539. {
  540. CpuPool->PeakBlocksInUse = BlocksInUse;
  541. }
  542. // Don't give anyone ideas about where this might point. I don't
  543. // want anyone trashing my pool because they thought this field
  544. // was valid for some reason.
  545. //
  546. ((PSINGLE_LIST_ENTRY)((PUCHAR)Block + Pool->FreeBlockLinkOffset))->Next = NULL;
  547. }
  548. KeLowerIrql(OldIrql);
  549. return Block;
  550. }
  551. // Free a block back to the pool from which it was allocated.
  552. //
  553. // Arguments:
  554. // Block - A block returned from a prior call to FsbAllocate.
  555. //
  556. // Caller IRQL: [PASSIVE_LEVEL, DISPATCH_LEVEL]
  557. //
  558. VOID
  559. FsbFree(
  560. IN PUCHAR Block
  561. )
  562. {
  563. PFSB_PAGE_HEADER Page;
  564. PFSB_CPU_POOL_HEADER CpuPool;
  565. PFSB_POOL_HEADER Pool;
  566. LARGE_INTEGER Ticks;
  567. LOGICAL PageIsOnUsedPageList;
  568. LOGICAL Scavenge = FALSE;
  569. ASSERT(Block);
  570. // Get the address of the page that this block lives on. This is where
  571. // our page header is stored.
  572. //
  573. Page = PAGE_ALIGN(Block);
  574. // Follow the back pointer in the page header to locate the owning
  575. // cpu's pool.
  576. //
  577. CpuPool = Page->Pool;
  578. // Locate the pool header.
  579. //
  580. Pool = PoolFromCpuPool(CpuPool);
  581. // See if the minimum time has passed since we last scavenged
  582. // the pool. If it has, we'll scavenge again.
  583. //
  584. KeQueryTickCount(&Ticks);
  585. if (Ticks.QuadPart > CpuPool->NextScavengeTick.QuadPart)
  586. {
  587. Scavenge = TRUE;
  588. }
  589. // Note the tick that this page was last used. If this is the last block
  590. // to be returned to this page, this sets the minimum time that this page
  591. // will continue to live unless it gets re-used.
  592. //
  593. Page->LastUsedTick.QuadPart = Ticks.QuadPart;
  594. // If this page is on the used-page list, we'll put it back on the normal
  595. // page list (only after pushing the block back on the page's free list)
  596. // if, after raising IRQL, we are on the processor that owns this
  597. // pool.
  598. //
  599. PageIsOnUsedPageList = Page->OnUsedPageList;
  600. InterlockedIncrement((PLONG)&CpuPool->TotalBlocksFreed);
  601. // Now return the block to the page's free list.
  602. //
  603. ExInterlockedPushEntrySList(
  604. &Page->FreeList,
  605. (PSLIST_ENTRY)((PUCHAR)Block + Pool->FreeBlockLinkOffset),
  606. &Pool->Interlock);
  607. //
  608. // Warning: Now that the block is back on the page, one cannot *reliably*
  609. // dereference anything through 'Page' anymore. It may have just been
  610. // scavenged by its owning processor and subsequently freed. This is a
  611. // particularly rare condition given that MINIMUM_PAGE_LIFETIME_IN_SECONDS
  612. // is 20s, so we choose to live with it. The alternative would be to walk
  613. // the UsedPageList whenever PageIsOnUsedPageList is true, making the
  614. // FsbFree operation potentially expensive. We saved off the value of
  615. // Page->OnUsedPageList before returning the block so we would not risk
  616. // touching Page to get this value only to find that it was false.
  617. //
  618. // If we need to move the page from the used-page list to the normal
  619. // page list, or if we need to scavenge, we need to be at DISPATCH_LEVEL
  620. // and be executing on the processor that owns this pool.
  621. // Find out if the CPU we are executing on right now owns this pool.
  622. // Note that if we are running at PASSIVE_LEVEL, the current CPU may
  623. // change over the duration of this function call, so this value is
  624. // not absolute over the life of the function.
  625. //
  626. if ((PageIsOnUsedPageList || Scavenge) &&
  627. ((ULONG)KeGetCurrentProcessorNumber() == CpuPool->OwnerCpu))
  628. {
  629. KIRQL OldIrql;
  630. OldIrql = KeRaiseIrqlToDpcLevel();
  631. // Now that we are at DISPATCH_LEVEL, perform the work if we are still
  632. // executing on the processor that owns the pool.
  633. //
  634. if ((ULONG)KeGetCurrentProcessorNumber() == CpuPool->OwnerCpu)
  635. {
  636. // If the page is still on the used-page list (meaning another
  637. // FsbFree didn't just sneak by) and still has a free block (for
  638. // instance, an FsbAllocate might sneak in on its owning processor,
  639. // scavenge the page, and allocate the block we just freed; thereby
  640. // putting it back on the used-page list), then put the page on the
  641. // normal list. Very important to do this after (not before)
  642. // returning the block to the free list because FsbAllocate expects
  643. // blocks to be available from pages on the page list.
  644. //
  645. if (PageIsOnUsedPageList &&
  646. Page->OnUsedPageList &&
  647. (0 != ExQueryDepthSList(&Page->FreeList)))
  648. {
  649. RemoveEntryList(&Page->PageLink);
  650. Page->OnUsedPageList = FALSE;
  651. InsertTailList(&CpuPool->PageList, &Page->PageLink);
  652. }
  653. // Perform the scavenge if we previously noted we needed to do so.
  654. //
  655. if (Scavenge)
  656. {
  657. FsbpScavengePool(CpuPool);
  658. }
  659. }
  660. KeLowerIrql(OldIrql);
  661. }
  662. }