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);
79 LIST_HEAD(Window, unused);
83 #define WINDOWS_MIN 64
84 #define WINDOW_SIZE (8ULL*1024ULL*1024ULL)
86 MMapCache* mmap_cache_new(void) {
89 m = new0(MMapCache, 1);
97 MMapCache* mmap_cache_ref(MMapCache *m) {
105 static void window_unlink(Window *w) {
111 munmap(w->ptr, w->size);
114 LIST_REMOVE(Window, by_fd, w->fd->windows, w);
117 if (w->cache->last_unused == w)
118 w->cache->last_unused = w->unused_prev;
120 LIST_REMOVE(Window, unused, w->cache->unused, w);
123 LIST_FOREACH(by_window, c, w->contexts) {
124 assert(c->window == w);
129 static void window_free(Window *w) {
133 w->cache->n_windows--;
137 static bool window_matches(Window *w, int fd, int prot, uint64_t offset, size_t size) {
146 offset >= w->offset &&
147 offset + size <= w->offset + w->size;
150 static Window *window_add(MMapCache *m) {
155 if (!m->last_unused || m->n_windows <= WINDOWS_MIN) {
157 /* Allocate a new window */
164 /* Reuse an existing one */
174 static void context_detach_window(Context *c) {
184 LIST_REMOVE(Context, by_window, w->contexts, c);
186 if (!w->contexts && !w->keep_always) {
187 /* Not used anymore? */
188 LIST_PREPEND(Window, unused, c->cache->unused, w);
189 if (!c->cache->last_unused)
190 c->cache->last_unused = w;
196 static void context_attach_window(Context *c, Window *w) {
203 context_detach_window(c);
207 LIST_REMOVE(Window, unused, c->cache->unused, w);
208 if (c->cache->last_unused == w)
209 c->cache->last_unused = w->unused_prev;
211 w->in_unused = false;
215 LIST_PREPEND(Context, by_window, w->contexts, c);
218 static Context *context_add(MMapCache *m, unsigned id) {
224 c = hashmap_get(m->contexts, UINT_TO_PTR(id + 1));
228 r = hashmap_ensure_allocated(&m->contexts, trivial_hash_func, trivial_compare_func);
232 c = new0(Context, 1);
239 r = hashmap_put(m->contexts, UINT_TO_PTR(id + 1), c);
248 static void context_free(Context *c) {
251 context_detach_window(c);
254 assert_se(hashmap_remove(c->cache->contexts, UINT_TO_PTR(c->id + 1)));
259 static void fd_free(FileDescriptor *f) {
263 window_free(f->windows);
266 assert_se(hashmap_remove(f->cache->fds, INT_TO_PTR(f->fd + 1)));
271 static FileDescriptor* fd_add(MMapCache *m, int fd) {
278 f = hashmap_get(m->fds, INT_TO_PTR(fd + 1));
282 r = hashmap_ensure_allocated(&m->fds, trivial_hash_func, trivial_compare_func);
286 f = new0(FileDescriptor, 1);
293 r = hashmap_put(m->fds, UINT_TO_PTR(fd + 1), f);
302 static void mmap_cache_free(MMapCache *m) {
308 while ((c = hashmap_first(m->contexts)))
311 while ((f = hashmap_first(m->fds)))
315 window_free(m->unused);
320 MMapCache* mmap_cache_unref(MMapCache *m) {
322 assert(m->n_ref > 0);
331 static int make_room(MMapCache *m) {
337 window_free(m->last_unused);
341 static int try_context(
354 assert(m->n_ref > 0);
359 c = hashmap_get(m->contexts, UINT_TO_PTR(context+1));
363 assert(c->id == context);
368 if (!window_matches(c->window, fd, prot, offset, size)) {
370 /* Drop the reference to the window, since it's unnecessary now */
371 context_detach_window(c);
375 c->window->keep_always = c->window->keep_always || keep_always;
377 *ret = (uint8_t*) c->window->ptr + (offset - c->window->offset);
381 static int find_mmap(
396 assert(m->n_ref > 0);
401 f = hashmap_get(m->fds, INT_TO_PTR(fd + 1));
407 LIST_FOREACH(by_fd, w, f->windows)
408 if (window_matches(w, fd, prot, offset, size))
414 c = context_add(m, context);
418 context_attach_window(c, w);
419 w->keep_always = w->keep_always || keep_always;
421 *ret = (uint8_t*) w->ptr + (offset - w->offset);
436 uint64_t woffset, wsize;
444 assert(m->n_ref > 0);
449 woffset = offset & ~((uint64_t) page_size() - 1ULL);
450 wsize = size + (offset - woffset);
451 wsize = PAGE_ALIGN(wsize);
453 if (wsize < WINDOW_SIZE) {
456 delta = PAGE_ALIGN((WINDOW_SIZE - wsize) / 2);
467 /* Memory maps that are larger then the files
468 underneath have undefined behavior. Hence, clamp
469 things to the file size if we know it */
471 if (woffset >= (uint64_t) st->st_size)
472 return -EADDRNOTAVAIL;
474 if (woffset + wsize > (uint64_t) st->st_size)
475 wsize = PAGE_ALIGN(st->st_size - woffset);
479 d = mmap(NULL, wsize, prot, MAP_SHARED, fd, woffset);
492 c = context_add(m, context);
504 w->keep_always = keep_always;
511 LIST_PREPEND(Window, by_fd, f->windows, w);
513 context_detach_window(c);
515 LIST_PREPEND(Context, by_window, w->contexts, c);
517 *ret = (uint8_t*) w->ptr + (offset - w->offset);
535 assert(m->n_ref > 0);
540 /* Check whether the current context is the right one already */
541 r = try_context(m, fd, prot, context, keep_always, offset, size, ret);
545 /* Search for a matching mmap */
546 r = find_mmap(m, fd, prot, context, keep_always, offset, size, ret);
550 /* Create a new mmap */
551 return add_mmap(m, fd, prot, context, keep_always, offset, size, st, ret);
554 void mmap_cache_close_fd(MMapCache *m, int fd) {
560 f = hashmap_get(m->fds, INT_TO_PTR(fd + 1));
567 void mmap_cache_close_context(MMapCache *m, unsigned context) {
572 c = hashmap_get(m->contexts, UINT_TO_PTR(context + 1));