1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
4 This file is part of systemd.
6 Copyright 2012 Lennart Poettering
8 systemd is free software; you can redistribute it and/or modify it
9 under the terms of the GNU Lesser General Public License as published by
10 the Free Software Foundation; either version 2.1 of the License, or
11 (at your option) any later version.
13 systemd is distributed in the hope that it will be useful, but
14 WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 Lesser General Public License for more details.
18 You should have received a copy of the GNU Lesser General Public License
19 along with systemd; If not, see <http://www.gnu.org/licenses/>.
32 #include "mmap-cache.h"
34 typedef struct Window Window;
35 typedef struct Context Context;
36 typedef struct FileDescriptor FileDescriptor;
51 LIST_FIELDS(Window, by_fd);
52 LIST_FIELDS(Window, unused);
54 LIST_HEAD(Context, contexts);
62 LIST_FIELDS(Context, by_window);
65 struct FileDescriptor {
68 LIST_HEAD(Window, windows);
75 unsigned n_hit, n_missed;
81 LIST_HEAD(Window, unused);
85 #define WINDOWS_MIN 64
86 #define WINDOW_SIZE (8ULL*1024ULL*1024ULL)
88 MMapCache* mmap_cache_new(void) {
91 m = new0(MMapCache, 1);
99 MMapCache* mmap_cache_ref(MMapCache *m) {
101 assert(m->n_ref > 0);
107 static void window_unlink(Window *w) {
113 munmap(w->ptr, w->size);
116 LIST_REMOVE(by_fd, w->fd->windows, w);
119 if (w->cache->last_unused == w)
120 w->cache->last_unused = w->unused_prev;
122 LIST_REMOVE(unused, w->cache->unused, w);
125 LIST_FOREACH(by_window, c, w->contexts) {
126 assert(c->window == w);
131 static void window_free(Window *w) {
135 w->cache->n_windows--;
139 _pure_ static bool window_matches(Window *w, int fd, int prot, uint64_t offset, size_t size) {
148 offset >= w->offset &&
149 offset + size <= w->offset + w->size;
152 static Window *window_add(MMapCache *m) {
157 if (!m->last_unused || m->n_windows <= WINDOWS_MIN) {
159 /* Allocate a new window */
166 /* Reuse an existing one */
176 static void context_detach_window(Context *c) {
186 LIST_REMOVE(by_window, w->contexts, c);
188 if (!w->contexts && w->keep_always == 0) {
189 /* Not used anymore? */
190 LIST_PREPEND(unused, c->cache->unused, w);
191 if (!c->cache->last_unused)
192 c->cache->last_unused = w;
198 static void context_attach_window(Context *c, Window *w) {
205 context_detach_window(c);
209 LIST_REMOVE(unused, c->cache->unused, w);
210 if (c->cache->last_unused == w)
211 c->cache->last_unused = w->unused_prev;
213 w->in_unused = false;
217 LIST_PREPEND(by_window, w->contexts, c);
220 static Context *context_add(MMapCache *m, unsigned id) {
226 c = hashmap_get(m->contexts, UINT_TO_PTR(id + 1));
230 r = hashmap_ensure_allocated(&m->contexts, NULL);
234 c = new0(Context, 1);
241 r = hashmap_put(m->contexts, UINT_TO_PTR(id + 1), c);
250 static void context_free(Context *c) {
253 context_detach_window(c);
256 assert_se(hashmap_remove(c->cache->contexts, UINT_TO_PTR(c->id + 1)));
261 static void fd_free(FileDescriptor *f) {
265 window_free(f->windows);
268 assert_se(hashmap_remove(f->cache->fds, INT_TO_PTR(f->fd + 1)));
273 static FileDescriptor* fd_add(MMapCache *m, int fd) {
280 f = hashmap_get(m->fds, INT_TO_PTR(fd + 1));
284 r = hashmap_ensure_allocated(&m->fds, NULL);
288 f = new0(FileDescriptor, 1);
295 r = hashmap_put(m->fds, UINT_TO_PTR(fd + 1), f);
304 static void mmap_cache_free(MMapCache *m) {
310 while ((c = hashmap_first(m->contexts)))
313 hashmap_free(m->contexts);
315 while ((f = hashmap_first(m->fds)))
318 hashmap_free(m->fds);
321 window_free(m->unused);
326 MMapCache* mmap_cache_unref(MMapCache *m) {
328 assert(m->n_ref > 0);
337 static int make_room(MMapCache *m) {
343 window_free(m->last_unused);
347 static int try_context(
356 void **release_cookie) {
361 assert(m->n_ref > 0);
365 c = hashmap_get(m->contexts, UINT_TO_PTR(context+1));
369 assert(c->id == context);
374 if (!window_matches(c->window, fd, prot, offset, size)) {
376 /* Drop the reference to the window, since it's unnecessary now */
377 context_detach_window(c);
381 c->window->keep_always += keep_always;
384 *ret = (uint8_t*) c->window->ptr + (offset - c->window->offset);
385 if (keep_always && release_cookie)
386 *release_cookie = c->window;
390 static int find_mmap(
399 void **release_cookie) {
406 assert(m->n_ref > 0);
410 f = hashmap_get(m->fds, INT_TO_PTR(fd + 1));
416 LIST_FOREACH(by_fd, w, f->windows)
417 if (window_matches(w, fd, prot, offset, size))
423 c = context_add(m, context);
427 context_attach_window(c, w);
428 w->keep_always += keep_always;
431 *ret = (uint8_t*) w->ptr + (offset - w->offset);
432 if (keep_always && release_cookie)
433 *release_cookie = c->window;
447 void **release_cookie) {
449 uint64_t woffset, wsize;
457 assert(m->n_ref > 0);
461 woffset = offset & ~((uint64_t) page_size() - 1ULL);
462 wsize = size + (offset - woffset);
463 wsize = PAGE_ALIGN(wsize);
465 if (wsize < WINDOW_SIZE) {
468 delta = PAGE_ALIGN((WINDOW_SIZE - wsize) / 2);
479 /* Memory maps that are larger then the files
480 underneath have undefined behavior. Hence, clamp
481 things to the file size if we know it */
483 if (woffset >= (uint64_t) st->st_size)
484 return -EADDRNOTAVAIL;
486 if (woffset + wsize > (uint64_t) st->st_size)
487 wsize = PAGE_ALIGN(st->st_size - woffset);
491 d = mmap(NULL, wsize, prot, MAP_SHARED, fd, woffset);
504 c = context_add(m, context);
516 w->keep_always = keep_always;
523 LIST_PREPEND(by_fd, f->windows, w);
525 context_detach_window(c);
527 LIST_PREPEND(by_window, w->contexts, c);
530 *ret = (uint8_t*) w->ptr + (offset - w->offset);
531 if (keep_always && release_cookie)
532 *release_cookie = c->window;
550 void **release_cookie) {
555 assert(m->n_ref > 0);
559 /* Check whether the current context is the right one already */
560 r = try_context(m, fd, prot, context, keep_always, offset, size, ret, release_cookie);
566 /* Search for a matching mmap */
567 r = find_mmap(m, fd, prot, context, keep_always, offset, size, ret, release_cookie);
575 /* Create a new mmap */
576 return add_mmap(m, fd, prot, context, keep_always, offset, size, st, ret, release_cookie);
579 int mmap_cache_release(
582 void *release_cookie) {
588 assert(m->n_ref > 0);
591 f = hashmap_get(m->fds, INT_TO_PTR(fd + 1));
597 LIST_FOREACH(by_fd, w, f->windows)
598 if (w == release_cookie)
604 if (w->keep_always == 0)
611 void mmap_cache_close_fd(MMapCache *m, int fd) {
617 f = hashmap_get(m->fds, INT_TO_PTR(fd + 1));
624 void mmap_cache_close_context(MMapCache *m, unsigned context) {
629 c = hashmap_get(m->contexts, UINT_TO_PTR(context + 1));
636 unsigned mmap_cache_get_hit(MMapCache *m) {
642 unsigned mmap_cache_get_missed(MMapCache *m) {