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.

930 lines
28 KiB

  1. /*++
  2. Copyright (c) 1999-2000 Microsoft Corporation
  3. Module Name:
  4. mdlpool.c
  5. Abstract:
  6. This file contains the implementation of an MDL buffer pool.
  7. Author:
  8. Shaun Cox (shaunco) 21-Oct-1999
  9. --*/
  10. #include "ntddk.h"
  11. #include "mdlpool.h"
  12. typedef PVOID LPVOID;
  13. #include "align.h" // Macros: ROUND_UP_POINTER, POINTER_IS_ALIGNED
  14. #define SHOW_DEBUG_OUTPUT 0
  15. #define SCAVENGE_PERIOD_IN_SECONDS 30
  16. #define MINIMUM_PAGE_LIFETIME_IN_SECONDS 20
  17. #define USED_PAGES_SCAVENGE_THRESHOLD 64
  18. #if defined (_WIN64)
  19. #define MAX_CACHE_LINE_SIZE 128
  20. #define BLOCK_TYPE SLIST_HEADER
  21. #else
  22. #define MAX_CACHE_LINE_SIZE 64
  23. #define BLOCK_TYPE PVOID
  24. #endif
  25. // The following structures are used in the single allocation that
  26. // a pool handle points to.
  27. // PoolHandle ---> [POOL_HEADER + CPU_POOL_HEADER for cpu 0 +
  28. // CPU_POOL_HEADER for cpu 1 + ...
  29. // CPU_POOL_HEADER for cpu N]
  30. //
  31. // POOL_HEADER is the data common to all CPUs for a given pool.
  32. //
  33. typedef struct _POOL_HEADER
  34. {
  35. // cache-line -----
  36. struct _POOL_HEADER_BASE
  37. {
  38. ULONG Tag;
  39. USHORT BufferSize;
  40. USHORT MdlsPerPage;
  41. PVOID Allocation;
  42. };
  43. UCHAR Alignment[MAX_CACHE_LINE_SIZE
  44. - (sizeof(struct _POOL_HEADER_BASE) % MAX_CACHE_LINE_SIZE)];
  45. } POOL_HEADER, *PPOOL_HEADER;
  46. C_ASSERT(sizeof(POOL_HEADER) % MAX_CACHE_LINE_SIZE == 0);
  47. // CPU_POOL_HEADER is the data specific to a CPU for a given pool.
  48. //
  49. typedef struct _CPU_POOL_HEADER
  50. {
  51. // cache-line -----
  52. struct _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 MDLs 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 MdpAllocate 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. // that this.
  66. //
  67. LARGE_INTEGER NextScavengeTick;
  68. // Count of pages on the used page list.
  69. // If this becomes greater than USED_PAGES_SCAVENGE_THRESHOLD
  70. // and we know we missed a page move during a prior MdpFree,
  71. // we will scavenge during the next MdpAllocate.
  72. //
  73. USHORT PagesOnUsedPageList;
  74. // Set to TRUE during MdpFree if could not move a previously used
  75. // page back to the normal list because the free was done by a
  76. // non-owning processor. Set to FALSE during MdpScavenge.
  77. //
  78. BOOLEAN MissedPageMove;
  79. // The number of the processor that owns this pool.
  80. //
  81. UCHAR OwnerCpu;
  82. ULONG TotalMdlsAllocated;
  83. ULONG TotalMdlsFreed;
  84. ULONG PeakMdlsInUse;
  85. ULONG TotalPagesAllocated;
  86. ULONG TotalPagesFreed;
  87. ULONG PeakPagesInUse;
  88. };
  89. UCHAR Alignment[MAX_CACHE_LINE_SIZE
  90. - (sizeof(struct _CPU_POOL_HEADER_BASE) % MAX_CACHE_LINE_SIZE)];
  91. } CPU_POOL_HEADER, *PCPU_POOL_HEADER;
  92. C_ASSERT(sizeof(CPU_POOL_HEADER) % MAX_CACHE_LINE_SIZE == 0);
  93. // PAGE_HEADER is the data at the beginning of each allocated pool page
  94. // that describes the current state of the MDLs on the page.
  95. //
  96. typedef struct _PAGE_HEADER
  97. {
  98. // cache-line -----
  99. // Back pointer to the owning cpu pool.
  100. //
  101. PCPU_POOL_HEADER Pool;
  102. // Linkage entry for the list of pages managed by the cpu pool.
  103. //
  104. LIST_ENTRY PageLink;
  105. // Number of MDLs built so far on this page. MDLs are built on
  106. // demand. When this number reaches Pool->MdlsPerPage, all MDLs on this
  107. // page have been built.
  108. //
  109. USHORT MdlsBuilt;
  110. // Boolean indicator of whether or not this page is on the cpu pool's
  111. // used-page list. This is checked during MdpFree to see if the page
  112. // should be moved back to the normal page list.
  113. // (it is a USHORT, instead of BOOLEAN, for proper padding)
  114. //
  115. USHORT OnUsedPageList;
  116. // List of free MDLs on this page.
  117. //
  118. SLIST_HEADER FreeList;
  119. // The value of KeQueryTickCount (normalized to units of seconds)
  120. // which represents the time after which this page can be freed back
  121. // to the system's pool. This time is only used once the depth of
  122. // FreeList is Pool->MdlsPerPage. (i.e. this time is only used if
  123. // the page is completely unused.)
  124. //
  125. LARGE_INTEGER LastUsedTick;
  126. } PAGE_HEADER, *PPAGE_HEADER;
  127. // MDLs that we build are always limited to one page and they never
  128. // describe buffers that span a page boundry.
  129. //
  130. #define MDLSIZE sizeof(MDL) + sizeof(PFN_NUMBER)
  131. // Get a pointer to the overall pool given a pointer to one of
  132. // the per-processor pools within it.
  133. //
  134. __inline
  135. PPOOL_HEADER
  136. PoolFromCpuPool(
  137. IN PCPU_POOL_HEADER CpuPool
  138. )
  139. {
  140. return (PPOOL_HEADER)(CpuPool - CpuPool->OwnerCpu) - 1;
  141. }
  142. __inline
  143. VOID
  144. ConvertSecondsToTicks(
  145. IN ULONG Seconds,
  146. OUT PLARGE_INTEGER Ticks
  147. )
  148. {
  149. // If the following assert fires, you need to cast Seconds below to
  150. // ULONGLONG so that 64 bit multiplication and division are used.
  151. // The current code assumes less that 430 seconds so that the
  152. // 32 multiplication below won't overflow.
  153. //
  154. ASSERT(Seconds < 430);
  155. Ticks->HighPart = 0;
  156. Ticks->LowPart = (Seconds * 10*1000*1000) / KeQueryTimeIncrement();
  157. }
  158. // Build the next MDL on the specified pool page.
  159. // This can only be called if not all of the MDLs have been built yet.
  160. //
  161. PMDL
  162. MdppBuildNextMdl(
  163. IN const POOL_HEADER* Pool,
  164. IN OUT PPAGE_HEADER Page
  165. )
  166. {
  167. PMDL Mdl;
  168. ULONG BlockSize = ALIGN_UP(MDLSIZE + Pool->BufferSize, BLOCK_TYPE);
  169. ASSERT(Page->MdlsBuilt < Pool->MdlsPerPage);
  170. ASSERT((PAGE_SIZE - sizeof(PAGE_HEADER)) / BlockSize == Pool->MdlsPerPage);
  171. Mdl = (PMDL)((PCHAR)(Page + 1) + (Page->MdlsBuilt * BlockSize));
  172. ASSERT(PAGE_ALIGN(Mdl) == Page);
  173. MmInitializeMdl(Mdl, (PCHAR)Mdl + MDLSIZE, Pool->BufferSize);
  174. MmBuildMdlForNonPagedPool(Mdl);
  175. ASSERT(MDLSIZE == Mdl->Size);
  176. ASSERT(MmGetMdlBaseVa(Mdl) == Page);
  177. ASSERT(MmGetMdlByteCount(Mdl) == Pool->BufferSize);
  178. Page->MdlsBuilt++;
  179. return Mdl;
  180. }
  181. // Allocate a new pool page and insert it at the head of the specified
  182. // CPU pool. Build the first MDL on the new page and return a pointer
  183. // to it.
  184. //
  185. PMDL
  186. MdppAllocateNewPageAndBuildOneMdl(
  187. IN const POOL_HEADER* Pool,
  188. IN PCPU_POOL_HEADER CpuPool
  189. )
  190. {
  191. PPAGE_HEADER Page;
  192. PMDL Mdl = NULL;
  193. ULONG PagesInUse;
  194. ASSERT(Pool);
  195. Page = ExAllocatePoolWithTagPriority(NonPagedPool, PAGE_SIZE, Pool->Tag,
  196. NormalPoolPriority);
  197. if (Page)
  198. {
  199. ASSERT(Page == PAGE_ALIGN(Page));
  200. RtlZeroMemory(Page, sizeof(PAGE_HEADER));
  201. Page->Pool = CpuPool;
  202. ExInitializeSListHead(&Page->FreeList);
  203. // Insert the page at the head of the cpu's pool.
  204. //
  205. InsertHeadList(&CpuPool->PageList, &Page->PageLink);
  206. CpuPool->TotalPagesAllocated++;
  207. // Update the pool's statistics.
  208. //
  209. PagesInUse = CpuPool->TotalPagesAllocated - CpuPool->TotalPagesFreed;
  210. if (PagesInUse > CpuPool->PeakPagesInUse)
  211. {
  212. CpuPool->PeakPagesInUse = PagesInUse;
  213. }
  214. Mdl = MdppBuildNextMdl(Pool, Page);
  215. ASSERT(Mdl);
  216. #if SHOW_DEBUG_OUTPUT
  217. DbgPrint(
  218. "[%d] %c%c%c%c page allocated : Pages(a%4d,u%4d,p%4d), Mdls(a%6d,u%6d,p%6d)\n",
  219. CpuPool->OwnerCpu,
  220. Pool->Tag, Pool->Tag >> 8, Pool->Tag >> 16, Pool->Tag >> 24,
  221. CpuPool->TotalPagesAllocated,
  222. CpuPool->TotalPagesAllocated - CpuPool->TotalPagesFreed,
  223. CpuPool->PeakPagesInUse,
  224. CpuPool->TotalMdlsAllocated,
  225. CpuPool->TotalMdlsAllocated - CpuPool->TotalMdlsFreed,
  226. CpuPool->PeakMdlsInUse);
  227. #endif
  228. }
  229. return Mdl;
  230. }
  231. // Free the specified pool page back to the system's pool.
  232. //
  233. VOID
  234. MdppFreePage(
  235. IN PCPU_POOL_HEADER CpuPool,
  236. IN PPAGE_HEADER Page
  237. )
  238. {
  239. #if SHOW_DEBUG_OUTPUT
  240. ULONG Tag;
  241. #endif
  242. ASSERT(Page == PAGE_ALIGN(Page));
  243. ASSERT(Page->Pool == CpuPool);
  244. ExFreePool (Page);
  245. CpuPool->TotalPagesFreed++;
  246. ASSERT(CpuPool->TotalPagesFreed <= CpuPool->TotalPagesAllocated);
  247. #if SHOW_DEBUG_OUTPUT
  248. Tag = PoolFromCpuPool(CpuPool)->Tag;
  249. DbgPrint(
  250. "[%d] %c%c%c%c page freed : Pages(a%4d,u%4d,p%4d), Mdls(a%6d,u%6d,p%6d)\n",
  251. CpuPool->OwnerCpu,
  252. Tag, Tag >> 8, Tag >> 16, Tag >> 24,
  253. CpuPool->TotalPagesAllocated,
  254. CpuPool->TotalPagesAllocated - CpuPool->TotalPagesFreed,
  255. CpuPool->PeakPagesInUse,
  256. CpuPool->TotalMdlsAllocated,
  257. CpuPool->TotalMdlsAllocated - CpuPool->TotalMdlsFreed,
  258. CpuPool->PeakMdlsInUse);
  259. #endif
  260. }
  261. // Free the specified pool page list back to the system's pool.
  262. //
  263. VOID
  264. MdppFreeList(
  265. IN PCPU_POOL_HEADER CpuPool,
  266. IN PLIST_ENTRY Head
  267. )
  268. {
  269. PPOOL_HEADER Pool;
  270. PPAGE_HEADER Page;
  271. PLIST_ENTRY Scan;
  272. PLIST_ENTRY Next;
  273. BOOLEAN UsedPageList;
  274. Pool = PoolFromCpuPool(CpuPool);
  275. UsedPageList = (Head == &CpuPool->UsedPageList);
  276. for (Scan = Head->Flink; Scan != Head; Scan = Next)
  277. {
  278. Page = CONTAINING_RECORD(Scan, PAGE_HEADER, PageLink);
  279. ASSERT(Page == PAGE_ALIGN(Page));
  280. ASSERT(CpuPool == Page->Pool);
  281. ASSERT(UsedPageList ? Page->OnUsedPageList : !Page->OnUsedPageList);
  282. ASSERT(Page->MdlsBuilt <= Pool->MdlsPerPage);
  283. ASSERT(Page->MdlsBuilt == ExQueryDepthSList(&Page->FreeList));
  284. // Step to the next link before we free this page.
  285. //
  286. Next = Scan->Flink;
  287. RemoveEntryList(Scan);
  288. MdppFreePage(CpuPool, Page);
  289. }
  290. }
  291. // Reclaim the memory consumed by completely unused pool pages belonging
  292. // to the specified per-processor pool.
  293. //
  294. // Caller IRQL: [DISPATCH_LEVEL]
  295. //
  296. VOID
  297. MdppScavengePool(
  298. IN OUT PCPU_POOL_HEADER CpuPool
  299. )
  300. {
  301. PPOOL_HEADER Pool;
  302. PPAGE_HEADER Page;
  303. PLIST_ENTRY Scan;
  304. PLIST_ENTRY Next;
  305. LARGE_INTEGER Ticks;
  306. LARGE_INTEGER TicksDelta;
  307. // We must not only be at DISPATCH_LEVEL (or higher), we must also
  308. // be called on the processor that owns the specified pool.
  309. //
  310. ASSERT(KeGetCurrentIrql() >= DISPATCH_LEVEL);
  311. ASSERT(KeGetCurrentProcessorNumber() == CpuPool->OwnerCpu);
  312. Pool = PoolFromCpuPool(CpuPool);
  313. KeQueryTickCount(&Ticks);
  314. // Compute the next tick value which represents the earliest time
  315. // that we will scavenge this pool again.
  316. //
  317. ConvertSecondsToTicks(SCAVENGE_PERIOD_IN_SECONDS, &TicksDelta);
  318. CpuPool->NextScavengeTick.QuadPart = Ticks.QuadPart + TicksDelta.QuadPart;
  319. // Compute the tick value which represents the last point at which
  320. // its okay to free a page.
  321. //
  322. ConvertSecondsToTicks(MINIMUM_PAGE_LIFETIME_IN_SECONDS, &TicksDelta);
  323. Ticks.QuadPart = Ticks.QuadPart - TicksDelta.QuadPart;
  324. for (Scan = CpuPool->PageList.Flink;
  325. Scan != &CpuPool->PageList;
  326. Scan = Next)
  327. {
  328. Page = CONTAINING_RECORD(Scan, PAGE_HEADER, PageLink);
  329. ASSERT(Page == PAGE_ALIGN(Page));
  330. ASSERT(CpuPool == Page->Pool);
  331. ASSERT(!Page->OnUsedPageList);
  332. // Step to the next link before we possibly unlink this page.
  333. //
  334. Next = Scan->Flink;
  335. if ((Pool->MdlsPerPage == ExQueryDepthSList(&Page->FreeList)) &&
  336. (Ticks.QuadPart > Page->LastUsedTick.QuadPart))
  337. {
  338. RemoveEntryList(Scan);
  339. MdppFreePage(CpuPool, Page);
  340. }
  341. }
  342. // Scan the used pages to see if they can be moved back to the normal
  343. // list. This can happen if too many frees by non-owning processors
  344. // are done. In that case, the pages get orphaned on the used-page
  345. // list after all of their MDLs have been freed to the page. Un-orphan
  346. // them here.
  347. //
  348. for (Scan = CpuPool->UsedPageList.Flink;
  349. Scan != &CpuPool->UsedPageList;
  350. Scan = Next)
  351. {
  352. Page = CONTAINING_RECORD(Scan, PAGE_HEADER, PageLink);
  353. ASSERT(Page == PAGE_ALIGN(Page));
  354. ASSERT(CpuPool == Page->Pool);
  355. ASSERT(Page->OnUsedPageList);
  356. // Step to the next link before we possibly unlink this page.
  357. //
  358. Next = Scan->Flink;
  359. if (0 != ExQueryDepthSList(&Page->FreeList))
  360. {
  361. RemoveEntryList(Scan);
  362. Page->OnUsedPageList = FALSE;
  363. InsertTailList(&CpuPool->PageList, Scan);
  364. CpuPool->PagesOnUsedPageList--;
  365. #if SHOW_DEBUG_OUTPUT
  366. DbgPrint(
  367. "[%d] %c%c%c%c page moved off of used-page list during scavenge\n",
  368. CpuPool->OwnerCpu,
  369. Pool->Tag, Pool->Tag >> 8, Pool->Tag >> 16, Pool->Tag >> 24);
  370. #endif
  371. }
  372. }
  373. // Reset our indicator of a missed page move now that we've scavenged.
  374. //
  375. CpuPool->MissedPageMove = FALSE;
  376. }
  377. // Creates a pool of MDLs built over non-paged pool. Each MDL describes
  378. // a buffer that is BufferSize bytes long. If NULL is not returned,
  379. // MdpDestroyPool should be called at a later time to reclaim the
  380. // resources used by the pool.
  381. //
  382. // Arguments:
  383. // BufferSize - The size, in bytes, of the buffer that each MDL
  384. // should describe.
  385. // Tag - The pool tag to be used internally for calls to
  386. // ExAllocatePoolWithTag. This allows callers to track
  387. // memory consumption for different pools.
  388. //
  389. // Returns the handle used to identify the pool.
  390. //
  391. // Caller IRQL: [PASSIVE_LEVEL, DISPATCH_LEVEL]
  392. //
  393. HANDLE
  394. MdpCreatePool(
  395. IN USHORT BufferSize,
  396. IN ULONG Tag
  397. )
  398. {
  399. SIZE_T Size;
  400. PVOID Allocation;
  401. PPOOL_HEADER Pool = NULL;
  402. PCPU_POOL_HEADER CpuPool;
  403. USHORT BlockSize;
  404. CCHAR NumberCpus = KeNumberProcessors;
  405. CCHAR i;
  406. ASSERT(BufferSize);
  407. // Compute the size of our pool header allocation.
  408. // Add padding to ensure that the pool header can begin on a cache line.
  409. //
  410. Size = sizeof(POOL_HEADER) + (sizeof(CPU_POOL_HEADER) * NumberCpus) +
  411. (MAX_CACHE_LINE_SIZE - MEMORY_ALLOCATION_ALIGNMENT);
  412. // Allocate the pool header.
  413. //
  414. Allocation = ExAllocatePoolWithTag(NonPagedPool, Size, ' pdM');
  415. if (Allocation)
  416. {
  417. ASSERT(POINTER_IS_ALIGNED(Allocation, MEMORY_ALLOCATION_ALIGNMENT));
  418. RtlZeroMemory(Allocation, Size);
  419. Pool = ROUND_UP_POINTER(Allocation, MAX_CACHE_LINE_SIZE);
  420. BlockSize = (USHORT)ALIGN_UP(MDLSIZE + BufferSize, BLOCK_TYPE);
  421. // Initialize the pool header fields.
  422. //
  423. Pool->Tag = Tag;
  424. Pool->BufferSize = BufferSize;
  425. Pool->MdlsPerPage = (PAGE_SIZE - sizeof(PAGE_HEADER)) / BlockSize;
  426. Pool->Allocation = Allocation;
  427. // Initialize the per-cpu pool headers.
  428. //
  429. CpuPool = (PCPU_POOL_HEADER)(Pool + 1);
  430. for (i = 0; i < NumberCpus; i++)
  431. {
  432. InitializeListHead(&CpuPool[i].PageList);
  433. InitializeListHead(&CpuPool[i].UsedPageList);
  434. CpuPool[i].OwnerCpu = i;
  435. }
  436. }
  437. return Pool;
  438. }
  439. // Destroys a pool of MDLs previously created by a call to MdpCreatePool.
  440. //
  441. // Arguments:
  442. // Pool - Handle which identifies the pool being destroyed.
  443. //
  444. // Caller IRQL: [PASSIVE_LEVEL, DISPATCH_LEVEL]
  445. //
  446. VOID
  447. MdpDestroyPool(
  448. IN HANDLE PoolHandle
  449. )
  450. {
  451. PPOOL_HEADER Pool;
  452. PCPU_POOL_HEADER CpuPool;
  453. CCHAR NumberCpus = KeNumberProcessors;
  454. CCHAR i;
  455. ASSERT(PoolHandle);
  456. Pool = (PPOOL_HEADER)PoolHandle;
  457. if (!Pool)
  458. {
  459. return;
  460. }
  461. for (i = 0, CpuPool = (PCPU_POOL_HEADER)(Pool + 1);
  462. i < NumberCpus;
  463. i++, CpuPool++)
  464. {
  465. ASSERT(CpuPool->OwnerCpu == (ULONG)i);
  466. MdppFreeList(CpuPool, &CpuPool->PageList);
  467. MdppFreeList(CpuPool, &CpuPool->UsedPageList);
  468. ASSERT(CpuPool->TotalPagesAllocated == CpuPool->TotalPagesFreed);
  469. ASSERT(CpuPool->TotalMdlsAllocated == CpuPool->TotalMdlsFreed);
  470. }
  471. ASSERT(Pool == ROUND_UP_POINTER(Pool->Allocation, MAX_CACHE_LINE_SIZE));
  472. ExFreePool(Pool->Allocation);
  473. }
  474. // Returns an MDL allocated from a pool. NULL is returned if the
  475. // request could not be granted.
  476. //
  477. // Arguments:
  478. // PoolHandle - Handle which identifies the pool being allocated from.
  479. // Buffer - Address to receive the pointer to the underlying mapped buffer
  480. // described by the MDL.
  481. //
  482. // Caller IRQL: [PASSIVE_LEVEL, DISPATCH_LEVEL]
  483. //
  484. PMDL
  485. MdpAllocate(
  486. IN HANDLE PoolHandle,
  487. OUT PVOID* Buffer
  488. )
  489. {
  490. KIRQL OldIrql;
  491. PMDL Mdl;
  492. OldIrql = KeRaiseIrqlToDpcLevel();
  493. Mdl = MdpAllocateAtDpcLevel(PoolHandle, Buffer);
  494. KeLowerIrql(OldIrql);
  495. return Mdl;
  496. }
  497. // Returns an MDL allocated from a pool. NULL is returned if the
  498. // request could not be granted.
  499. //
  500. // Arguments:
  501. // PoolHandle - Handle which identifies the pool being allocated from.
  502. // Buffer - Address to receive the pointer to the underlying mapped buffer
  503. // described by the MDL.
  504. //
  505. // Caller IRQL: [DISPATCH_LEVEL]
  506. //
  507. PMDL
  508. MdpAllocateAtDpcLevel(
  509. IN HANDLE PoolHandle,
  510. OUT PVOID* Buffer
  511. )
  512. {
  513. PPOOL_HEADER Pool;
  514. PCPU_POOL_HEADER CpuPool;
  515. PPAGE_HEADER Page;
  516. PSLIST_ENTRY MdlLink;
  517. PMDL Mdl = NULL;
  518. ULONG Cpu;
  519. LARGE_INTEGER Ticks;
  520. #if DBG
  521. ASSERT(PoolHandle);
  522. ASSERT(Buffer);
  523. ASSERT(KeGetCurrentIrql() >= DISPATCH_LEVEL);
  524. #endif
  525. *Buffer = NULL;
  526. Pool = (PPOOL_HEADER)PoolHandle;
  527. Cpu = KeGetCurrentProcessorNumber();
  528. CpuPool = (PCPU_POOL_HEADER)(Pool + 1) + Cpu;
  529. // If we know we've had frees by non-owning processors and there
  530. // are more than USED_PAGES_SCAVENGE_THRESHOLD pages on the used
  531. // page list, it is time to scavenge. This is common in situations
  532. // where the buffer size is very large causing there to be just a few
  533. // MDLs per page. Pages get used up quickly and if non-owning frees
  534. // are prevalent, the used page list can get very big even in
  535. // the normal scavenge period.
  536. //
  537. if (CpuPool->MissedPageMove &&
  538. (CpuPool->PagesOnUsedPageList > USED_PAGES_SCAVENGE_THRESHOLD))
  539. {
  540. #if SHOW_DEBUG_OUTPUT
  541. DbgPrint(
  542. "[%d] %c%c%c%c Scavenging because of excessive used pages.\n",
  543. CpuPool->OwnerCpu,
  544. Pool->Tag, Pool->Tag >> 8, Pool->Tag >> 16, Pool->Tag >> 24);
  545. #endif
  546. MdppScavengePool(CpuPool);
  547. }
  548. else
  549. {
  550. // See if the minimum time has passed since we last scavenged
  551. // the pool. If it has, we'll scavenge again. Normally, scavenging
  552. // should only be performed when we free. However, for the case when
  553. // the caller constantly frees on a non-owning processor, we'll
  554. // take this chance to do the scavenging.
  555. //
  556. KeQueryTickCount(&Ticks);
  557. if (Ticks.QuadPart > CpuPool->NextScavengeTick.QuadPart)
  558. {
  559. MdppScavengePool(CpuPool);
  560. }
  561. }
  562. if (!IsListEmpty(&CpuPool->PageList))
  563. {
  564. Page = CONTAINING_RECORD(CpuPool->PageList.Flink, PAGE_HEADER, PageLink);
  565. ASSERT(Page == PAGE_ALIGN(Page));
  566. ASSERT(CpuPool == Page->Pool);
  567. ASSERT(!Page->OnUsedPageList);
  568. MdlLink = InterlockedPopEntrySList(&Page->FreeList);
  569. if (MdlLink)
  570. {
  571. Mdl = CONTAINING_RECORD(MdlLink, MDL, Next);
  572. }
  573. else
  574. {
  575. // If there were no MDLs on this page's free list, it had better
  576. // mean we haven't yet built all of the MDLs on the page.
  577. // (Otherwise, what is a fully used page doing on the page list
  578. // and not on the used-page list?)
  579. //
  580. ASSERT(Page->MdlsBuilt < Pool->MdlsPerPage);
  581. Mdl = MdppBuildNextMdl(Pool, Page);
  582. ASSERT(Mdl);
  583. }
  584. if ((Page != PAGE_ALIGN(Page)) || (CpuPool != Page->Pool) ||
  585. Page->OnUsedPageList || (PAGE_ALIGN(Mdl) != Page))
  586. {
  587. KeBugCheckEx(BAD_POOL_CALLER, 2, (ULONG_PTR)Mdl,
  588. (ULONG_PTR)Page, (ULONG_PTR)CpuPool);
  589. }
  590. // Got an MDL. Now check to see if it was the last one on a fully
  591. // built page. If so, move the page to the used-page list.
  592. //
  593. if ((0 == ExQueryDepthSList(&Page->FreeList)) &&
  594. (Page->MdlsBuilt == Pool->MdlsPerPage))
  595. {
  596. PLIST_ENTRY PageLink;
  597. PageLink = RemoveHeadList(&CpuPool->PageList);
  598. InsertTailList(&CpuPool->UsedPageList, PageLink);
  599. Page->OnUsedPageList = TRUE;
  600. CpuPool->PagesOnUsedPageList++;
  601. ASSERT(Page == CONTAINING_RECORD(PageLink, PAGE_HEADER, PageLink));
  602. #if SHOW_DEBUG_OUTPUT
  603. DbgPrint(
  604. "[%d] %c%c%c%c page moved to used-page list\n",
  605. CpuPool->OwnerCpu,
  606. Pool->Tag, Pool->Tag >> 8, Pool->Tag >> 16, Pool->Tag >> 24);
  607. #endif
  608. }
  609. ASSERT(Mdl);
  610. goto GotAnMdl;
  611. }
  612. else
  613. {
  614. // The page list is empty so we have to allocate and add a new page.
  615. //
  616. Mdl = MdppAllocateNewPageAndBuildOneMdl(Pool, CpuPool);
  617. }
  618. // If we are returning an MDL, update the statistics.
  619. //
  620. if (Mdl)
  621. {
  622. ULONG MdlsInUse;
  623. GotAnMdl:
  624. CpuPool->TotalMdlsAllocated++;
  625. MdlsInUse = CpuPool->TotalMdlsAllocated - CpuPool->TotalMdlsFreed;
  626. if (MdlsInUse > CpuPool->PeakMdlsInUse)
  627. {
  628. CpuPool->PeakMdlsInUse = MdlsInUse;
  629. }
  630. // Don't give anyone ideas about where this might point. I don't
  631. // want anyone trashing my pool because they thought this field
  632. // was valid for some reason.
  633. //
  634. Mdl->Next = NULL;
  635. // Reset the length of the buffer described by the MDL. This is
  636. // a convienence to callers who sometimes adjust this length while
  637. // using the MDL, but who expect it to be reset on subsequent MDL
  638. // allocations.
  639. //
  640. Mdl->ByteCount = Pool->BufferSize;
  641. ASSERT(Mdl->MdlFlags & MDL_SOURCE_IS_NONPAGED_POOL);
  642. *Buffer = Mdl->MappedSystemVa;
  643. }
  644. return Mdl;
  645. }
  646. // Free an MDL to the pool from which it was allocated.
  647. //
  648. // Arguments:
  649. // Mdl - An Mdl returned from a prior call to MdpAllocate.
  650. //
  651. // Caller IRQL: [PASSIVE_LEVEL, DISPATCH_LEVEL]
  652. //
  653. VOID
  654. MdpFree(
  655. IN PMDL Mdl
  656. )
  657. {
  658. PPAGE_HEADER Page;
  659. PCPU_POOL_HEADER CpuPool;
  660. PPOOL_HEADER Pool;
  661. LARGE_INTEGER Ticks;
  662. LOGICAL PageIsOnUsedPageList;
  663. LOGICAL Scavenge = FALSE;
  664. ASSERT(Mdl);
  665. // Get the address of the page that this MDL maps. This is where
  666. // our page header is stored.
  667. //
  668. Page = PAGE_ALIGN(Mdl);
  669. // Follow the back pointer in the page header to locate the owning
  670. // cpu's pool.
  671. //
  672. CpuPool = Page->Pool;
  673. // Locate the pool header.
  674. //
  675. Pool = PoolFromCpuPool(CpuPool);
  676. //#if DBG
  677. // If someone changed the MDL to point to there own buffer,
  678. // or otherwise corrupted it, we'll stop here and let them know.
  679. //
  680. if ((MmGetMdlBaseVa(Mdl) != Page) ||
  681. (MDLSIZE != Mdl->Size) ||
  682. ((ULONG_PTR)Mdl->MappedSystemVa != (ULONG_PTR)Mdl + MDLSIZE) ||
  683. (MmGetMdlVirtualAddress(Mdl) != Mdl->MappedSystemVa))
  684. {
  685. KeBugCheckEx(BAD_POOL_CALLER, 3, (ULONG_PTR)Mdl,
  686. (ULONG_PTR)CpuPool, (ULONG_PTR)Pool);
  687. }
  688. //#endif
  689. // See if the minimum time has passed since we last scavenged
  690. // the pool. If it has, we'll scavenge again.
  691. //
  692. KeQueryTickCount(&Ticks);
  693. if (Ticks.QuadPart > CpuPool->NextScavengeTick.QuadPart)
  694. {
  695. Scavenge = TRUE;
  696. }
  697. // Note the tick that this page was last used. If this is the last MDL to
  698. // be returned to this page, this sets the minimum time that this page will
  699. // continue to live unless it gets re-used.
  700. //
  701. Page->LastUsedTick.QuadPart = Ticks.QuadPart;
  702. // If this page is on the used-page list, we'll put it back on the normal
  703. // page list (only after pushing the MDL back on the page's free list)
  704. // if, after raising IRQL, we are on the processor that owns this
  705. // pool.
  706. //
  707. PageIsOnUsedPageList = Page->OnUsedPageList;
  708. InterlockedIncrement(&CpuPool->TotalMdlsFreed);
  709. // Now return the MDL to the page's free list.
  710. //
  711. InterlockedPushEntrySList(&Page->FreeList, (PSLIST_ENTRY)&Mdl->Next);
  712. //
  713. // Warning: Now that the MDL is back on the page, one cannot *reliably*
  714. // dereference anything through 'Page' anymore. It may have just been
  715. // scavenged by its owning processor and subsequently freed. This is a
  716. // particularly rare condition given that MINIMUM_PAGE_LIFETIME_IN_SECONDS
  717. // is 20s, so we choose to live with it. The alternative would be to walk
  718. // the UsedPageList whenever PageIsOnUsedPageList is true, making the
  719. // MdpFree operation potentially expensive. We saved off the value of
  720. // Page->OnUsedPageList before returning the MDL so we would not risk
  721. // touching Page to get this value only to find that it was false.
  722. //
  723. // If we need to move the page from the used-page list to the normal
  724. // page list, or if we need to scavenge, we need to be at DISPATCH_LEVEL
  725. // and be executing on the processor that owns this pool.
  726. // Find out if the CPU we are executing on right now owns this pool.
  727. // Note that if we are running at PASSIVE_LEVEL, the current CPU may
  728. // change over the duration of this function call, so this value is
  729. // not absolute over the life of the function.
  730. //
  731. if ((PageIsOnUsedPageList || Scavenge) &&
  732. (KeGetCurrentProcessorNumber() == CpuPool->OwnerCpu))
  733. {
  734. KIRQL OldIrql;
  735. OldIrql = KeRaiseIrqlToDpcLevel();
  736. // Now that we are at DISPATCH_LEVEL, perform the work if we are still
  737. // executing on the processor that owns the pool.
  738. //
  739. if (KeGetCurrentProcessorNumber() == CpuPool->OwnerCpu)
  740. {
  741. // If the page is still on the used-page list (meaning another
  742. // MdpFree didn't just sneak by) and still has a free MDL (for
  743. // instance, an MdpAllocate might sneak in on its owning processor,
  744. // scavenge the page, and allocate the MDL we just freed; thereby
  745. // putting it back on the used-page list), then put the page on the
  746. // normal list. Very important to do this after (not before)
  747. // returning the MDL to the free list because MdpAllocate expects
  748. // MDL's to be available from pages on the page list.
  749. //
  750. if (PageIsOnUsedPageList &&
  751. Page->OnUsedPageList &&
  752. (0 != ExQueryDepthSList(&Page->FreeList)))
  753. {
  754. RemoveEntryList(&Page->PageLink);
  755. Page->OnUsedPageList = FALSE;
  756. InsertTailList(&CpuPool->PageList, &Page->PageLink);
  757. CpuPool->PagesOnUsedPageList--;
  758. PageIsOnUsedPageList = FALSE;
  759. #if SHOW_DEBUG_OUTPUT
  760. DbgPrint(
  761. "[%d] %c%c%c%c page moved off of used-page list\n",
  762. CpuPool->OwnerCpu,
  763. Pool->Tag, Pool->Tag >> 8, Pool->Tag >> 16, Pool->Tag >> 24);
  764. #endif
  765. }
  766. // Perform the scavenge if we previously noted we needed to do so.
  767. //
  768. if (Scavenge)
  769. {
  770. MdppScavengePool(CpuPool);
  771. }
  772. }
  773. KeLowerIrql(OldIrql);
  774. }
  775. // If we missed being able to put this page back on the normal list.
  776. // note it.
  777. //
  778. if (PageIsOnUsedPageList)
  779. {
  780. CpuPool->MissedPageMove = TRUE;
  781. }
  782. }