2 * Copyright (C) 2008 The Android Open Source Project
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 * Linear memory allocation, tied to class loaders.
25 //#define DISABLE_LINEAR_ALLOC
27 // Use ashmem to name the LinearAlloc section
31 #include <cutils/ashmem.h>
32 #endif /* USE_ASHMEM */
37 This is intended to be a simple, fast allocator for "write-once" storage.
38 The expectation is that this will hold small allocations that don't change,
39 such as parts of classes (vtables, fields, methods, interfaces). Because
40 the lifetime of these items is tied to classes, which in turn are tied
41 to class loaders, we associate the storage with a ClassLoader object.
43 [ We don't yet support class unloading, and our ClassLoader implementation
44 is in flux, so for now we just have a single global region and the
45 "classLoader" argument is ignored. ]
47 By storing the data here, rather than on the system heap, we reduce heap
48 clutter, speed class loading, reduce the memory footprint (reduced heap
49 structure overhead), and most importantly we increase the number of pages
50 that remain shared between processes launched in "Zygote mode".
52 The 4 bytes preceding each block contain the block length. This allows us
53 to support "free" and "realloc" calls in a limited way. We don't free
54 storage once it has been allocated, but in some circumstances it could be
55 useful to erase storage to garbage values after a "free" or "realloc".
56 (Bad idea if we're trying to share pages.) We need to align to 8-byte
57 boundaries for some architectures, so we have a 50-50 chance of getting
58 this for free in a given block.
60 A NULL value for the "classLoader" argument refers to the bootstrap class
61 loader, which is never unloaded (until the VM shuts down).
63 Because the memory is not expected to be updated, we can use mprotect to
64 guard the pages on debug builds. Handy when tracking down corruption.
67 /* alignment for allocations; must be power of 2, and currently >= hdr_xtra */
70 /* default length of memory segment (worst case is probably "dexopt") */
71 #define DEFAULT_MAX_LENGTH (4*1024*1024)
73 /* leave enough space for a length word */
74 #define HEADER_EXTRA 4
76 /* overload the length word */
77 #define LENGTHFLAG_FREE 0x80000000
78 #define LENGTHFLAG_RW 0x40000000
79 #define LENGTHFLAG_MASK (~(LENGTHFLAG_FREE|LENGTHFLAG_RW))
81 /* in case limits.h doesn't have it; must be a power of 2 */
83 # define PAGESIZE 4096
88 static void checkAllFree(Object* classLoader);
92 * Someday, retrieve the linear alloc struct associated with a particular
93 * class loader. For now, always use the boostrap loader's instance.
95 static inline LinearAllocHdr* getHeader(Object* classLoader)
97 return gDvm.pBootLoaderAlloc;
101 * Convert a pointer to memory to a pointer to the block header (which is
102 * currently just a length word).
104 static inline u4* getBlockHeader(void* mem)
106 return ((u4*) mem) -1;
110 * Create a new linear allocation block.
112 LinearAllocHdr* dvmLinearAllocCreate(Object* classLoader)
114 #ifdef DISABLE_LINEAR_ALLOC
115 return (LinearAllocHdr*) 0x12345;
117 LinearAllocHdr* pHdr;
119 pHdr = (LinearAllocHdr*) malloc(sizeof(*pHdr));
123 * "curOffset" points to the location of the next pre-block header,
124 * which means we have to advance to the next BLOCK_ALIGN address and
127 * Note we leave the first page empty (see below), and start the
128 * first entry on the second page at an offset that ensures the next
129 * chunk of data will be properly aligned.
131 assert(BLOCK_ALIGN >= HEADER_EXTRA);
132 pHdr->curOffset = pHdr->firstOffset = (BLOCK_ALIGN-HEADER_EXTRA) + PAGESIZE;
133 pHdr->mapLength = DEFAULT_MAX_LENGTH;
138 fd = ashmem_create_region("dalvik-LinearAlloc", DEFAULT_MAX_LENGTH);
140 LOGE("ashmem LinearAlloc failed %s", strerror(errno));
145 pHdr->mapAddr = mmap(NULL, pHdr->mapLength, PROT_READ | PROT_WRITE,
147 if (pHdr->mapAddr == MAP_FAILED) {
148 LOGE("LinearAlloc mmap(%d) failed: %s\n", pHdr->mapLength,
157 // MAP_ANON is listed as "deprecated" on Linux,
158 // but MAP_ANONYMOUS is not defined under Mac OS X.
159 pHdr->mapAddr = mmap(NULL, pHdr->mapLength, PROT_READ | PROT_WRITE,
160 MAP_PRIVATE | MAP_ANON, -1, 0);
161 if (pHdr->mapAddr == MAP_FAILED) {
162 LOGE("LinearAlloc mmap(%d) failed: %s\n", pHdr->mapLength,
167 #endif /*USE_ASHMEM*/
169 /* region expected to begin on a page boundary */
170 assert(((int) pHdr->mapAddr & (PAGESIZE-1)) == 0);
172 /* the system should initialize newly-mapped memory to zero */
173 assert(*(u4*) (pHdr->mapAddr + pHdr->curOffset) == 0);
176 * Disable access to all except starting page. We will enable pages
177 * as we use them. This helps prevent bad pointers from working. The
178 * pages start out PROT_NONE, become read/write while we access them,
179 * then go to read-only after we finish our changes.
181 * We have to make the first page readable because we have 4 pad bytes,
182 * followed by 4 length bytes, giving an initial offset of 8. The
183 * generic code below assumes that there could have been a previous
184 * allocation that wrote into those 4 pad bytes, therefore the page
185 * must have been marked readable by the previous allocation.
187 * We insert an extra page in here to force a break in the memory map
188 * so we can see ourselves more easily in "showmap". Otherwise this
189 * stuff blends into the neighboring pages. [TODO: do we still need
190 * the extra page now that we have ashmem?]
192 if (mprotect(pHdr->mapAddr, pHdr->mapLength, PROT_NONE) != 0) {
193 LOGW("LinearAlloc init mprotect failed: %s\n", strerror(errno));
197 if (mprotect(pHdr->mapAddr + PAGESIZE, PAGESIZE,
198 ENFORCE_READ_ONLY ? PROT_READ : PROT_READ|PROT_WRITE) != 0)
200 LOGW("LinearAlloc init mprotect #2 failed: %s\n", strerror(errno));
205 if (ENFORCE_READ_ONLY) {
206 /* allocate the per-page ref count */
207 int numPages = (pHdr->mapLength+PAGESIZE-1) / PAGESIZE;
208 pHdr->writeRefCount = calloc(numPages, sizeof(short));
209 if (pHdr->writeRefCount == NULL) {
215 dvmInitMutex(&pHdr->lock);
217 LOGV("LinearAlloc: created region at %p-%p\n",
218 pHdr->mapAddr, pHdr->mapAddr + pHdr->mapLength-1);
224 * Destroy a linear allocation area.
226 * We do a trivial "has everything been freed?" check before unmapping the
227 * memory and freeing the LinearAllocHdr.
229 void dvmLinearAllocDestroy(Object* classLoader)
231 #ifdef DISABLE_LINEAR_ALLOC
234 LinearAllocHdr* pHdr = getHeader(classLoader);
238 checkAllFree(classLoader);
240 //dvmLinearAllocDump(classLoader);
242 LOGV("Unmapping linear allocator base=%p\n", pHdr->mapAddr);
243 LOGD("LinearAlloc %p used %d of %d (%d%%)\n",
244 classLoader, pHdr->curOffset, pHdr->mapLength,
245 (pHdr->curOffset * 100) / pHdr->mapLength);
247 if (munmap(pHdr->mapAddr, pHdr->mapLength) != 0) {
248 LOGW("LinearAlloc munmap(%p, %d) failed: %s\n",
249 pHdr->mapAddr, pHdr->mapLength, strerror(errno));
255 * Allocate "size" bytes of storage, associated with a particular class
258 * It's okay for size to be zero.
260 * We always leave "curOffset" pointing at the next place where we will
261 * store the header that precedes the returned storage.
263 * This aborts the VM on failure, so it's not necessary to check for a
266 void* dvmLinearAlloc(Object* classLoader, size_t size)
268 LinearAllocHdr* pHdr = getHeader(classLoader);
269 int startOffset, nextOffset;
270 int lastGoodOff, firstWriteOff, lastWriteOff;
272 #ifdef DISABLE_LINEAR_ALLOC
273 return calloc(1, size);
276 LOGVV("--- LinearAlloc(%p, %d)\n", classLoader, size);
279 * What we'd like to do is just determine the new end-of-alloc size
280 * and atomic-swap the updated value in. The trouble is that, the
281 * first time we reach a new page, we need to call mprotect() to
282 * make the page available, and we don't want to call mprotect() on
283 * every allocation. The troubled situation is:
284 * - thread A allocs across a page boundary, but gets preempted
285 * before mprotect() completes
286 * - thread B allocs within the new page, and doesn't call mprotect()
288 dvmLockMutex(&pHdr->lock);
290 startOffset = pHdr->curOffset;
291 assert(((startOffset + HEADER_EXTRA) & (BLOCK_ALIGN-1)) == 0);
294 * Compute the new offset. The old offset points at the address where
295 * we will store the hidden block header, so we advance past that,
296 * add the size of data they want, add another header's worth so we
297 * know we have room for that, and round up to BLOCK_ALIGN. That's
298 * the next location where we'll put user data. We then subtract the
299 * chunk header size off so we're back to the header pointer.
302 * old=12 size=3 new=((12+(4*2)+3+7) & ~7)-4 = 24-4 --> 20
303 * old=12 size=5 new=((12+(4*2)+5+7) & ~7)-4 = 32-4 --> 28
305 nextOffset = ((startOffset + HEADER_EXTRA*2 + size + (BLOCK_ALIGN-1))
306 & ~(BLOCK_ALIGN-1)) - HEADER_EXTRA;
307 LOGVV("--- old=%d size=%d new=%d\n", startOffset, size, nextOffset);
309 if (nextOffset > pHdr->mapLength) {
311 * We don't have to abort here. We could fall back on the system
312 * malloc(), and have our "free" call figure out what to do. Only
313 * works if the users of these functions actually free everything
316 LOGE("LinearAlloc exceeded capacity, last=%d\n", (int) size);
321 * Round up "size" to encompass the entire region, including the 0-7
322 * pad bytes before the next chunk header. This way we get maximum
323 * utility out of "realloc", and when we're doing ENFORCE_READ_ONLY
324 * stuff we always treat the full extent.
326 size = nextOffset - (startOffset + HEADER_EXTRA);
327 LOGVV("--- (size now %d)\n", size);
330 * See if we are starting on or have crossed into a new page. If so,
331 * call mprotect on the page(s) we're about to write to. We have to
332 * page-align the start address, but don't have to make the length a
333 * PAGESIZE multiple (but we do it anyway).
335 * Note that "startOffset" is not the last *allocated* byte, but rather
336 * the offset of the first *unallocated* byte (which we are about to
337 * write the chunk header to). "nextOffset" is similar.
339 * If ENFORCE_READ_ONLY is enabled, we have to call mprotect even if
340 * we've written to this page before, because it might be read-only.
342 lastGoodOff = (startOffset-1) & ~(PAGESIZE-1);
343 firstWriteOff = startOffset & ~(PAGESIZE-1);
344 lastWriteOff = (nextOffset-1) & ~(PAGESIZE-1);
345 LOGVV("--- lastGood=0x%04x firstWrite=0x%04x lastWrite=0x%04x\n",
346 lastGoodOff, firstWriteOff, lastWriteOff);
347 if (lastGoodOff != lastWriteOff || ENFORCE_READ_ONLY) {
350 start = firstWriteOff;
351 assert(start <= nextOffset);
352 len = (lastWriteOff - firstWriteOff) + PAGESIZE;
354 LOGVV("--- calling mprotect(start=%d len=%d RW)\n", start, len);
355 cc = mprotect(pHdr->mapAddr + start, len, PROT_READ | PROT_WRITE);
357 LOGE("LinearAlloc mprotect (+%d %d) failed: %s\n",
358 start, len, strerror(errno));
359 /* we're going to fail soon, might as do it now */
364 /* update the ref counts on the now-writable pages */
365 if (ENFORCE_READ_ONLY) {
368 start = firstWriteOff / PAGESIZE;
369 end = lastWriteOff / PAGESIZE;
371 LOGVV("--- marking pages %d-%d RW (alloc %d at %p)\n",
372 start, end, size, pHdr->mapAddr + startOffset + HEADER_EXTRA);
373 for (i = start; i <= end; i++)
374 pHdr->writeRefCount[i]++;
377 /* stow the size in the header */
378 if (ENFORCE_READ_ONLY)
379 *(u4*)(pHdr->mapAddr + startOffset) = size | LENGTHFLAG_RW;
381 *(u4*)(pHdr->mapAddr + startOffset) = size;
384 * Update data structure.
386 pHdr->curOffset = nextOffset;
388 dvmUnlockMutex(&pHdr->lock);
389 return pHdr->mapAddr + startOffset + HEADER_EXTRA;
393 * Helper function, replaces strdup().
395 char* dvmLinearStrdup(Object* classLoader, const char* str)
397 #ifdef DISABLE_LINEAR_ALLOC
400 int len = strlen(str);
401 void* mem = dvmLinearAlloc(classLoader, len+1);
402 memcpy(mem, str, len+1);
403 if (ENFORCE_READ_ONLY)
404 dvmLinearSetReadOnly(classLoader, mem);
409 * "Reallocate" a piece of memory.
411 * If the new size is <= the old size, we return the original pointer
412 * without doing anything.
414 * If the new size is > the old size, we allocate new storage, copy the
415 * old stuff over, and mark the new stuff as free.
417 void* dvmLinearRealloc(Object* classLoader, void* mem, size_t newSize)
419 #ifdef DISABLE_LINEAR_ALLOC
420 return realloc(mem, newSize);
422 LinearAllocHdr* pHdr = getHeader(classLoader);
424 /* make sure we have the right region (and mem != NULL) */
426 assert(mem >= (void*) pHdr->mapAddr &&
427 mem < (void*) (pHdr->mapAddr + pHdr->curOffset));
429 const u4* pLen = getBlockHeader(mem);
430 LOGV("--- LinearRealloc(%d) old=%d\n", newSize, *pLen);
432 /* handle size reduction case */
433 if (*pLen >= newSize) {
434 if (ENFORCE_READ_ONLY)
435 dvmLinearSetReadWrite(classLoader, mem);
441 newMem = dvmLinearAlloc(classLoader, newSize);
442 assert(newMem != NULL);
443 memcpy(newMem, mem, *pLen);
444 dvmLinearFree(classLoader, mem);
451 * Update the read/write status of one or more pages.
453 static void updatePages(Object* classLoader, void* mem, int direction)
455 LinearAllocHdr* pHdr = getHeader(classLoader);
456 dvmLockMutex(&pHdr->lock);
458 /* make sure we have the right region */
459 assert(mem >= (void*) pHdr->mapAddr &&
460 mem < (void*) (pHdr->mapAddr + pHdr->curOffset));
462 u4* pLen = getBlockHeader(mem);
463 u4 len = *pLen & LENGTHFLAG_MASK;
464 int firstPage, lastPage;
466 firstPage = ((u1*)pLen - (u1*)pHdr->mapAddr) / PAGESIZE;
467 lastPage = ((u1*)mem - (u1*)pHdr->mapAddr + (len-1)) / PAGESIZE;
468 LOGVV("--- updating pages %d-%d (%d)\n", firstPage, lastPage, direction);
473 * Update individual pages. We could do some sort of "lazy update" to
474 * combine mprotect calls, but that's almost certainly more trouble
477 for (i = firstPage; i <= lastPage; i++) {
480 * Trying to mark read-only.
482 if (i == firstPage) {
483 if ((*pLen & LENGTHFLAG_RW) == 0) {
484 LOGW("Double RO on %p\n", mem);
487 *pLen &= ~LENGTHFLAG_RW;
490 if (pHdr->writeRefCount[i] == 0) {
491 LOGE("Can't make page %d any less writable\n", i);
494 pHdr->writeRefCount[i]--;
495 if (pHdr->writeRefCount[i] == 0) {
496 LOGVV("--- prot page %d RO\n", i);
497 cc = mprotect(pHdr->mapAddr + PAGESIZE * i, PAGESIZE, PROT_READ);
502 * Trying to mark writable.
504 if (pHdr->writeRefCount[i] >= 32767) {
505 LOGE("Can't make page %d any more writable\n", i);
508 if (pHdr->writeRefCount[i] == 0) {
509 LOGVV("--- prot page %d RW\n", i);
510 cc = mprotect(pHdr->mapAddr + PAGESIZE * i, PAGESIZE,
511 PROT_READ | PROT_WRITE);
514 pHdr->writeRefCount[i]++;
516 if (i == firstPage) {
517 if ((*pLen & LENGTHFLAG_RW) != 0) {
518 LOGW("Double RW on %p\n", mem);
521 *pLen |= LENGTHFLAG_RW;
526 dvmUnlockMutex(&pHdr->lock);
530 * Try to mark the pages in which a chunk of memory lives as read-only.
531 * Whether or not the pages actually change state depends on how many
532 * others are trying to access the same pages.
534 * Only call here if ENFORCE_READ_ONLY is true.
536 void dvmLinearSetReadOnly(Object* classLoader, void* mem)
538 #ifdef DISABLE_LINEAR_ALLOC
541 updatePages(classLoader, mem, -1);
545 * Make the pages on which "mem" sits read-write.
547 * This covers the header as well as the data itself. (We could add a
548 * "header-only" mode for dvmLinearFree.)
550 * Only call here if ENFORCE_READ_ONLY is true.
552 void dvmLinearSetReadWrite(Object* classLoader, void* mem)
554 #ifdef DISABLE_LINEAR_ALLOC
557 updatePages(classLoader, mem, 1);
561 * Mark an allocation as free.
563 void dvmLinearFree(Object* classLoader, void* mem)
565 #ifdef DISABLE_LINEAR_ALLOC
572 LinearAllocHdr* pHdr = getHeader(classLoader);
574 /* make sure we have the right region */
575 assert(mem >= (void*) pHdr->mapAddr &&
576 mem < (void*) (pHdr->mapAddr + pHdr->curOffset));
578 if (ENFORCE_READ_ONLY)
579 dvmLinearSetReadWrite(classLoader, mem);
581 u4* pLen = getBlockHeader(mem);
582 *pLen |= LENGTHFLAG_FREE;
584 if (ENFORCE_READ_ONLY)
585 dvmLinearSetReadOnly(classLoader, mem);
589 * For debugging, dump the contents of a linear alloc area.
591 * We grab the lock so that the header contents and list output are
594 void dvmLinearAllocDump(Object* classLoader)
596 #ifdef DISABLE_LINEAR_ALLOC
599 LinearAllocHdr* pHdr = getHeader(classLoader);
601 dvmLockMutex(&pHdr->lock);
603 LOGI("LinearAlloc classLoader=%p\n", classLoader);
604 LOGI(" mapAddr=%p mapLength=%d firstOffset=%d\n",
605 pHdr->mapAddr, pHdr->mapLength, pHdr->firstOffset);
606 LOGI(" curOffset=%d\n", pHdr->curOffset);
608 int off = pHdr->firstOffset;
611 while (off < pHdr->curOffset) {
612 rawLen = *(u4*) (pHdr->mapAddr + off);
613 fullLen = ((HEADER_EXTRA*2 + (rawLen & LENGTHFLAG_MASK))
616 LOGI(" %p (%3d): %clen=%d%s\n", pHdr->mapAddr + off + HEADER_EXTRA,
617 (int) ((off + HEADER_EXTRA) / PAGESIZE),
618 (rawLen & LENGTHFLAG_FREE) != 0 ? '*' : ' ',
619 rawLen & LENGTHFLAG_MASK,
620 (rawLen & LENGTHFLAG_RW) != 0 ? " [RW]" : "");
625 if (ENFORCE_READ_ONLY) {
626 LOGI("writeRefCount map:\n");
628 int numPages = (pHdr->mapLength+PAGESIZE-1) / PAGESIZE;
632 for (i = 0; i < numPages; i++) {
633 int count = pHdr->writeRefCount[i];
637 printf(" %d-%d: zero\n", zstart, i-1);
638 else if (zstart == i-1)
639 printf(" %d: zero\n", zstart);
641 printf(" %d: %d\n", i, count);
645 printf(" %d-%d: zero\n", zstart, i-1);
648 LOGD("LinearAlloc %p using %d of %d (%d%%)\n",
649 classLoader, pHdr->curOffset, pHdr->mapLength,
650 (pHdr->curOffset * 100) / pHdr->mapLength);
652 dvmUnlockMutex(&pHdr->lock);
656 * Verify that all blocks are freed.
658 * This should only be done as we're shutting down, but there could be a
659 * daemon thread that's still trying to do something, so we grab the locks.
661 static void checkAllFree(Object* classLoader)
663 #ifdef DISABLE_LINEAR_ALLOC
666 LinearAllocHdr* pHdr = getHeader(classLoader);
668 dvmLockMutex(&pHdr->lock);
670 int off = pHdr->firstOffset;
673 while (off < pHdr->curOffset) {
674 rawLen = *(u4*) (pHdr->mapAddr + off);
675 fullLen = ((HEADER_EXTRA*2 + (rawLen & LENGTHFLAG_MASK))
678 if ((rawLen & LENGTHFLAG_FREE) == 0) {
679 LOGW("LinearAlloc %p not freed: %p len=%d\n", classLoader,
680 pHdr->mapAddr + off + HEADER_EXTRA, rawLen & LENGTHFLAG_MASK);
686 dvmUnlockMutex(&pHdr->lock);