diff options
author | otto <> | 2022-02-26 16:14:42 +0000 |
---|---|---|
committer | otto <> | 2022-02-26 16:14:42 +0000 |
commit | c5273921395aa49c913ac5a1325c7eb78a50292c (patch) | |
tree | eedb761db3c5aebae973ef73ef1f63a888027635 | |
parent | 241f81f05560b5b5be14de78e45d18c5a5899b06 (diff) | |
download | openbsd-c5273921395aa49c913ac5a1325c7eb78a50292c.tar.gz openbsd-c5273921395aa49c913ac5a1325c7eb78a50292c.tar.bz2 openbsd-c5273921395aa49c913ac5a1325c7eb78a50292c.zip |
Currently malloc caches a number of free'ed regions up to 128k
in size. This cache is indexed by size (in # of pages), so it is
very quick to check. Some programs allocate and deallocate larger
allocations in a frantic way. Accomodate those programs by also
keeping a cache of regions between 128k and 2M, in a cache of variable
sized regions.
Tested by many in snaps; ok deraadt@
-rw-r--r-- | src/lib/libc/stdlib/malloc.c | 193 |
1 files changed, 160 insertions, 33 deletions
diff --git a/src/lib/libc/stdlib/malloc.c b/src/lib/libc/stdlib/malloc.c index 64273b49fe..a700087534 100644 --- a/src/lib/libc/stdlib/malloc.c +++ b/src/lib/libc/stdlib/malloc.c | |||
@@ -1,4 +1,4 @@ | |||
1 | /* $OpenBSD: malloc.c,v 1.272 2021/09/19 09:15:22 tb Exp $ */ | 1 | /* $OpenBSD: malloc.c,v 1.273 2022/02/26 16:14:42 otto Exp $ */ |
2 | /* | 2 | /* |
3 | * Copyright (c) 2008, 2010, 2011, 2016 Otto Moerbeek <otto@drijf.net> | 3 | * Copyright (c) 2008, 2010, 2011, 2016 Otto Moerbeek <otto@drijf.net> |
4 | * Copyright (c) 2012 Matthew Dempsky <matthew@openbsd.org> | 4 | * Copyright (c) 2012 Matthew Dempsky <matthew@openbsd.org> |
@@ -113,13 +113,27 @@ struct region_info { | |||
113 | 113 | ||
114 | LIST_HEAD(chunk_head, chunk_info); | 114 | LIST_HEAD(chunk_head, chunk_info); |
115 | 115 | ||
116 | #define MAX_CACHEABLE_SIZE 32 | 116 | /* |
117 | struct cache { | 117 | * Two caches, one for "small" regions, one for "big". |
118 | void *pages[MALLOC_MAXCACHE]; | 118 | * Small cache is an array per size, big cache is one array with different |
119 | * sized regions | ||
120 | */ | ||
121 | #define MAX_SMALLCACHEABLE_SIZE 32 | ||
122 | #define MAX_BIGCACHEABLE_SIZE 512 | ||
123 | /* If the total # of pages is larger than this, evict before inserting */ | ||
124 | #define BIGCACHE_FILL(sz) (MAX_BIGCACHEABLE_SIZE * (sz) / 4) | ||
125 | |||
126 | struct smallcache { | ||
127 | void **pages; | ||
119 | ushort length; | 128 | ushort length; |
120 | ushort max; | 129 | ushort max; |
121 | }; | 130 | }; |
122 | 131 | ||
132 | struct bigcache { | ||
133 | void *page; | ||
134 | size_t psize; | ||
135 | }; | ||
136 | |||
123 | struct dir_info { | 137 | struct dir_info { |
124 | u_int32_t canary1; | 138 | u_int32_t canary1; |
125 | int active; /* status of malloc */ | 139 | int active; /* status of malloc */ |
@@ -139,7 +153,10 @@ struct dir_info { | |||
139 | void *delayed_chunks[MALLOC_DELAYED_CHUNK_MASK + 1]; | 153 | void *delayed_chunks[MALLOC_DELAYED_CHUNK_MASK + 1]; |
140 | u_char rbytes[32]; /* random bytes */ | 154 | u_char rbytes[32]; /* random bytes */ |
141 | /* free pages cache */ | 155 | /* free pages cache */ |
142 | struct cache cache[MAX_CACHEABLE_SIZE]; | 156 | struct smallcache smallcache[MAX_SMALLCACHEABLE_SIZE]; |
157 | size_t bigcache_used; | ||
158 | size_t bigcache_size; | ||
159 | struct bigcache *bigcache; | ||
143 | #ifdef MALLOC_STATS | 160 | #ifdef MALLOC_STATS |
144 | size_t inserts; | 161 | size_t inserts; |
145 | size_t insert_collisions; | 162 | size_t insert_collisions; |
@@ -207,7 +224,7 @@ struct malloc_readonly { | |||
207 | #ifdef MALLOC_STATS | 224 | #ifdef MALLOC_STATS |
208 | int malloc_stats; /* dump statistics at end */ | 225 | int malloc_stats; /* dump statistics at end */ |
209 | #endif | 226 | #endif |
210 | u_int32_t malloc_canary; /* Matched against ones in malloc_pool */ | 227 | u_int32_t malloc_canary; /* Matched against ones in pool */ |
211 | }; | 228 | }; |
212 | 229 | ||
213 | /* This object is mapped PROT_READ after initialisation to prevent tampering */ | 230 | /* This object is mapped PROT_READ after initialisation to prevent tampering */ |
@@ -714,18 +731,61 @@ unmap(struct dir_info *d, void *p, size_t sz, size_t clear) | |||
714 | size_t psz = sz >> MALLOC_PAGESHIFT; | 731 | size_t psz = sz >> MALLOC_PAGESHIFT; |
715 | void *r; | 732 | void *r; |
716 | u_short i; | 733 | u_short i; |
717 | struct cache *cache; | 734 | struct smallcache *cache; |
718 | 735 | ||
719 | if (sz != PAGEROUND(sz) || psz == 0) | 736 | if (sz != PAGEROUND(sz) || psz == 0) |
720 | wrterror(d, "munmap round"); | 737 | wrterror(d, "munmap round"); |
721 | 738 | ||
722 | if (psz > MAX_CACHEABLE_SIZE || d->cache[psz - 1].max == 0) { | 739 | if (d->bigcache_size > 0 && psz > MAX_SMALLCACHEABLE_SIZE && |
740 | psz <= MAX_BIGCACHEABLE_SIZE) { | ||
741 | u_short base = getrbyte(d); | ||
742 | u_short j; | ||
743 | |||
744 | /* don't look through all slots */ | ||
745 | for (j = 0; j < d->bigcache_size / 4; j++) { | ||
746 | i = (base + j) % d->bigcache_size; | ||
747 | if (d->bigcache_used < | ||
748 | BIGCACHE_FILL(d->bigcache_size)) { | ||
749 | if (d->bigcache[i].psize == 0) | ||
750 | break; | ||
751 | } else { | ||
752 | if (d->bigcache[i].psize != 0) | ||
753 | break; | ||
754 | } | ||
755 | } | ||
756 | /* if we didn't find a preferred slot, use random one */ | ||
757 | if (d->bigcache[i].psize != 0) { | ||
758 | size_t tmp; | ||
759 | |||
760 | r = d->bigcache[i].page; | ||
761 | d->bigcache_used -= d->bigcache[i].psize; | ||
762 | tmp = d->bigcache[i].psize << MALLOC_PAGESHIFT; | ||
763 | if (!mopts.malloc_freeunmap) | ||
764 | validate_junk(d, r, tmp); | ||
765 | if (munmap(r, tmp)) | ||
766 | wrterror(d, "munmap %p", r); | ||
767 | STATS_SUB(d->malloc_used, tmp); | ||
768 | } | ||
769 | |||
770 | if (clear > 0) | ||
771 | explicit_bzero(p, clear); | ||
772 | if (mopts.malloc_freeunmap) { | ||
773 | if (mprotect(p, sz, PROT_NONE)) | ||
774 | wrterror(d, "mprotect %p", r); | ||
775 | } else | ||
776 | junk_free(d->malloc_junk, p, sz); | ||
777 | d->bigcache[i].page = p; | ||
778 | d->bigcache[i].psize = psz; | ||
779 | d->bigcache_used += psz; | ||
780 | return; | ||
781 | } | ||
782 | if (psz > MAX_SMALLCACHEABLE_SIZE || d->smallcache[psz - 1].max == 0) { | ||
723 | if (munmap(p, sz)) | 783 | if (munmap(p, sz)) |
724 | wrterror(d, "munmap %p", p); | 784 | wrterror(d, "munmap %p", p); |
725 | STATS_SUB(d->malloc_used, sz); | 785 | STATS_SUB(d->malloc_used, sz); |
726 | return; | 786 | return; |
727 | } | 787 | } |
728 | cache = &d->cache[psz - 1]; | 788 | cache = &d->smallcache[psz - 1]; |
729 | if (cache->length == cache->max) { | 789 | if (cache->length == cache->max) { |
730 | /* use a random slot */ | 790 | /* use a random slot */ |
731 | i = getrbyte(d) % cache->max; | 791 | i = getrbyte(d) % cache->max; |
@@ -753,9 +813,10 @@ unmap(struct dir_info *d, void *p, size_t sz, size_t clear) | |||
753 | static void * | 813 | static void * |
754 | map(struct dir_info *d, size_t sz, int zero_fill) | 814 | map(struct dir_info *d, size_t sz, int zero_fill) |
755 | { | 815 | { |
756 | size_t i, psz = sz >> MALLOC_PAGESHIFT; | 816 | size_t psz = sz >> MALLOC_PAGESHIFT; |
817 | u_short i; | ||
757 | void *p; | 818 | void *p; |
758 | struct cache *cache; | 819 | struct smallcache *cache; |
759 | 820 | ||
760 | if (mopts.malloc_canary != (d->canary1 ^ (u_int32_t)(uintptr_t)d) || | 821 | if (mopts.malloc_canary != (d->canary1 ^ (u_int32_t)(uintptr_t)d) || |
761 | d->canary1 != ~d->canary2) | 822 | d->canary1 != ~d->canary2) |
@@ -764,8 +825,35 @@ map(struct dir_info *d, size_t sz, int zero_fill) | |||
764 | wrterror(d, "map round"); | 825 | wrterror(d, "map round"); |
765 | 826 | ||
766 | 827 | ||
767 | if (psz <= MAX_CACHEABLE_SIZE && d->cache[psz - 1].max > 0) { | 828 | if (d->bigcache_size > 0 && psz > MAX_SMALLCACHEABLE_SIZE && |
768 | cache = &d->cache[psz - 1]; | 829 | psz <= MAX_BIGCACHEABLE_SIZE) { |
830 | size_t base = getrbyte(d); | ||
831 | size_t cached = d->bigcache_used; | ||
832 | ushort j; | ||
833 | |||
834 | for (j = 0; j < d->bigcache_size && cached >= psz; j++) { | ||
835 | i = (j + base) % d->bigcache_size; | ||
836 | if (d->bigcache[i].psize == psz) { | ||
837 | p = d->bigcache[i].page; | ||
838 | d->bigcache_used -= psz; | ||
839 | d->bigcache[i].page = NULL; | ||
840 | d->bigcache[i].psize = 0; | ||
841 | |||
842 | if (!mopts.malloc_freeunmap) | ||
843 | validate_junk(d, p, sz); | ||
844 | if (mopts.malloc_freeunmap) | ||
845 | mprotect(p, sz, PROT_READ | PROT_WRITE); | ||
846 | if (zero_fill) | ||
847 | memset(p, 0, sz); | ||
848 | else if (mopts.malloc_freeunmap) | ||
849 | junk_free(d->malloc_junk, p, sz); | ||
850 | return p; | ||
851 | } | ||
852 | cached -= d->bigcache[i].psize; | ||
853 | } | ||
854 | } | ||
855 | if (psz <= MAX_SMALLCACHEABLE_SIZE && d->smallcache[psz - 1].max > 0) { | ||
856 | cache = &d->smallcache[psz - 1]; | ||
769 | if (cache->length > 0) { | 857 | if (cache->length > 0) { |
770 | if (cache->length == 1) | 858 | if (cache->length == 1) |
771 | p = cache->pages[--cache->length]; | 859 | p = cache->pages[--cache->length]; |
@@ -795,10 +883,12 @@ map(struct dir_info *d, size_t sz, int zero_fill) | |||
795 | void *q = (char*)p + i * sz; | 883 | void *q = (char*)p + i * sz; |
796 | cache->pages[i] = q; | 884 | cache->pages[i] = q; |
797 | if (!mopts.malloc_freeunmap) | 885 | if (!mopts.malloc_freeunmap) |
798 | junk_free(d->malloc_junk, q, sz); | 886 | junk_free(d->malloc_junk, q, |
887 | sz); | ||
799 | } | 888 | } |
800 | if (mopts.malloc_freeunmap) | 889 | if (mopts.malloc_freeunmap) |
801 | mprotect(p, (cache->max - 1) * sz, PROT_NONE); | 890 | mprotect(p, (cache->max - 1) * sz, |
891 | PROT_NONE); | ||
802 | p = (char*)p + (cache->max - 1) * sz; | 892 | p = (char*)p + (cache->max - 1) * sz; |
803 | /* zero fill not needed */ | 893 | /* zero fill not needed */ |
804 | return p; | 894 | return p; |
@@ -1161,8 +1251,9 @@ omalloc(struct dir_info *pool, size_t sz, int zero_fill, void *f) | |||
1161 | } else { | 1251 | } else { |
1162 | if (pool->malloc_junk == 2) { | 1252 | if (pool->malloc_junk == 2) { |
1163 | if (zero_fill) | 1253 | if (zero_fill) |
1164 | memset((char *)p + sz - mopts.malloc_guard, | 1254 | memset((char *)p + sz - |
1165 | SOME_JUNK, psz - sz); | 1255 | mopts.malloc_guard, SOME_JUNK, |
1256 | psz - sz); | ||
1166 | else | 1257 | else |
1167 | memset(p, SOME_JUNK, | 1258 | memset(p, SOME_JUNK, |
1168 | psz - mopts.malloc_guard); | 1259 | psz - mopts.malloc_guard); |
@@ -1224,13 +1315,33 @@ _malloc_init(int from_rthreads) | |||
1224 | if (i == 0) { | 1315 | if (i == 0) { |
1225 | omalloc_poolinit(&d, MAP_CONCEAL); | 1316 | omalloc_poolinit(&d, MAP_CONCEAL); |
1226 | d->malloc_junk = 2; | 1317 | d->malloc_junk = 2; |
1227 | for (j = 0; j < MAX_CACHEABLE_SIZE; j++) | 1318 | d->bigcache_size = 0; |
1228 | d->cache[j].max = 0; | 1319 | for (j = 0; j < MAX_SMALLCACHEABLE_SIZE; j++) |
1320 | d->smallcache[j].max = 0; | ||
1229 | } else { | 1321 | } else { |
1322 | size_t sz = 0; | ||
1323 | |||
1230 | omalloc_poolinit(&d, 0); | 1324 | omalloc_poolinit(&d, 0); |
1231 | d->malloc_junk = mopts.def_malloc_junk; | 1325 | d->malloc_junk = mopts.def_malloc_junk; |
1232 | for (j = 0; j < MAX_CACHEABLE_SIZE; j++) | 1326 | d->bigcache_size = mopts.def_maxcache; |
1233 | d->cache[j].max = mopts.def_maxcache >> (j / 8); | 1327 | for (j = 0; j < MAX_SMALLCACHEABLE_SIZE; j++) { |
1328 | d->smallcache[j].max = | ||
1329 | mopts.def_maxcache >> (j / 8); | ||
1330 | sz += d->smallcache[j].max * sizeof(void *); | ||
1331 | } | ||
1332 | sz += d->bigcache_size * sizeof(struct bigcache); | ||
1333 | if (sz > 0) { | ||
1334 | void *p = MMAP(sz, 0); | ||
1335 | if (p == MAP_FAILED) | ||
1336 | wrterror(NULL, | ||
1337 | "malloc init mmap failed"); | ||
1338 | for (j = 0; j < MAX_SMALLCACHEABLE_SIZE; j++) { | ||
1339 | d->smallcache[j].pages = p; | ||
1340 | p = (char *)p + d->smallcache[j].max * | ||
1341 | sizeof(void *); | ||
1342 | } | ||
1343 | d->bigcache = p; | ||
1344 | } | ||
1234 | } | 1345 | } |
1235 | d->mutex = i; | 1346 | d->mutex = i; |
1236 | mopts.malloc_pool[i] = d; | 1347 | mopts.malloc_pool[i] = d; |
@@ -1348,8 +1459,11 @@ ofree(struct dir_info **argpool, void *p, int clear, int check, size_t argsz) | |||
1348 | REALSIZE(sz, r); | 1459 | REALSIZE(sz, r); |
1349 | if (pool->mmap_flag) { | 1460 | if (pool->mmap_flag) { |
1350 | clear = 1; | 1461 | clear = 1; |
1351 | if (!check) | 1462 | if (!check) { |
1352 | argsz = sz; | 1463 | argsz = sz; |
1464 | if (sz > MALLOC_MAXCHUNK) | ||
1465 | argsz -= mopts.malloc_guard; | ||
1466 | } | ||
1353 | } | 1467 | } |
1354 | if (check) { | 1468 | if (check) { |
1355 | if (sz <= MALLOC_MAXCHUNK) { | 1469 | if (sz <= MALLOC_MAXCHUNK) { |
@@ -1609,7 +1723,8 @@ orealloc(struct dir_info **argpool, void *p, size_t newsz, void *f) | |||
1609 | wrterror(pool, "mprotect"); | 1723 | wrterror(pool, "mprotect"); |
1610 | } | 1724 | } |
1611 | if (munmap((char *)r->p + rnewsz, roldsz - rnewsz)) | 1725 | if (munmap((char *)r->p + rnewsz, roldsz - rnewsz)) |
1612 | wrterror(pool, "munmap %p", (char *)r->p + rnewsz); | 1726 | wrterror(pool, "munmap %p", (char *)r->p + |
1727 | rnewsz); | ||
1613 | STATS_SUB(pool->malloc_used, roldsz - rnewsz); | 1728 | STATS_SUB(pool->malloc_used, roldsz - rnewsz); |
1614 | r->size = gnewsz; | 1729 | r->size = gnewsz; |
1615 | if (MALLOC_MOVE_COND(gnewsz)) { | 1730 | if (MALLOC_MOVE_COND(gnewsz)) { |
@@ -2091,7 +2206,7 @@ putleakinfo(void *f, size_t sz, int cnt) | |||
2091 | { | 2206 | { |
2092 | struct leaknode key, *p; | 2207 | struct leaknode key, *p; |
2093 | static struct leaknode *page; | 2208 | static struct leaknode *page; |
2094 | static int used; | 2209 | static unsigned int used; |
2095 | 2210 | ||
2096 | if (cnt == 0 || page == MAP_FAILED) | 2211 | if (cnt == 0 || page == MAP_FAILED) |
2097 | return; | 2212 | return; |
@@ -2123,7 +2238,7 @@ static void | |||
2123 | dump_leaks(int fd) | 2238 | dump_leaks(int fd) |
2124 | { | 2239 | { |
2125 | struct leaknode *p; | 2240 | struct leaknode *p; |
2126 | int i = 0; | 2241 | unsigned int i = 0; |
2127 | 2242 | ||
2128 | dprintf(fd, "Leak report\n"); | 2243 | dprintf(fd, "Leak report\n"); |
2129 | dprintf(fd, " f sum # avg\n"); | 2244 | dprintf(fd, " f sum # avg\n"); |
@@ -2196,16 +2311,25 @@ dump_free_chunk_info(int fd, struct dir_info *d) | |||
2196 | static void | 2311 | static void |
2197 | dump_free_page_info(int fd, struct dir_info *d) | 2312 | dump_free_page_info(int fd, struct dir_info *d) |
2198 | { | 2313 | { |
2199 | struct cache *cache; | 2314 | struct smallcache *cache; |
2200 | size_t i, total = 0; | 2315 | size_t i, total = 0; |
2201 | 2316 | ||
2202 | dprintf(fd, "Cached:\n"); | 2317 | dprintf(fd, "Cached in small cache:\n"); |
2203 | for (i = 0; i < MAX_CACHEABLE_SIZE; i++) { | 2318 | for (i = 0; i < MAX_SMALLCACHEABLE_SIZE; i++) { |
2204 | cache = &d->cache[i]; | 2319 | cache = &d->smallcache[i]; |
2205 | if (cache->length != 0) | 2320 | if (cache->length != 0) |
2206 | dprintf(fd, "%zu(%u): %u = %zu\n", i + 1, cache->max, cache->length, cache->length * (i + 1)); | 2321 | dprintf(fd, "%zu(%u): %u = %zu\n", i + 1, cache->max, |
2322 | cache->length, cache->length * (i + 1)); | ||
2207 | total += cache->length * (i + 1); | 2323 | total += cache->length * (i + 1); |
2208 | } | 2324 | } |
2325 | |||
2326 | dprintf(fd, "Cached in big cache: %zu/%zu\n", d->bigcache_used, | ||
2327 | d->bigcache_size); | ||
2328 | for (i = 0; i < d->bigcache_size; i++) { | ||
2329 | if (d->bigcache[i].psize != 0) | ||
2330 | dprintf(fd, "%zu: %zu\n", i, d->bigcache[i].psize); | ||
2331 | total += d->bigcache[i].psize; | ||
2332 | } | ||
2209 | dprintf(fd, "Free pages cached: %zu\n", total); | 2333 | dprintf(fd, "Free pages cached: %zu\n", total); |
2210 | } | 2334 | } |
2211 | 2335 | ||
@@ -2232,7 +2356,8 @@ malloc_dump1(int fd, int poolno, struct dir_info *d) | |||
2232 | dump_free_chunk_info(fd, d); | 2356 | dump_free_chunk_info(fd, d); |
2233 | dump_free_page_info(fd, d); | 2357 | dump_free_page_info(fd, d); |
2234 | dprintf(fd, | 2358 | dprintf(fd, |
2235 | "slot) hash d type page f size [free/n]\n"); | 2359 | "slot) hash d type page f " |
2360 | "size [free/n]\n"); | ||
2236 | for (i = 0; i < d->regions_total; i++) { | 2361 | for (i = 0; i < d->regions_total; i++) { |
2237 | if (d->r[i].p != NULL) { | 2362 | if (d->r[i].p != NULL) { |
2238 | size_t h = hash(d->r[i].p) & | 2363 | size_t h = hash(d->r[i].p) & |
@@ -2298,13 +2423,15 @@ DEF_WEAK(malloc_gdump); | |||
2298 | static void | 2423 | static void |
2299 | malloc_exit(void) | 2424 | malloc_exit(void) |
2300 | { | 2425 | { |
2301 | int save_errno = errno, fd, i; | 2426 | int save_errno = errno, fd; |
2427 | unsigned i; | ||
2302 | 2428 | ||
2303 | fd = open("malloc.out", O_RDWR|O_APPEND); | 2429 | fd = open("malloc.out", O_RDWR|O_APPEND); |
2304 | if (fd != -1) { | 2430 | if (fd != -1) { |
2305 | dprintf(fd, "******** Start dump %s *******\n", __progname); | 2431 | dprintf(fd, "******** Start dump %s *******\n", __progname); |
2306 | dprintf(fd, | 2432 | dprintf(fd, |
2307 | "MT=%d M=%u I=%d F=%d U=%d J=%d R=%d X=%d C=%d cache=%u G=%zu\n", | 2433 | "MT=%d M=%u I=%d F=%d U=%d J=%d R=%d X=%d C=%d cache=%u " |
2434 | "G=%zu\n", | ||
2308 | mopts.malloc_mt, mopts.malloc_mutexes, | 2435 | mopts.malloc_mt, mopts.malloc_mutexes, |
2309 | mopts.internal_funcs, mopts.malloc_freecheck, | 2436 | mopts.internal_funcs, mopts.malloc_freecheck, |
2310 | mopts.malloc_freeunmap, mopts.def_malloc_junk, | 2437 | mopts.malloc_freeunmap, mopts.def_malloc_junk, |