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.

5062 lines
144 KiB

  1. /*++
  2. Copyright (c) 1989 Microsoft Corporation
  3. Module Name:
  4. wslist.c
  5. Abstract:
  6. This module contains routines which operate on the working
  7. set list structure.
  8. Author:
  9. Lou Perazzoli (loup) 10-Apr-1989
  10. Landy Wang (landyw) 02-Jun-1997
  11. Revision History:
  12. --*/
  13. #include "mi.h"
  14. #pragma alloc_text(INIT, MiInitializeSessionWsSupport)
  15. #pragma alloc_text(PAGE, MmAssignProcessToJob)
  16. #pragma alloc_text(PAGE, MiInitializeWorkingSetList)
  17. extern WSLE_NUMBER MmMaximumWorkingSetSize;
  18. ULONG MmSystemCodePage;
  19. ULONG MmSystemCachePage;
  20. ULONG MmPagedPoolPage;
  21. ULONG MmSystemDriverPage;
  22. extern LOGICAL MiReplacing;
  23. #define MM_RETRY_COUNT 2
  24. extern PFN_NUMBER MmTransitionSharedPages;
  25. PFN_NUMBER MmTransitionSharedPagesPeak;
  26. extern LOGICAL MiTrimRemovalPagesOnly;
  27. typedef enum _WSLE_ALLOCATION_TYPE {
  28. WsleAllocationAny = 0,
  29. WsleAllocationReplace = 1,
  30. WsleAllocationDontTrim = 2
  31. } WSLE_ALLOCATION_TYPE, *PWSLE_ALLOCATION_TYPE;
  32. VOID
  33. MiDoReplacement (
  34. IN PMMSUPPORT WsInfo,
  35. IN WSLE_ALLOCATION_TYPE Flags
  36. );
  37. VOID
  38. MiReplaceWorkingSetEntry (
  39. IN PMMSUPPORT WsInfo,
  40. IN WSLE_ALLOCATION_TYPE Flags
  41. );
  42. VOID
  43. MiUpdateWsle (
  44. IN OUT PWSLE_NUMBER DesiredIndex,
  45. IN PVOID VirtualAddress,
  46. IN PMMSUPPORT WsInfo,
  47. IN PMMPFN Pfn
  48. );
  49. VOID
  50. MiCheckWsleHash (
  51. IN PMMWSL WorkingSetList
  52. );
  53. VOID
  54. MiEliminateWorkingSetEntry (
  55. IN WSLE_NUMBER WorkingSetIndex,
  56. IN PMMPTE PointerPte,
  57. IN PMMPFN Pfn,
  58. IN PMMWSLE Wsle
  59. );
  60. ULONG
  61. MiAddWorkingSetPage (
  62. IN PMMSUPPORT WsInfo
  63. );
  64. VOID
  65. MiRemoveWorkingSetPages (
  66. IN PMMSUPPORT WsInfo
  67. );
  68. VOID
  69. MiCheckNullIndex (
  70. IN PMMWSL WorkingSetList
  71. );
  72. VOID
  73. MiDumpWsleInCacheBlock (
  74. IN PMMPTE CachePte
  75. );
  76. ULONG
  77. MiDumpPteInCacheBlock (
  78. IN PMMPTE PointerPte
  79. );
  80. #if defined (_MI_DEBUG_WSLE)
  81. VOID
  82. MiCheckWsleList (
  83. IN PMMSUPPORT WsInfo
  84. );
  85. #endif
  86. #ifdef ALLOC_PRAGMA
  87. #pragma alloc_text(PAGELK, MmAdjustWorkingSetSize)
  88. #pragma alloc_text(PAGELK, MmAdjustWorkingSetSizeEx)
  89. #pragma alloc_text(PAGELK, MiSessionInitializeWorkingSetList)
  90. #pragma alloc_text(PAGE, MmQueryWorkingSetInformation)
  91. #endif
  92. ULONG MiWsleFailures;
  93. WSLE_NUMBER
  94. MiAllocateWsle (
  95. IN PMMSUPPORT WsInfo,
  96. IN PMMPTE PointerPte,
  97. IN PMMPFN Pfn1,
  98. IN ULONG_PTR WsleMask
  99. )
  100. /*++
  101. Routine Description:
  102. This function examines the working set list for the specified
  103. working set and locates an entry to contain a new page.
  104. If the memory is not tight, the new page is added without removing a page.
  105. If memory is tight (or this working set is at its limit), a page
  106. is removed from the working set and the new page added in its place.
  107. Arguments:
  108. WsInfo - Supplies the working set list.
  109. PointerPte - Supplies the PTE of the virtual address to insert.
  110. Pfn1 - Supplies the PFN entry of the virtual address being inserted. If
  111. this pointer has the low bit set, no trimming can be done at this
  112. time (because it is a WSLE hash table page insertion). Strip the
  113. low bit for these cases.
  114. WsleMask - Supplies the mask to logical OR into the new working set entry.
  115. Return Value:
  116. Returns the working set index which was used to insert the specified entry,
  117. or 0 if no index was available.
  118. Environment:
  119. Kernel mode, APCs disabled, working set lock. PFN lock NOT held.
  120. --*/
  121. {
  122. PVOID VirtualAddress;
  123. PMMWSLE Wsle;
  124. PMMWSL WorkingSetList;
  125. WSLE_NUMBER WorkingSetIndex;
  126. WorkingSetList = WsInfo->VmWorkingSetList;
  127. Wsle = WorkingSetList->Wsle;
  128. //
  129. // Update page fault counts.
  130. //
  131. WsInfo->PageFaultCount += 1;
  132. InterlockedIncrement ((PLONG) &MmInfoCounters.PageFaultCount);
  133. //
  134. // Determine if a page should be removed from the working set to make
  135. // room for the new page. If so, remove it.
  136. //
  137. if ((ULONG_PTR)Pfn1 & 0x1) {
  138. MiDoReplacement (WsInfo, WsleAllocationDontTrim);
  139. if (WorkingSetList->FirstFree == WSLE_NULL_INDEX) {
  140. return 0;
  141. }
  142. Pfn1 = (PMMPFN)((ULONG_PTR)Pfn1 & ~0x1);
  143. }
  144. else {
  145. MiDoReplacement (WsInfo, WsleAllocationAny);
  146. if (WorkingSetList->FirstFree == WSLE_NULL_INDEX) {
  147. //
  148. // Add more pages to the working set list structure.
  149. //
  150. if (MiAddWorkingSetPage (WsInfo) == FALSE) {
  151. //
  152. // No page was added to the working set list structure.
  153. // We must replace a page within this working set.
  154. //
  155. MiDoReplacement (WsInfo, WsleAllocationReplace);
  156. if (WorkingSetList->FirstFree == WSLE_NULL_INDEX) {
  157. MiWsleFailures += 1;
  158. return 0;
  159. }
  160. }
  161. }
  162. }
  163. //
  164. // Get the working set entry from the free list.
  165. //
  166. ASSERT (WorkingSetList->FirstFree <= WorkingSetList->LastInitializedWsle);
  167. ASSERT (WorkingSetList->FirstFree >= WorkingSetList->FirstDynamic);
  168. WorkingSetIndex = WorkingSetList->FirstFree;
  169. WorkingSetList->FirstFree = (WSLE_NUMBER)(Wsle[WorkingSetIndex].u1.Long >> MM_FREE_WSLE_SHIFT);
  170. ASSERT ((WorkingSetList->FirstFree <= WorkingSetList->LastInitializedWsle) ||
  171. (WorkingSetList->FirstFree == WSLE_NULL_INDEX));
  172. ASSERT (WsInfo->WorkingSetSize <= (WorkingSetList->LastInitializedWsle + 1));
  173. WsInfo->WorkingSetSize += 1;
  174. #if defined (_MI_DEBUG_WSLE)
  175. WorkingSetList->Quota += 1;
  176. ASSERT (WsInfo->WorkingSetSize == WorkingSetList->Quota);
  177. #endif
  178. if (WsInfo->WorkingSetSize > WsInfo->MinimumWorkingSetSize) {
  179. InterlockedExchangeAddSizeT (&MmPagesAboveWsMinimum, 1);
  180. }
  181. if (WsInfo->WorkingSetSize > WsInfo->PeakWorkingSetSize) {
  182. WsInfo->PeakWorkingSetSize = WsInfo->WorkingSetSize;
  183. }
  184. if (WsInfo == &MmSystemCacheWs) {
  185. if (WsInfo->WorkingSetSize + MmTransitionSharedPages > MmTransitionSharedPagesPeak) {
  186. MmTransitionSharedPagesPeak = WsInfo->WorkingSetSize + MmTransitionSharedPages;
  187. }
  188. }
  189. if (WorkingSetIndex > WorkingSetList->LastEntry) {
  190. WorkingSetList->LastEntry = WorkingSetIndex;
  191. }
  192. //
  193. // The returned entry is guaranteed to be available at this point.
  194. //
  195. ASSERT (Wsle[WorkingSetIndex].u1.e1.Valid == 0);
  196. VirtualAddress = MiGetVirtualAddressMappedByPte (PointerPte);
  197. MiUpdateWsle (&WorkingSetIndex, VirtualAddress, WsInfo, Pfn1);
  198. if (WsleMask != 0) {
  199. Wsle[WorkingSetIndex].u1.Long |= WsleMask;
  200. }
  201. #if DBG
  202. if (MI_IS_SYSTEM_CACHE_ADDRESS (VirtualAddress)) {
  203. ASSERT (MmSystemCacheWsle[WorkingSetIndex].u1.e1.SameProtectAsProto);
  204. }
  205. #endif
  206. MI_SET_PTE_IN_WORKING_SET (PointerPte, WorkingSetIndex);
  207. return WorkingSetIndex;
  208. }
  209. //
  210. // Nonpaged helper routine.
  211. //
  212. VOID
  213. MiSetWorkingSetForce (
  214. IN PMMSUPPORT WsInfo,
  215. IN LOGICAL ForceTrim
  216. )
  217. {
  218. KIRQL OldIrql;
  219. LOCK_EXPANSION (OldIrql);
  220. WsInfo->Flags.ForceTrim = (UCHAR) ForceTrim;
  221. UNLOCK_EXPANSION (OldIrql);
  222. return;
  223. }
  224. VOID
  225. MiDoReplacement (
  226. IN PMMSUPPORT WsInfo,
  227. IN WSLE_ALLOCATION_TYPE Flags
  228. )
  229. /*++
  230. Routine Description:
  231. This function determines whether the working set should be
  232. grown or if a page should be replaced. Replacement is
  233. done here if deemed necessary.
  234. Arguments:
  235. WsInfo - Supplies the working set information structure to replace within.
  236. Flags - Supplies 0 if replacement is not required.
  237. Supplies 1 if replacement is desired.
  238. Supplies 2 if working set trimming must not be done here - this
  239. is only used for inserting new WSLE hash table pages as a trim
  240. would not know how to interpret them.
  241. Return Value:
  242. None.
  243. Environment:
  244. Kernel mode, APCs disabled, working set lock. PFN lock NOT held.
  245. --*/
  246. {
  247. KIRQL OldIrql;
  248. WSLE_NUMBER PagesTrimmed;
  249. ULONG MemoryMaker;
  250. PMMWSL WorkingSetList;
  251. WSLE_NUMBER CurrentSize;
  252. LARGE_INTEGER CurrentTime;
  253. PFN_NUMBER Dummy1;
  254. PFN_NUMBER Dummy2;
  255. WSLE_NUMBER Trim;
  256. ULONG TrimAge;
  257. ULONG GrowthSinceLastEstimate;
  258. WSLE_ALLOCATION_TYPE TrimFlags;
  259. TrimFlags = Flags;
  260. Flags &= ~WsleAllocationDontTrim;
  261. WorkingSetList = WsInfo->VmWorkingSetList;
  262. GrowthSinceLastEstimate = 1;
  263. PERFINFO_BIGFOOT_REPLACEMENT_CLAIMS(WorkingSetList, WsInfo);
  264. PagesTrimmed = 0;
  265. //
  266. // Determine whether the working set can be grown or whether a
  267. // page needs to be replaced.
  268. //
  269. recheck:
  270. if (WsInfo->WorkingSetSize >= WsInfo->MinimumWorkingSetSize) {
  271. if ((WsInfo->Flags.ForceTrim == 1) && (TrimFlags != WsleAllocationDontTrim)) {
  272. //
  273. // The working set manager cannot attach to this process
  274. // to trim it. Force a trim now and update the working
  275. // set manager's fields properly to indicate a trim occurred.
  276. //
  277. Trim = WsInfo->Claim >>
  278. ((WsInfo->Flags.MemoryPriority == MEMORY_PRIORITY_FOREGROUND)
  279. ? MI_FOREGROUND_CLAIM_AVAILABLE_SHIFT
  280. : MI_BACKGROUND_CLAIM_AVAILABLE_SHIFT);
  281. if (MmAvailablePages < MM_HIGH_LIMIT + 64) {
  282. if (WsInfo->WorkingSetSize > WsInfo->MinimumWorkingSetSize) {
  283. Trim = (WsInfo->WorkingSetSize - WsInfo->MinimumWorkingSetSize) >> 2;
  284. }
  285. TrimAge = MI_PASS4_TRIM_AGE;
  286. }
  287. else {
  288. TrimAge = MI_PASS0_TRIM_AGE;
  289. }
  290. PagesTrimmed += MiTrimWorkingSet (Trim, WsInfo, TrimAge);
  291. MiAgeAndEstimateAvailableInWorkingSet (WsInfo,
  292. TRUE,
  293. NULL,
  294. &Dummy1,
  295. &Dummy2);
  296. KeQuerySystemTime (&CurrentTime);
  297. LOCK_EXPANSION (OldIrql);
  298. WsInfo->LastTrimTime = CurrentTime;
  299. WsInfo->Flags.ForceTrim = 0;
  300. UNLOCK_EXPANSION (OldIrql);
  301. goto recheck;
  302. }
  303. CurrentSize = WsInfo->WorkingSetSize;
  304. ASSERT (CurrentSize <= (WorkingSetList->LastInitializedWsle + 1));
  305. if ((WsInfo->Flags.MaximumWorkingSetHard) &&
  306. (CurrentSize >= WsInfo->MaximumWorkingSetSize)) {
  307. //
  308. // This is an enforced working set maximum triggering a replace.
  309. //
  310. MiReplaceWorkingSetEntry (WsInfo, Flags);
  311. return;
  312. }
  313. //
  314. // Don't grow if :
  315. // - we're over the max
  316. // - there aren't any pages to take
  317. // - or if we are growing too much in this time interval
  318. // and there isn't much memory available
  319. //
  320. MemoryMaker = PsGetCurrentThread()->MemoryMaker;
  321. if (((CurrentSize > MM_MAXIMUM_WORKING_SET) && (MemoryMaker == 0)) ||
  322. (MmAvailablePages == 0) ||
  323. (Flags == WsleAllocationReplace) ||
  324. ((MmAvailablePages < MM_VERY_HIGH_LIMIT) &&
  325. (MI_WS_GROWING_TOO_FAST(WsInfo)) &&
  326. (MemoryMaker == 0))) {
  327. //
  328. // Can't grow this one.
  329. //
  330. MiReplacing = TRUE;
  331. if ((Flags == WsleAllocationReplace) || (MemoryMaker == 0)) {
  332. MiReplaceWorkingSetEntry (WsInfo, Flags);
  333. //
  334. // Set the must trim flag because this could be a realtime
  335. // thread where the fault straddles a page boundary. If
  336. // it's realtime, the balance set manager will never get to
  337. // run and the thread will endlessly replace one WSL entry
  338. // with the other half of the straddler. Setting this flag
  339. // guarantees the next fault will guarantee a forced trim
  340. // and allow a reasonable available page threshold trim
  341. // calculation since GrowthSinceLastEstimate will be
  342. // cleared.
  343. //
  344. MiSetWorkingSetForce (WsInfo, TRUE);
  345. GrowthSinceLastEstimate = 0;
  346. }
  347. else {
  348. //
  349. // If we've only trimmed a single page, then don't force
  350. // replacement on the next fault. This prevents a single
  351. // instruction causing alternating faults on the referenced
  352. // code & data in a (realtime) thread from looping endlessly.
  353. //
  354. if (PagesTrimmed > 1) {
  355. MiSetWorkingSetForce (WsInfo, TRUE);
  356. }
  357. }
  358. }
  359. }
  360. //
  361. // If there isn't enough memory to allow growth, find a good page
  362. // to remove and remove it.
  363. //
  364. WsInfo->GrowthSinceLastEstimate += GrowthSinceLastEstimate;
  365. return;
  366. }
  367. NTSTATUS
  368. MmEnforceWorkingSetLimit (
  369. IN PEPROCESS Process,
  370. IN ULONG Flags
  371. )
  372. /*++
  373. Routine Description:
  374. This function enables/disables hard enforcement of the working set minimums
  375. and maximums for the specified WsInfo.
  376. Arguments:
  377. Process - Supplies the target process.
  378. Flags - Supplies new flags (MM_WORKING_SET_MAX_HARD_ENABLE, etc).
  379. Return Value:
  380. NTSTATUS.
  381. Environment:
  382. Kernel mode, APCs disabled. The working set mutex must NOT be held.
  383. The caller guarantees that the target WsInfo cannot go away.
  384. --*/
  385. {
  386. KIRQL OldIrql;
  387. PMMSUPPORT WsInfo;
  388. MMSUPPORT_FLAGS PreviousBits;
  389. MMSUPPORT_FLAGS TempBits = {0};
  390. WsInfo = &Process->Vm;
  391. if (Flags & MM_WORKING_SET_MIN_HARD_ENABLE) {
  392. Flags &= ~MM_WORKING_SET_MIN_HARD_DISABLE;
  393. TempBits.MinimumWorkingSetHard = 1;
  394. }
  395. if (Flags & MM_WORKING_SET_MAX_HARD_ENABLE) {
  396. Flags &= ~MM_WORKING_SET_MAX_HARD_DISABLE;
  397. TempBits.MaximumWorkingSetHard = 1;
  398. }
  399. LOCK_WS (Process);
  400. LOCK_EXPANSION (OldIrql);
  401. if (Flags & MM_WORKING_SET_MIN_HARD_DISABLE) {
  402. WsInfo->Flags.MinimumWorkingSetHard = 0;
  403. }
  404. if (Flags & MM_WORKING_SET_MAX_HARD_DISABLE) {
  405. WsInfo->Flags.MaximumWorkingSetHard = 0;
  406. }
  407. PreviousBits = WsInfo->Flags;
  408. //
  409. // If the caller's request will result in hard enforcement of both limits
  410. // is enabled, then check whether the current minimum and maximum working
  411. // set values will guarantee forward progress even in pathological
  412. // scenarios.
  413. //
  414. if (PreviousBits.MinimumWorkingSetHard == 1) {
  415. TempBits.MinimumWorkingSetHard = 1;
  416. }
  417. if (PreviousBits.MaximumWorkingSetHard == 1) {
  418. TempBits.MaximumWorkingSetHard = 1;
  419. }
  420. if ((TempBits.MinimumWorkingSetHard == 1) &&
  421. (TempBits.MaximumWorkingSetHard == 1)) {
  422. //
  423. // Net result is hard enforcement on both limits so check that the
  424. // two limits cannot result in lack of forward progress.
  425. //
  426. if (WsInfo->MinimumWorkingSetSize + MM_FLUID_WORKING_SET >= WsInfo->MaximumWorkingSetSize) {
  427. UNLOCK_EXPANSION (OldIrql);
  428. UNLOCK_WS (Process);
  429. return STATUS_BAD_WORKING_SET_LIMIT;
  430. }
  431. }
  432. if (Flags & MM_WORKING_SET_MIN_HARD_ENABLE) {
  433. WsInfo->Flags.MinimumWorkingSetHard = 1;
  434. }
  435. if (Flags & MM_WORKING_SET_MAX_HARD_ENABLE) {
  436. WsInfo->Flags.MaximumWorkingSetHard = 1;
  437. }
  438. UNLOCK_EXPANSION (OldIrql);
  439. UNLOCK_WS (Process);
  440. return STATUS_SUCCESS;
  441. }
  442. VOID
  443. MiReplaceWorkingSetEntry (
  444. IN PMMSUPPORT WsInfo,
  445. IN WSLE_ALLOCATION_TYPE Flags
  446. )
  447. /*++
  448. Routine Description:
  449. This function tries to find a good working set entry to replace.
  450. Arguments:
  451. WsInfo - Supplies the working set info pointer.
  452. Flags - Supplies 0 if replacement is not required.
  453. Supplies 1 if replacement is desired. Note replacement cannot
  454. be guaranteed (the entire existing working set may
  455. be locked down) - if no entry can be released the caller
  456. can detect this because MMWSL->FirstFree will not contain
  457. any free entries - and so the caller should release the
  458. working set mutex and retry the operation.
  459. Return Value:
  460. None.
  461. Environment:
  462. Kernel mode, APCs disabled, working set lock. PFN lock NOT held.
  463. --*/
  464. {
  465. WSLE_NUMBER WorkingSetIndex;
  466. WSLE_NUMBER FirstDynamic;
  467. WSLE_NUMBER LastEntry;
  468. PMMWSL WorkingSetList;
  469. PMMWSLE Wsle;
  470. ULONG NumberOfCandidates;
  471. PMMPTE PointerPte;
  472. WSLE_NUMBER TheNextSlot;
  473. WSLE_NUMBER OldestWorkingSetIndex;
  474. LONG OldestAge;
  475. WorkingSetList = WsInfo->VmWorkingSetList;
  476. Wsle = WorkingSetList->Wsle;
  477. //
  478. // Toss a page out of the working set.
  479. //
  480. LastEntry = WorkingSetList->LastEntry;
  481. FirstDynamic = WorkingSetList->FirstDynamic;
  482. WorkingSetIndex = WorkingSetList->NextSlot;
  483. if (WorkingSetIndex > LastEntry || WorkingSetIndex < FirstDynamic) {
  484. WorkingSetIndex = FirstDynamic;
  485. }
  486. TheNextSlot = WorkingSetIndex;
  487. NumberOfCandidates = 0;
  488. OldestWorkingSetIndex = WSLE_NULL_INDEX;
  489. OldestAge = -1;
  490. while (TRUE) {
  491. //
  492. // Keep track of the oldest page along the way in case we
  493. // don't find one that's >= MI_IMMEDIATE_REPLACEMENT_AGE
  494. // before we've looked at MM_WORKING_SET_LIST_SEARCH
  495. // entries.
  496. //
  497. while (Wsle[WorkingSetIndex].u1.e1.Valid == 0) {
  498. WorkingSetIndex += 1;
  499. if (WorkingSetIndex > LastEntry) {
  500. WorkingSetIndex = FirstDynamic;
  501. }
  502. if (WorkingSetIndex == TheNextSlot) {
  503. if (Flags == WsleAllocationAny) {
  504. //
  505. // Entire working set list has been searched, increase
  506. // the working set size.
  507. //
  508. ASSERT ((WsInfo->Flags.MaximumWorkingSetHard == 0) ||
  509. (WsInfo->WorkingSetSize < WsInfo->MaximumWorkingSetSize));
  510. WsInfo->GrowthSinceLastEstimate += 1;
  511. }
  512. return;
  513. }
  514. }
  515. if (OldestWorkingSetIndex == WSLE_NULL_INDEX) {
  516. //
  517. // First time through, so initialize the OldestWorkingSetIndex
  518. // to the first valid WSLE. As we go along, this will be repointed
  519. // at the oldest candidate we come across.
  520. //
  521. OldestWorkingSetIndex = WorkingSetIndex;
  522. OldestAge = -1;
  523. }
  524. PointerPte = MiGetPteAddress(Wsle[WorkingSetIndex].u1.VirtualAddress);
  525. if ((Flags == WsleAllocationReplace) ||
  526. ((MI_GET_ACCESSED_IN_PTE(PointerPte) == 0) &&
  527. (OldestAge < (LONG) MI_GET_WSLE_AGE(PointerPte, &Wsle[WorkingSetIndex])))) {
  528. //
  529. // This one is not used and it's older.
  530. //
  531. OldestAge = MI_GET_WSLE_AGE(PointerPte, &Wsle[WorkingSetIndex]);
  532. OldestWorkingSetIndex = WorkingSetIndex;
  533. }
  534. //
  535. // If it's old enough or we've searched too much then use this entry.
  536. //
  537. if ((Flags == WsleAllocationReplace) ||
  538. OldestAge >= MI_IMMEDIATE_REPLACEMENT_AGE ||
  539. NumberOfCandidates > MM_WORKING_SET_LIST_SEARCH) {
  540. PERFINFO_PAGE_INFO_REPLACEMENT_DECL();
  541. if (OldestWorkingSetIndex != WorkingSetIndex) {
  542. WorkingSetIndex = OldestWorkingSetIndex;
  543. PointerPte = MiGetPteAddress(Wsle[WorkingSetIndex].u1.VirtualAddress);
  544. }
  545. PERFINFO_GET_PAGE_INFO_REPLACEMENT(PointerPte);
  546. if (MiFreeWsle (WorkingSetIndex, WsInfo, PointerPte)) {
  547. PERFINFO_LOG_WS_REPLACEMENT(WsInfo);
  548. //
  549. // This entry was removed.
  550. //
  551. WorkingSetList->NextSlot = WorkingSetIndex + 1;
  552. break;
  553. }
  554. //
  555. // We failed to remove a page, try the next one.
  556. //
  557. // Clear the OldestWorkingSetIndex so that
  558. // it gets set to the next valid entry above like the
  559. // first time around.
  560. //
  561. WorkingSetIndex = OldestWorkingSetIndex + 1;
  562. OldestWorkingSetIndex = WSLE_NULL_INDEX;
  563. }
  564. else {
  565. WorkingSetIndex += 1;
  566. }
  567. if (WorkingSetIndex > LastEntry) {
  568. WorkingSetIndex = FirstDynamic;
  569. }
  570. NumberOfCandidates += 1;
  571. if (WorkingSetIndex == TheNextSlot) {
  572. if (Flags == WsleAllocationAny) {
  573. //
  574. // Entire working set list has been searched, increase
  575. // the working set size.
  576. //
  577. ASSERT ((WsInfo->Flags.MaximumWorkingSetHard == 0) ||
  578. (WsInfo->WorkingSetSize < WsInfo->MaximumWorkingSetSize));
  579. WsInfo->GrowthSinceLastEstimate += 1;
  580. }
  581. break;
  582. }
  583. }
  584. return;
  585. }
  586. ULONG
  587. MiRemovePageFromWorkingSet (
  588. IN PMMPTE PointerPte,
  589. IN PMMPFN Pfn1,
  590. IN PMMSUPPORT WsInfo
  591. )
  592. /*++
  593. Routine Description:
  594. This function removes the page mapped by the specified PTE from
  595. the process's working set list.
  596. Arguments:
  597. PointerPte - Supplies a pointer to the PTE mapping the page to
  598. be removed from the working set list.
  599. Pfn1 - Supplies a pointer to the PFN database element referred to
  600. by the PointerPte.
  601. Return Value:
  602. Returns TRUE if the specified page was locked in the working set,
  603. FALSE otherwise.
  604. Environment:
  605. Kernel mode, APCs disabled, working set mutex held.
  606. --*/
  607. {
  608. WSLE_NUMBER WorkingSetIndex;
  609. PVOID VirtualAddress;
  610. WSLE_NUMBER Entry;
  611. MMWSLENTRY Locked;
  612. PMMWSL WorkingSetList;
  613. PMMWSLE Wsle;
  614. KIRQL OldIrql;
  615. #if DBG
  616. PVOID SwapVa;
  617. #endif
  618. WorkingSetList = WsInfo->VmWorkingSetList;
  619. Wsle = WorkingSetList->Wsle;
  620. VirtualAddress = MiGetVirtualAddressMappedByPte (PointerPte);
  621. WorkingSetIndex = MiLocateWsle (VirtualAddress,
  622. WorkingSetList,
  623. Pfn1->u1.WsIndex);
  624. ASSERT (WorkingSetIndex != WSLE_NULL_INDEX);
  625. LOCK_PFN (OldIrql);
  626. MiEliminateWorkingSetEntry (WorkingSetIndex,
  627. PointerPte,
  628. Pfn1,
  629. Wsle);
  630. UNLOCK_PFN (OldIrql);
  631. //
  632. // Check to see if this entry is locked in the working set
  633. // or locked in memory.
  634. //
  635. Locked = Wsle[WorkingSetIndex].u1.e1;
  636. MiRemoveWsle (WorkingSetIndex, WorkingSetList);
  637. //
  638. // Add this entry to the list of free working set entries
  639. // and adjust the working set count.
  640. //
  641. MiReleaseWsle ((WSLE_NUMBER)WorkingSetIndex, WsInfo);
  642. if ((Locked.LockedInWs == 1) || (Locked.LockedInMemory == 1)) {
  643. //
  644. // This entry is locked.
  645. //
  646. WorkingSetList->FirstDynamic -= 1;
  647. if (WorkingSetIndex != WorkingSetList->FirstDynamic) {
  648. Entry = WorkingSetList->FirstDynamic;
  649. #if DBG
  650. SwapVa = Wsle[WorkingSetList->FirstDynamic].u1.VirtualAddress;
  651. SwapVa = PAGE_ALIGN (SwapVa);
  652. PointerPte = MiGetPteAddress (SwapVa);
  653. Pfn1 = MI_PFN_ELEMENT (PointerPte->u.Hard.PageFrameNumber);
  654. ASSERT (Entry == MiLocateWsle (SwapVa, WorkingSetList, Pfn1->u1.WsIndex));
  655. #endif
  656. MiSwapWslEntries (Entry, WorkingSetIndex, WsInfo, FALSE);
  657. }
  658. return TRUE;
  659. }
  660. else {
  661. ASSERT (WorkingSetIndex >= WorkingSetList->FirstDynamic);
  662. }
  663. return FALSE;
  664. }
  665. VOID
  666. MiReleaseWsle (
  667. IN WSLE_NUMBER WorkingSetIndex,
  668. IN PMMSUPPORT WsInfo
  669. )
  670. /*++
  671. Routine Description:
  672. This function releases a previously reserved working set entry to
  673. be reused. A release occurs when a page fault is retried due to
  674. changes in PTEs and working sets during an I/O operation.
  675. Arguments:
  676. WorkingSetIndex - Supplies the index of the working set entry to
  677. release.
  678. Return Value:
  679. None.
  680. Environment:
  681. Kernel mode, APCs disabled, working set lock held and PFN lock held.
  682. --*/
  683. {
  684. PMMWSL WorkingSetList;
  685. PMMWSLE Wsle;
  686. MMWSLE WsleContents;
  687. WorkingSetList = WsInfo->VmWorkingSetList;
  688. Wsle = WorkingSetList->Wsle;
  689. MM_WS_LOCK_ASSERT (WsInfo);
  690. ASSERT (WorkingSetIndex <= WorkingSetList->LastInitializedWsle);
  691. //
  692. // Put the entry on the free list and decrement the current size.
  693. //
  694. ASSERT ((WorkingSetList->FirstFree <= WorkingSetList->LastInitializedWsle) ||
  695. (WorkingSetList->FirstFree == WSLE_NULL_INDEX));
  696. WsleContents.u1.Long = WorkingSetList->FirstFree << MM_FREE_WSLE_SHIFT;
  697. MI_LOG_WSLE_CHANGE (WorkingSetList, WorkingSetIndex, WsleContents);
  698. Wsle[WorkingSetIndex] = WsleContents;
  699. WorkingSetList->FirstFree = WorkingSetIndex;
  700. ASSERT ((WorkingSetList->FirstFree <= WorkingSetList->LastInitializedWsle) ||
  701. (WorkingSetList->FirstFree == WSLE_NULL_INDEX));
  702. if (WsInfo->WorkingSetSize > WsInfo->MinimumWorkingSetSize) {
  703. InterlockedExchangeAddSizeT (&MmPagesAboveWsMinimum, -1);
  704. }
  705. WsInfo->WorkingSetSize -= 1;
  706. #if defined (_MI_DEBUG_WSLE)
  707. WorkingSetList->Quota -= 1;
  708. ASSERT (WsInfo->WorkingSetSize == WorkingSetList->Quota);
  709. MiCheckWsleList (WsInfo);
  710. #endif
  711. return;
  712. }
  713. VOID
  714. MiUpdateWsle (
  715. IN OUT PWSLE_NUMBER DesiredIndex,
  716. IN PVOID VirtualAddress,
  717. IN PMMSUPPORT WsInfo,
  718. IN PMMPFN Pfn
  719. )
  720. /*++
  721. Routine Description:
  722. This routine updates a reserved working set entry to place it into
  723. the valid state.
  724. Arguments:
  725. DesiredIndex - Supplies the index of the working set entry to update.
  726. VirtualAddress - Supplies the virtual address which the working set
  727. entry maps.
  728. WsInfo - Supplies the relevant working set information to update.
  729. Pfn - Supplies a pointer to the PFN element for the page.
  730. Return Value:
  731. None.
  732. Environment:
  733. Kernel mode, APCs disabled, working set lock held.
  734. --*/
  735. {
  736. ULONG_PTR OldValue;
  737. PMMWSLE Wsle;
  738. MMWSLE WsleContents;
  739. PMMWSL WorkingSetList;
  740. WSLE_NUMBER Index;
  741. WSLE_NUMBER WorkingSetIndex;
  742. MM_WS_LOCK_ASSERT (WsInfo);
  743. WorkingSetList = WsInfo->VmWorkingSetList;
  744. WorkingSetIndex = *DesiredIndex;
  745. ASSERT (WorkingSetIndex >= WorkingSetList->FirstDynamic);
  746. Wsle = WorkingSetList->Wsle;
  747. if (WorkingSetList == MmSystemCacheWorkingSetList) {
  748. //
  749. // This assert doesn't hold for NT64 as we can be adding page
  750. // directories and page tables for the system cache WSLE hash tables.
  751. //
  752. ASSERT32 ((VirtualAddress < (PVOID)PTE_BASE) ||
  753. (VirtualAddress >= (PVOID)MM_SYSTEM_SPACE_START));
  754. //
  755. // Count system space inserts and removals.
  756. //
  757. #if defined(_X86_)
  758. if (MI_IS_SYSTEM_CACHE_ADDRESS(VirtualAddress)) {
  759. MmSystemCachePage += 1;
  760. }
  761. else
  762. #endif
  763. if (VirtualAddress < MmSystemCacheStart) {
  764. MmSystemCodePage += 1;
  765. }
  766. else if (VirtualAddress < MM_PAGED_POOL_START) {
  767. MmSystemCachePage += 1;
  768. }
  769. else if (VirtualAddress < MmNonPagedSystemStart) {
  770. MmPagedPoolPage += 1;
  771. }
  772. else {
  773. MmSystemDriverPage += 1;
  774. }
  775. }
  776. else {
  777. ASSERT ((VirtualAddress < (PVOID)MM_SYSTEM_SPACE_START) ||
  778. (MI_IS_SESSION_ADDRESS (VirtualAddress)));
  779. }
  780. //
  781. // Make the WSLE valid, referring to the corresponding virtual
  782. // page number.
  783. //
  784. #if DBG
  785. if (Pfn->u1.WsIndex <= WorkingSetList->LastInitializedWsle) {
  786. ASSERT ((PAGE_ALIGN(VirtualAddress) !=
  787. PAGE_ALIGN(Wsle[Pfn->u1.WsIndex].u1.VirtualAddress)) ||
  788. (Wsle[Pfn->u1.WsIndex].u1.e1.Valid == 0));
  789. }
  790. #endif
  791. WsleContents.u1.VirtualAddress = PAGE_ALIGN (VirtualAddress);
  792. WsleContents.u1.e1.Valid = 1;
  793. //
  794. // The working set mutex is a process wide mutex and two threads in
  795. // different processes could be adding the same physical page to
  796. // their working sets. Each one could see the WsIndex field in the
  797. // PFN as 0 and therefore want to set the direct bit.
  798. //
  799. // To solve this, the WsIndex field is updated with an interlocked
  800. // operation. Note for private pages, there can be no race so a
  801. // simple update is enough.
  802. //
  803. if (Pfn->u1.Event == NULL) {
  804. //
  805. // Directly index into the WSL for this entry via the PFN database
  806. // element.
  807. //
  808. // The entire working set index union must be zeroed on NT64. ie:
  809. // The WSLE_NUMBER is currently 32 bits and the PKEVENT is 64 - we
  810. // must zero the top 32 bits as well. So instead of setting the
  811. // WsIndex field, set the overlaid Event field with appropriate casts.
  812. //
  813. if (Pfn->u3.e1.PrototypePte == 0) {
  814. //
  815. // This is a private page so this thread is the only one that
  816. // can be updating the PFN, so no need to use an interlocked update.
  817. // Note this is true even if the process is being forked because in
  818. // that case, the working set mutex is held throughout the fork so
  819. // this thread would have blocked earlier on that mutex first.
  820. //
  821. Pfn->u1.Event = (PKEVENT) (ULONG_PTR) WorkingSetIndex;
  822. ASSERT (Pfn->u1.Event == (PKEVENT) (ULONG_PTR) WorkingSetIndex);
  823. OldValue = 0;
  824. }
  825. else {
  826. //
  827. // This is a sharable page so a thread in another process could
  828. // be trying to update the PFN at the same time. Use an interlocked
  829. // update so only one thread gets to set the value.
  830. //
  831. #if defined (_WIN64)
  832. OldValue = InterlockedCompareExchange64 ((PLONGLONG)&Pfn->u1.Event,
  833. (LONGLONG) (ULONG_PTR) WorkingSetIndex,
  834. 0);
  835. #else
  836. OldValue = InterlockedCompareExchange ((PLONG)&Pfn->u1.Event,
  837. WorkingSetIndex,
  838. 0);
  839. #endif
  840. }
  841. if (OldValue == 0) {
  842. WsleContents.u1.e1.Direct = 1;
  843. MI_LOG_WSLE_CHANGE (WorkingSetList, WorkingSetIndex, WsleContents);
  844. Wsle[WorkingSetIndex] = WsleContents;
  845. return;
  846. }
  847. }
  848. MI_LOG_WSLE_CHANGE (WorkingSetList, WorkingSetIndex, WsleContents);
  849. Wsle[WorkingSetIndex] = WsleContents;
  850. //
  851. // Try to insert at WsIndex.
  852. //
  853. Index = Pfn->u1.WsIndex;
  854. if ((Index < WorkingSetList->LastInitializedWsle) &&
  855. (Index > WorkingSetList->FirstDynamic) &&
  856. (Index != WorkingSetIndex)) {
  857. if (Wsle[Index].u1.e1.Valid) {
  858. if (Wsle[Index].u1.e1.Direct) {
  859. //
  860. // Only move direct indexed entries.
  861. //
  862. MiSwapWslEntries (Index, WorkingSetIndex, WsInfo, TRUE);
  863. WorkingSetIndex = Index;
  864. }
  865. }
  866. else {
  867. //
  868. // On free list, try to remove quickly without walking
  869. // all the free pages.
  870. //
  871. WSLE_NUMBER FreeIndex;
  872. MMWSLE Temp;
  873. FreeIndex = 0;
  874. ASSERT (WorkingSetList->FirstFree >= WorkingSetList->FirstDynamic);
  875. ASSERT (WorkingSetIndex >= WorkingSetList->FirstDynamic);
  876. if (WorkingSetList->FirstFree == Index) {
  877. WorkingSetList->FirstFree = WorkingSetIndex;
  878. Temp = Wsle[WorkingSetIndex];
  879. MI_LOG_WSLE_CHANGE (WorkingSetList, WorkingSetIndex, Wsle[Index]);
  880. Wsle[WorkingSetIndex] = Wsle[Index];
  881. MI_LOG_WSLE_CHANGE (WorkingSetList, Index, Temp);
  882. Wsle[Index] = Temp;
  883. WorkingSetIndex = Index;
  884. ASSERT (((Wsle[WorkingSetList->FirstFree].u1.Long >> MM_FREE_WSLE_SHIFT)
  885. <= WorkingSetList->LastInitializedWsle) ||
  886. ((Wsle[WorkingSetList->FirstFree].u1.Long >> MM_FREE_WSLE_SHIFT)
  887. == WSLE_NULL_INDEX));
  888. }
  889. else if (Wsle[Index - 1].u1.e1.Valid == 0) {
  890. if ((Wsle[Index - 1].u1.Long >> MM_FREE_WSLE_SHIFT) == Index) {
  891. FreeIndex = Index - 1;
  892. }
  893. }
  894. else if (Wsle[Index + 1].u1.e1.Valid == 0) {
  895. if ((Wsle[Index + 1].u1.Long >> MM_FREE_WSLE_SHIFT) == Index) {
  896. FreeIndex = Index + 1;
  897. }
  898. }
  899. if (FreeIndex != 0) {
  900. //
  901. // Link the WSLE into the free list.
  902. //
  903. Temp = Wsle[WorkingSetIndex];
  904. Wsle[FreeIndex].u1.Long = WorkingSetIndex << MM_FREE_WSLE_SHIFT;
  905. MI_LOG_WSLE_CHANGE (WorkingSetList, WorkingSetIndex, Wsle[Index]);
  906. Wsle[WorkingSetIndex] = Wsle[Index];
  907. MI_LOG_WSLE_CHANGE (WorkingSetList, Index, Temp);
  908. Wsle[Index] = Temp;
  909. WorkingSetIndex = Index;
  910. ASSERT (((Wsle[FreeIndex].u1.Long >> MM_FREE_WSLE_SHIFT)
  911. <= WorkingSetList->LastInitializedWsle) ||
  912. ((Wsle[FreeIndex].u1.Long >> MM_FREE_WSLE_SHIFT)
  913. == WSLE_NULL_INDEX));
  914. }
  915. }
  916. *DesiredIndex = WorkingSetIndex;
  917. if (WorkingSetIndex > WorkingSetList->LastEntry) {
  918. WorkingSetList->LastEntry = WorkingSetIndex;
  919. }
  920. }
  921. ASSERT (Wsle[WorkingSetIndex].u1.e1.Valid == 1);
  922. ASSERT (Wsle[WorkingSetIndex].u1.e1.Direct != 1);
  923. WorkingSetList->NonDirectCount += 1;
  924. #if defined (_MI_DEBUG_WSLE)
  925. MiCheckWsleList (WsInfo);
  926. #endif
  927. if (WorkingSetList->HashTable != NULL) {
  928. //
  929. // Insert the valid WSLE into the working set hash list.
  930. //
  931. MiInsertWsleHash (WorkingSetIndex, WsInfo);
  932. }
  933. return;
  934. }
  935. ULONG
  936. MiFreeWsle (
  937. IN WSLE_NUMBER WorkingSetIndex,
  938. IN PMMSUPPORT WsInfo,
  939. IN PMMPTE PointerPte
  940. )
  941. /*++
  942. Routine Description:
  943. This routine frees the specified WSLE and decrements the share
  944. count for the corresponding page, putting the PTE into a transition
  945. state if the share count goes to 0.
  946. Arguments:
  947. WorkingSetIndex - Supplies the index of the working set entry to free.
  948. WsInfo - Supplies a pointer to the working set structure.
  949. PointerPte - Supplies a pointer to the PTE for the working set entry.
  950. Return Value:
  951. Returns TRUE if the WSLE was removed, FALSE if it was not removed.
  952. Pages with valid PTEs are not removed (i.e. page table pages
  953. that contain valid or transition PTEs).
  954. Environment:
  955. Kernel mode, APCs disabled, working set lock. PFN lock NOT held.
  956. --*/
  957. {
  958. PMMPFN Pfn1;
  959. PMMWSL WorkingSetList;
  960. PMMWSLE Wsle;
  961. KIRQL OldIrql;
  962. WorkingSetList = WsInfo->VmWorkingSetList;
  963. Wsle = WorkingSetList->Wsle;
  964. MM_WS_LOCK_ASSERT (WsInfo);
  965. ASSERT (Wsle[WorkingSetIndex].u1.e1.Valid == 1);
  966. //
  967. // Check to see if the located entry is eligible for removal.
  968. //
  969. ASSERT (PointerPte->u.Hard.Valid == 1);
  970. ASSERT (WorkingSetIndex >= WorkingSetList->FirstDynamic);
  971. //
  972. // Check to see if this is a page table with valid PTEs.
  973. //
  974. // Note, don't clear the access bit for page table pages
  975. // with valid PTEs as this could cause an access trap fault which
  976. // would not be handled (it is only handled for PTEs not PDEs).
  977. //
  978. Pfn1 = MI_PFN_ELEMENT (PointerPte->u.Hard.PageFrameNumber);
  979. //
  980. // Perform a preliminary check without the PFN lock so that lock
  981. // contention is avoided for cases that cannot possibly succeed.
  982. //
  983. if (WsInfo == &MmSystemCacheWs) {
  984. if (Pfn1->u3.e2.ReferenceCount > 1) {
  985. return FALSE;
  986. }
  987. }
  988. else {
  989. if ((Pfn1->u2.ShareCount > 1) && (Pfn1->u3.e1.PrototypePte == 0)) {
  990. return FALSE;
  991. }
  992. }
  993. LOCK_PFN (OldIrql);
  994. //
  995. // If the PTE is a page table page with non-zero share count or
  996. // within the system cache with its reference count greater
  997. // than 1, don't remove it.
  998. //
  999. if (WsInfo == &MmSystemCacheWs) {
  1000. if (Pfn1->u3.e2.ReferenceCount > 1) {
  1001. UNLOCK_PFN (OldIrql);
  1002. return FALSE;
  1003. }
  1004. }
  1005. else {
  1006. if ((Pfn1->u2.ShareCount > 1) && (Pfn1->u3.e1.PrototypePte == 0)) {
  1007. #if DBG
  1008. if (WsInfo->Flags.SessionSpace == 1) {
  1009. ASSERT (MI_IS_SESSION_ADDRESS (Wsle[WorkingSetIndex].u1.VirtualAddress));
  1010. }
  1011. else {
  1012. ASSERT32 ((Wsle[WorkingSetIndex].u1.VirtualAddress >= (PVOID)PTE_BASE) &&
  1013. (Wsle[WorkingSetIndex].u1.VirtualAddress<= (PVOID)PTE_TOP));
  1014. }
  1015. #endif
  1016. //
  1017. // Don't remove page table pages from the working set until
  1018. // all transition pages have exited.
  1019. //
  1020. UNLOCK_PFN (OldIrql);
  1021. return FALSE;
  1022. }
  1023. }
  1024. //
  1025. // Found a candidate, remove the page from the working set.
  1026. //
  1027. MiEliminateWorkingSetEntry (WorkingSetIndex,
  1028. PointerPte,
  1029. Pfn1,
  1030. Wsle);
  1031. UNLOCK_PFN (OldIrql);
  1032. //
  1033. // Remove the working set entry from the working set.
  1034. //
  1035. MiRemoveWsle (WorkingSetIndex, WorkingSetList);
  1036. ASSERT (WorkingSetList->FirstFree >= WorkingSetList->FirstDynamic);
  1037. ASSERT (WorkingSetIndex >= WorkingSetList->FirstDynamic);
  1038. //
  1039. // Put the entry on the free list and decrement the current size.
  1040. //
  1041. ASSERT ((WorkingSetList->FirstFree <= WorkingSetList->LastInitializedWsle) ||
  1042. (WorkingSetList->FirstFree == WSLE_NULL_INDEX));
  1043. Wsle[WorkingSetIndex].u1.Long = WorkingSetList->FirstFree << MM_FREE_WSLE_SHIFT;
  1044. WorkingSetList->FirstFree = WorkingSetIndex;
  1045. ASSERT ((WorkingSetList->FirstFree <= WorkingSetList->LastInitializedWsle) ||
  1046. (WorkingSetList->FirstFree == WSLE_NULL_INDEX));
  1047. if (WsInfo->WorkingSetSize > WsInfo->MinimumWorkingSetSize) {
  1048. InterlockedExchangeAddSizeT (&MmPagesAboveWsMinimum, -1);
  1049. }
  1050. WsInfo->WorkingSetSize -= 1;
  1051. #if defined (_MI_DEBUG_WSLE)
  1052. WorkingSetList->Quota -= 1;
  1053. ASSERT (WsInfo->WorkingSetSize == WorkingSetList->Quota);
  1054. MiCheckWsleList (WsInfo);
  1055. #endif
  1056. return TRUE;
  1057. }
  1058. #define MI_INITIALIZE_WSLE(_VirtualAddress, _WslEntry) { \
  1059. PMMPFN _Pfn1; \
  1060. _WslEntry->u1.VirtualAddress = (PVOID)(_VirtualAddress); \
  1061. _WslEntry->u1.e1.Valid = 1; \
  1062. _WslEntry->u1.e1.LockedInWs = 1; \
  1063. _WslEntry->u1.e1.Direct = 1; \
  1064. _Pfn1 = MI_PFN_ELEMENT (MiGetPteAddress ((PVOID)(_VirtualAddress))->u.Hard.PageFrameNumber); \
  1065. ASSERT (_Pfn1->u1.WsIndex == 0); \
  1066. _Pfn1->u1.WsIndex = (WSLE_NUMBER)(_WslEntry - MmWsle); \
  1067. (_WslEntry) += 1; \
  1068. }
  1069. VOID
  1070. MiInitializeWorkingSetList (
  1071. IN PEPROCESS CurrentProcess
  1072. )
  1073. /*++
  1074. Routine Description:
  1075. This routine initializes a process's working set to the empty
  1076. state.
  1077. Arguments:
  1078. CurrentProcess - Supplies a pointer to the process to initialize.
  1079. Return Value:
  1080. None.
  1081. Environment:
  1082. Kernel mode, APCs disabled.
  1083. --*/
  1084. {
  1085. PMMPFN Pfn1;
  1086. WSLE_NUMBER i;
  1087. PMMWSLE WslEntry;
  1088. WSLE_NUMBER CurrentWsIndex;
  1089. WSLE_NUMBER NumberOfEntriesMapped;
  1090. PVOID VirtualAddress;
  1091. WslEntry = MmWsle;
  1092. //
  1093. // Initialize the working set list control cells.
  1094. //
  1095. MmWorkingSetList->LastEntry = CurrentProcess->Vm.MinimumWorkingSetSize;
  1096. MmWorkingSetList->HashTable = NULL;
  1097. MmWorkingSetList->HashTableSize = 0;
  1098. MmWorkingSetList->NumberOfImageWaiters = 0;
  1099. MmWorkingSetList->Wsle = MmWsle;
  1100. MmWorkingSetList->VadBitMapHint = 1;
  1101. MmWorkingSetList->HashTableStart =
  1102. (PVOID)((PCHAR)PAGE_ALIGN (&MmWsle[MM_MAXIMUM_WORKING_SET]) + PAGE_SIZE);
  1103. #if defined (_X86PAE_)
  1104. MmWorkingSetList->HighestPermittedHashAddress = (PVOID)((ULONG_PTR)MmHyperSpaceEnd + 1);
  1105. #else
  1106. MmWorkingSetList->HighestPermittedHashAddress = (PVOID)((ULONG_PTR)HYPER_SPACE_END + 1);
  1107. #endif
  1108. //
  1109. // Fill in the reserved slots.
  1110. // Start with the top level page directory page.
  1111. //
  1112. #if (_MI_PAGING_LEVELS >= 4)
  1113. VirtualAddress = (PVOID) PXE_BASE;
  1114. #elif (_MI_PAGING_LEVELS >= 3)
  1115. VirtualAddress = (PVOID) PDE_TBASE;
  1116. #else
  1117. VirtualAddress = (PVOID) PDE_BASE;
  1118. #endif
  1119. MI_INITIALIZE_WSLE (VirtualAddress, WslEntry);
  1120. #if defined (_X86PAE_)
  1121. //
  1122. // Fill in the additional page directory entries.
  1123. //
  1124. for (i = 1; i < PD_PER_SYSTEM; i += 1) {
  1125. MI_INITIALIZE_WSLE (PDE_BASE + i * PAGE_SIZE, WslEntry);
  1126. }
  1127. VirtualAddress = (PVOID)((ULONG_PTR)VirtualAddress + ((PD_PER_SYSTEM - 1) * PAGE_SIZE));
  1128. #endif
  1129. Pfn1 = MI_PFN_ELEMENT (MiGetPteAddress ((PVOID)(VirtualAddress))->u.Hard.PageFrameNumber);
  1130. ASSERT (Pfn1->u4.PteFrame == (ULONG_PTR)MI_PFN_ELEMENT_TO_INDEX (Pfn1));
  1131. Pfn1->u1.Event = (PVOID) CurrentProcess;
  1132. #if (_MI_PAGING_LEVELS >= 4)
  1133. //
  1134. // Fill in the entry for the hyper space page directory parent page.
  1135. //
  1136. MI_INITIALIZE_WSLE (MiGetPpeAddress (HYPER_SPACE), WslEntry);
  1137. #endif
  1138. #if (_MI_PAGING_LEVELS >= 3)
  1139. //
  1140. // Fill in the entry for the hyper space page directory page.
  1141. //
  1142. MI_INITIALIZE_WSLE (MiGetPdeAddress (HYPER_SPACE), WslEntry);
  1143. #endif
  1144. //
  1145. // Fill in the entry for the page table page which maps hyper space.
  1146. //
  1147. MI_INITIALIZE_WSLE (MiGetPteAddress (HYPER_SPACE), WslEntry);
  1148. #if defined (_X86PAE_)
  1149. //
  1150. // Fill in the entry for the second page table page which maps hyper space.
  1151. //
  1152. MI_INITIALIZE_WSLE (MiGetPteAddress (HYPER_SPACE2), WslEntry);
  1153. #endif
  1154. //
  1155. // Fill in the entry for the first VAD bitmap page.
  1156. //
  1157. // Note when booted /3GB, the second VAD bitmap page is automatically
  1158. // inserted as part of the working set list page as the page is shared
  1159. // by both.
  1160. //
  1161. MI_INITIALIZE_WSLE (VAD_BITMAP_SPACE, WslEntry);
  1162. //
  1163. // Fill in the entry for the page which contains the working set list.
  1164. //
  1165. MI_INITIALIZE_WSLE (MmWorkingSetList, WslEntry);
  1166. NumberOfEntriesMapped = (PAGE_SIZE - BYTE_OFFSET (MmWsle)) / sizeof (MMWSLE);
  1167. CurrentWsIndex = (WSLE_NUMBER)(WslEntry - MmWsle);
  1168. CurrentProcess->Vm.WorkingSetSize = CurrentWsIndex;
  1169. #if defined (_MI_DEBUG_WSLE)
  1170. MmWorkingSetList->Quota = CurrentWsIndex;
  1171. #endif
  1172. MmWorkingSetList->FirstFree = CurrentWsIndex;
  1173. MmWorkingSetList->FirstDynamic = CurrentWsIndex;
  1174. MmWorkingSetList->NextSlot = CurrentWsIndex;
  1175. //
  1176. //
  1177. // Build the free list starting at the first dynamic entry.
  1178. //
  1179. i = CurrentWsIndex + 1;
  1180. do {
  1181. WslEntry->u1.Long = i << MM_FREE_WSLE_SHIFT;
  1182. WslEntry += 1;
  1183. i += 1;
  1184. } while (i <= NumberOfEntriesMapped);
  1185. //
  1186. // Mark the end of the list.
  1187. //
  1188. WslEntry -= 1;
  1189. WslEntry->u1.Long = WSLE_NULL_INDEX << MM_FREE_WSLE_SHIFT;
  1190. MmWorkingSetList->LastInitializedWsle = NumberOfEntriesMapped - 1;
  1191. return;
  1192. }
  1193. VOID
  1194. MiInitializeSessionWsSupport (
  1195. VOID
  1196. )
  1197. /*++
  1198. Routine Description:
  1199. This routine initializes the session space working set support.
  1200. Arguments:
  1201. None.
  1202. Return Value:
  1203. None.
  1204. Environment:
  1205. Kernel mode, APC_LEVEL or below, no mutexes held.
  1206. --*/
  1207. {
  1208. //
  1209. // This is the list of all session spaces ordered in a working set list.
  1210. //
  1211. InitializeListHead (&MiSessionWsList);
  1212. }
  1213. NTSTATUS
  1214. MiSessionInitializeWorkingSetList (
  1215. VOID
  1216. )
  1217. /*++
  1218. Routine Description:
  1219. This function initializes the working set for the session space and adds
  1220. it to the list of session space working sets.
  1221. Arguments:
  1222. None.
  1223. Return Value:
  1224. NT_SUCCESS if success or STATUS_NO_MEMORY on failure.
  1225. Environment:
  1226. Kernel mode, APC_LEVEL or below, no mutexes held.
  1227. --*/
  1228. {
  1229. WSLE_NUMBER i;
  1230. ULONG MaximumEntries;
  1231. ULONG PageTableCost;
  1232. KIRQL OldIrql;
  1233. PMMPTE PointerPte;
  1234. PMMPTE PointerPde;
  1235. MMPTE TempPte;
  1236. PMMWSLE WslEntry;
  1237. PMMPFN Pfn1;
  1238. ULONG PageColor;
  1239. PFN_NUMBER ResidentPages;
  1240. PFN_NUMBER PageFrameIndex;
  1241. WSLE_NUMBER CurrentEntry;
  1242. WSLE_NUMBER NumberOfEntriesMapped;
  1243. WSLE_NUMBER NumberOfEntriesMappedByFirstPage;
  1244. ULONG WorkingSetMaximum;
  1245. PMM_SESSION_SPACE SessionGlobal;
  1246. LOGICAL AllocatedPageTable;
  1247. PMMWSL WorkingSetList;
  1248. MMPTE DemandZeroWritePte;
  1249. #if (_MI_PAGING_LEVELS < 3)
  1250. ULONG Index;
  1251. #endif
  1252. //
  1253. // Use the global address for pointer references by
  1254. // MmWorkingSetManager before it attaches to the address space.
  1255. //
  1256. SessionGlobal = SESSION_GLOBAL (MmSessionSpace);
  1257. //
  1258. // Set up the working set variables.
  1259. //
  1260. WorkingSetMaximum = MI_SESSION_SPACE_WORKING_SET_MAXIMUM;
  1261. WorkingSetList = (PMMWSL) MiSessionSpaceWs;
  1262. MmSessionSpace->Vm.VmWorkingSetList = WorkingSetList;
  1263. #if (_MI_PAGING_LEVELS >= 3)
  1264. MmSessionSpace->Wsle = (PMMWSLE) (WorkingSetList + 1);
  1265. #else
  1266. MmSessionSpace->Wsle = (PMMWSLE) (&WorkingSetList->UsedPageTableEntries[0]);
  1267. #endif
  1268. ASSERT (KeGetOwnerGuardedMutex (&MmSessionSpace->Vm.WorkingSetMutex) == NULL);
  1269. //
  1270. // Build the PDE entry for the working set - note that the global bit
  1271. // must be turned off.
  1272. //
  1273. PointerPde = MiGetPdeAddress (WorkingSetList);
  1274. //
  1275. // The page table page for the working set and its first data page
  1276. // are charged against MmResidentAvailablePages and for commitment.
  1277. //
  1278. if (PointerPde->u.Hard.Valid == 1) {
  1279. //
  1280. // The page directory entry for the working set is the same
  1281. // as for another range in the session space. Share the PDE.
  1282. //
  1283. #ifndef _IA64_
  1284. ASSERT (PointerPde->u.Hard.Global == 0);
  1285. #endif
  1286. AllocatedPageTable = FALSE;
  1287. ResidentPages = 1;
  1288. }
  1289. else {
  1290. AllocatedPageTable = TRUE;
  1291. ResidentPages = 2;
  1292. }
  1293. PointerPte = MiGetPteAddress (WorkingSetList);
  1294. //
  1295. // The data pages needed to map up to maximum working set size are also
  1296. // charged against MmResidentAvailablePages and for commitment.
  1297. //
  1298. NumberOfEntriesMappedByFirstPage = (WSLE_NUMBER)(
  1299. ((PMMWSLE)((ULONG_PTR)WorkingSetList + PAGE_SIZE)) -
  1300. MmSessionSpace->Wsle);
  1301. if (MiChargeCommitment (ResidentPages, NULL) == FALSE) {
  1302. MM_BUMP_SESSION_FAILURES (MM_SESSION_FAILURE_NO_COMMIT);
  1303. return STATUS_NO_MEMORY;
  1304. }
  1305. //
  1306. // Use the global address for mutexes since the contained event
  1307. // must be accesible from any address space.
  1308. //
  1309. KeInitializeGuardedMutex (&SessionGlobal->Vm.WorkingSetMutex);
  1310. MmLockPagableSectionByHandle (ExPageLockHandle);
  1311. LOCK_PFN (OldIrql);
  1312. //
  1313. // Check to make sure the physical pages are available.
  1314. //
  1315. if ((SPFN_NUMBER)ResidentPages > MI_NONPAGABLE_MEMORY_AVAILABLE() - 20) {
  1316. UNLOCK_PFN (OldIrql);
  1317. MmUnlockPagableImageSection (ExPageLockHandle);
  1318. MiReturnCommitment (ResidentPages);
  1319. MM_BUMP_SESSION_FAILURES (MM_SESSION_FAILURE_NO_RESIDENT);
  1320. return STATUS_NO_MEMORY;
  1321. }
  1322. MM_TRACK_COMMIT (MM_DBG_COMMIT_SESSION_WS_INIT, ResidentPages);
  1323. MI_DECREMENT_RESIDENT_AVAILABLE (ResidentPages,
  1324. MM_RESAVAIL_ALLOCATE_INIT_SESSION_WS);
  1325. if (AllocatedPageTable == TRUE) {
  1326. MM_BUMP_SESS_COUNTER (MM_DBG_SESSION_WS_PAGETABLE_ALLOC, 1);
  1327. if (MmAvailablePages < MM_HIGH_LIMIT) {
  1328. MiEnsureAvailablePageOrWait (NULL, NULL, OldIrql);
  1329. }
  1330. PageColor = MI_GET_PAGE_COLOR_FROM_VA (NULL);
  1331. PageFrameIndex = MiRemoveZeroPageMayReleaseLocks (PageColor, OldIrql);
  1332. //
  1333. // The global bit is masked off since we need to make sure the TB entry
  1334. // is flushed when we switch to a process in a different session space.
  1335. //
  1336. TempPte.u.Long = ValidKernelPdeLocal.u.Long;
  1337. TempPte.u.Hard.PageFrameNumber = PageFrameIndex;
  1338. MI_WRITE_VALID_PTE (PointerPde, TempPte);
  1339. #if (_MI_PAGING_LEVELS < 3)
  1340. //
  1341. // Add this to the session structure so other processes can fault it in.
  1342. //
  1343. Index = MiGetPdeSessionIndex (WorkingSetList);
  1344. MmSessionSpace->PageTables[Index] = TempPte;
  1345. #endif
  1346. //
  1347. // This page frame references the session space page table page.
  1348. //
  1349. MiInitializePfnForOtherProcess (PageFrameIndex,
  1350. PointerPde,
  1351. MmSessionSpace->SessionPageDirectoryIndex);
  1352. KeZeroPages (PointerPte, PAGE_SIZE);
  1353. Pfn1 = MI_PFN_ELEMENT (PageFrameIndex);
  1354. //
  1355. // This page is never paged, ensure that its WsIndex stays clear so the
  1356. // release of the page will be handled correctly.
  1357. //
  1358. ASSERT (Pfn1->u1.WsIndex == 0);
  1359. }
  1360. if (MmAvailablePages < MM_HIGH_LIMIT) {
  1361. MiEnsureAvailablePageOrWait (NULL, NULL, OldIrql);
  1362. }
  1363. PageColor = MI_GET_PAGE_COLOR_FROM_VA (NULL);
  1364. PageFrameIndex = MiRemoveZeroPageIfAny (PageColor);
  1365. if (PageFrameIndex == 0) {
  1366. PageFrameIndex = MiRemoveAnyPage (PageColor);
  1367. UNLOCK_PFN (OldIrql);
  1368. MiZeroPhysicalPage (PageFrameIndex, PageColor);
  1369. LOCK_PFN (OldIrql);
  1370. }
  1371. MM_BUMP_SESS_COUNTER (MM_DBG_SESSION_WS_PAGE_ALLOC, (ULONG)(ResidentPages - 1));
  1372. #if DBG
  1373. Pfn1 = MI_PFN_ELEMENT (PageFrameIndex);
  1374. ASSERT (Pfn1->u1.WsIndex == 0);
  1375. #endif
  1376. //
  1377. // The global bit is masked off since we need to make sure the TB entry
  1378. // is flushed when we switch to a process in a different session space.
  1379. //
  1380. TempPte.u.Long = ValidKernelPteLocal.u.Long;
  1381. MI_SET_PTE_DIRTY (TempPte);
  1382. TempPte.u.Hard.PageFrameNumber = PageFrameIndex;
  1383. MI_WRITE_VALID_PTE (PointerPte, TempPte);
  1384. MiInitializePfn (PageFrameIndex, PointerPte, 1);
  1385. #if DBG
  1386. Pfn1 = MI_PFN_ELEMENT (PageFrameIndex);
  1387. ASSERT (Pfn1->u1.WsIndex == 0);
  1388. #endif
  1389. UNLOCK_PFN (OldIrql);
  1390. #define MI_INITIALIZE_SESSION_WSLE(_VirtualAddress, _WslEntry) { \
  1391. PMMPFN _Pfn1; \
  1392. _WslEntry->u1.VirtualAddress = (PVOID)(_VirtualAddress); \
  1393. _WslEntry->u1.e1.Valid = 1; \
  1394. _WslEntry->u1.e1.LockedInWs = 1; \
  1395. _WslEntry->u1.e1.Direct = 1; \
  1396. _Pfn1 = MI_PFN_ELEMENT (MiGetPteAddress ((PVOID)(_VirtualAddress))->u.Hard.PageFrameNumber); \
  1397. ASSERT (_Pfn1->u1.WsIndex == 0); \
  1398. _Pfn1->u1.WsIndex = (WSLE_NUMBER)(_WslEntry - MmSessionSpace->Wsle); \
  1399. (_WslEntry) += 1; \
  1400. }
  1401. //
  1402. // Fill in the reserved slots starting with the 2 session data pages.
  1403. //
  1404. WslEntry = MmSessionSpace->Wsle;
  1405. //
  1406. // The first reserved slot is for the page table page mapping
  1407. // the session data page.
  1408. //
  1409. MI_INITIALIZE_SESSION_WSLE (MiGetPteAddress (MmSessionSpace), WslEntry);
  1410. //
  1411. // The next reserved slot is for the working set page.
  1412. //
  1413. MI_INITIALIZE_SESSION_WSLE (WorkingSetList, WslEntry);
  1414. if (AllocatedPageTable == TRUE) {
  1415. //
  1416. // The next reserved slot is for the page table page
  1417. // mapping the working set page.
  1418. //
  1419. MI_INITIALIZE_SESSION_WSLE (PointerPte, WslEntry);
  1420. }
  1421. //
  1422. // The next reserved slot is for the page table page
  1423. // mapping the first session paged pool page.
  1424. //
  1425. MI_INITIALIZE_SESSION_WSLE (MiGetPteAddress (MmSessionSpace->PagedPoolStart), WslEntry);
  1426. CurrentEntry = (WSLE_NUMBER)(WslEntry - MmSessionSpace->Wsle);
  1427. MmSessionSpace->Vm.Flags.SessionSpace = 1;
  1428. MmSessionSpace->Vm.MinimumWorkingSetSize = MI_SESSION_SPACE_WORKING_SET_MINIMUM;
  1429. MmSessionSpace->Vm.MaximumWorkingSetSize = WorkingSetMaximum;
  1430. WorkingSetList->LastEntry = MI_SESSION_SPACE_WORKING_SET_MINIMUM;
  1431. WorkingSetList->HashTable = NULL;
  1432. WorkingSetList->HashTableSize = 0;
  1433. WorkingSetList->Wsle = MmSessionSpace->Wsle;
  1434. //
  1435. // Calculate the maximum number of entries dynamically as the size of
  1436. // session space is registry configurable. Then add in page table and
  1437. // page directory overhead.
  1438. //
  1439. MaximumEntries = (ULONG)((MiSessionSpaceEnd - MmSessionBase) >> PAGE_SHIFT);
  1440. PageTableCost = MaximumEntries / PTE_PER_PAGE + 1;
  1441. MaximumEntries += PageTableCost;
  1442. WorkingSetList->HashTableStart =
  1443. (PVOID)((PCHAR)PAGE_ALIGN (&MmSessionSpace->Wsle[MaximumEntries]) + PAGE_SIZE);
  1444. #if defined (_X86PAE_)
  1445. //
  1446. // One less page table page is needed on PAE systems as the session
  1447. // working set structures easily fit within 2MB.
  1448. //
  1449. WorkingSetList->HighestPermittedHashAddress =
  1450. (PVOID)(MiSessionImageStart - MM_VA_MAPPED_BY_PDE);
  1451. #else
  1452. WorkingSetList->HighestPermittedHashAddress =
  1453. (PVOID)(MiSessionImageStart - MI_SESSION_SPACE_STRUCT_SIZE);
  1454. #endif
  1455. DemandZeroWritePte.u.Long = MM_DEMAND_ZERO_WRITE_PTE;
  1456. NumberOfEntriesMapped = (WSLE_NUMBER)(((PMMWSLE)((ULONG_PTR)WorkingSetList +
  1457. PAGE_SIZE)) - MmSessionSpace->Wsle);
  1458. MmSessionSpace->Vm.WorkingSetSize = CurrentEntry;
  1459. #if defined (_MI_DEBUG_WSLE)
  1460. WorkingSetList->Quota = CurrentEntry;
  1461. #endif
  1462. WorkingSetList->FirstFree = CurrentEntry;
  1463. WorkingSetList->FirstDynamic = CurrentEntry;
  1464. WorkingSetList->NextSlot = CurrentEntry;
  1465. MM_BUMP_SESS_COUNTER (MM_DBG_SESSION_NP_INIT_WS, (ULONG)ResidentPages);
  1466. InterlockedExchangeAddSizeT (&MmSessionSpace->NonPagablePages,
  1467. ResidentPages);
  1468. InterlockedExchangeAddSizeT (&MmSessionSpace->CommittedPages,
  1469. ResidentPages);
  1470. //
  1471. // Initialize the following slots as free.
  1472. //
  1473. WslEntry = MmSessionSpace->Wsle + CurrentEntry;
  1474. for (i = CurrentEntry + 1; i < NumberOfEntriesMapped; i += 1) {
  1475. //
  1476. // Build the free list, note that the first working
  1477. // set entries (CurrentEntry) are not on the free list.
  1478. // These entries are reserved for the pages which
  1479. // map the working set and the page which contains the PDE.
  1480. //
  1481. WslEntry->u1.Long = i << MM_FREE_WSLE_SHIFT;
  1482. WslEntry += 1;
  1483. }
  1484. WslEntry->u1.Long = WSLE_NULL_INDEX << MM_FREE_WSLE_SHIFT; // End of list.
  1485. WorkingSetList->LastInitializedWsle = NumberOfEntriesMapped - 1;
  1486. //
  1487. // Put this session's working set in lists using its global address.
  1488. //
  1489. ASSERT (SessionGlobal->Vm.WorkingSetExpansionLinks.Flink == NULL);
  1490. ASSERT (SessionGlobal->Vm.WorkingSetExpansionLinks.Blink == NULL);
  1491. LOCK_EXPANSION (OldIrql);
  1492. ASSERT (SessionGlobal->WsListEntry.Flink == NULL);
  1493. ASSERT (SessionGlobal->WsListEntry.Blink == NULL);
  1494. InsertTailList (&MiSessionWsList, &SessionGlobal->WsListEntry);
  1495. InsertTailList (&MmWorkingSetExpansionHead.ListHead,
  1496. &SessionGlobal->Vm.WorkingSetExpansionLinks);
  1497. UNLOCK_EXPANSION (OldIrql);
  1498. MmUnlockPagableImageSection (ExPageLockHandle);
  1499. return STATUS_SUCCESS;
  1500. }
  1501. LOGICAL
  1502. MmAssignProcessToJob (
  1503. IN PEPROCESS Process
  1504. )
  1505. /*++
  1506. Routine Description:
  1507. This routine acquires the address space mutex so a consistent snapshot of
  1508. the argument process' commit charges can be used by Ps when adding this
  1509. process to a job.
  1510. Note that the working set mutex is not acquired here so the argument
  1511. process' working set sizes cannot be reliably snapped by Ps, but since Ps
  1512. doesn't look at that anyway, it's not a problem.
  1513. Arguments:
  1514. Process - Supplies a pointer to the process to operate upon.
  1515. Return Value:
  1516. TRUE if the process is allowed to join the job, FALSE otherwise.
  1517. Note that FALSE cannot be returned without changing the code in Ps.
  1518. Environment:
  1519. Kernel mode, IRQL APC_LEVEL or below. The caller provides protection
  1520. from the target process going away.
  1521. --*/
  1522. {
  1523. LOGICAL Attached;
  1524. LOGICAL Status;
  1525. KAPC_STATE ApcState;
  1526. PAGED_CODE ();
  1527. Attached = FALSE;
  1528. if (PsGetCurrentProcess() != Process) {
  1529. KeStackAttachProcess (&Process->Pcb, &ApcState);
  1530. Attached = TRUE;
  1531. }
  1532. LOCK_ADDRESS_SPACE (Process);
  1533. Status = PsChangeJobMemoryUsage (PS_JOB_STATUS_REPORT_COMMIT_CHANGES, Process->CommitCharge);
  1534. //
  1535. // Join the job unconditionally. If the process is over any limits, it
  1536. // will be caught on its next request.
  1537. //
  1538. PS_SET_BITS (&Process->JobStatus, PS_JOB_STATUS_REPORT_COMMIT_CHANGES);
  1539. UNLOCK_ADDRESS_SPACE (Process);
  1540. if (Attached) {
  1541. KeUnstackDetachProcess (&ApcState);
  1542. }
  1543. //
  1544. // Note that FALSE cannot be returned without changing the code in Ps.
  1545. //
  1546. return TRUE;
  1547. }
  1548. NTSTATUS
  1549. MmQueryWorkingSetInformation (
  1550. IN PSIZE_T PeakWorkingSetSize,
  1551. IN PSIZE_T WorkingSetSize,
  1552. IN PSIZE_T MinimumWorkingSetSize,
  1553. IN PSIZE_T MaximumWorkingSetSize,
  1554. IN PULONG HardEnforcementFlags
  1555. )
  1556. /*++
  1557. Routine Description:
  1558. This routine returns various working set information fields for the
  1559. current process.
  1560. Arguments:
  1561. PeakWorkingSetSize - Supplies an address to receive the peak working set
  1562. size in bytes.
  1563. WorkingSetSize - Supplies an address to receive the current working set
  1564. size in bytes.
  1565. MinimumWorkingSetSize - Supplies an address to receive the minimum
  1566. working set size in bytes.
  1567. MaximumWorkingSetSize - Supplies an address to receive the maximum
  1568. working set size in bytes.
  1569. HardEnforcementFlags - Supplies an address to receive the current
  1570. working set enforcement policy.
  1571. Return Value:
  1572. NTSTATUS.
  1573. Environment:
  1574. Kernel mode, IRQL APC_LEVEL or below.
  1575. --*/
  1576. {
  1577. PEPROCESS Process;
  1578. ASSERT (KeGetCurrentIrql () <= APC_LEVEL);
  1579. *HardEnforcementFlags = 0;
  1580. Process = PsGetCurrentProcess ();
  1581. LOCK_WS (Process);
  1582. *PeakWorkingSetSize = (SIZE_T) Process->Vm.PeakWorkingSetSize << PAGE_SHIFT;
  1583. *WorkingSetSize = (SIZE_T) Process->Vm.WorkingSetSize << PAGE_SHIFT;
  1584. *MinimumWorkingSetSize = (SIZE_T) Process->Vm.MinimumWorkingSetSize << PAGE_SHIFT;
  1585. *MaximumWorkingSetSize = (SIZE_T) Process->Vm.MaximumWorkingSetSize << PAGE_SHIFT;
  1586. if (Process->Vm.Flags.MinimumWorkingSetHard == 1) {
  1587. *HardEnforcementFlags |= MM_WORKING_SET_MIN_HARD_ENABLE;
  1588. }
  1589. if (Process->Vm.Flags.MaximumWorkingSetHard == 1) {
  1590. *HardEnforcementFlags |= MM_WORKING_SET_MAX_HARD_ENABLE;
  1591. }
  1592. UNLOCK_WS (Process);
  1593. return STATUS_SUCCESS;
  1594. }
  1595. NTSTATUS
  1596. MmAdjustWorkingSetSizeEx (
  1597. IN SIZE_T WorkingSetMinimumInBytes,
  1598. IN SIZE_T WorkingSetMaximumInBytes,
  1599. IN ULONG SystemCache,
  1600. IN BOOLEAN IncreaseOkay,
  1601. IN ULONG Flags
  1602. )
  1603. /*++
  1604. Routine Description:
  1605. This routine adjusts the current size of a process's working set
  1606. list. If the maximum value is above the current maximum, pages
  1607. are removed from the working set list.
  1608. A failure status is returned if the limit cannot be granted. This
  1609. could occur if too many pages were locked in the process's
  1610. working set.
  1611. Note: if the minimum and maximum are both (SIZE_T)-1, the working set
  1612. is purged, but the default sizes are not changed.
  1613. Arguments:
  1614. WorkingSetMinimumInBytes - Supplies the new minimum working set size in
  1615. bytes.
  1616. WorkingSetMaximumInBytes - Supplies the new maximum working set size in
  1617. bytes.
  1618. SystemCache - Supplies TRUE if the system cache working set is being
  1619. adjusted, FALSE for all other working sets.
  1620. IncreaseOkay - Supplies TRUE if this routine should allow increases to
  1621. the working set minimum.
  1622. Flags - Supplies flags (MM_WORKING_SET_MAX_HARD_ENABLE, etc) for
  1623. enabling/disabling hard enforcement of the working set minimums
  1624. and maximums for the specified WsInfo.
  1625. Return Value:
  1626. NTSTATUS.
  1627. Environment:
  1628. Kernel mode, IRQL APC_LEVEL or below.
  1629. --*/
  1630. {
  1631. PETHREAD CurrentThread;
  1632. PEPROCESS CurrentProcess;
  1633. WSLE_NUMBER Entry;
  1634. WSLE_NUMBER LastFreed;
  1635. PMMWSLE Wsle;
  1636. KIRQL OldIrql;
  1637. SPFN_NUMBER i;
  1638. PMMPTE PointerPte;
  1639. NTSTATUS ReturnStatus;
  1640. LONG PagesAbove;
  1641. LONG NewPagesAbove;
  1642. ULONG FreeTryCount;
  1643. PMMSUPPORT WsInfo;
  1644. PMMWSL WorkingSetList;
  1645. WSLE_NUMBER WorkingSetMinimum;
  1646. WSLE_NUMBER WorkingSetMaximum;
  1647. PERFINFO_PAGE_INFO_DECL();
  1648. FreeTryCount = 0;
  1649. if (SystemCache) {
  1650. //
  1651. // Initializing CurrentProcess is not needed for correctness, but
  1652. // without it the compiler cannot compile this code W4 to check
  1653. // for use of uninitialized variables.
  1654. //
  1655. CurrentProcess = NULL;
  1656. WsInfo = &MmSystemCacheWs;
  1657. }
  1658. else {
  1659. CurrentProcess = PsGetCurrentProcess ();
  1660. WsInfo = &CurrentProcess->Vm;
  1661. }
  1662. if ((WorkingSetMinimumInBytes == (SIZE_T)-1) &&
  1663. (WorkingSetMaximumInBytes == (SIZE_T)-1)) {
  1664. return MiEmptyWorkingSet (WsInfo, TRUE);
  1665. }
  1666. ReturnStatus = STATUS_SUCCESS;
  1667. MmLockPagableSectionByHandle (ExPageLockHandle);
  1668. //
  1669. // Get the working set lock and disable APCs.
  1670. //
  1671. if (SystemCache) {
  1672. CurrentThread = PsGetCurrentThread ();
  1673. LOCK_SYSTEM_WS (CurrentThread);
  1674. }
  1675. else {
  1676. LOCK_WS (CurrentProcess);
  1677. if (CurrentProcess->Flags & PS_PROCESS_FLAGS_VM_DELETED) {
  1678. ReturnStatus = STATUS_PROCESS_IS_TERMINATING;
  1679. goto Returns;
  1680. }
  1681. }
  1682. if (WorkingSetMinimumInBytes == 0) {
  1683. WorkingSetMinimum = WsInfo->MinimumWorkingSetSize;
  1684. }
  1685. else {
  1686. WorkingSetMinimum = (WSLE_NUMBER)(WorkingSetMinimumInBytes >> PAGE_SHIFT);
  1687. }
  1688. if (WorkingSetMaximumInBytes == 0) {
  1689. WorkingSetMaximum = WsInfo->MaximumWorkingSetSize;
  1690. }
  1691. else {
  1692. WorkingSetMaximum = (WSLE_NUMBER)(WorkingSetMaximumInBytes >> PAGE_SHIFT);
  1693. }
  1694. if (WorkingSetMinimum > WorkingSetMaximum) {
  1695. ReturnStatus = STATUS_BAD_WORKING_SET_LIMIT;
  1696. goto Returns;
  1697. }
  1698. if (WorkingSetMaximum > MmMaximumWorkingSetSize) {
  1699. WorkingSetMaximum = MmMaximumWorkingSetSize;
  1700. ReturnStatus = STATUS_WORKING_SET_LIMIT_RANGE;
  1701. }
  1702. if (WorkingSetMinimum > MmMaximumWorkingSetSize) {
  1703. WorkingSetMinimum = MmMaximumWorkingSetSize;
  1704. ReturnStatus = STATUS_WORKING_SET_LIMIT_RANGE;
  1705. }
  1706. if (WorkingSetMinimum < MmMinimumWorkingSetSize) {
  1707. WorkingSetMinimum = (ULONG)MmMinimumWorkingSetSize;
  1708. ReturnStatus = STATUS_WORKING_SET_LIMIT_RANGE;
  1709. }
  1710. //
  1711. // Make sure that the number of locked pages will not
  1712. // make the working set not fluid.
  1713. //
  1714. if ((WsInfo->VmWorkingSetList->FirstDynamic + MM_FLUID_WORKING_SET) >=
  1715. WorkingSetMaximum) {
  1716. ReturnStatus = STATUS_BAD_WORKING_SET_LIMIT;
  1717. goto Returns;
  1718. }
  1719. //
  1720. // If hard working set limits are being enabled (or are already enabled),
  1721. // then make sure the minimum and maximum will not starve this process.
  1722. //
  1723. if ((Flags & MM_WORKING_SET_MIN_HARD_ENABLE) ||
  1724. ((WsInfo->Flags.MinimumWorkingSetHard == 1) &&
  1725. ((Flags & MM_WORKING_SET_MIN_HARD_DISABLE) == 0))) {
  1726. //
  1727. // Working set minimum is (or will be hard). Check the maximum.
  1728. //
  1729. if ((Flags & MM_WORKING_SET_MAX_HARD_ENABLE) ||
  1730. ((WsInfo->Flags.MaximumWorkingSetHard == 1) &&
  1731. ((Flags & MM_WORKING_SET_MAX_HARD_DISABLE) == 0))) {
  1732. //
  1733. // Working set maximum is (or will be hard) as well.
  1734. //
  1735. // Check whether the requested minimum and maximum working
  1736. // set values will guarantee forward progress even in pathological
  1737. // scenarios.
  1738. //
  1739. if (WorkingSetMinimum + MM_FLUID_WORKING_SET >= WorkingSetMaximum) {
  1740. ReturnStatus = STATUS_BAD_WORKING_SET_LIMIT;
  1741. goto Returns;
  1742. }
  1743. }
  1744. }
  1745. WorkingSetList = WsInfo->VmWorkingSetList;
  1746. Wsle = WorkingSetList->Wsle;
  1747. i = (SPFN_NUMBER)WorkingSetMinimum - (SPFN_NUMBER)WsInfo->MinimumWorkingSetSize;
  1748. //
  1749. // Check to make sure ample resident physical pages exist for
  1750. // this operation.
  1751. //
  1752. LOCK_PFN (OldIrql);
  1753. if (i > 0) {
  1754. //
  1755. // New minimum working set is greater than the old one.
  1756. // Ensure that increasing is okay, and that we don't allow
  1757. // this process' working set minimum to increase to a point
  1758. // where subsequent nonpaged pool allocations could cause us
  1759. // to run out of pages. Additionally, leave 100 extra pages
  1760. // around so the user can later bring up tlist and kill
  1761. // processes if necessary.
  1762. //
  1763. if (IncreaseOkay == FALSE) {
  1764. UNLOCK_PFN (OldIrql);
  1765. ReturnStatus = STATUS_PRIVILEGE_NOT_HELD;
  1766. goto Returns;
  1767. }
  1768. if ((SPFN_NUMBER)((i / (PAGE_SIZE / sizeof (MMWSLE)))) >
  1769. (SPFN_NUMBER)(MmAvailablePages - MM_HIGH_LIMIT)) {
  1770. UNLOCK_PFN (OldIrql);
  1771. ReturnStatus = STATUS_INSUFFICIENT_RESOURCES;
  1772. goto Returns;
  1773. }
  1774. if (MI_NONPAGABLE_MEMORY_AVAILABLE() - 100 < i) {
  1775. UNLOCK_PFN (OldIrql);
  1776. ReturnStatus = STATUS_INSUFFICIENT_RESOURCES;
  1777. goto Returns;
  1778. }
  1779. }
  1780. //
  1781. // Adjust the number of resident pages up or down dependent on
  1782. // the size of the new minimum working set size versus the previous
  1783. // minimum size.
  1784. //
  1785. MI_DECREMENT_RESIDENT_AVAILABLE (i, MM_RESAVAIL_ALLOCATEORFREE_WS_ADJUST);
  1786. UNLOCK_PFN (OldIrql);
  1787. if (WorkingSetMaximum < WorkingSetList->LastInitializedWsle) {
  1788. //
  1789. // The new working set maximum is less than the current working set
  1790. // maximum.
  1791. //
  1792. if (WsInfo->WorkingSetSize > WorkingSetMaximum) {
  1793. //
  1794. // Remove some pages from the working set.
  1795. //
  1796. // Make sure that the number of locked pages will not
  1797. // make the working set not fluid.
  1798. //
  1799. if ((WorkingSetList->FirstDynamic + MM_FLUID_WORKING_SET) >=
  1800. WorkingSetMaximum) {
  1801. ReturnStatus = STATUS_BAD_WORKING_SET_LIMIT;
  1802. LOCK_PFN (OldIrql);
  1803. MI_INCREMENT_RESIDENT_AVAILABLE (i, MM_RESAVAIL_ALLOCATEORFREE_WS_ADJUST2);
  1804. UNLOCK_PFN (OldIrql);
  1805. goto Returns;
  1806. }
  1807. //
  1808. // Attempt to remove the pages from the Maximum downward.
  1809. //
  1810. LastFreed = WorkingSetList->LastEntry;
  1811. while (LastFreed >= WorkingSetMaximum) {
  1812. PointerPte = MiGetPteAddress(Wsle[LastFreed].u1.VirtualAddress);
  1813. PERFINFO_GET_PAGE_INFO(PointerPte);
  1814. if ((Wsle[LastFreed].u1.e1.Valid != 0) &&
  1815. (!MiFreeWsle (LastFreed, WsInfo, PointerPte))) {
  1816. //
  1817. // This LastFreed could not be removed.
  1818. //
  1819. break;
  1820. }
  1821. PERFINFO_LOG_WS_REMOVAL(PERFINFO_LOG_TYPE_OUTWS_ADJUSTWS, WsInfo);
  1822. LastFreed -= 1;
  1823. }
  1824. WorkingSetList->LastEntry = LastFreed;
  1825. //
  1826. // Remove pages.
  1827. //
  1828. Entry = WorkingSetList->FirstDynamic;
  1829. while (WsInfo->WorkingSetSize > WorkingSetMaximum) {
  1830. if (Wsle[Entry].u1.e1.Valid != 0) {
  1831. PointerPte = MiGetPteAddress (
  1832. Wsle[Entry].u1.VirtualAddress);
  1833. PERFINFO_GET_PAGE_INFO(PointerPte);
  1834. if (MiFreeWsle (Entry, WsInfo, PointerPte)) {
  1835. PERFINFO_LOG_WS_REMOVAL(PERFINFO_LOG_TYPE_OUTWS_ADJUSTWS,
  1836. WsInfo);
  1837. }
  1838. }
  1839. Entry += 1;
  1840. if (Entry > LastFreed) {
  1841. FreeTryCount += 1;
  1842. if (FreeTryCount > MM_RETRY_COUNT) {
  1843. //
  1844. // Page table pages are not becoming free, give up
  1845. // and return an error.
  1846. //
  1847. ReturnStatus = STATUS_BAD_WORKING_SET_LIMIT;
  1848. break;
  1849. }
  1850. Entry = WorkingSetList->FirstDynamic;
  1851. }
  1852. }
  1853. }
  1854. }
  1855. //
  1856. // Adjust the number of pages above the working set minimum.
  1857. //
  1858. PagesAbove = (LONG)WsInfo->WorkingSetSize -
  1859. (LONG)WsInfo->MinimumWorkingSetSize;
  1860. NewPagesAbove = (LONG)WsInfo->WorkingSetSize - (LONG)WorkingSetMinimum;
  1861. if (PagesAbove > 0) {
  1862. InterlockedExchangeAddSizeT (&MmPagesAboveWsMinimum, 0 - (PFN_NUMBER)PagesAbove);
  1863. }
  1864. if (NewPagesAbove > 0) {
  1865. InterlockedExchangeAddSizeT (&MmPagesAboveWsMinimum, (PFN_NUMBER)NewPagesAbove);
  1866. }
  1867. if (FreeTryCount <= MM_RETRY_COUNT) {
  1868. WsInfo->MaximumWorkingSetSize = WorkingSetMaximum;
  1869. WsInfo->MinimumWorkingSetSize = WorkingSetMinimum;
  1870. //
  1871. // A change in hard working set limits is being requested.
  1872. //
  1873. // If the caller's request will result in hard enforcement of both
  1874. // limits, the minimum and maximum working set values must
  1875. // guarantee forward progress even in pathological scenarios.
  1876. // This was already checked above.
  1877. //
  1878. if (Flags != 0) {
  1879. LOCK_EXPANSION (OldIrql);
  1880. if (Flags & MM_WORKING_SET_MIN_HARD_ENABLE) {
  1881. WsInfo->Flags.MinimumWorkingSetHard = 1;
  1882. }
  1883. else if (Flags & MM_WORKING_SET_MIN_HARD_DISABLE) {
  1884. WsInfo->Flags.MinimumWorkingSetHard = 0;
  1885. }
  1886. if (Flags & MM_WORKING_SET_MAX_HARD_ENABLE) {
  1887. WsInfo->Flags.MaximumWorkingSetHard = 1;
  1888. }
  1889. else if (Flags & MM_WORKING_SET_MAX_HARD_DISABLE) {
  1890. WsInfo->Flags.MaximumWorkingSetHard = 0;
  1891. }
  1892. UNLOCK_EXPANSION (OldIrql);
  1893. }
  1894. }
  1895. else {
  1896. MI_INCREMENT_RESIDENT_AVAILABLE (i, MM_RESAVAIL_ALLOCATEORFREE_WS_ADJUST3);
  1897. }
  1898. ASSERT ((WorkingSetList->FirstFree <= WorkingSetList->LastInitializedWsle) ||
  1899. (WorkingSetList->FirstFree == WSLE_NULL_INDEX));
  1900. Returns:
  1901. if (SystemCache) {
  1902. UNLOCK_SYSTEM_WS ();
  1903. }
  1904. else {
  1905. UNLOCK_WS (CurrentProcess);
  1906. }
  1907. MmUnlockPagableImageSection (ExPageLockHandle);
  1908. return ReturnStatus;
  1909. }
  1910. NTSTATUS
  1911. MmAdjustWorkingSetSize (
  1912. IN SIZE_T WorkingSetMinimumInBytes,
  1913. IN SIZE_T WorkingSetMaximumInBytes,
  1914. IN ULONG SystemCache,
  1915. IN BOOLEAN IncreaseOkay
  1916. )
  1917. /*++
  1918. Routine Description:
  1919. This routine adjusts the current size of a process's working set
  1920. list. If the maximum value is above the current maximum, pages
  1921. are removed from the working set list.
  1922. A failure status is returned if the limit cannot be granted. This
  1923. could occur if too many pages were locked in the process's
  1924. working set.
  1925. Note: if the minimum and maximum are both (SIZE_T)-1, the working set
  1926. is purged, but the default sizes are not changed.
  1927. Arguments:
  1928. WorkingSetMinimumInBytes - Supplies the new minimum working set size in
  1929. bytes.
  1930. WorkingSetMaximumInBytes - Supplies the new maximum working set size in
  1931. bytes.
  1932. SystemCache - Supplies TRUE if the system cache working set is being
  1933. adjusted, FALSE for all other working sets.
  1934. Return Value:
  1935. NTSTATUS.
  1936. Environment:
  1937. Kernel mode, IRQL APC_LEVEL or below.
  1938. --*/
  1939. {
  1940. return MmAdjustWorkingSetSizeEx (WorkingSetMinimumInBytes,
  1941. WorkingSetMaximumInBytes,
  1942. SystemCache,
  1943. IncreaseOkay,
  1944. 0);
  1945. }
  1946. #define MI_ALLOCATED_PAGE_TABLE 0x1
  1947. #define MI_ALLOCATED_PAGE_DIRECTORY 0x2
  1948. ULONG
  1949. MiAddWorkingSetPage (
  1950. IN PMMSUPPORT WsInfo
  1951. )
  1952. /*++
  1953. Routine Description:
  1954. This function grows the working set list above working set
  1955. maximum during working set adjustment. At most one page
  1956. can be added at a time.
  1957. Arguments:
  1958. None.
  1959. Return Value:
  1960. Returns FALSE if no working set page could be added.
  1961. Environment:
  1962. Kernel mode, APCs disabled, working set mutex held.
  1963. --*/
  1964. {
  1965. PETHREAD CurrentThread;
  1966. WSLE_NUMBER SwapEntry;
  1967. WSLE_NUMBER CurrentEntry;
  1968. PMMWSLE WslEntry;
  1969. WSLE_NUMBER i;
  1970. PMMPTE PointerPte;
  1971. PMMPTE Va;
  1972. MMPTE TempPte;
  1973. WSLE_NUMBER NumberOfEntriesMapped;
  1974. PFN_NUMBER WorkingSetPage;
  1975. WSLE_NUMBER WorkingSetIndex;
  1976. PMMWSL WorkingSetList;
  1977. PMMWSLE Wsle;
  1978. PMMPFN Pfn1;
  1979. KIRQL OldIrql;
  1980. ULONG PageTablePageAllocated;
  1981. LOGICAL PfnHeld;
  1982. ULONG NumberOfPages;
  1983. MMPTE DemandZeroWritePte;
  1984. #if (_MI_PAGING_LEVELS >= 3)
  1985. PVOID VirtualAddress;
  1986. PMMPTE PointerPde;
  1987. #endif
  1988. #if (_MI_PAGING_LEVELS >= 4)
  1989. PMMPTE PointerPpe;
  1990. #endif
  1991. //
  1992. // Initializing OldIrql is not needed for correctness, but
  1993. // without it the compiler cannot compile this code W4 to check
  1994. // for use of uninitialized variables.
  1995. //
  1996. OldIrql = PASSIVE_LEVEL;
  1997. WorkingSetList = WsInfo->VmWorkingSetList;
  1998. Wsle = WorkingSetList->Wsle;
  1999. MM_WS_LOCK_ASSERT (WsInfo);
  2000. //
  2001. // The maximum size of the working set is being increased, check
  2002. // to ensure the proper number of pages are mapped to cover
  2003. // the complete working set list.
  2004. //
  2005. PointerPte = MiGetPteAddress (&Wsle[WorkingSetList->LastInitializedWsle]);
  2006. ASSERT (PointerPte->u.Hard.Valid == 1);
  2007. PointerPte += 1;
  2008. Va = (PMMPTE)MiGetVirtualAddressMappedByPte (PointerPte);
  2009. if ((PVOID)Va >= WorkingSetList->HashTableStart) {
  2010. //
  2011. // Adding this entry would overrun the hash table. The caller
  2012. // must replace instead.
  2013. //
  2014. return FALSE;
  2015. }
  2016. //
  2017. // Ensure enough commitment is available prior to acquiring pages.
  2018. // Excess is released after the pages are acquired.
  2019. //
  2020. if (MiChargeCommitmentCantExpand (_MI_PAGING_LEVELS - 1, FALSE) == FALSE) {
  2021. return FALSE;
  2022. }
  2023. MM_TRACK_COMMIT (MM_DBG_COMMIT_SESSION_ADDITIONAL_WS_PAGES, _MI_PAGING_LEVELS - 1);
  2024. PageTablePageAllocated = 0;
  2025. PfnHeld = FALSE;
  2026. NumberOfPages = 0;
  2027. DemandZeroWritePte.u.Long = MM_DEMAND_ZERO_WRITE_PTE;
  2028. //
  2029. // The PPE is guaranteed to always be resident for architectures using
  2030. // 3 level lookup. This is because the hash table PPE immediately
  2031. // follows the working set PPE.
  2032. //
  2033. // For x86 PAE the same paradigm holds in guaranteeing that the PDE is
  2034. // always resident.
  2035. //
  2036. // x86 non-PAE uses the same PDE and hence it also guarantees PDE residency.
  2037. //
  2038. // Architectures employing 4 level lookup use a single PXE for this, but
  2039. // each PPE must be checked.
  2040. //
  2041. // All architectures must check for page table page residency.
  2042. //
  2043. #if (_MI_PAGING_LEVELS >= 4)
  2044. //
  2045. // Allocate a PPE if one is needed.
  2046. //
  2047. PointerPpe = MiGetPdeAddress (PointerPte);
  2048. if (PointerPpe->u.Hard.Valid == 0) {
  2049. ASSERT (WsInfo->Flags.SessionSpace == 0);
  2050. //
  2051. // Map in a new page directory for the working set expansion.
  2052. // Continue holding the PFN lock until the entire hierarchy is
  2053. // allocated. This eliminates error recovery which would be needed
  2054. // if the lock was released and then when reacquired it is discovered
  2055. // that one of the pages cannot be allocated.
  2056. //
  2057. PfnHeld = TRUE;
  2058. LOCK_PFN (OldIrql);
  2059. if ((MmAvailablePages < MM_HIGH_LIMIT) ||
  2060. (MI_NONPAGABLE_MEMORY_AVAILABLE() < MM_HIGH_LIMIT)) {
  2061. //
  2062. // No pages are available, the caller will have to replace.
  2063. //
  2064. UNLOCK_PFN (OldIrql);
  2065. MiReturnCommitment (_MI_PAGING_LEVELS - 1 - NumberOfPages);
  2066. MM_TRACK_COMMIT_REDUCTION (MM_DBG_COMMIT_SESSION_ADDITIONAL_WS_PAGES,
  2067. _MI_PAGING_LEVELS - 1 - NumberOfPages);
  2068. return FALSE;
  2069. }
  2070. //
  2071. // Apply the resident available charge for the working set page
  2072. // directory table page now before releasing the PFN lock.
  2073. //
  2074. MI_DECREMENT_RESIDENT_AVAILABLE (1, MM_RESAVAIL_ALLOCATE_ADD_WS_PAGE);
  2075. PageTablePageAllocated |= MI_ALLOCATED_PAGE_DIRECTORY;
  2076. WorkingSetPage = MiRemoveZeroPage (MI_GET_PAGE_COLOR_FROM_PTE (PointerPpe));
  2077. MI_WRITE_INVALID_PTE (PointerPpe, DemandZeroWritePte);
  2078. MiInitializePfn (WorkingSetPage, PointerPpe, 1);
  2079. MI_MAKE_VALID_PTE (TempPte,
  2080. WorkingSetPage,
  2081. MM_READWRITE,
  2082. PointerPpe);
  2083. MI_SET_PTE_DIRTY (TempPte);
  2084. MI_WRITE_VALID_PTE (PointerPpe, TempPte);
  2085. NumberOfPages += 1;
  2086. }
  2087. #endif
  2088. #if (_MI_PAGING_LEVELS >= 3)
  2089. //
  2090. // Map in a new page table (if needed) for the working set expansion.
  2091. //
  2092. PointerPde = MiGetPteAddress (PointerPte);
  2093. if (PointerPde->u.Hard.Valid == 0) {
  2094. PageTablePageAllocated |= MI_ALLOCATED_PAGE_TABLE;
  2095. if (PfnHeld == FALSE) {
  2096. PfnHeld = TRUE;
  2097. LOCK_PFN (OldIrql);
  2098. if ((MmAvailablePages < MM_HIGH_LIMIT) ||
  2099. (MI_NONPAGABLE_MEMORY_AVAILABLE() < MM_HIGH_LIMIT)) {
  2100. //
  2101. // No pages are available, the caller will have to replace.
  2102. //
  2103. UNLOCK_PFN (OldIrql);
  2104. MiReturnCommitment (_MI_PAGING_LEVELS - 1 - NumberOfPages);
  2105. MM_TRACK_COMMIT_REDUCTION (MM_DBG_COMMIT_SESSION_ADDITIONAL_WS_PAGES,
  2106. _MI_PAGING_LEVELS - 1 - NumberOfPages);
  2107. return FALSE;
  2108. }
  2109. }
  2110. //
  2111. // Apply the resident available charge for the working set page table
  2112. // page now before releasing the PFN lock.
  2113. //
  2114. MI_DECREMENT_RESIDENT_AVAILABLE (1, MM_RESAVAIL_ALLOCATE_ADD_WS_PAGE);
  2115. WorkingSetPage = MiRemoveZeroPage (MI_GET_PAGE_COLOR_FROM_PTE (PointerPde));
  2116. MI_WRITE_INVALID_PTE (PointerPde, DemandZeroWritePte);
  2117. MiInitializePfn (WorkingSetPage, PointerPde, 1);
  2118. MI_MAKE_VALID_PTE (TempPte,
  2119. WorkingSetPage,
  2120. MM_READWRITE,
  2121. PointerPde);
  2122. MI_SET_PTE_DIRTY (TempPte);
  2123. MI_WRITE_VALID_PTE (PointerPde, TempPte);
  2124. NumberOfPages += 1;
  2125. }
  2126. #endif
  2127. ASSERT (PointerPte->u.Hard.Valid == 0);
  2128. //
  2129. // Finally allocate and map the actual working set page now. The PFN lock
  2130. // is only held if another page in the hierarchy needed to be allocated.
  2131. //
  2132. // Further down in this routine (once an actual working set page has been
  2133. // allocated) the working set size will be increased by 1 to reflect
  2134. // the working set size entry for the new page directory page.
  2135. // The page directory page will be put in a working set entry which will
  2136. // be locked into the working set.
  2137. //
  2138. if (PfnHeld == FALSE) {
  2139. LOCK_PFN (OldIrql);
  2140. if ((MmAvailablePages < MM_HIGH_LIMIT) ||
  2141. (MI_NONPAGABLE_MEMORY_AVAILABLE() < MM_HIGH_LIMIT)) {
  2142. //
  2143. // No pages are available, the caller will have to replace.
  2144. //
  2145. UNLOCK_PFN (OldIrql);
  2146. MiReturnCommitment (_MI_PAGING_LEVELS - 1 - NumberOfPages);
  2147. MM_TRACK_COMMIT_REDUCTION (MM_DBG_COMMIT_SESSION_ADDITIONAL_WS_PAGES,
  2148. _MI_PAGING_LEVELS - 1 - NumberOfPages);
  2149. return FALSE;
  2150. }
  2151. }
  2152. //
  2153. // Apply the resident available charge for the working set page now
  2154. // before releasing the PFN lock.
  2155. //
  2156. MI_DECREMENT_RESIDENT_AVAILABLE (1, MM_RESAVAIL_ALLOCATE_ADD_WS_PAGE);
  2157. WorkingSetPage = MiRemoveZeroPage (MI_GET_PAGE_COLOR_FROM_PTE (PointerPte));
  2158. MI_WRITE_INVALID_PTE (PointerPte, DemandZeroWritePte);
  2159. MiInitializePfn (WorkingSetPage, PointerPte, 1);
  2160. UNLOCK_PFN (OldIrql);
  2161. NumberOfPages += 1;
  2162. if (_MI_PAGING_LEVELS - 1 - NumberOfPages != 0) {
  2163. MiReturnCommitment (_MI_PAGING_LEVELS - 1 - NumberOfPages);
  2164. MM_TRACK_COMMIT_REDUCTION (MM_DBG_COMMIT_SESSION_ADDITIONAL_WS_PAGES,
  2165. _MI_PAGING_LEVELS - 1 - NumberOfPages);
  2166. }
  2167. MI_MAKE_VALID_PTE (TempPte, WorkingSetPage, MM_READWRITE, PointerPte);
  2168. MI_SET_PTE_DIRTY (TempPte);
  2169. MI_WRITE_VALID_PTE (PointerPte, TempPte);
  2170. NumberOfEntriesMapped = (WSLE_NUMBER)(((PMMWSLE)((PCHAR)Va + PAGE_SIZE)) - Wsle);
  2171. if (WsInfo->Flags.SessionSpace == 1) {
  2172. MM_BUMP_SESS_COUNTER (MM_DBG_SESSION_NP_WS_GROW, NumberOfPages);
  2173. MM_BUMP_SESS_COUNTER (MM_DBG_SESSION_WS_PAGE_ALLOC_GROWTH, NumberOfPages);
  2174. InterlockedExchangeAddSizeT (&MmSessionSpace->NonPagablePages,
  2175. NumberOfPages);
  2176. InterlockedExchangeAddSizeT (&MmSessionSpace->CommittedPages,
  2177. NumberOfPages);
  2178. }
  2179. CurrentEntry = WorkingSetList->LastInitializedWsle + 1;
  2180. ASSERT (NumberOfEntriesMapped > CurrentEntry);
  2181. WslEntry = &Wsle[CurrentEntry - 1];
  2182. for (i = CurrentEntry; i < NumberOfEntriesMapped; i += 1) {
  2183. //
  2184. // Build the free list, note that the first working
  2185. // set entries (CurrentEntry) are not on the free list.
  2186. // These entries are reserved for the pages which
  2187. // map the working set and the page which contains the PDE.
  2188. //
  2189. WslEntry += 1;
  2190. WslEntry->u1.Long = (i + 1) << MM_FREE_WSLE_SHIFT;
  2191. }
  2192. WslEntry->u1.Long = WorkingSetList->FirstFree << MM_FREE_WSLE_SHIFT;
  2193. ASSERT (CurrentEntry >= WorkingSetList->FirstDynamic);
  2194. WorkingSetList->FirstFree = CurrentEntry;
  2195. WorkingSetList->LastInitializedWsle = (NumberOfEntriesMapped - 1);
  2196. ASSERT ((WorkingSetList->FirstFree <= WorkingSetList->LastInitializedWsle) ||
  2197. (WorkingSetList->FirstFree == WSLE_NULL_INDEX));
  2198. Pfn1 = MI_PFN_ELEMENT (PointerPte->u.Hard.PageFrameNumber);
  2199. CurrentThread = PsGetCurrentThread ();
  2200. Pfn1->u1.Event = NULL;
  2201. //
  2202. // Get a working set entry.
  2203. //
  2204. ASSERT (WsInfo->WorkingSetSize <= (WorkingSetList->LastInitializedWsle + 1));
  2205. WsInfo->WorkingSetSize += 1;
  2206. #if defined (_MI_DEBUG_WSLE)
  2207. WorkingSetList->Quota += 1;
  2208. ASSERT (WsInfo->WorkingSetSize == WorkingSetList->Quota);
  2209. #endif
  2210. ASSERT (WorkingSetList->FirstFree != WSLE_NULL_INDEX);
  2211. ASSERT (WorkingSetList->FirstFree >= WorkingSetList->FirstDynamic);
  2212. WorkingSetIndex = WorkingSetList->FirstFree;
  2213. WorkingSetList->FirstFree = (WSLE_NUMBER)(Wsle[WorkingSetIndex].u1.Long >> MM_FREE_WSLE_SHIFT);
  2214. ASSERT ((WorkingSetList->FirstFree <= WorkingSetList->LastInitializedWsle) ||
  2215. (WorkingSetList->FirstFree == WSLE_NULL_INDEX));
  2216. if (WsInfo->WorkingSetSize > WsInfo->MinimumWorkingSetSize) {
  2217. InterlockedExchangeAddSizeT (&MmPagesAboveWsMinimum, 1);
  2218. }
  2219. if (WorkingSetIndex > WorkingSetList->LastEntry) {
  2220. WorkingSetList->LastEntry = WorkingSetIndex;
  2221. }
  2222. MiUpdateWsle (&WorkingSetIndex, Va, WsInfo, Pfn1);
  2223. MI_SET_PTE_IN_WORKING_SET (PointerPte, WorkingSetIndex);
  2224. //
  2225. // Lock any created page table pages into the working set.
  2226. //
  2227. if (WorkingSetIndex >= WorkingSetList->FirstDynamic) {
  2228. SwapEntry = WorkingSetList->FirstDynamic;
  2229. if (WorkingSetIndex != WorkingSetList->FirstDynamic) {
  2230. //
  2231. // Swap this entry with the one at first dynamic.
  2232. //
  2233. MiSwapWslEntries (WorkingSetIndex, SwapEntry, WsInfo, FALSE);
  2234. }
  2235. WorkingSetList->FirstDynamic += 1;
  2236. Wsle[SwapEntry].u1.e1.LockedInWs = 1;
  2237. ASSERT (Wsle[SwapEntry].u1.e1.Valid == 1);
  2238. }
  2239. #if (_MI_PAGING_LEVELS >= 3)
  2240. while (PageTablePageAllocated != 0) {
  2241. if (PageTablePageAllocated & MI_ALLOCATED_PAGE_TABLE) {
  2242. PageTablePageAllocated &= ~MI_ALLOCATED_PAGE_TABLE;
  2243. Pfn1 = MI_PFN_ELEMENT (PointerPde->u.Hard.PageFrameNumber);
  2244. VirtualAddress = PointerPte;
  2245. }
  2246. #if (_MI_PAGING_LEVELS >= 4)
  2247. else if (PageTablePageAllocated & MI_ALLOCATED_PAGE_DIRECTORY) {
  2248. PageTablePageAllocated &= ~MI_ALLOCATED_PAGE_DIRECTORY;
  2249. Pfn1 = MI_PFN_ELEMENT (PointerPpe->u.Hard.PageFrameNumber);
  2250. VirtualAddress = PointerPde;
  2251. }
  2252. #endif
  2253. else {
  2254. ASSERT (FALSE);
  2255. SATISFY_OVERZEALOUS_COMPILER (VirtualAddress = NULL);
  2256. }
  2257. Pfn1->u1.Event = NULL;
  2258. //
  2259. // Get a working set entry.
  2260. //
  2261. WsInfo->WorkingSetSize += 1;
  2262. #if defined (_MI_DEBUG_WSLE)
  2263. WorkingSetList->Quota += 1;
  2264. ASSERT (WsInfo->WorkingSetSize == WorkingSetList->Quota);
  2265. #endif
  2266. ASSERT (WorkingSetList->FirstFree != WSLE_NULL_INDEX);
  2267. ASSERT (WorkingSetList->FirstFree >= WorkingSetList->FirstDynamic);
  2268. WorkingSetIndex = WorkingSetList->FirstFree;
  2269. WorkingSetList->FirstFree = (WSLE_NUMBER)(Wsle[WorkingSetIndex].u1.Long >> MM_FREE_WSLE_SHIFT);
  2270. ASSERT ((WorkingSetList->FirstFree <= WorkingSetList->LastInitializedWsle) ||
  2271. (WorkingSetList->FirstFree == WSLE_NULL_INDEX));
  2272. if (WsInfo->WorkingSetSize > WsInfo->MinimumWorkingSetSize) {
  2273. InterlockedExchangeAddSizeT (&MmPagesAboveWsMinimum, 1);
  2274. }
  2275. if (WorkingSetIndex > WorkingSetList->LastEntry) {
  2276. WorkingSetList->LastEntry = WorkingSetIndex;
  2277. }
  2278. MiUpdateWsle (&WorkingSetIndex, VirtualAddress, WsInfo, Pfn1);
  2279. MI_SET_PTE_IN_WORKING_SET (MiGetPteAddress (VirtualAddress),
  2280. WorkingSetIndex);
  2281. //
  2282. // Lock the created page table page into the working set.
  2283. //
  2284. if (WorkingSetIndex >= WorkingSetList->FirstDynamic) {
  2285. SwapEntry = WorkingSetList->FirstDynamic;
  2286. if (WorkingSetIndex != WorkingSetList->FirstDynamic) {
  2287. //
  2288. // Swap this entry with the one at first dynamic.
  2289. //
  2290. MiSwapWslEntries (WorkingSetIndex, SwapEntry, WsInfo, FALSE);
  2291. }
  2292. WorkingSetList->FirstDynamic += 1;
  2293. Wsle[SwapEntry].u1.e1.LockedInWs = 1;
  2294. ASSERT (Wsle[SwapEntry].u1.e1.Valid == 1);
  2295. }
  2296. }
  2297. #endif
  2298. ASSERT ((MiGetPteAddress(&Wsle[WorkingSetList->LastInitializedWsle]))->u.Hard.Valid == 1);
  2299. if ((WorkingSetList->HashTable == NULL) &&
  2300. (MmAvailablePages > MM_HIGH_LIMIT)) {
  2301. //
  2302. // Add a hash table to support shared pages in the working set to
  2303. // eliminate costly lookups.
  2304. //
  2305. WsInfo->Flags.GrowWsleHash = 1;
  2306. }
  2307. return TRUE;
  2308. }
  2309. LOGICAL
  2310. MiAddWsleHash (
  2311. IN PMMSUPPORT WsInfo,
  2312. IN PMMPTE PointerPte
  2313. )
  2314. /*++
  2315. Routine Description:
  2316. This function adds a page directory, page table or actual mapping page
  2317. for hash table creation (or expansion) for the current process.
  2318. Arguments:
  2319. WsInfo - Supplies a pointer to the working set info structure.
  2320. PointerPte - Supplies a pointer to the PTE to be filled.
  2321. Return Value:
  2322. None.
  2323. Environment:
  2324. Kernel mode, APCs disabled, working set lock held.
  2325. --*/
  2326. {
  2327. KIRQL OldIrql;
  2328. PMMPFN Pfn1;
  2329. WSLE_NUMBER SwapEntry;
  2330. MMPTE TempPte;
  2331. PMMWSLE Wsle;
  2332. PFN_NUMBER WorkingSetPage;
  2333. WSLE_NUMBER WorkingSetIndex;
  2334. PMMWSL WorkingSetList;
  2335. MMPTE DemandZeroWritePte;
  2336. if (MiChargeCommitmentCantExpand (1, FALSE) == FALSE) {
  2337. return FALSE;
  2338. }
  2339. WorkingSetList = WsInfo->VmWorkingSetList;
  2340. Wsle = WorkingSetList->Wsle;
  2341. ASSERT (PointerPte->u.Hard.Valid == 0);
  2342. DemandZeroWritePte.u.Long = MM_DEMAND_ZERO_WRITE_PTE;
  2343. LOCK_PFN (OldIrql);
  2344. if (MmAvailablePages < MM_HIGH_LIMIT) {
  2345. UNLOCK_PFN (OldIrql);
  2346. MiReturnCommitment (1);
  2347. return FALSE;
  2348. }
  2349. if (MI_NONPAGABLE_MEMORY_AVAILABLE() < MM_HIGH_LIMIT) {
  2350. UNLOCK_PFN (OldIrql);
  2351. MiReturnCommitment (1);
  2352. return FALSE;
  2353. }
  2354. MI_DECREMENT_RESIDENT_AVAILABLE (1, MM_RESAVAIL_ALLOCATE_WSLE_HASH);
  2355. MM_TRACK_COMMIT (MM_DBG_COMMIT_SESSION_ADDITIONAL_WS_HASHPAGES, 1);
  2356. WorkingSetPage = MiRemoveZeroPage (MI_GET_PAGE_COLOR_FROM_PTE (PointerPte));
  2357. MI_WRITE_INVALID_PTE (PointerPte, DemandZeroWritePte);
  2358. MiInitializePfn (WorkingSetPage, PointerPte, 1);
  2359. UNLOCK_PFN (OldIrql);
  2360. MI_MAKE_VALID_PTE (TempPte,
  2361. WorkingSetPage,
  2362. MM_READWRITE,
  2363. PointerPte);
  2364. MI_SET_PTE_DIRTY (TempPte);
  2365. MI_WRITE_VALID_PTE (PointerPte, TempPte);
  2366. //
  2367. // As we have grown the working set, take the
  2368. // next free WSLE from the list and use it.
  2369. //
  2370. Pfn1 = MI_PFN_ELEMENT (WorkingSetPage);
  2371. Pfn1->u1.Event = NULL;
  2372. //
  2373. // Set the low bit in the PFN pointer to indicate that the working set
  2374. // should not be trimmed during the WSLE allocation as the PTEs for the
  2375. // new hash pages are valid but we are still in the midst of making all
  2376. // the associated fields valid.
  2377. //
  2378. WorkingSetIndex = MiAllocateWsle (WsInfo,
  2379. PointerPte,
  2380. (PMMPFN)((ULONG_PTR)Pfn1 | 0x1),
  2381. 0);
  2382. if (WorkingSetIndex == 0) {
  2383. //
  2384. // No working set index was available, flush the PTE and the page,
  2385. // and decrement the count on the containing page.
  2386. //
  2387. ASSERT (Pfn1->u3.e1.PrototypePte == 0);
  2388. LOCK_PFN (OldIrql);
  2389. MI_SET_PFN_DELETED (Pfn1);
  2390. UNLOCK_PFN (OldIrql);
  2391. MiTrimPte (MiGetVirtualAddressMappedByPte (PointerPte),
  2392. PointerPte,
  2393. Pfn1,
  2394. PsGetCurrentProcess (),
  2395. ZeroPte);
  2396. MI_INCREMENT_RESIDENT_AVAILABLE (1, MM_RESAVAIL_FREE_WSLE_HASH);
  2397. MiReturnCommitment (1);
  2398. return FALSE;
  2399. }
  2400. //
  2401. // Lock any created page table pages into the working set.
  2402. //
  2403. if (WorkingSetIndex >= WorkingSetList->FirstDynamic) {
  2404. SwapEntry = WorkingSetList->FirstDynamic;
  2405. if (WorkingSetIndex != WorkingSetList->FirstDynamic) {
  2406. //
  2407. // Swap this entry with the one at first dynamic.
  2408. //
  2409. MiSwapWslEntries (WorkingSetIndex, SwapEntry, WsInfo, FALSE);
  2410. }
  2411. WorkingSetList->FirstDynamic += 1;
  2412. Wsle[SwapEntry].u1.e1.LockedInWs = 1;
  2413. ASSERT (Wsle[SwapEntry].u1.e1.Valid == 1);
  2414. }
  2415. if (WsInfo->Flags.SessionSpace == 1) {
  2416. MM_BUMP_SESS_COUNTER (MM_DBG_SESSION_NP_HASH_GROW, 1);
  2417. InterlockedExchangeAddSizeT (&MmSessionSpace->NonPagablePages, 1);
  2418. MM_BUMP_SESS_COUNTER (MM_DBG_SESSION_WS_HASHPAGE_ALLOC, 1);
  2419. InterlockedExchangeAddSizeT (&MmSessionSpace->CommittedPages, 1);
  2420. }
  2421. return TRUE;
  2422. }
  2423. VOID
  2424. MiGrowWsleHash (
  2425. IN PMMSUPPORT WsInfo
  2426. )
  2427. /*++
  2428. Routine Description:
  2429. This function grows (or adds) a hash table to the working set list
  2430. to allow direct indexing for WSLEs than cannot be located via the
  2431. PFN database WSINDEX field.
  2432. The hash table is located AFTER the WSLE array and the pages are
  2433. locked into the working set just like standard WSLEs.
  2434. Note that the hash table is expanded by setting the hash table
  2435. field in the working set to NULL, but leaving the size as non-zero.
  2436. This indicates that the hash should be expanded and the initial
  2437. portion of the table zeroed.
  2438. Arguments:
  2439. WsInfo - Supplies a pointer to the working set info structure.
  2440. Return Value:
  2441. None.
  2442. Environment:
  2443. Kernel mode, APCs disabled, working set lock held.
  2444. --*/
  2445. {
  2446. ULONG Tries;
  2447. LONG Size;
  2448. PMMWSLE Wsle;
  2449. PMMPTE StartPte;
  2450. PMMPTE EndPte;
  2451. PMMPTE PointerPte;
  2452. ULONG First;
  2453. WSLE_NUMBER Hash;
  2454. ULONG NewSize;
  2455. PMMWSLE_HASH Table;
  2456. PMMWSLE_HASH OriginalTable;
  2457. ULONG j;
  2458. PMMWSL WorkingSetList;
  2459. WSLE_NUMBER Count;
  2460. PVOID EntryHashTableEnd;
  2461. PVOID VirtualAddress;
  2462. #if (_MI_PAGING_LEVELS >= 3) || defined (_X86PAE_)
  2463. KIRQL OldIrql;
  2464. PVOID TempVa;
  2465. PEPROCESS CurrentProcess;
  2466. LOGICAL LoopStart;
  2467. PMMPTE AllocatedPde;
  2468. PMMPTE AllocatedPpe;
  2469. PMMPTE AllocatedPxe;
  2470. PMMPTE PointerPde;
  2471. #endif
  2472. #if (_MI_PAGING_LEVELS >= 3)
  2473. PMMPTE PointerPpe;
  2474. PMMPTE PointerPxe;
  2475. #endif
  2476. WorkingSetList = WsInfo->VmWorkingSetList;
  2477. Wsle = WorkingSetList->Wsle;
  2478. Table = WorkingSetList->HashTable;
  2479. OriginalTable = WorkingSetList->HashTable;
  2480. First = WorkingSetList->HashTableSize;
  2481. if (Table == NULL) {
  2482. NewSize = PtrToUlong(PAGE_ALIGN (((1 + WorkingSetList->NonDirectCount) *
  2483. 2 * sizeof(MMWSLE_HASH)) + PAGE_SIZE - 1));
  2484. //
  2485. // Note that the Table may be NULL and the HashTableSize/PTEs nonzero
  2486. // in the case where the hash has been contracted.
  2487. //
  2488. j = First * sizeof(MMWSLE_HASH);
  2489. //
  2490. // Don't try for additional hash pages if we already have
  2491. // the right amount (or too many).
  2492. //
  2493. if ((j + PAGE_SIZE > NewSize) && (j != 0)) {
  2494. WsInfo->Flags.GrowWsleHash = 0;
  2495. return;
  2496. }
  2497. Table = (PMMWSLE_HASH)(WorkingSetList->HashTableStart);
  2498. EntryHashTableEnd = &Table[WorkingSetList->HashTableSize];
  2499. WorkingSetList->HashTableSize = 0;
  2500. }
  2501. else {
  2502. //
  2503. // Attempt to add 4 pages, make sure the working set list has
  2504. // 4 free entries.
  2505. //
  2506. if ((WorkingSetList->LastInitializedWsle + 5) > WsInfo->WorkingSetSize) {
  2507. NewSize = PAGE_SIZE * 4;
  2508. }
  2509. else {
  2510. NewSize = PAGE_SIZE;
  2511. }
  2512. EntryHashTableEnd = &Table[WorkingSetList->HashTableSize];
  2513. }
  2514. if ((PCHAR)EntryHashTableEnd + NewSize > (PCHAR)WorkingSetList->HighestPermittedHashAddress) {
  2515. NewSize =
  2516. (ULONG)((PCHAR)(WorkingSetList->HighestPermittedHashAddress) -
  2517. ((PCHAR)EntryHashTableEnd));
  2518. if (NewSize == 0) {
  2519. if (OriginalTable == NULL) {
  2520. WorkingSetList->HashTableSize = First;
  2521. }
  2522. WsInfo->Flags.GrowWsleHash = 0;
  2523. return;
  2524. }
  2525. }
  2526. #if (_MI_PAGING_LEVELS >= 4)
  2527. ASSERT64 ((MiGetPxeAddress(EntryHashTableEnd)->u.Hard.Valid == 0) ||
  2528. (MiGetPpeAddress(EntryHashTableEnd)->u.Hard.Valid == 0) ||
  2529. (MiGetPdeAddress(EntryHashTableEnd)->u.Hard.Valid == 0) ||
  2530. (MiGetPteAddress(EntryHashTableEnd)->u.Hard.Valid == 0));
  2531. #else
  2532. ASSERT64 ((MiGetPpeAddress(EntryHashTableEnd)->u.Hard.Valid == 0) ||
  2533. (MiGetPdeAddress(EntryHashTableEnd)->u.Hard.Valid == 0) ||
  2534. (MiGetPteAddress(EntryHashTableEnd)->u.Hard.Valid == 0));
  2535. #endif
  2536. //
  2537. // Note PAE virtual address space is packed even more densely than
  2538. // regular x86. The working set list hash table can grow until it
  2539. // is directly beneath the system cache data structures. Hence the
  2540. // assert below factors that in by checking HighestPermittedHashAddress
  2541. // first.
  2542. //
  2543. ASSERT32 ((EntryHashTableEnd == WorkingSetList->HighestPermittedHashAddress) ||
  2544. (MiGetPdeAddress(EntryHashTableEnd)->u.Hard.Valid == 0) ||
  2545. (MiGetPteAddress(EntryHashTableEnd)->u.Hard.Valid == 0));
  2546. Size = NewSize;
  2547. PointerPte = MiGetPteAddress (EntryHashTableEnd);
  2548. StartPte = PointerPte;
  2549. EndPte = PointerPte + (NewSize >> PAGE_SHIFT);
  2550. #if (_MI_PAGING_LEVELS >= 3) || defined (_X86PAE_)
  2551. PointerPde = NULL;
  2552. LoopStart = TRUE;
  2553. AllocatedPde = NULL;
  2554. #endif
  2555. #if (_MI_PAGING_LEVELS >= 3)
  2556. AllocatedPpe = NULL;
  2557. AllocatedPxe = NULL;
  2558. PointerPpe = NULL;
  2559. PointerPxe = NULL;
  2560. #endif
  2561. do {
  2562. #if (_MI_PAGING_LEVELS >= 3) || defined (_X86PAE_)
  2563. if (LoopStart == TRUE || MiIsPteOnPdeBoundary(PointerPte)) {
  2564. PointerPde = MiGetPteAddress (PointerPte);
  2565. #if (_MI_PAGING_LEVELS >= 3)
  2566. PointerPxe = MiGetPpeAddress (PointerPte);
  2567. PointerPpe = MiGetPdeAddress (PointerPte);
  2568. #if (_MI_PAGING_LEVELS >= 4)
  2569. if (PointerPxe->u.Hard.Valid == 0) {
  2570. if (MiAddWsleHash (WsInfo, PointerPxe) == FALSE) {
  2571. break;
  2572. }
  2573. AllocatedPxe = PointerPxe;
  2574. }
  2575. #endif
  2576. if (PointerPpe->u.Hard.Valid == 0) {
  2577. if (MiAddWsleHash (WsInfo, PointerPpe) == FALSE) {
  2578. break;
  2579. }
  2580. AllocatedPpe = PointerPpe;
  2581. }
  2582. #endif
  2583. if (PointerPde->u.Hard.Valid == 0) {
  2584. if (MiAddWsleHash (WsInfo, PointerPde) == FALSE) {
  2585. break;
  2586. }
  2587. AllocatedPde = PointerPde;
  2588. }
  2589. LoopStart = FALSE;
  2590. }
  2591. else {
  2592. AllocatedPde = NULL;
  2593. AllocatedPpe = NULL;
  2594. AllocatedPxe = NULL;
  2595. }
  2596. #endif
  2597. if (PointerPte->u.Hard.Valid == 0) {
  2598. if (MiAddWsleHash (WsInfo, PointerPte) == FALSE) {
  2599. break;
  2600. }
  2601. }
  2602. PointerPte += 1;
  2603. Size -= PAGE_SIZE;
  2604. } while (Size > 0);
  2605. //
  2606. // If MiAddWsleHash was unable to allocate memory above, then roll back
  2607. // any extra PPEs & PDEs that may have been created. Note NewSize must
  2608. // be recalculated to handle the fact that memory may have run out.
  2609. //
  2610. #if (_MI_PAGING_LEVELS >= 3) || defined (_X86PAE_)
  2611. if (PointerPte != EndPte) {
  2612. CurrentProcess = PsGetCurrentProcess ();
  2613. //
  2614. // Clean up the last allocated PPE/PDE as they are not needed.
  2615. // Note that the system cache and the session space working sets
  2616. // have no current process (which MiDeletePte requires) which is
  2617. // needed for WSLE and PrivatePages adjustments.
  2618. //
  2619. if (AllocatedPde != NULL) {
  2620. ASSERT (AllocatedPde->u.Hard.Valid == 1);
  2621. TempVa = MiGetVirtualAddressMappedByPte (AllocatedPde);
  2622. if (WsInfo->VmWorkingSetList == MmWorkingSetList) {
  2623. LOCK_PFN (OldIrql);
  2624. MiDeletePte (AllocatedPde,
  2625. TempVa,
  2626. FALSE,
  2627. CurrentProcess,
  2628. NULL,
  2629. NULL,
  2630. OldIrql);
  2631. UNLOCK_PFN (OldIrql);
  2632. //
  2633. // Add back in the private page MiDeletePte subtracted.
  2634. //
  2635. CurrentProcess->NumberOfPrivatePages += 1;
  2636. }
  2637. else {
  2638. LOCK_PFN (OldIrql);
  2639. MiDeleteValidSystemPte (AllocatedPde,
  2640. TempVa,
  2641. WsInfo,
  2642. NULL);
  2643. UNLOCK_PFN (OldIrql);
  2644. }
  2645. MiReturnCommitment (1);
  2646. MI_INCREMENT_RESIDENT_AVAILABLE (1, MM_RESAVAIL_FREE_WSLE_HASH);
  2647. }
  2648. #if (_MI_PAGING_LEVELS >= 3)
  2649. if (AllocatedPpe != NULL) {
  2650. ASSERT (AllocatedPpe->u.Hard.Valid == 1);
  2651. TempVa = MiGetVirtualAddressMappedByPte (AllocatedPpe);
  2652. if (WsInfo->VmWorkingSetList == MmWorkingSetList) {
  2653. LOCK_PFN (OldIrql);
  2654. MiDeletePte (AllocatedPpe,
  2655. TempVa,
  2656. FALSE,
  2657. CurrentProcess,
  2658. NULL,
  2659. NULL,
  2660. OldIrql);
  2661. UNLOCK_PFN (OldIrql);
  2662. //
  2663. // Add back in the private page MiDeletePte subtracted.
  2664. //
  2665. CurrentProcess->NumberOfPrivatePages += 1;
  2666. }
  2667. else {
  2668. LOCK_PFN (OldIrql);
  2669. MiDeleteValidSystemPte (AllocatedPpe,
  2670. TempVa,
  2671. WsInfo,
  2672. NULL);
  2673. UNLOCK_PFN (OldIrql);
  2674. }
  2675. MiReturnCommitment (1);
  2676. MI_INCREMENT_RESIDENT_AVAILABLE (1, MM_RESAVAIL_FREE_WSLE_HASH);
  2677. }
  2678. if (AllocatedPxe != NULL) {
  2679. ASSERT (AllocatedPxe->u.Hard.Valid == 1);
  2680. TempVa = MiGetVirtualAddressMappedByPte (AllocatedPxe);
  2681. if (WsInfo->VmWorkingSetList == MmWorkingSetList) {
  2682. LOCK_PFN (OldIrql);
  2683. MiDeletePte (AllocatedPxe,
  2684. TempVa,
  2685. FALSE,
  2686. CurrentProcess,
  2687. NULL,
  2688. NULL,
  2689. OldIrql);
  2690. UNLOCK_PFN (OldIrql);
  2691. //
  2692. // Add back in the private page MiDeletePte subtracted.
  2693. //
  2694. CurrentProcess->NumberOfPrivatePages += 1;
  2695. }
  2696. else {
  2697. LOCK_PFN (OldIrql);
  2698. MiDeleteValidSystemPte (AllocatedPxe,
  2699. TempVa,
  2700. WsInfo,
  2701. NULL);
  2702. UNLOCK_PFN (OldIrql);
  2703. }
  2704. MiReturnCommitment (1);
  2705. MI_INCREMENT_RESIDENT_AVAILABLE (1, MM_RESAVAIL_FREE_WSLE_HASH);
  2706. }
  2707. #endif
  2708. if (PointerPte == StartPte) {
  2709. if (OriginalTable == NULL) {
  2710. WorkingSetList->HashTableSize = First;
  2711. }
  2712. WsInfo->Flags.GrowWsleHash = 0;
  2713. return;
  2714. }
  2715. }
  2716. #else
  2717. if (PointerPte == StartPte) {
  2718. if (OriginalTable == NULL) {
  2719. WorkingSetList->HashTableSize = First;
  2720. }
  2721. WsInfo->Flags.GrowWsleHash = 0;
  2722. return;
  2723. }
  2724. #endif
  2725. NewSize = (ULONG)((PointerPte - StartPte) << PAGE_SHIFT);
  2726. VirtualAddress = MiGetVirtualAddressMappedByPte (PointerPte);
  2727. ASSERT ((VirtualAddress == WorkingSetList->HighestPermittedHashAddress) ||
  2728. (MiIsAddressValid (VirtualAddress, FALSE) == FALSE));
  2729. WorkingSetList->HashTableSize = First + NewSize / sizeof (MMWSLE_HASH);
  2730. WorkingSetList->HashTable = Table;
  2731. VirtualAddress = &Table[WorkingSetList->HashTableSize];
  2732. ASSERT ((VirtualAddress == WorkingSetList->HighestPermittedHashAddress) ||
  2733. (MiIsAddressValid (VirtualAddress, FALSE) == FALSE));
  2734. if (First != 0) {
  2735. RtlZeroMemory (Table, First * sizeof(MMWSLE_HASH));
  2736. }
  2737. //
  2738. // Fill hash table.
  2739. //
  2740. j = 0;
  2741. Count = WorkingSetList->NonDirectCount;
  2742. Size = WorkingSetList->HashTableSize;
  2743. do {
  2744. if ((Wsle[j].u1.e1.Valid == 1) &&
  2745. (Wsle[j].u1.e1.Direct == 0)) {
  2746. //
  2747. // Hash this.
  2748. //
  2749. Count -= 1;
  2750. Hash = MI_WSLE_HASH(Wsle[j].u1.Long, WorkingSetList);
  2751. Tries = 0;
  2752. while (Table[Hash].Key != 0) {
  2753. Hash += 1;
  2754. if (Hash >= (ULONG)Size) {
  2755. if (Tries != 0) {
  2756. //
  2757. // Not enough space to hash everything but that's ok.
  2758. // Just bail out, we'll do linear walks to lookup this
  2759. // entry until the hash can be further expanded later.
  2760. //
  2761. WsInfo->Flags.GrowWsleHash = 0;
  2762. return;
  2763. }
  2764. Tries = 1;
  2765. Hash = 0;
  2766. Size = MI_WSLE_HASH(Wsle[j].u1.Long, WorkingSetList);
  2767. }
  2768. }
  2769. Table[Hash].Key = MI_GENERATE_VALID_WSLE (&Wsle[j]);
  2770. Table[Hash].Index = j;
  2771. #if DBG
  2772. PointerPte = MiGetPteAddress(Wsle[j].u1.VirtualAddress);
  2773. ASSERT (PointerPte->u.Hard.Valid);
  2774. #endif
  2775. }
  2776. ASSERT (j <= WorkingSetList->LastEntry);
  2777. j += 1;
  2778. } while (Count);
  2779. #if DBG
  2780. MiCheckWsleHash (WorkingSetList);
  2781. #endif
  2782. WsInfo->Flags.GrowWsleHash = 0;
  2783. return;
  2784. }
  2785. WSLE_NUMBER
  2786. MiFreeWsleList (
  2787. IN PMMSUPPORT WsInfo,
  2788. IN PMMWSLE_FLUSH_LIST WsleFlushList
  2789. )
  2790. /*++
  2791. Routine Description:
  2792. This routine frees the specified list of WSLEs decrementing the share
  2793. count for the corresponding pages, putting each PTE into a transition
  2794. state if the share count goes to 0.
  2795. Arguments:
  2796. WsInfo - Supplies a pointer to the working set structure.
  2797. WsleFlushList - Supplies the list of WSLEs to flush.
  2798. Return Value:
  2799. Returns the number of WSLEs that were NOT removed.
  2800. Environment:
  2801. Kernel mode, APCs disabled, working set lock. PFN lock NOT held.
  2802. --*/
  2803. {
  2804. PMMPFN Pfn1;
  2805. KIRQL OldIrql;
  2806. PMMPTE PointerPte;
  2807. WSLE_NUMBER i;
  2808. WSLE_NUMBER WorkingSetIndex;
  2809. WSLE_NUMBER NumberNotFlushed;
  2810. PMMWSL WorkingSetList;
  2811. PMMWSLE Wsle;
  2812. PMMPTE ContainingPageTablePage;
  2813. MMPTE TempPte;
  2814. PFN_NUMBER PageFrameIndex;
  2815. PFN_NUMBER PageTableFrameIndex;
  2816. PEPROCESS Process;
  2817. PMMPFN Pfn2;
  2818. MMPTE_FLUSH_LIST PteFlushList;
  2819. PteFlushList.Count = 0;
  2820. ASSERT (WsleFlushList->Count != 0);
  2821. WorkingSetList = WsInfo->VmWorkingSetList;
  2822. Wsle = WorkingSetList->Wsle;
  2823. MM_WS_LOCK_ASSERT (WsInfo);
  2824. LOCK_PFN (OldIrql);
  2825. for (i = 0; i < WsleFlushList->Count; i += 1) {
  2826. WorkingSetIndex = WsleFlushList->FlushIndex[i];
  2827. ASSERT (WorkingSetIndex >= WorkingSetList->FirstDynamic);
  2828. ASSERT (Wsle[WorkingSetIndex].u1.e1.Valid == 1);
  2829. PointerPte = MiGetPteAddress (Wsle[WorkingSetIndex].u1.VirtualAddress);
  2830. TempPte = *PointerPte;
  2831. ASSERT (TempPte.u.Hard.Valid == 1);
  2832. PageFrameIndex = MI_GET_PAGE_FRAME_FROM_PTE (&TempPte);
  2833. Pfn1 = MI_PFN_ELEMENT (PageFrameIndex);
  2834. //
  2835. // Check to see if the located entry is eligible for removal.
  2836. //
  2837. // Note, don't clear the access bit for page table pages
  2838. // with valid PTEs as this could cause an access trap fault which
  2839. // would not be handled (it is only handled for PTEs not PDEs).
  2840. //
  2841. // If the PTE is a page table page with non-zero share count or
  2842. // within the system cache with its reference count greater
  2843. // than 1, don't remove it.
  2844. //
  2845. if (WsInfo == &MmSystemCacheWs) {
  2846. if (Pfn1->u3.e2.ReferenceCount > 1) {
  2847. WsleFlushList->FlushIndex[i] = 0;
  2848. continue;
  2849. }
  2850. }
  2851. else {
  2852. if ((Pfn1->u2.ShareCount > 1) && (Pfn1->u3.e1.PrototypePte == 0)) {
  2853. #if DBG
  2854. if (WsInfo->Flags.SessionSpace == 1) {
  2855. ASSERT (MI_IS_SESSION_ADDRESS (Wsle[WorkingSetIndex].u1.VirtualAddress));
  2856. }
  2857. else {
  2858. ASSERT32 ((Wsle[WorkingSetIndex].u1.VirtualAddress >= (PVOID)PTE_BASE) &&
  2859. (Wsle[WorkingSetIndex].u1.VirtualAddress<= (PVOID)PTE_TOP));
  2860. }
  2861. #endif
  2862. //
  2863. // Don't remove page table pages from the working set until
  2864. // all transition pages have exited.
  2865. //
  2866. WsleFlushList->FlushIndex[i] = 0;
  2867. continue;
  2868. }
  2869. }
  2870. //
  2871. // Found a candidate, remove the page from the working set.
  2872. //
  2873. ASSERT (MI_IS_PFN_DELETED (Pfn1) == 0);
  2874. #ifdef _X86_
  2875. #if DBG
  2876. #if !defined(NT_UP)
  2877. if (TempPte.u.Hard.Writable == 1) {
  2878. ASSERT (TempPte.u.Hard.Dirty == 1);
  2879. }
  2880. #endif //NTUP
  2881. #endif //DBG
  2882. #endif //X86
  2883. //
  2884. // This page is being removed from the working set, the dirty
  2885. // bit must be ORed into the modify bit in the PFN element.
  2886. //
  2887. MI_CAPTURE_DIRTY_BIT_TO_PFN (&TempPte, Pfn1);
  2888. MI_MAKING_VALID_PTE_INVALID (FALSE);
  2889. if (Pfn1->u3.e1.PrototypePte) {
  2890. //
  2891. // This is a prototype PTE. The PFN database does not contain
  2892. // the contents of this PTE it contains the contents of the
  2893. // prototype PTE. This PTE must be reconstructed to contain
  2894. // a pointer to the prototype PTE.
  2895. //
  2896. // The working set list entry contains information about
  2897. // how to reconstruct the PTE.
  2898. //
  2899. if (Wsle[WorkingSetIndex].u1.e1.SameProtectAsProto == 0) {
  2900. //
  2901. // The protection for the prototype PTE is in the WSLE.
  2902. //
  2903. ASSERT (Wsle[WorkingSetIndex].u1.e1.Protection != 0);
  2904. TempPte.u.Long = 0;
  2905. TempPte.u.Soft.Protection =
  2906. MI_GET_PROTECTION_FROM_WSLE (&Wsle[WorkingSetIndex]);
  2907. TempPte.u.Soft.PageFileHigh = MI_PTE_LOOKUP_NEEDED;
  2908. }
  2909. else {
  2910. //
  2911. // The protection is in the prototype PTE.
  2912. //
  2913. TempPte.u.Long = MiProtoAddressForPte (Pfn1->PteAddress);
  2914. }
  2915. TempPte.u.Proto.Prototype = 1;
  2916. //
  2917. // Decrement the share count of the containing page table
  2918. // page as the PTE for the removed page is no longer valid
  2919. // or in transition.
  2920. //
  2921. ContainingPageTablePage = MiGetPteAddress (PointerPte);
  2922. #if (_MI_PAGING_LEVELS >= 3)
  2923. ASSERT (ContainingPageTablePage->u.Hard.Valid == 1);
  2924. #else
  2925. if (ContainingPageTablePage->u.Hard.Valid == 0) {
  2926. if (!NT_SUCCESS(MiCheckPdeForPagedPool (PointerPte))) {
  2927. KeBugCheckEx (MEMORY_MANAGEMENT,
  2928. 0x61940,
  2929. (ULONG_PTR)PointerPte,
  2930. (ULONG_PTR)ContainingPageTablePage->u.Long,
  2931. (ULONG_PTR)MiGetVirtualAddressMappedByPte(PointerPte));
  2932. }
  2933. }
  2934. #endif
  2935. PageTableFrameIndex = MI_GET_PAGE_FRAME_FROM_PTE (ContainingPageTablePage);
  2936. Pfn2 = MI_PFN_ELEMENT (PageTableFrameIndex);
  2937. MiDecrementShareCountInline (Pfn2, PageTableFrameIndex);
  2938. }
  2939. else {
  2940. //
  2941. // This is a private page, make it transition.
  2942. //
  2943. // If the PTE indicates the page has been modified (this is
  2944. // different from the PFN indicating this), then ripple it
  2945. // back to the write watch bitmap now since we are still in
  2946. // the correct process context.
  2947. //
  2948. if ((MI_IS_PTE_DIRTY(TempPte)) && (Wsle == MmWsle)) {
  2949. Process = CONTAINING_RECORD (WsInfo, EPROCESS, Vm);
  2950. ASSERT (Process == PsGetCurrentProcess ());
  2951. if (Process->Flags & PS_PROCESS_FLAGS_USING_WRITE_WATCH) {
  2952. //
  2953. // This process has (or had) write watch VADs. Search now
  2954. // for a write watch region encapsulating the PTE being
  2955. // invalidated.
  2956. //
  2957. MiCaptureWriteWatchDirtyBit (Process,
  2958. Wsle[WorkingSetIndex].u1.VirtualAddress);
  2959. }
  2960. }
  2961. //
  2962. // Assert that the share count is 1 for all user mode pages.
  2963. //
  2964. ASSERT ((Pfn1->u2.ShareCount == 1) ||
  2965. (Wsle[WorkingSetIndex].u1.VirtualAddress >
  2966. (PVOID)MM_HIGHEST_USER_ADDRESS));
  2967. //
  2968. // Set the working set index to zero. This allows page table
  2969. // pages to be brought back in with the proper WSINDEX.
  2970. //
  2971. ASSERT (Pfn1->u1.WsIndex != 0);
  2972. MI_ZERO_WSINDEX (Pfn1);
  2973. MI_MAKE_VALID_PTE_TRANSITION (TempPte,
  2974. Pfn1->OriginalPte.u.Soft.Protection);
  2975. }
  2976. MI_WRITE_INVALID_PTE (PointerPte, TempPte);
  2977. if (PteFlushList.Count < MM_MAXIMUM_FLUSH_COUNT) {
  2978. PteFlushList.FlushVa[PteFlushList.Count] =
  2979. Wsle[WorkingSetIndex].u1.VirtualAddress;
  2980. PteFlushList.Count += 1;
  2981. }
  2982. //
  2983. // Flush the translation buffer and decrement the number of valid
  2984. // PTEs within the containing page table page. Note that for a
  2985. // private page, the page table page is still needed because the
  2986. // page is in transition.
  2987. //
  2988. MiDecrementShareCountInline (Pfn1, PageFrameIndex);
  2989. }
  2990. if (PteFlushList.Count != 0) {
  2991. if (Wsle == MmWsle) {
  2992. MiFlushPteList (&PteFlushList, FALSE);
  2993. }
  2994. else if (Wsle == MmSystemCacheWsle) {
  2995. //
  2996. // Must be the system cache.
  2997. //
  2998. MiFlushPteList (&PteFlushList, TRUE);
  2999. }
  3000. else {
  3001. //
  3002. // Must be a session space.
  3003. //
  3004. MiFlushPteList (&PteFlushList, TRUE);
  3005. //
  3006. // Session space has no ASN - flush the entire TB.
  3007. //
  3008. MI_FLUSH_ENTIRE_SESSION_TB (TRUE, TRUE);
  3009. }
  3010. }
  3011. UNLOCK_PFN (OldIrql);
  3012. NumberNotFlushed = 0;
  3013. //
  3014. // Remove the working set entries (the PFN lock is not needed for this).
  3015. //
  3016. for (i = 0; i < WsleFlushList->Count; i += 1) {
  3017. WorkingSetIndex = WsleFlushList->FlushIndex[i];
  3018. if (WorkingSetIndex == 0) {
  3019. NumberNotFlushed += 1;
  3020. continue;
  3021. }
  3022. ASSERT (WorkingSetIndex >= WorkingSetList->FirstDynamic);
  3023. ASSERT (Wsle[WorkingSetIndex].u1.e1.Valid == 1);
  3024. MiRemoveWsle (WorkingSetIndex, WorkingSetList);
  3025. ASSERT (WorkingSetList->FirstFree >= WorkingSetList->FirstDynamic);
  3026. ASSERT (WorkingSetIndex >= WorkingSetList->FirstDynamic);
  3027. //
  3028. // Put the entry on the free list and decrement the current size.
  3029. //
  3030. ASSERT ((WorkingSetList->FirstFree <= WorkingSetList->LastInitializedWsle) ||
  3031. (WorkingSetList->FirstFree == WSLE_NULL_INDEX));
  3032. Wsle[WorkingSetIndex].u1.Long = WorkingSetList->FirstFree << MM_FREE_WSLE_SHIFT;
  3033. WorkingSetList->FirstFree = WorkingSetIndex;
  3034. ASSERT ((WorkingSetList->FirstFree <= WorkingSetList->LastInitializedWsle) ||
  3035. (WorkingSetList->FirstFree == WSLE_NULL_INDEX));
  3036. if (WsInfo->WorkingSetSize > WsInfo->MinimumWorkingSetSize) {
  3037. InterlockedExchangeAddSizeT (&MmPagesAboveWsMinimum, -1);
  3038. }
  3039. WsInfo->WorkingSetSize -= 1;
  3040. #if defined (_MI_DEBUG_WSLE)
  3041. WorkingSetList->Quota -= 1;
  3042. ASSERT (WsInfo->WorkingSetSize == WorkingSetList->Quota);
  3043. #endif
  3044. PERFINFO_LOG_WS_REMOVAL(PERFINFO_LOG_TYPE_OUTWS_VOLUNTRIM, WsInfo);
  3045. }
  3046. return NumberNotFlushed;
  3047. }
  3048. WSLE_NUMBER
  3049. MiTrimWorkingSet (
  3050. IN WSLE_NUMBER Reduction,
  3051. IN PMMSUPPORT WsInfo,
  3052. IN ULONG TrimAge
  3053. )
  3054. /*++
  3055. Routine Description:
  3056. This function reduces the working set by the specified amount.
  3057. Arguments:
  3058. Reduction - Supplies the number of pages to remove from the working set.
  3059. WsInfo - Supplies a pointer to the working set information to trim.
  3060. TrimAge - Supplies the age value to use - ie: pages of this age or older
  3061. will be removed.
  3062. Return Value:
  3063. Returns the actual number of pages removed.
  3064. Environment:
  3065. Kernel mode, APCs disabled, working set lock. PFN lock NOT held.
  3066. --*/
  3067. {
  3068. WSLE_NUMBER TryToFree;
  3069. WSLE_NUMBER StartEntry;
  3070. WSLE_NUMBER LastEntry;
  3071. PMMWSL WorkingSetList;
  3072. PMMWSLE Wsle;
  3073. PMMPTE PointerPte;
  3074. WSLE_NUMBER NumberLeftToRemove;
  3075. WSLE_NUMBER NumberNotFlushed;
  3076. MMWSLE_FLUSH_LIST WsleFlushList;
  3077. WsleFlushList.Count = 0;
  3078. NumberLeftToRemove = Reduction;
  3079. WorkingSetList = WsInfo->VmWorkingSetList;
  3080. Wsle = WorkingSetList->Wsle;
  3081. MM_WS_LOCK_ASSERT (WsInfo);
  3082. LastEntry = WorkingSetList->LastEntry;
  3083. TryToFree = WorkingSetList->NextSlot;
  3084. if (TryToFree > LastEntry || TryToFree < WorkingSetList->FirstDynamic) {
  3085. TryToFree = WorkingSetList->FirstDynamic;
  3086. }
  3087. StartEntry = TryToFree;
  3088. TrimMore:
  3089. while (NumberLeftToRemove != 0) {
  3090. if (Wsle[TryToFree].u1.e1.Valid == 1) {
  3091. PointerPte = MiGetPteAddress (Wsle[TryToFree].u1.VirtualAddress);
  3092. if ((TrimAge == 0) ||
  3093. ((MI_GET_ACCESSED_IN_PTE (PointerPte) == 0) &&
  3094. (MI_GET_WSLE_AGE(PointerPte, &Wsle[TryToFree]) >= TrimAge))) {
  3095. PERFINFO_GET_PAGE_INFO_WITH_DECL(PointerPte);
  3096. WsleFlushList.FlushIndex[WsleFlushList.Count] = TryToFree;
  3097. WsleFlushList.Count += 1;
  3098. NumberLeftToRemove -= 1;
  3099. if (WsleFlushList.Count == MM_MAXIMUM_FLUSH_COUNT) {
  3100. NumberNotFlushed = MiFreeWsleList (WsInfo, &WsleFlushList);
  3101. WsleFlushList.Count = 0;
  3102. NumberLeftToRemove += NumberNotFlushed;
  3103. }
  3104. }
  3105. }
  3106. TryToFree += 1;
  3107. if (TryToFree > LastEntry) {
  3108. TryToFree = WorkingSetList->FirstDynamic;
  3109. }
  3110. if (TryToFree == StartEntry) {
  3111. break;
  3112. }
  3113. }
  3114. if (WsleFlushList.Count != 0) {
  3115. NumberNotFlushed = MiFreeWsleList (WsInfo, &WsleFlushList);
  3116. NumberLeftToRemove += NumberNotFlushed;
  3117. if (NumberLeftToRemove != 0) {
  3118. if (TryToFree != StartEntry) {
  3119. WsleFlushList.Count = 0;
  3120. goto TrimMore;
  3121. }
  3122. }
  3123. }
  3124. WorkingSetList->NextSlot = TryToFree;
  3125. //
  3126. // See if the working set list can be contracted.
  3127. //
  3128. // Make sure we are at least a page above the working set maximum.
  3129. //
  3130. if (WorkingSetList->FirstDynamic == WsInfo->WorkingSetSize) {
  3131. MiRemoveWorkingSetPages (WsInfo);
  3132. }
  3133. else {
  3134. if ((WsInfo->WorkingSetSize + 15 + (PAGE_SIZE / sizeof(MMWSLE))) <
  3135. WorkingSetList->LastEntry) {
  3136. if ((WsInfo->MaximumWorkingSetSize + 15 + (PAGE_SIZE / sizeof(MMWSLE))) <
  3137. WorkingSetList->LastEntry ) {
  3138. MiRemoveWorkingSetPages (WsInfo);
  3139. }
  3140. }
  3141. }
  3142. return Reduction - NumberLeftToRemove;
  3143. }
  3144. VOID
  3145. MiEliminateWorkingSetEntry (
  3146. IN WSLE_NUMBER WorkingSetIndex,
  3147. IN PMMPTE PointerPte,
  3148. IN PMMPFN Pfn,
  3149. IN PMMWSLE Wsle
  3150. )
  3151. /*++
  3152. Routine Description:
  3153. This routine removes the specified working set list entry
  3154. from the working set, flushes the TB for the page, decrements
  3155. the share count for the physical page, and, if necessary turns
  3156. the PTE into a transition PTE.
  3157. Arguments:
  3158. WorkingSetIndex - Supplies the working set index to remove.
  3159. PointerPte - Supplies a pointer to the PTE corresponding to the virtual
  3160. address in the working set.
  3161. Pfn - Supplies a pointer to the PFN element corresponding to the PTE.
  3162. Wsle - Supplies a pointer to the first working set list entry for this
  3163. working set.
  3164. Return Value:
  3165. None.
  3166. Environment:
  3167. Kernel mode, Working set lock and PFN lock held, APCs disabled.
  3168. --*/
  3169. {
  3170. PMMPTE ContainingPageTablePage;
  3171. MMPTE TempPte;
  3172. MMPTE PreviousPte;
  3173. PFN_NUMBER PageFrameIndex;
  3174. PFN_NUMBER PageTableFrameIndex;
  3175. PEPROCESS Process;
  3176. PVOID VirtualAddress;
  3177. PMMPFN Pfn2;
  3178. //
  3179. // Remove the page from the working set.
  3180. //
  3181. MM_PFN_LOCK_ASSERT ();
  3182. TempPte = *PointerPte;
  3183. ASSERT (TempPte.u.Hard.Valid == 1);
  3184. PageFrameIndex = MI_GET_PAGE_FRAME_FROM_PTE (&TempPte);
  3185. ASSERT (Pfn == MI_PFN_ELEMENT(PageFrameIndex));
  3186. ASSERT (MI_IS_PFN_DELETED (Pfn) == 0);
  3187. #ifdef _X86_
  3188. #if DBG
  3189. #if !defined(NT_UP)
  3190. if (TempPte.u.Hard.Writable == 1) {
  3191. ASSERT (TempPte.u.Hard.Dirty == 1);
  3192. }
  3193. #endif //NTUP
  3194. #endif //DBG
  3195. #endif //X86
  3196. MI_MAKING_VALID_PTE_INVALID (FALSE);
  3197. if (Pfn->u3.e1.PrototypePte) {
  3198. //
  3199. // This is a prototype PTE. The PFN database does not contain
  3200. // the contents of this PTE it contains the contents of the
  3201. // prototype PTE. This PTE must be reconstructed to contain
  3202. // a pointer to the prototype PTE.
  3203. //
  3204. // The working set list entry contains information about
  3205. // how to reconstruct the PTE.
  3206. //
  3207. if (Wsle[WorkingSetIndex].u1.e1.SameProtectAsProto == 0) {
  3208. //
  3209. // The protection for the prototype PTE is in the WSLE.
  3210. //
  3211. ASSERT (Wsle[WorkingSetIndex].u1.e1.Protection != 0);
  3212. TempPte.u.Long = 0;
  3213. TempPte.u.Soft.Protection =
  3214. MI_GET_PROTECTION_FROM_WSLE (&Wsle[WorkingSetIndex]);
  3215. TempPte.u.Soft.PageFileHigh = MI_PTE_LOOKUP_NEEDED;
  3216. }
  3217. else {
  3218. //
  3219. // The protection is in the prototype PTE.
  3220. //
  3221. TempPte.u.Long = MiProtoAddressForPte (Pfn->PteAddress);
  3222. }
  3223. TempPte.u.Proto.Prototype = 1;
  3224. //
  3225. // Decrement the share count of the containing page table
  3226. // page as the PTE for the removed page is no longer valid
  3227. // or in transition.
  3228. //
  3229. ContainingPageTablePage = MiGetPteAddress (PointerPte);
  3230. #if (_MI_PAGING_LEVELS >= 3)
  3231. ASSERT (ContainingPageTablePage->u.Hard.Valid == 1);
  3232. #else
  3233. if (ContainingPageTablePage->u.Hard.Valid == 0) {
  3234. if (!NT_SUCCESS(MiCheckPdeForPagedPool (PointerPte))) {
  3235. KeBugCheckEx (MEMORY_MANAGEMENT,
  3236. 0x61940,
  3237. (ULONG_PTR)PointerPte,
  3238. (ULONG_PTR)ContainingPageTablePage->u.Long,
  3239. (ULONG_PTR)MiGetVirtualAddressMappedByPte(PointerPte));
  3240. }
  3241. }
  3242. #endif
  3243. PageTableFrameIndex = MI_GET_PAGE_FRAME_FROM_PTE (ContainingPageTablePage);
  3244. Pfn2 = MI_PFN_ELEMENT (PageTableFrameIndex);
  3245. MiDecrementShareCountInline (Pfn2, PageTableFrameIndex);
  3246. }
  3247. else {
  3248. //
  3249. // This is a private page, make it transition.
  3250. //
  3251. //
  3252. // Assert that the share count is 1 for all user mode pages.
  3253. //
  3254. ASSERT ((Pfn->u2.ShareCount == 1) ||
  3255. (Wsle[WorkingSetIndex].u1.VirtualAddress >
  3256. (PVOID)MM_HIGHEST_USER_ADDRESS));
  3257. //
  3258. // Set the working set index to zero. This allows page table
  3259. // pages to be brought back in with the proper WSINDEX.
  3260. //
  3261. ASSERT (Pfn->u1.WsIndex != 0);
  3262. MI_ZERO_WSINDEX (Pfn);
  3263. MI_MAKE_VALID_PTE_TRANSITION (TempPte,
  3264. Pfn->OriginalPte.u.Soft.Protection);
  3265. }
  3266. PreviousPte = *PointerPte;
  3267. ASSERT (PreviousPte.u.Hard.Valid == 1);
  3268. MI_WRITE_INVALID_PTE (PointerPte, TempPte);
  3269. //
  3270. // Flush the translation buffer.
  3271. //
  3272. if (Wsle == MmWsle) {
  3273. KeFlushSingleTb (Wsle[WorkingSetIndex].u1.VirtualAddress, FALSE);
  3274. }
  3275. else if (Wsle == MmSystemCacheWsle) {
  3276. //
  3277. // Must be the system cache.
  3278. //
  3279. KeFlushSingleTb (Wsle[WorkingSetIndex].u1.VirtualAddress, TRUE);
  3280. }
  3281. else {
  3282. //
  3283. // Must be a session space.
  3284. //
  3285. MI_FLUSH_SINGLE_SESSION_TB (Wsle[WorkingSetIndex].u1.VirtualAddress);
  3286. }
  3287. ASSERT (PreviousPte.u.Hard.Valid == 1);
  3288. //
  3289. // A page is being removed from the working set, on certain
  3290. // hardware the dirty bit should be ORed into the modify bit in
  3291. // the PFN element.
  3292. //
  3293. MI_CAPTURE_DIRTY_BIT_TO_PFN (&PreviousPte, Pfn);
  3294. //
  3295. // If the PTE indicates the page has been modified (this is different
  3296. // from the PFN indicating this), then ripple it back to the write watch
  3297. // bitmap now since we are still in the correct process context.
  3298. //
  3299. if ((Pfn->u3.e1.PrototypePte == 0) && (MI_IS_PTE_DIRTY(PreviousPte))) {
  3300. Process = PsGetCurrentProcess ();
  3301. if (Process->Flags & PS_PROCESS_FLAGS_USING_WRITE_WATCH) {
  3302. //
  3303. // This process has (or had) write watch VADs. Search now
  3304. // for a write watch region encapsulating the PTE being
  3305. // invalidated.
  3306. //
  3307. VirtualAddress = MiGetVirtualAddressMappedByPte (PointerPte);
  3308. MiCaptureWriteWatchDirtyBit (Process, VirtualAddress);
  3309. }
  3310. }
  3311. //
  3312. // Decrement the share count on the page. Note that for a
  3313. // private page, the page table page is still needed because the
  3314. // page is in transition.
  3315. //
  3316. MiDecrementShareCountInline (Pfn, PageFrameIndex);
  3317. return;
  3318. }
  3319. VOID
  3320. MiRemoveWorkingSetPages (
  3321. IN PMMSUPPORT WsInfo
  3322. )
  3323. /*++
  3324. Routine Description:
  3325. This routine compresses the WSLEs into the front of the working set
  3326. and frees the pages for unneeded working set entries.
  3327. Arguments:
  3328. WsInfo - Supplies a pointer to the working set structure to compress.
  3329. Return Value:
  3330. None.
  3331. Environment:
  3332. Kernel mode, Working set lock held, APCs disabled.
  3333. --*/
  3334. {
  3335. LOGICAL MovedOne;
  3336. PFN_NUMBER PageFrameIndex;
  3337. PMMWSLE FreeEntry;
  3338. PMMWSLE LastEntry;
  3339. PMMWSLE Wsle;
  3340. WSLE_NUMBER DynamicEntries;
  3341. WSLE_NUMBER LockedEntries;
  3342. WSLE_NUMBER FreeIndex;
  3343. WSLE_NUMBER LastIndex;
  3344. PMMPTE LastPte;
  3345. PMMPTE PointerPte;
  3346. PMMPFN Pfn1;
  3347. ULONG NewSize;
  3348. PMMWSLE_HASH Table;
  3349. MMWSLE WsleContents;
  3350. PMMWSL WorkingSetList;
  3351. WorkingSetList = WsInfo->VmWorkingSetList;
  3352. #if DBG
  3353. MiCheckNullIndex (WorkingSetList);
  3354. #endif
  3355. //
  3356. // Check to see if the WSLE hash table should be contracted.
  3357. //
  3358. if (WorkingSetList->HashTable) {
  3359. Table = WorkingSetList->HashTable;
  3360. #if DBG
  3361. if ((PVOID)(&Table[WorkingSetList->HashTableSize]) < WorkingSetList->HighestPermittedHashAddress) {
  3362. ASSERT (MiIsAddressValid (&Table[WorkingSetList->HashTableSize], FALSE) == FALSE);
  3363. }
  3364. #endif
  3365. if (WsInfo->WorkingSetSize < 200) {
  3366. NewSize = 0;
  3367. }
  3368. else {
  3369. NewSize = PtrToUlong(PAGE_ALIGN ((WorkingSetList->NonDirectCount * 2 *
  3370. sizeof(MMWSLE_HASH)) + PAGE_SIZE - 1));
  3371. NewSize = NewSize / sizeof(MMWSLE_HASH);
  3372. }
  3373. if (NewSize < WorkingSetList->HashTableSize) {
  3374. if (NewSize != 0) {
  3375. WsInfo->Flags.GrowWsleHash = 1;
  3376. }
  3377. //
  3378. // Remove pages from hash table.
  3379. //
  3380. ASSERT (((ULONG_PTR)&WorkingSetList->HashTable[NewSize] &
  3381. (PAGE_SIZE - 1)) == 0);
  3382. PointerPte = MiGetPteAddress (&WorkingSetList->HashTable[NewSize]);
  3383. LastPte = MiGetPteAddress (WorkingSetList->HighestPermittedHashAddress);
  3384. //
  3385. // Set the hash table to null indicating that no hashing
  3386. // is going on.
  3387. //
  3388. WorkingSetList->HashTable = NULL;
  3389. WorkingSetList->HashTableSize = NewSize;
  3390. MiDeletePteRange (WsInfo, PointerPte, LastPte, FALSE);
  3391. }
  3392. #if (_MI_PAGING_LEVELS >= 4)
  3393. //
  3394. // For NT64, the page tables and page directories are also
  3395. // deleted during contraction.
  3396. //
  3397. ASSERT ((MiGetPxeAddress(&Table[WorkingSetList->HashTableSize])->u.Hard.Valid == 0) ||
  3398. (MiGetPpeAddress(&Table[WorkingSetList->HashTableSize])->u.Hard.Valid == 0) ||
  3399. (MiGetPdeAddress(&Table[WorkingSetList->HashTableSize])->u.Hard.Valid == 0) ||
  3400. (MiGetPteAddress(&Table[WorkingSetList->HashTableSize])->u.Hard.Valid == 0));
  3401. #elif (_MI_PAGING_LEVELS >= 3)
  3402. //
  3403. // For NT64, the page tables and page directories are also
  3404. // deleted during contraction.
  3405. //
  3406. ASSERT ((MiGetPpeAddress(&Table[WorkingSetList->HashTableSize])->u.Hard.Valid == 0) ||
  3407. (MiGetPdeAddress(&Table[WorkingSetList->HashTableSize])->u.Hard.Valid == 0) ||
  3408. (MiGetPteAddress(&Table[WorkingSetList->HashTableSize])->u.Hard.Valid == 0));
  3409. #else
  3410. ASSERT ((&Table[WorkingSetList->HashTableSize] == WorkingSetList->HighestPermittedHashAddress) || (MiGetPteAddress(&Table[WorkingSetList->HashTableSize])->u.Hard.Valid == 0));
  3411. #endif
  3412. }
  3413. //
  3414. // Compress all the valid working set entries to the front of the list.
  3415. //
  3416. Wsle = WorkingSetList->Wsle;
  3417. LockedEntries = WorkingSetList->FirstDynamic;
  3418. if (WsInfo == &MmSystemCacheWs) {
  3419. //
  3420. // The first entry of the system cache working set list is never used
  3421. // because WSL index 0 in a PFN is treated specially by the fault
  3422. // processing code (and trimming) to mean that the entry should be
  3423. // inserted into the WSL by the current thread.
  3424. //
  3425. // This is not an issue for process or session working sets because
  3426. // for them, entry 0 is the top level page directory which must already
  3427. // be resident in order for the process to even get to run (ie: so
  3428. // the process cannot fault on it).
  3429. //
  3430. ASSERT (WorkingSetList->FirstDynamic != 0);
  3431. LockedEntries -= 1;
  3432. }
  3433. ASSERT (WsInfo->WorkingSetSize >= LockedEntries);
  3434. MovedOne = FALSE;
  3435. DynamicEntries = WsInfo->WorkingSetSize - LockedEntries;
  3436. if (DynamicEntries == 0) {
  3437. //
  3438. // If the only pages in the working set are locked pages (that
  3439. // is all pages are BEFORE first dynamic, just reorganize the
  3440. // free list).
  3441. //
  3442. LastIndex = WorkingSetList->FirstDynamic;
  3443. LastEntry = &Wsle[LastIndex];
  3444. }
  3445. else {
  3446. //
  3447. // Start from the first dynamic and move towards the end looking
  3448. // for free entries. At the same time start from the end and
  3449. // move towards first dynamic looking for valid entries.
  3450. //
  3451. FreeIndex = WorkingSetList->FirstDynamic;
  3452. FreeEntry = &Wsle[FreeIndex];
  3453. LastIndex = WorkingSetList->LastEntry;
  3454. LastEntry = &Wsle[LastIndex];
  3455. while (FreeEntry < LastEntry) {
  3456. if (FreeEntry->u1.e1.Valid == 1) {
  3457. FreeEntry += 1;
  3458. FreeIndex += 1;
  3459. DynamicEntries -= 1;
  3460. }
  3461. else if (LastEntry->u1.e1.Valid == 0) {
  3462. LastEntry -= 1;
  3463. LastIndex -= 1;
  3464. }
  3465. else {
  3466. //
  3467. // Move the WSLE at LastEntry to the free slot at FreeEntry.
  3468. //
  3469. MovedOne = TRUE;
  3470. WsleContents = *LastEntry;
  3471. PointerPte = MiGetPteAddress (LastEntry->u1.VirtualAddress);
  3472. ASSERT (PointerPte->u.Hard.Valid == 1);
  3473. PageFrameIndex = MI_GET_PAGE_FRAME_FROM_PTE (PointerPte);
  3474. #if defined (_MI_DEBUG_WSLE)
  3475. // Set these so the traces make more sense and no false dup hits...
  3476. LastEntry->u1.Long = 0xb1b1b100;
  3477. #endif
  3478. MI_LOG_WSLE_CHANGE (WorkingSetList, FreeIndex, WsleContents);
  3479. *FreeEntry = WsleContents;
  3480. if (WsleContents.u1.e1.Direct) {
  3481. Pfn1 = MI_PFN_ELEMENT (PageFrameIndex);
  3482. Pfn1->u1.WsIndex = FreeIndex;
  3483. #if defined (_MI_DEBUG_WSLE)
  3484. WsleContents.u1.Long = 0xb1b1b100;
  3485. MI_LOG_WSLE_CHANGE (WorkingSetList, LastIndex, WsleContents);
  3486. #endif
  3487. }
  3488. else {
  3489. //
  3490. // This last working set entry is not direct.
  3491. // Remove it from there and re-insert it in the hash at the
  3492. // lowest free slot.
  3493. //
  3494. MiRemoveWsle (LastIndex, WorkingSetList);
  3495. WorkingSetList->NonDirectCount += 1;
  3496. MiInsertWsleHash (FreeIndex, WsInfo);
  3497. }
  3498. MI_SET_PTE_IN_WORKING_SET (PointerPte, FreeIndex);
  3499. LastEntry->u1.Long = 0;
  3500. LastEntry -= 1;
  3501. LastIndex -= 1;
  3502. FreeEntry += 1;
  3503. FreeIndex += 1;
  3504. DynamicEntries -= 1;
  3505. }
  3506. if (DynamicEntries == 0) {
  3507. //
  3508. // The last dynamic entry has been processed, no need to look
  3509. // at any more - the rest are all invalid.
  3510. //
  3511. LastEntry = FreeEntry;
  3512. LastIndex = FreeIndex;
  3513. break;
  3514. }
  3515. }
  3516. }
  3517. //
  3518. // Reorganize the free list. Make last entry the first free.
  3519. //
  3520. ASSERT (((LastEntry - 1)->u1.e1.Valid == 1) ||
  3521. (WsInfo->WorkingSetSize == 0) ||
  3522. ((WsInfo == &MmSystemCacheWs) && (LastEntry - 1 == WorkingSetList->Wsle)));
  3523. if (LastEntry->u1.e1.Valid == 1) {
  3524. LastEntry += 1;
  3525. LastIndex += 1;
  3526. }
  3527. ASSERT (((LastEntry - 1)->u1.e1.Valid == 1) || (WsInfo->WorkingSetSize == 0));
  3528. ASSERT ((MiIsAddressValid (LastEntry, FALSE) == FALSE) || (LastEntry->u1.e1.Valid == 0));
  3529. //
  3530. // If the working set valid & free entries are already compressed optimally
  3531. // (or fit into a single page) then bail.
  3532. //
  3533. if ((MovedOne == FALSE) &&
  3534. ((WorkingSetList->LastInitializedWsle + 1 == (PAGE_SIZE - BYTE_OFFSET (MmWsle)) / sizeof (MMWSLE)) ||
  3535. ((WorkingSetList->FirstFree == LastIndex) &&
  3536. ((WorkingSetList->LastEntry == LastIndex - 1) ||
  3537. (WorkingSetList->LastEntry == WorkingSetList->FirstDynamic))))) {
  3538. #if DBG
  3539. while (LastIndex < WorkingSetList->LastInitializedWsle) {
  3540. ASSERT (LastEntry->u1.e1.Valid == 0);
  3541. LastIndex += 1;
  3542. LastEntry += 1;
  3543. }
  3544. #endif
  3545. return;
  3546. }
  3547. WorkingSetList->LastEntry = LastIndex - 1;
  3548. if (WorkingSetList->FirstFree != WSLE_NULL_INDEX) {
  3549. WorkingSetList->FirstFree = LastIndex;
  3550. }
  3551. //
  3552. // Point free entry to the first invalid page.
  3553. //
  3554. FreeEntry = LastEntry;
  3555. while (LastIndex < WorkingSetList->LastInitializedWsle) {
  3556. //
  3557. // Put the remainder of the WSLEs on the free list.
  3558. //
  3559. ASSERT (LastEntry->u1.e1.Valid == 0);
  3560. LastIndex += 1;
  3561. LastEntry->u1.Long = LastIndex << MM_FREE_WSLE_SHIFT;
  3562. LastEntry += 1;
  3563. }
  3564. //
  3565. // Calculate the start and end of the working set pages at the end
  3566. // that we will delete shortly. Don't delete them until after
  3567. // LastInitializedWsle is reduced so that debug WSL validation code
  3568. // in MiReleaseWsle (called from MiDeletePte) will see a
  3569. // consistent snapshot.
  3570. //
  3571. LastPte = MiGetPteAddress (&Wsle[WorkingSetList->LastInitializedWsle]) + 1;
  3572. PointerPte = MiGetPteAddress (FreeEntry) + 1;
  3573. //
  3574. // Mark the last working set entry in the list as free. Note if the list
  3575. // has no free entries, the marker is in FirstFree (and cannot be put into
  3576. // the list anyway because there is no space).
  3577. //
  3578. if (WorkingSetList->FirstFree == WSLE_NULL_INDEX) {
  3579. FreeEntry -= 1;
  3580. PointerPte -= 1;
  3581. }
  3582. else {
  3583. ASSERT (WorkingSetList->FirstFree >= WorkingSetList->FirstDynamic);
  3584. LastEntry = (PMMWSLE)((PCHAR)(PAGE_ALIGN(FreeEntry)) + PAGE_SIZE);
  3585. LastEntry -= 1;
  3586. ASSERT (LastEntry->u1.e1.Valid == 0);
  3587. //
  3588. // Insert the end of list delimiter.
  3589. //
  3590. LastEntry->u1.Long = WSLE_NULL_INDEX << MM_FREE_WSLE_SHIFT;
  3591. ASSERT (LastEntry > &Wsle[0]);
  3592. ASSERT (WsInfo->WorkingSetSize <= (WSLE_NUMBER)(LastEntry - &Wsle[0] + 1));
  3593. WorkingSetList->LastInitializedWsle = (WSLE_NUMBER)(LastEntry - &Wsle[0]);
  3594. }
  3595. WorkingSetList->NextSlot = WorkingSetList->FirstDynamic;
  3596. ASSERT (WorkingSetList->LastEntry <= WorkingSetList->LastInitializedWsle);
  3597. ASSERT ((MiGetPteAddress(&Wsle[WorkingSetList->LastInitializedWsle]))->u.Hard.Valid == 1);
  3598. ASSERT ((WorkingSetList->FirstFree <= WorkingSetList->LastInitializedWsle) ||
  3599. (WorkingSetList->FirstFree == WSLE_NULL_INDEX));
  3600. #if DBG
  3601. MiCheckNullIndex (WorkingSetList);
  3602. #endif
  3603. //
  3604. // Delete the working set pages at the end.
  3605. //
  3606. ASSERT (WorkingSetList->FirstFree >= WorkingSetList->FirstDynamic);
  3607. MiDeletePteRange (WsInfo, PointerPte, LastPte, FALSE);
  3608. ASSERT (WorkingSetList->FirstFree >= WorkingSetList->FirstDynamic);
  3609. ASSERT (WorkingSetList->LastEntry <= WorkingSetList->LastInitializedWsle);
  3610. ASSERT ((MiGetPteAddress(&Wsle[WorkingSetList->LastInitializedWsle]))->u.Hard.Valid == 1);
  3611. ASSERT ((WorkingSetList->FirstFree <= WorkingSetList->LastInitializedWsle) ||
  3612. (WorkingSetList->FirstFree == WSLE_NULL_INDEX));
  3613. #if DBG
  3614. MiCheckNullIndex (WorkingSetList);
  3615. #endif
  3616. return;
  3617. }
  3618. NTSTATUS
  3619. MiEmptyWorkingSet (
  3620. IN PMMSUPPORT WsInfo,
  3621. IN LOGICAL NeedLock
  3622. )
  3623. /*++
  3624. Routine Description:
  3625. This routine frees all pages from the working set.
  3626. Arguments:
  3627. WsInfo - Supplies the working set information entry to trim.
  3628. NeedLock - Supplies TRUE if the caller needs us to acquire mutex
  3629. synchronization for the working set. Supplies FALSE if the
  3630. caller has already acquired synchronization.
  3631. Return Value:
  3632. Status of operation.
  3633. Environment:
  3634. Kernel mode. No locks. For session operations, the caller is responsible
  3635. for attaching into the proper session.
  3636. --*/
  3637. {
  3638. PEPROCESS Process;
  3639. PMMPTE PointerPte;
  3640. WSLE_NUMBER Entry;
  3641. WSLE_NUMBER FirstDynamic;
  3642. PMMWSL WorkingSetList;
  3643. PMMWSLE Wsle;
  3644. PMMPFN Pfn1;
  3645. PFN_NUMBER PageFrameIndex;
  3646. MMWSLE_FLUSH_LIST WsleFlushList;
  3647. WsleFlushList.Count = 0;
  3648. WorkingSetList = WsInfo->VmWorkingSetList;
  3649. Wsle = WorkingSetList->Wsle;
  3650. if (NeedLock == TRUE) {
  3651. LOCK_WORKING_SET (WsInfo);
  3652. }
  3653. else {
  3654. MM_WS_LOCK_ASSERT (WsInfo);
  3655. }
  3656. if (WsInfo->VmWorkingSetList == MmWorkingSetList) {
  3657. Process = PsGetCurrentProcess ();
  3658. if (Process->Flags & PS_PROCESS_FLAGS_VM_DELETED) {
  3659. if (NeedLock == TRUE) {
  3660. UNLOCK_WORKING_SET (WsInfo);
  3661. }
  3662. return STATUS_PROCESS_IS_TERMINATING;
  3663. }
  3664. }
  3665. //
  3666. // Attempt to remove the pages starting at the top to keep the free list
  3667. // compressed as entries are added to the freelist in FILO order.
  3668. //
  3669. FirstDynamic = WorkingSetList->FirstDynamic;
  3670. for (Entry = WorkingSetList->LastEntry; Entry >= FirstDynamic; Entry -= 1) {
  3671. if (Wsle[Entry].u1.e1.Valid != 0) {
  3672. PERFINFO_PAGE_INFO_DECL();
  3673. PERFINFO_GET_PAGE_INFO (PointerPte);
  3674. if (MiTrimRemovalPagesOnly == TRUE) {
  3675. PointerPte = MiGetPteAddress (Wsle[Entry].u1.VirtualAddress);
  3676. ASSERT (PointerPte->u.Hard.Valid == 1);
  3677. PageFrameIndex = MI_GET_PAGE_FRAME_FROM_PTE (PointerPte);
  3678. Pfn1 = MI_PFN_ELEMENT (PageFrameIndex);
  3679. if (Pfn1->u3.e1.RemovalRequested == 0) {
  3680. Pfn1 = MI_PFN_ELEMENT (Pfn1->u4.PteFrame);
  3681. if (Pfn1->u3.e1.RemovalRequested == 0) {
  3682. #if (_MI_PAGING_LEVELS >= 3)
  3683. Pfn1 = MI_PFN_ELEMENT (Pfn1->u4.PteFrame);
  3684. if (Pfn1->u3.e1.RemovalRequested == 0) {
  3685. continue;
  3686. }
  3687. #else
  3688. continue;
  3689. #endif
  3690. }
  3691. }
  3692. }
  3693. WsleFlushList.FlushIndex[WsleFlushList.Count] = Entry;
  3694. WsleFlushList.Count += 1;
  3695. if (WsleFlushList.Count == MM_MAXIMUM_FLUSH_COUNT) {
  3696. MiFreeWsleList (WsInfo, &WsleFlushList);
  3697. WsleFlushList.Count = 0;
  3698. }
  3699. }
  3700. }
  3701. if (WsleFlushList.Count != 0) {
  3702. MiFreeWsleList (WsInfo, &WsleFlushList);
  3703. }
  3704. MiRemoveWorkingSetPages (WsInfo);
  3705. if (NeedLock == TRUE) {
  3706. UNLOCK_WORKING_SET (WsInfo);
  3707. }
  3708. return STATUS_SUCCESS;
  3709. }
  3710. #if DBG
  3711. VOID
  3712. MiCheckNullIndex (
  3713. IN PMMWSL WorkingSetList
  3714. )
  3715. {
  3716. PMMWSLE Wsle;
  3717. ULONG j;
  3718. ULONG Nulls = 0;
  3719. Wsle = WorkingSetList->Wsle;
  3720. for (j = 0;j <= WorkingSetList->LastInitializedWsle; j += 1) {
  3721. if ((((Wsle[j].u1.Long)) >> MM_FREE_WSLE_SHIFT) == WSLE_NULL_INDEX) {
  3722. Nulls += 1;
  3723. }
  3724. }
  3725. ASSERT ((Nulls == 1) || (WorkingSetList->FirstFree == WSLE_NULL_INDEX));
  3726. return;
  3727. }
  3728. #endif