|
1 // Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies). |
|
2 // All rights reserved. |
|
3 // This component and the accompanying materials are made available |
|
4 // under the terms of the License "Eclipse Public License v1.0" |
|
5 // which accompanies this distribution, and is available |
|
6 // at the URL "http://www.eclipse.org/legal/epl-v10.html". |
|
7 // |
|
8 // Initial Contributors: |
|
9 // Nokia Corporation - initial contribution. |
|
10 // |
|
11 // Contributors: |
|
12 // |
|
13 // Description: |
|
14 // e32test\demandpaging\t_pagetable_limit.cpp |
|
15 // Tests to expose the limit of page table virtual address space. |
|
16 // |
|
17 // |
|
18 |
|
19 //! @SYMTestCaseID KBASE-T_PAGETABLE_LIMIT |
|
20 //! @SYMTestType UT |
|
21 //! @SYMPREQ PREQ1490 |
|
22 //! @SYMTestCaseDesc Tests to expose the limit of page table virtual address space. |
|
23 //! @SYMTestActions Test that a paged page table can always be acquired. |
|
24 //! @SYMTestExpectedResults All tests should pass. |
|
25 //! @SYMTestPriority High |
|
26 //! @SYMTestStatus Implemented |
|
27 |
|
28 #define __E32TEST_EXTENSION__ |
|
29 #include <e32test.h> |
|
30 #include <dptest.h> |
|
31 #include <e32svr.h> |
|
32 #include <u32std.h> |
|
33 #include <hal.h> |
|
34 |
|
35 #include "t_dpcmn.h" |
|
36 |
|
37 RTest test(_L("T_PAGETABLE_LIMIT")); |
|
38 |
|
39 |
|
40 _LIT(KClientPtServerName, "CClientPtServer"); |
|
41 _LIT(KClientProcessName, "T_PAGETABLE_LIMIT"); |
|
42 |
|
43 enum TClientMsgType |
|
44 { |
|
45 EClientConnect = -1, |
|
46 EClientDisconnect = -2, |
|
47 EClientGetChunk = 0, |
|
48 EClientReadChunks = 1, |
|
49 }; |
|
50 |
|
51 class RDataPagingSession : public RSessionBase |
|
52 { |
|
53 public: |
|
54 TInt CreateSession(const TDesC& aServerName, TInt aMsgSlots) |
|
55 { |
|
56 return RSessionBase::CreateSession(aServerName,User::Version(),aMsgSlots); |
|
57 } |
|
58 TInt PublicSendReceive(TInt aFunction, const TIpcArgs &aPtr) |
|
59 { |
|
60 return (SendReceive(aFunction, aPtr)); |
|
61 } |
|
62 TInt PublicSend(TInt aFunction, const TIpcArgs &aPtr) |
|
63 { |
|
64 return (Send(aFunction, aPtr)); |
|
65 } |
|
66 }; |
|
67 |
|
68 |
|
69 TInt ClientProcess(TInt aLen) |
|
70 { |
|
71 // Read the command line to get the number of chunk to map and whether or |
|
72 // not to access their data. |
|
73 HBufC* buf = HBufC::New(aLen); |
|
74 test(buf != NULL); |
|
75 TPtr ptr = buf->Des(); |
|
76 User::CommandLine(ptr); |
|
77 |
|
78 TLex lex(ptr); |
|
79 TInt chunkCount; |
|
80 TInt r = lex.Val(chunkCount); |
|
81 test_KErrNone(r); |
|
82 lex.SkipSpace(); |
|
83 |
|
84 TBool accessData; |
|
85 r = lex.Val(accessData); |
|
86 test_KErrNone(r); |
|
87 |
|
88 |
|
89 RDataPagingSession session; |
|
90 test_KErrNone(session.CreateSession(KClientPtServerName, 1)); |
|
91 |
|
92 RChunk* chunks = new RChunk[chunkCount]; |
|
93 for (TInt i = 0; i < chunkCount; i++) |
|
94 { |
|
95 TInt r = chunks[i].SetReturnedHandle(session.PublicSendReceive(EClientGetChunk, TIpcArgs(i))); |
|
96 if (r != KErrNone) |
|
97 { |
|
98 test.Printf(_L("Failed to create a handle to the server's chunk r=%d\n"), r); |
|
99 for (TInt j = 0; j < i; j++) |
|
100 chunks[j].Close(); |
|
101 session.Close(); |
|
102 return r; |
|
103 } |
|
104 test_Value(chunks[i].Size(), chunks[i].Size() >= gPageSize); |
|
105 } |
|
106 if (!accessData) |
|
107 { |
|
108 // Touch the 1st page of each of the chunks. |
|
109 for (TInt i = 0; i < chunkCount; i++) |
|
110 { |
|
111 // Write the chunk data from top to bottom of the chunk's first page. |
|
112 TUint8* base = chunks[i].Base(); |
|
113 TUint8* end = base + gPageSize - 1; |
|
114 *base = *end; |
|
115 } |
|
116 // Tell parent we've touched each chunk. |
|
117 TInt r = (TThreadId)session.PublicSendReceive(EClientReadChunks,TIpcArgs()); // Assumes id is only 32-bit. |
|
118 test_KErrNone(r); |
|
119 for(;;) |
|
120 {// Wake up every 100ms to be killed by the main process. |
|
121 User::After(100000); |
|
122 } |
|
123 } |
|
124 else |
|
125 { |
|
126 for (;;) |
|
127 { |
|
128 TInt offset = 0; |
|
129 for (TInt i = 0; i < chunkCount; i++) |
|
130 { |
|
131 // Write the chunk data from top to bottom of the chunk's first page. |
|
132 TUint8* base = chunks[i].Base(); |
|
133 TUint8* end = base + gPageSize - 1; |
|
134 *(base + offset) = *(end - offset); |
|
135 } |
|
136 if (++offset >= (gPageSize >> 1)) |
|
137 offset = 0; |
|
138 } |
|
139 } |
|
140 } |
|
141 |
|
142 |
|
143 void TestMaxPt() |
|
144 { |
|
145 // Flexible memory model reserves 0xF800000-0xFFF00000 for page tables |
|
146 // this allows 130,048 pages tables. Therefore mapping 1000 one |
|
147 // page chunks into 256 processes would require 256,000 page tables, i.e. |
|
148 // more than enough to hit the limit. So that the limit is reached in the middle, |
|
149 // map 500 unpaged and 500 paged chunks in each process. |
|
150 const TUint KNumChunks = 1000; |
|
151 const TUint KPagedChunksStart = (KNumChunks >> 1); |
|
152 const TUint KNumProcesses = 256; |
|
153 const TInt KMinFreeRam = (1000 * gPageSize) + (130048 * (gPageSize>>2)); |
|
154 TInt freeRam; |
|
155 HAL::Get(HALData::EMemoryRAMFree, freeRam); |
|
156 if (freeRam < KMinFreeRam) |
|
157 { |
|
158 test.Printf(_L("Only 0x%x bytes of free RAM not enough to perform the test. Skipping test.\n"), freeRam); |
|
159 return; |
|
160 } |
|
161 |
|
162 // Remove the maximum limit on the cache size as the test requires that it can |
|
163 // allocate as many page tables as possible but without stealing any pages as |
|
164 // stealing pages may indirectly steal paged page table pages. |
|
165 TUint minCacheSize, maxCacheSize, currentCacheSize; |
|
166 DPTest::CacheSize(minCacheSize,maxCacheSize,currentCacheSize); |
|
167 test_KErrNone(DPTest::SetCacheSize(minCacheSize, KMaxTUint)); |
|
168 |
|
169 RServer2 ptServer; |
|
170 TInt r = ptServer.CreateGlobal(KClientPtServerName); |
|
171 test_KErrNone(r); |
|
172 |
|
173 // Create the global unpaged chunks. They have one page committed |
|
174 // but have a maximum size large enough to prevent their page tables being |
|
175 // shared between the chunks. On arm with 4KB pages each page table maps 1MB |
|
176 // so make chunk 1MB+4KB so chunk requires 2 page tables and is not aligned on |
|
177 // a 1MB boundary so it is a fine memory object. |
|
178 const TUint KChunkSize = (1024 * 1024) + gPageSize; |
|
179 RChunk* chunks = new RChunk[KNumChunks]; |
|
180 TChunkCreateInfo createInfo; |
|
181 createInfo.SetNormal(gPageSize, KChunkSize); |
|
182 createInfo.SetGlobal(KNullDesC); |
|
183 createInfo.SetPaging(TChunkCreateInfo::EUnpaged); |
|
184 TUint i = 0; |
|
185 for (; i < KPagedChunksStart; i++) |
|
186 { |
|
187 r = chunks[i].Create(createInfo); |
|
188 test_KErrNone(r); |
|
189 } |
|
190 // Create paged chunks. |
|
191 createInfo.SetPaging(TChunkCreateInfo::EPaged); |
|
192 for (; i< KNumChunks; i++) |
|
193 { |
|
194 r = chunks[i].Create(createInfo); |
|
195 test_KErrNone(r); |
|
196 } |
|
197 |
|
198 // Start remote processes, giving each process handles to each chunk. |
|
199 RProcess* processes = new RProcess[KNumProcesses]; |
|
200 RMessage2 ptMessage; |
|
201 TUint processIndex = 0; |
|
202 TUint processLimit = 0; |
|
203 for (; processIndex < KNumProcesses; processIndex++) |
|
204 { |
|
205 // Start the process. |
|
206 test.Printf(_L("Creating process %d\n"), processIndex); |
|
207 TBuf<80> args; |
|
208 args.AppendFormat(_L("%d %d"), KNumChunks, EFalse); |
|
209 r = processes[processIndex].Create(KClientProcessName, args); |
|
210 test_KErrNone(r); |
|
211 TRequestStatus s; |
|
212 processes[processIndex].Logon(s); |
|
213 test_Equal(KRequestPending, s.Int()); |
|
214 processes[processIndex].Resume(); |
|
215 |
|
216 ptServer.Receive(ptMessage); |
|
217 test_Equal(EClientConnect, ptMessage.Function()); |
|
218 ptMessage.Complete(KErrNone); |
|
219 TInt func = EClientGetChunk; |
|
220 TUint chunkIndex = 0; |
|
221 for (; chunkIndex < KNumChunks && func == EClientGetChunk; chunkIndex++) |
|
222 {// Pass handles to all the unpaged chunks to the new process. |
|
223 ptServer.Receive(ptMessage); |
|
224 func = ptMessage.Function(); |
|
225 if (func == EClientGetChunk) |
|
226 { |
|
227 TUint index = ptMessage.Int0(); |
|
228 ptMessage.Complete(chunks[index]); |
|
229 } |
|
230 } |
|
231 if (func != EClientGetChunk) |
|
232 { |
|
233 // Should hit the limit of page tables and this process instance should exit |
|
234 // sending a disconnect message in the process. |
|
235 test_Equal(EClientDisconnect, func); |
|
236 // Should only fail when mapping unpaged chunks. |
|
237 test_Value(chunkIndex, chunkIndex < (KNumChunks >> 1)); |
|
238 break; |
|
239 } |
|
240 // Wait for the process to access all the chunks and therefore |
|
241 // allocate the paged page tables before moving onto the next process. |
|
242 ptServer.Receive(ptMessage); |
|
243 func = ptMessage.Function(); |
|
244 test_Equal(EClientReadChunks, func); |
|
245 ptMessage.Complete(KErrNone); |
|
246 |
|
247 // Should have mapped all the required chunks. |
|
248 test_Equal(KNumChunks, chunkIndex); |
|
249 } |
|
250 // Should hit page table limit before KNumProcesses have been created. |
|
251 test_Value(processIndex, processIndex < KNumProcesses - 1); |
|
252 processLimit = processIndex; |
|
253 |
|
254 // Now create more processes to access paged data even though the page table |
|
255 // address space has been exhausted. Limit to 10 more processes as test takes |
|
256 // long enough already. |
|
257 processIndex++; |
|
258 TUint excessProcesses = KNumProcesses - processIndex; |
|
259 TUint pagedIndexEnd = (excessProcesses > 10)? processIndex + 10 : processIndex + excessProcesses; |
|
260 for (; processIndex < pagedIndexEnd; processIndex++) |
|
261 { |
|
262 // Start the process. |
|
263 test.Printf(_L("Creating process %d\n"), processIndex); |
|
264 TBuf<80> args; |
|
265 args.AppendFormat(_L("%d %d"), KNumChunks-KPagedChunksStart, ETrue); |
|
266 r = processes[processIndex].Create(KClientProcessName, args); |
|
267 if (r != KErrNone) |
|
268 {// Have hit the limit of processes. |
|
269 processIndex--; |
|
270 // Should have created at least one more process. |
|
271 test_Value(processIndex, processIndex > processLimit); |
|
272 break; |
|
273 } |
|
274 TRequestStatus s; |
|
275 processes[processIndex].Logon(s); |
|
276 test_Equal(KRequestPending, s.Int()); |
|
277 processes[processIndex].Resume(); |
|
278 |
|
279 ptServer.Receive(ptMessage); |
|
280 test_Equal(EClientConnect, ptMessage.Function()); |
|
281 ptMessage.Complete(KErrNone); |
|
282 |
|
283 TInt func = EClientGetChunk; |
|
284 TUint chunkIndex = KPagedChunksStart; |
|
285 for (; chunkIndex < KNumChunks && func == EClientGetChunk; chunkIndex++) |
|
286 {// Pass handles to all the unpaged chunks to the new process. |
|
287 ptServer.Receive(ptMessage); |
|
288 func = ptMessage.Function(); |
|
289 if (func == EClientGetChunk) |
|
290 { |
|
291 TUint index = ptMessage.Int0() + KPagedChunksStart; |
|
292 ptMessage.Complete(chunks[index]); |
|
293 } |
|
294 } |
|
295 if (func != EClientGetChunk) |
|
296 {// Reached memory limits so exit. |
|
297 test_Equal(EClientDisconnect, func); |
|
298 // Should have created at least one more process. |
|
299 test_Value(processIndex, processIndex > processLimit+1); |
|
300 break; |
|
301 } |
|
302 |
|
303 // Should have mapped all the required chunks. |
|
304 test_Equal(KNumChunks, chunkIndex); |
|
305 } |
|
306 // If we reached the end of then ensure that we kill only the running processes. |
|
307 if (processIndex == pagedIndexEnd) |
|
308 processIndex--; |
|
309 // Kill all the remote processes |
|
310 for(TInt j = processIndex; j >= 0; j--) |
|
311 { |
|
312 test.Printf(_L("killing process %d\n"), j); |
|
313 TRequestStatus req; |
|
314 processes[j].Logon(req); |
|
315 if (req == KRequestPending) |
|
316 { |
|
317 processes[j].Kill(KErrNone); |
|
318 User::WaitForRequest(req); |
|
319 } |
|
320 processes[j].Close(); |
|
321 } |
|
322 delete[] processes; |
|
323 // Close the chunks. |
|
324 for (TUint k = 0; k < KNumChunks; k++) |
|
325 chunks[k].Close(); |
|
326 delete[] chunks; |
|
327 |
|
328 test_KErrNone(DPTest::SetCacheSize(minCacheSize, maxCacheSize)); |
|
329 } |
|
330 |
|
331 |
|
332 TInt E32Main() |
|
333 { |
|
334 test_KErrNone(UserHal::PageSizeInBytes(gPageSize)); |
|
335 |
|
336 TUint len = User::CommandLineLength(); |
|
337 if (len > 0) |
|
338 { |
|
339 return ClientProcess(len); |
|
340 } |
|
341 |
|
342 test.Title(); |
|
343 test_KErrNone(GetGlobalPolicies()); |
|
344 |
|
345 if (!gDataPagingSupported) |
|
346 { |
|
347 test.Printf(_L("Data paging not enabled so skipping test...\n")); |
|
348 return KErrNone; |
|
349 } |
|
350 |
|
351 test.Start(_L("Test the system can always acquire a paged page table")); |
|
352 TestMaxPt(); |
|
353 |
|
354 test.End(); |
|
355 return KErrNone; |
|
356 } |