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.

629 lines
15 KiB

  1. /*++
  2. Copyright (c) 1989 Microsoft Corporation
  3. Module Name:
  4. zeropage.c
  5. Abstract:
  6. This module contains the zero page thread for memory management.
  7. Author:
  8. Lou Perazzoli (loup) 6-Apr-1991
  9. Landy Wang (landyw) 02-June-1997
  10. Revision History:
  11. --*/
  12. #include "mi.h"
  13. #define MM_ZERO_PAGE_OBJECT 0
  14. #define PO_SYS_IDLE_OBJECT 1
  15. #define NUMBER_WAIT_OBJECTS 2
  16. #define MACHINE_ZERO_PAGE(ZeroBase,NumberOfBytes) KeZeroPagesFromIdleThread(ZeroBase,NumberOfBytes)
  17. LOGICAL MiZeroingDisabled = FALSE;
  18. #if !defined(NT_UP)
  19. LONG MiNextZeroProcessor = (LONG)-1;
  20. #ifdef ALLOC_PRAGMA
  21. #pragma alloc_text(INIT,MiStartZeroPageWorkers)
  22. #endif
  23. #endif
  24. VOID
  25. MmZeroPageThread (
  26. VOID
  27. )
  28. /*++
  29. Routine Description:
  30. Implements the NT zeroing page thread. This thread runs
  31. at priority zero and removes a page from the free list,
  32. zeroes it, and places it on the zeroed page list.
  33. Arguments:
  34. None.
  35. Return Value:
  36. None.
  37. Environment:
  38. Kernel mode.
  39. --*/
  40. {
  41. KIRQL OldIrql;
  42. PFN_NUMBER PageFrame1;
  43. PFN_NUMBER PageFrame;
  44. PMMPFN Pfn1;
  45. PKTHREAD Thread;
  46. PVOID ZeroBase;
  47. PVOID WaitObjects[NUMBER_WAIT_OBJECTS];
  48. NTSTATUS Status;
  49. PVOID StartVa;
  50. PVOID EndVa;
  51. PFN_COUNT PagesToZero;
  52. PFN_COUNT MaximumPagesToZero;
  53. ULONG Color;
  54. ULONG StartColor;
  55. PMMPFN PfnAllocation;
  56. #if defined(MI_MULTINODE)
  57. ULONG i;
  58. ULONG n;
  59. ULONG LastNodeZeroing;
  60. n = 0;
  61. LastNodeZeroing = 0;
  62. #endif
  63. //
  64. // Before this becomes the zero page thread, free the kernel
  65. // initialization code.
  66. //
  67. MiFindInitializationCode (&StartVa, &EndVa);
  68. if (StartVa != NULL) {
  69. MiFreeInitializationCode (StartVa, EndVa);
  70. }
  71. MaximumPagesToZero = 1;
  72. #if !defined(NT_UP)
  73. //
  74. // Zero groups of pages at once to reduce PFN lock contention.
  75. // Charge commitment as well as resident available up front since
  76. // zeroing may get starved priority-wise.
  77. //
  78. // Note using MmSecondaryColors here would be excessively wasteful
  79. // on NUMA systems. MmSecondaryColorMask + 1 is correct for all platforms.
  80. //
  81. PagesToZero = MmSecondaryColorMask + 1;
  82. if (PagesToZero > NUMBER_OF_ZEROING_PTES) {
  83. PagesToZero = NUMBER_OF_ZEROING_PTES;
  84. }
  85. if (MiChargeCommitment (PagesToZero, NULL) == TRUE) {
  86. LOCK_PFN (OldIrql);
  87. //
  88. // Check to make sure the physical pages are available.
  89. //
  90. if (MI_NONPAGABLE_MEMORY_AVAILABLE() > (SPFN_NUMBER)(PagesToZero)) {
  91. MI_DECREMENT_RESIDENT_AVAILABLE (PagesToZero,
  92. MM_RESAVAIL_ALLOCATE_ZERO_PAGE_CLUSTERS);
  93. MaximumPagesToZero = PagesToZero;
  94. }
  95. UNLOCK_PFN (OldIrql);
  96. }
  97. #endif
  98. //
  99. // The following code sets the current thread's base priority to zero
  100. // and then sets its current priority to zero. This ensures that the
  101. // thread always runs at a priority of zero.
  102. //
  103. Thread = KeGetCurrentThread ();
  104. Thread->BasePriority = 0;
  105. KeSetPriorityThread (Thread, 0);
  106. //
  107. // Initialize wait object array for multiple wait
  108. //
  109. WaitObjects[MM_ZERO_PAGE_OBJECT] = &MmZeroingPageEvent;
  110. WaitObjects[PO_SYS_IDLE_OBJECT] = &PoSystemIdleTimer;
  111. Color = 0;
  112. PfnAllocation = (PMMPFN) MM_EMPTY_LIST;
  113. //
  114. // Loop forever zeroing pages.
  115. //
  116. do {
  117. //
  118. // Wait until there are at least MmZeroPageMinimum pages
  119. // on the free list.
  120. //
  121. Status = KeWaitForMultipleObjects (NUMBER_WAIT_OBJECTS,
  122. WaitObjects,
  123. WaitAny,
  124. WrFreePage,
  125. KernelMode,
  126. FALSE,
  127. NULL,
  128. NULL);
  129. if (Status == PO_SYS_IDLE_OBJECT) {
  130. PoSystemIdleWorker (TRUE);
  131. continue;
  132. }
  133. PagesToZero = 0;
  134. LOCK_PFN (OldIrql);
  135. do {
  136. if (MmFreePageListHead.Total == 0) {
  137. //
  138. // No pages on the free list at this time, wait for
  139. // some more.
  140. //
  141. MmZeroingPageThreadActive = FALSE;
  142. UNLOCK_PFN (OldIrql);
  143. break;
  144. }
  145. if (MiZeroingDisabled == TRUE) {
  146. MmZeroingPageThreadActive = FALSE;
  147. UNLOCK_PFN (OldIrql);
  148. KeDelayExecutionThread (KernelMode,
  149. FALSE,
  150. (PLARGE_INTEGER)&MmHalfSecond);
  151. break;
  152. }
  153. #if defined(MI_MULTINODE)
  154. //
  155. // In a multinode system, zero pages by node. Resume on
  156. // the last node examined, find a node with free pages that
  157. // need to be zeroed.
  158. //
  159. if (KeNumberNodes > 1) {
  160. n = LastNodeZeroing;
  161. for (i = 0; i < KeNumberNodes; i += 1) {
  162. if (KeNodeBlock[n]->FreeCount[FreePageList] != 0) {
  163. break;
  164. }
  165. n = (n + 1) % KeNumberNodes;
  166. }
  167. ASSERT (i != KeNumberNodes);
  168. ASSERT (KeNodeBlock[n]->FreeCount[FreePageList] != 0);
  169. if (n != LastNodeZeroing) {
  170. Color = KeNodeBlock[n]->MmShiftedColor;
  171. }
  172. }
  173. #endif
  174. ASSERT (PagesToZero == 0);
  175. StartColor = Color;
  176. do {
  177. PageFrame = MmFreePagesByColor[FreePageList][Color].Flink;
  178. if (PageFrame != MM_EMPTY_LIST) {
  179. PageFrame1 = MiRemoveAnyPage (Color);
  180. ASSERT (PageFrame == PageFrame1);
  181. Pfn1 = MI_PFN_ELEMENT (PageFrame);
  182. Pfn1->u1.Flink = (PFN_NUMBER) PfnAllocation;
  183. //
  184. // Temporarily mark the page as bad so that contiguous
  185. // memory allocators won't steal it when we release
  186. // the PFN lock below. This also prevents the
  187. // MiIdentifyPfn code from trying to identify it as
  188. // we haven't filled in all the fields yet.
  189. //
  190. Pfn1->u3.e1.PageLocation = BadPageList;
  191. PfnAllocation = Pfn1;
  192. PagesToZero += 1;
  193. }
  194. //
  195. // March to the next color - this will be used to finish
  196. // filling the current chunk or to start the next one.
  197. //
  198. Color = (Color & ~MmSecondaryColorMask) |
  199. ((Color + 1) & MmSecondaryColorMask);
  200. if (PagesToZero == MaximumPagesToZero) {
  201. break;
  202. }
  203. if (Color == StartColor) {
  204. break;
  205. }
  206. } while (TRUE);
  207. ASSERT (PfnAllocation != (PMMPFN) MM_EMPTY_LIST);
  208. UNLOCK_PFN (OldIrql);
  209. ZeroBase = MiMapPagesToZeroInHyperSpace (PfnAllocation, PagesToZero);
  210. #if defined(MI_MULTINODE)
  211. //
  212. // If a node switch is in order, do it now that the PFN
  213. // lock has been released.
  214. //
  215. if ((KeNumberNodes > 1) && (n != LastNodeZeroing)) {
  216. LastNodeZeroing = n;
  217. KeFindFirstSetLeftAffinity (KeNodeBlock[n]->ProcessorMask, &i);
  218. KeSetIdealProcessorThread (Thread, (UCHAR)i);
  219. }
  220. #endif
  221. MACHINE_ZERO_PAGE (ZeroBase, PagesToZero << PAGE_SHIFT);
  222. #if 0
  223. ASSERT (RtlCompareMemoryUlong (ZeroBase, PagesToZero << PAGE_SHIFT, 0) == PagesToZero << PAGE_SHIFT);
  224. #endif
  225. MiUnmapPagesInZeroSpace (ZeroBase, PagesToZero);
  226. PagesToZero = 0;
  227. Pfn1 = PfnAllocation;
  228. LOCK_PFN (OldIrql);
  229. do {
  230. PageFrame = MI_PFN_ELEMENT_TO_INDEX (Pfn1);
  231. Pfn1 = (PMMPFN) Pfn1->u1.Flink;
  232. MiInsertPageInList (&MmZeroedPageListHead, PageFrame);
  233. } while (Pfn1 != (PMMPFN) MM_EMPTY_LIST);
  234. //
  235. // We just finished processing a cluster of pages - briefly
  236. // release the PFN lock to allow other threads to make progress.
  237. //
  238. UNLOCK_PFN (OldIrql);
  239. PfnAllocation = (PMMPFN) MM_EMPTY_LIST;
  240. LOCK_PFN (OldIrql);
  241. } while (TRUE);
  242. } while (TRUE);
  243. }
  244. #if !defined(NT_UP)
  245. VOID
  246. MiZeroPageWorker (
  247. IN PVOID Context
  248. )
  249. /*++
  250. Routine Description:
  251. This routine is the worker routine executed by all processors so that
  252. initial page zeroing occurs in parallel.
  253. Arguments:
  254. Context - Supplies a pointer to the workitem.
  255. Return Value:
  256. None.
  257. Environment:
  258. Kernel mode initialization time, PASSIVE_LEVEL. Because this is INIT
  259. only code, don't bother charging commit for the pages.
  260. --*/
  261. {
  262. MMPTE TempPte;
  263. PMMPTE PointerPte;
  264. KAFFINITY Affinity;
  265. KIRQL OldIrql;
  266. PVOID ZeroBase;
  267. PKTHREAD Thread;
  268. CCHAR OldProcessor;
  269. SCHAR OldBasePriority;
  270. KPRIORITY OldPriority;
  271. PWORK_QUEUE_ITEM WorkItem;
  272. PMMPFN Pfn1;
  273. PFN_NUMBER NewPage;
  274. PFN_NUMBER PageFrame;
  275. #if defined(MI_MULTINODE)
  276. PKNODE Node;
  277. ULONG Color;
  278. ULONG FinalColor;
  279. #endif
  280. WorkItem = (PWORK_QUEUE_ITEM) Context;
  281. ExFreePool (WorkItem);
  282. TempPte = ValidKernelPte;
  283. //
  284. // The following code sets the current thread's base and current priorities
  285. // to one so all other code (except the zero page thread) can preempt it.
  286. //
  287. Thread = KeGetCurrentThread ();
  288. OldBasePriority = Thread->BasePriority;
  289. Thread->BasePriority = 1;
  290. OldPriority = KeSetPriorityThread (Thread, 1);
  291. //
  292. // Dispatch each worker thread to the next processor in line.
  293. //
  294. OldProcessor = (CCHAR) InterlockedIncrement (&MiNextZeroProcessor);
  295. Affinity = AFFINITY_MASK (OldProcessor);
  296. Affinity = KeSetAffinityThread (Thread, Affinity);
  297. //
  298. // Zero all local pages.
  299. //
  300. #if defined(MI_MULTINODE)
  301. if (KeNumberNodes > 1) {
  302. Node = KeGetCurrentNode ();
  303. Color = Node->MmShiftedColor;
  304. FinalColor = Color + MmSecondaryColorMask + 1;
  305. }
  306. else {
  307. SATISFY_OVERZEALOUS_COMPILER (Node = NULL);
  308. SATISFY_OVERZEALOUS_COMPILER (Color = 0);
  309. SATISFY_OVERZEALOUS_COMPILER (FinalColor = 0);
  310. }
  311. #endif
  312. LOCK_PFN (OldIrql);
  313. do {
  314. #if defined(MI_MULTINODE)
  315. //
  316. // In a multinode system, zero pages by node.
  317. //
  318. if (KeNumberNodes > 1) {
  319. if (Node->FreeCount[FreePageList] == 0) {
  320. //
  321. // No pages on the free list at this time, bail.
  322. //
  323. UNLOCK_PFN (OldIrql);
  324. break;
  325. }
  326. //
  327. // Must start with a color MiRemoveAnyPage will
  328. // satisfy from the free list otherwise it will
  329. // return an already zeroed page.
  330. //
  331. while (MmFreePagesByColor[FreePageList][Color].Flink == MM_EMPTY_LIST) {
  332. //
  333. // No pages on this free list color, march to the next one.
  334. //
  335. Color += 1;
  336. if (Color == FinalColor) {
  337. UNLOCK_PFN (OldIrql);
  338. goto ZeroingFinished;
  339. }
  340. }
  341. PageFrame = MiRemoveAnyPage (Color);
  342. }
  343. else {
  344. #endif
  345. if (MmFreePageListHead.Total == 0) {
  346. //
  347. // No pages on the free list at this time, bail.
  348. //
  349. UNLOCK_PFN (OldIrql);
  350. break;
  351. }
  352. PageFrame = MmFreePageListHead.Flink;
  353. ASSERT (PageFrame != MM_EMPTY_LIST);
  354. Pfn1 = MI_PFN_ELEMENT(PageFrame);
  355. NewPage = MiRemoveAnyPage (MI_GET_COLOR_FROM_LIST_ENTRY(PageFrame, Pfn1));
  356. if (NewPage != PageFrame) {
  357. //
  358. // Someone has removed a page from the colored lists
  359. // chain without updating the freelist chain.
  360. //
  361. KeBugCheckEx (PFN_LIST_CORRUPT,
  362. 0x8F,
  363. NewPage,
  364. PageFrame,
  365. 0);
  366. }
  367. #if defined(MI_MULTINODE)
  368. }
  369. #endif
  370. //
  371. // Use system PTEs instead of hyperspace to zero the page so that
  372. // a spinlock (ie: interrupts blocked) is not held while zeroing.
  373. // Since system PTE acquisition is lock free and the TB lazy flushed,
  374. // this is perhaps the best path regardless.
  375. //
  376. UNLOCK_PFN (OldIrql);
  377. PointerPte = MiReserveSystemPtes (1, SystemPteSpace);
  378. if (PointerPte == NULL) {
  379. //
  380. // Put this page back on the freelist.
  381. //
  382. LOCK_PFN (OldIrql);
  383. MiInsertPageInFreeList (PageFrame);
  384. UNLOCK_PFN (OldIrql);
  385. break;
  386. }
  387. ASSERT (PointerPte->u.Hard.Valid == 0);
  388. ZeroBase = MiGetVirtualAddressMappedByPte (PointerPte);
  389. TempPte.u.Hard.PageFrameNumber = PageFrame;
  390. MI_WRITE_VALID_PTE (PointerPte, TempPte);
  391. KeZeroPages (ZeroBase, PAGE_SIZE);
  392. MiReleaseSystemPtes (PointerPte, 1, SystemPteSpace);
  393. LOCK_PFN (OldIrql);
  394. MiInsertPageInList (&MmZeroedPageListHead, PageFrame);
  395. } while (TRUE);
  396. #if defined(MI_MULTINODE)
  397. ZeroingFinished:
  398. #endif
  399. //
  400. // Restore the entry thread priority and processor affinity.
  401. //
  402. KeSetAffinityThread (Thread, Affinity);
  403. KeSetPriorityThread (Thread, OldPriority);
  404. Thread->BasePriority = OldBasePriority;
  405. }
  406. VOID
  407. MiStartZeroPageWorkers (
  408. VOID
  409. )
  410. /*++
  411. Routine Description:
  412. This routine starts the zero page worker threads.
  413. Arguments:
  414. None.
  415. Return Value:
  416. None.
  417. Environment:
  418. Kernel mode initialization phase 1, PASSIVE_LEVEL.
  419. --*/
  420. {
  421. ULONG i;
  422. PWORK_QUEUE_ITEM WorkItem;
  423. for (i = 0; i < (ULONG) KeNumberProcessors; i += 1) {
  424. WorkItem = ExAllocatePoolWithTag (NonPagedPool,
  425. sizeof (WORK_QUEUE_ITEM),
  426. 'wZmM');
  427. if (WorkItem == NULL) {
  428. break;
  429. }
  430. ExInitializeWorkItem (WorkItem, MiZeroPageWorker, (PVOID) WorkItem);
  431. ExQueueWorkItem (WorkItem, CriticalWorkQueue);
  432. }
  433. }
  434. #endif