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, trivial_hash_func, trivial_compare_func);
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, trivial_hash_func, trivial_compare_func);
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(
360 assert(m->n_ref > 0);
364 c = hashmap_get(m->contexts, UINT_TO_PTR(context+1));
368 assert(c->id == context);
373 if (!window_matches(c->window, fd, prot, offset, size)) {
375 /* Drop the reference to the window, since it's unnecessary now */
376 context_detach_window(c);
380 c->window->keep_always += keep_always;
383 *ret = (uint8_t*) c->window->ptr + (offset - c->window->offset);
387 static int find_mmap(
402 assert(m->n_ref > 0);
406 f = hashmap_get(m->fds, INT_TO_PTR(fd + 1));
412 LIST_FOREACH(by_fd, w, f->windows)
413 if (window_matches(w, fd, prot, offset, size))
419 c = context_add(m, context);
423 context_attach_window(c, w);
424 w->keep_always += keep_always;
427 *ret = (uint8_t*) w->ptr + (offset - w->offset);
442 uint64_t woffset, wsize;
450 assert(m->n_ref > 0);
454 woffset = offset & ~((uint64_t) page_size() - 1ULL);
455 wsize = size + (offset - woffset);
456 wsize = PAGE_ALIGN(wsize);
458 if (wsize < WINDOW_SIZE) {
461 delta = PAGE_ALIGN((WINDOW_SIZE - wsize) / 2);
472 /* Memory maps that are larger then the files
473 underneath have undefined behavior. Hence, clamp
474 things to the file size if we know it */
476 if (woffset >= (uint64_t) st->st_size)
477 return -EADDRNOTAVAIL;
479 if (woffset + wsize > (uint64_t) st->st_size)
480 wsize = PAGE_ALIGN(st->st_size - woffset);
484 d = mmap(NULL, wsize, prot, MAP_SHARED, fd, woffset);
497 c = context_add(m, context);
509 w->keep_always = keep_always;
516 LIST_PREPEND(by_fd, f->windows, w);
518 context_detach_window(c);
520 LIST_PREPEND(by_window, w->contexts, c);
523 *ret = (uint8_t*) w->ptr + (offset - w->offset);
541 assert(m->n_ref > 0);
545 /* Check whether the current context is the right one already */
546 r = try_context(m, fd, prot, context, keep_always, offset, size, ret);
552 /* Search for a matching mmap */
553 r = find_mmap(m, fd, prot, context, keep_always, offset, size, ret);
561 /* Create a new mmap */
562 return add_mmap(m, fd, prot, context, keep_always, offset, size, st, ret);
565 int mmap_cache_release(
577 assert(m->n_ref > 0);
581 f = hashmap_get(m->fds, INT_TO_PTR(fd + 1));
587 LIST_FOREACH(by_fd, w, f->windows)
588 if (window_matches(w, fd, prot, offset, size))
594 if (w->keep_always == 0)
601 void mmap_cache_close_fd(MMapCache *m, int fd) {
607 f = hashmap_get(m->fds, INT_TO_PTR(fd + 1));
614 void mmap_cache_close_context(MMapCache *m, unsigned context) {
619 c = hashmap_get(m->contexts, UINT_TO_PTR(context + 1));
626 unsigned mmap_cache_get_hit(MMapCache *m) {
632 unsigned mmap_cache_get_missed(MMapCache *m) {