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) {
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);
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 = 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);
407 f = hashmap_get(m->fds, INT_TO_PTR(fd + 1));
413 LIST_FOREACH(by_fd, w, f->windows)
414 if (window_matches(w, fd, prot, offset, size))
420 c = context_add(m, context);
424 context_attach_window(c, w);
425 w->keep_always = 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);
455 woffset = offset & ~((uint64_t) page_size() - 1ULL);
456 wsize = size + (offset - woffset);
457 wsize = PAGE_ALIGN(wsize);
459 if (wsize < WINDOW_SIZE) {
462 delta = PAGE_ALIGN((WINDOW_SIZE - wsize) / 2);
473 /* Memory maps that are larger then the files
474 underneath have undefined behavior. Hence, clamp
475 things to the file size if we know it */
477 if (woffset >= (uint64_t) st->st_size)
478 return -EADDRNOTAVAIL;
480 if (woffset + wsize > (uint64_t) st->st_size)
481 wsize = PAGE_ALIGN(st->st_size - woffset);
485 d = mmap(NULL, wsize, prot, MAP_SHARED, fd, woffset);
498 c = context_add(m, context);
510 w->keep_always = keep_always;
517 LIST_PREPEND(by_fd, f->windows, w);
519 context_detach_window(c);
521 LIST_PREPEND(by_window, w->contexts, c);
523 *ret = (uint8_t*) w->ptr + (offset - w->offset);
541 assert(m->n_ref > 0);
546 /* Check whether the current context is the right one already */
547 r = try_context(m, fd, prot, context, keep_always, offset, size, ret);
553 /* Search for a matching mmap */
554 r = find_mmap(m, fd, prot, context, keep_always, offset, size, ret);
562 /* Create a new mmap */
563 return add_mmap(m, fd, prot, context, keep_always, offset, size, st, ret);
566 void mmap_cache_close_fd(MMapCache *m, int fd) {
572 f = hashmap_get(m->fds, INT_TO_PTR(fd + 1));
579 void mmap_cache_close_context(MMapCache *m, unsigned context) {
584 c = hashmap_get(m->contexts, UINT_TO_PTR(context + 1));
591 unsigned mmap_cache_get_hit(MMapCache *m) {
597 unsigned mmap_cache_get_missed(MMapCache *m) {