xref: /reactos/win32ss/gdi/ntgdi/gdipool.c (revision 2ea03b5b)
1 /*
2  * PROJECT:         ReactOS win32 kernel mode subsystem
3  * LICENSE:         GPL - See COPYING in the top level directory
4  * FILE:            win32ss/gdi/ntgdi/gdipool.c
5  * PURPOSE:         Static size allocator for user mode object attributes
6  * PROGRAMMERS:     Timo Kreuzer
7  */
8 
9 /* INCLUDES ******************************************************************/
10 
11 #include <win32k.h>
12 #define NDEBUG
13 #include <debug.h>
14 
15 typedef struct _GDI_POOL_SECTION
16 {
17     LIST_ENTRY leInUseLink;
18     LIST_ENTRY leReadyLink;
19 
20     PVOID pvBaseAddress;
21 
22     ULONG ulCommitBitmap;
23     ULONG cAllocCount;
24 
25     RTL_BITMAP bitmap;
26     ULONG aulBits[1];
27 } GDI_POOL_SECTION, *PGDI_POOL_SECTION;
28 
29 typedef struct _GDI_POOL
30 {
31     ULONG ulTag;
32     ULONG cjAllocSize;
33     ULONG cjSectionSize; // 32 * cjAllocSize, rounded up to pages
34     ULONG cSlotsPerSection;
35     ULONG cEmptySections;
36     EX_PUSH_LOCK pushlock; // For pool growth
37 #if DBG_ENABLE_EVENT_LOGGING
38     SLIST_HEADER slhLog;
39 #endif
40 
41     LIST_ENTRY leInUseList;
42     LIST_ENTRY leEmptyList;
43     LIST_ENTRY leReadyList;
44 } GDI_POOL;
45 
46 #define GDI_POOL_ALLOCATION_GRANULARITY 64 * 1024
47 
48 static
49 PGDI_POOL_SECTION
GdiPoolAllocateSection(PGDI_POOL pPool)50 GdiPoolAllocateSection(PGDI_POOL pPool)
51 {
52     PGDI_POOL_SECTION pSection;
53     PVOID pvBaseAddress;
54     SIZE_T cjSize;
55     NTSTATUS status;
56 
57     /* Allocate a section object */
58     cjSize = sizeof(GDI_POOL_SECTION) + pPool->cSlotsPerSection / sizeof(ULONG);
59     pSection = EngAllocMem(0, cjSize, pPool->ulTag);
60     if (!pSection)
61     {
62         return NULL;
63     }
64 
65     /* Reserve user mode memory */
66     cjSize = GDI_POOL_ALLOCATION_GRANULARITY;
67     pvBaseAddress = NULL;
68     status = ZwAllocateVirtualMemory(NtCurrentProcess(),
69                                      &pvBaseAddress,
70                                      0,
71                                      &cjSize,
72                                      MEM_RESERVE,
73                                      PAGE_READWRITE);
74     if (!NT_SUCCESS(status))
75     {
76         EngFreeMem(pSection);
77         return NULL;
78     }
79 
80     /* Initialize the section */
81     pSection->pvBaseAddress = pvBaseAddress;
82     pSection->ulCommitBitmap = 0;
83     pSection->cAllocCount = 0;
84     RtlInitializeBitMap(&pSection->bitmap,
85                         pSection->aulBits,
86                         pPool->cSlotsPerSection);
87     RtlClearAllBits(&pSection->bitmap);
88 
89     /* Return the section */
90     return pSection;
91 }
92 
93 static
94 VOID
GdiPoolDeleteSection(PGDI_POOL pPool,PGDI_POOL_SECTION pSection)95 GdiPoolDeleteSection(PGDI_POOL pPool, PGDI_POOL_SECTION pSection)
96 {
97     NTSTATUS status;
98     SIZE_T cjSize = 0;
99 
100     /* Should not have any allocations */
101     if (pSection->cAllocCount != 0)
102     {
103         DPRINT1("There are %lu allocations left, section=%p, pool=%p\n",
104                 pSection->cAllocCount, pSection, pPool);
105         DBG_DUMP_EVENT_LIST(&pPool->slhLog);
106         ASSERT(FALSE);
107     }
108 
109     /* Release the virtual memory */
110     status = ZwFreeVirtualMemory(NtCurrentProcess(),
111                                  &pSection->pvBaseAddress,
112                                  &cjSize,
113                                  MEM_RELEASE);
114     ASSERT(NT_SUCCESS(status));
115 
116     /* Free the section object */
117     EngFreeMem(pSection);
118 }
119 
120 PVOID
121 NTAPI
GdiPoolAllocate(PGDI_POOL pPool)122 GdiPoolAllocate(
123     PGDI_POOL pPool)
124 {
125     PGDI_POOL_SECTION pSection;
126     ULONG ulIndex, cjOffset, ulPageBit;
127     PLIST_ENTRY ple;
128     PVOID pvAlloc, pvBaseAddress;
129     SIZE_T cjSize;
130     NTSTATUS status;
131 
132     /* Disable APCs and acquire the pool lock */
133     KeEnterCriticalRegion();
134     ExAcquirePushLockExclusive(&pPool->pushlock);
135 
136     /* Check if we have a ready section */
137     if (!IsListEmpty(&pPool->leReadyList))
138     {
139         /* Get a free section */
140         ple = pPool->leReadyList.Flink;
141         pSection = CONTAINING_RECORD(ple, GDI_POOL_SECTION, leReadyLink);
142         if (pSection->cAllocCount >= pPool->cSlotsPerSection)
143         {
144             DPRINT1("pSection->cAllocCount=%lu, pPool->cSlotsPerSection=%lu\n",
145                     pSection->cAllocCount, pPool->cSlotsPerSection);
146             DBG_DUMP_EVENT_LIST(&pPool->slhLog);
147             ASSERT(FALSE);
148         }
149         ASSERT(pSection->cAllocCount < pPool->cSlotsPerSection);
150     }
151     else
152     {
153         /* No, check if we have something on the empty list */
154         if (!IsListEmpty(&pPool->leEmptyList))
155         {
156             /* Yes, remove it from the empty list */
157             ple = RemoveHeadList(&pPool->leEmptyList);
158             pSection = CONTAINING_RECORD(ple, GDI_POOL_SECTION, leInUseLink);
159             pPool->cEmptySections--;
160             ASSERT(pSection->cAllocCount == 0);
161         }
162         else
163         {
164             /* No, allocate a new section */
165             pSection = GdiPoolAllocateSection(pPool);
166             if (!pSection)
167             {
168                 DPRINT1("Couldn't allocate a section\n");
169                 pvAlloc = NULL;
170                 goto done;
171             }
172         }
173 
174         /* Insert it into the in-use and ready list */
175         InsertHeadList(&pPool->leInUseList, &pSection->leInUseLink);
176         InsertHeadList(&pPool->leReadyList, &pSection->leReadyLink);
177     }
178 
179     /* Find and set a single bit */
180     ulIndex = RtlFindClearBitsAndSet(&pSection->bitmap, 1, 0);
181     ASSERT(ulIndex != MAXULONG);
182 
183     /* Calculate the allocation address */
184     cjOffset = ulIndex * pPool->cjAllocSize;
185     pvAlloc = (PVOID)((ULONG_PTR)pSection->pvBaseAddress + cjOffset);
186 
187     /* Check if memory is committed */
188     ulPageBit = 1 << (cjOffset / PAGE_SIZE);
189     ulPageBit |= 1 << ((cjOffset + pPool->cjAllocSize - 1) / PAGE_SIZE);
190     if ((pSection->ulCommitBitmap & ulPageBit) != ulPageBit)
191     {
192         /* Commit the pages */
193         pvBaseAddress = PAGE_ALIGN(pvAlloc);
194         cjSize = ADDRESS_AND_SIZE_TO_SPAN_PAGES(pvAlloc, pPool->cjAllocSize) * PAGE_SIZE;
195         status = ZwAllocateVirtualMemory(NtCurrentProcess(),
196                                          &pvBaseAddress,
197                                          0,
198                                          &cjSize,
199                                          MEM_COMMIT,
200                                          PAGE_READWRITE);
201         if (!NT_SUCCESS(status))
202         {
203             pvAlloc = NULL;
204             goto done;
205         }
206 
207         pSection->ulCommitBitmap |= ulPageBit;
208     }
209 
210     /* Increase alloc count */
211     pSection->cAllocCount++;
212     ASSERT(RtlNumberOfSetBits(&pSection->bitmap) == pSection->cAllocCount);
213     DBG_LOGEVENT(&pPool->slhLog, EVENT_ALLOCATE, pvAlloc);
214 
215     /* Check if section is now busy */
216     if (pSection->cAllocCount == pPool->cSlotsPerSection)
217     {
218         /* Remove the section from the ready list */
219         RemoveEntryList(&pSection->leReadyLink);
220     }
221 
222 done:
223     /* Release the pool lock and enable APCs */
224     ExReleasePushLockExclusive(&pPool->pushlock);
225     KeLeaveCriticalRegion();
226 
227     DPRINT("GdiPoolallocate: %p\n", pvAlloc);
228     return pvAlloc;
229 }
230 
231 VOID
232 NTAPI
GdiPoolFree(PGDI_POOL pPool,PVOID pvAlloc)233 GdiPoolFree(
234     PGDI_POOL pPool,
235     PVOID pvAlloc)
236 {
237     PLIST_ENTRY ple;
238     PGDI_POOL_SECTION pSection = NULL;
239     ULONG_PTR cjOffset;
240     ULONG ulIndex;
241     DPRINT("GdiPoolFree: %p\n", pvAlloc);
242 
243     /* Disable APCs and acquire the pool lock */
244     KeEnterCriticalRegion();
245     ExAcquirePushLockExclusive(&pPool->pushlock);
246 
247     /* Loop all used sections */
248     for (ple = pPool->leInUseList.Flink;
249          ple != &pPool->leInUseList;
250          ple = ple->Flink)
251     {
252         /* Get the pointer to the section */
253         pSection = CONTAINING_RECORD(ple, GDI_POOL_SECTION, leInUseLink);
254 
255         /* Calculate offset */
256         cjOffset = (ULONG_PTR)pvAlloc - (ULONG_PTR)pSection->pvBaseAddress;
257 
258         /* Check if the allocation is from this section */
259         if (cjOffset < pPool->cjSectionSize)
260         {
261             /* Calculate the index of the allocation */
262             ulIndex = cjOffset / pPool->cjAllocSize;
263 
264             /* Mark it as free */
265             ASSERT(RtlTestBit(&pSection->bitmap, ulIndex) == TRUE);
266             RtlClearBit(&pSection->bitmap, ulIndex);
267 
268             /* Decrease allocation count */
269             pSection->cAllocCount--;
270             ASSERT(RtlNumberOfSetBits(&pSection->bitmap) == pSection->cAllocCount);
271             DBG_LOGEVENT(&pPool->slhLog, EVENT_FREE, pvAlloc);
272 
273             /* Check if the section got valid now */
274             if (pSection->cAllocCount == pPool->cSlotsPerSection - 1)
275             {
276                 /* Insert it into the ready list */
277                 InsertTailList(&pPool->leReadyList, &pSection->leReadyLink);
278             }
279             /* Check if it got empty now */
280             else if (pSection->cAllocCount == 0)
281             {
282                 /* Remove the section from the lists */
283                 RemoveEntryList(&pSection->leInUseLink);
284                 RemoveEntryList(&pSection->leReadyLink);
285 
286                 if (pPool->cEmptySections >= 1)
287                 {
288                     /* Delete the section */
289                     GdiPoolDeleteSection(pPool, pSection);
290                 }
291                 else
292                 {
293                     /* Insert it into the empty list */
294                     InsertHeadList(&pPool->leEmptyList, &pSection->leInUseLink);
295                     pPool->cEmptySections++;
296                 }
297             }
298 
299             goto done;
300         }
301     }
302 
303     DbgPrint("failed to free. pvAlloc=%p, base=%p, size=%lx\n",
304              pvAlloc, pSection ? pSection->pvBaseAddress : NULL, pPool->cjSectionSize);
305     ASSERT(FALSE);
306     // KeBugCheck()
307 
308 done:
309     /* Release the pool lock and enable APCs */
310     ExReleasePushLockExclusive(&pPool->pushlock);
311     KeLeaveCriticalRegion();
312 }
313 
314 PGDI_POOL
315 NTAPI
GdiPoolCreate(ULONG cjAllocSize,ULONG ulTag)316 GdiPoolCreate(
317     ULONG cjAllocSize,
318     ULONG ulTag)
319 {
320     PGDI_POOL pPool;
321 
322     /* Allocate a pool object */
323     pPool = EngAllocMem(0, sizeof(GDI_POOL), 'lopG');
324     if (!pPool) return NULL;
325 
326     /* Initialize the object */
327     ExInitializePushLock(&pPool->pushlock);
328     InitializeListHead(&pPool->leInUseList);
329     InitializeListHead(&pPool->leReadyList);
330     InitializeListHead(&pPool->leEmptyList);
331     pPool->cEmptySections = 0;
332     pPool->cjAllocSize = cjAllocSize;
333     pPool->ulTag = ulTag;
334     pPool->cjSectionSize = GDI_POOL_ALLOCATION_GRANULARITY;
335     pPool->cSlotsPerSection = pPool->cjSectionSize / cjAllocSize;
336     DBG_INITLOG(&pPool->slhLog);
337 
338     return pPool;
339 }
340 
341 VOID
342 NTAPI
GdiPoolDestroy(PGDI_POOL pPool)343 GdiPoolDestroy(PGDI_POOL pPool)
344 {
345     PGDI_POOL_SECTION pSection;
346     PLIST_ENTRY ple;
347 
348     /* Loop all empty sections, removing them */
349     while (!IsListEmpty(&pPool->leEmptyList))
350     {
351         /* Delete the section */
352         ple = RemoveHeadList(&pPool->leEmptyList);
353         pSection = CONTAINING_RECORD(ple, GDI_POOL_SECTION, leInUseLink);
354         GdiPoolDeleteSection(pPool, pSection);
355     }
356 
357     /* Loop all ready sections, removing them */
358     while (!IsListEmpty(&pPool->leInUseList))
359     {
360         /* Delete the section */
361         ple = RemoveHeadList(&pPool->leInUseList);
362         pSection = CONTAINING_RECORD(ple, GDI_POOL_SECTION, leInUseLink);
363         GdiPoolDeleteSection(pPool, pSection);
364     }
365 
366     DBG_CLEANUP_EVENT_LIST(&pPool->slhLog);
367 
368     EngFreeMem(pPool);
369 }
370