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.

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