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);
78
LIST_HEAD(Window, unused);
82
#define WINDOWS_MIN 64
83
#define WINDOW_SIZE (8ULL*1024ULL*1024ULL)
85
MMapCache* mmap_cache_new(void) {
88
m = new0(MMapCache, 1);
96
MMapCache* mmap_cache_ref(MMapCache *m) {
104
static void window_unlink(Window *w) {
110
munmap(w->ptr, w->size);
113
LIST_REMOVE(Window, by_fd, w->fd->windows, w);
116
if (w->cache->last_unused == w)
117
w->cache->last_unused = w->unused_prev;
119
LIST_REMOVE(Window, unused, w->cache->unused, w);
122
LIST_FOREACH(by_window, c, w->contexts) {
123
assert(c->window == w);
128
static void window_free(Window *w) {
132
w->cache->n_windows--;
136
_pure_ static bool window_matches(Window *w, int fd, int prot, uint64_t offset, size_t size) {
145
offset >= w->offset &&
146
offset + size <= w->offset + w->size;
149
static Window *window_add(MMapCache *m) {
154
if (!m->last_unused || m->n_windows <= WINDOWS_MIN) {
156
/* Allocate a new window */
163
/* Reuse an existing one */
173
static void context_detach_window(Context *c) {
183
LIST_REMOVE(Context, by_window, w->contexts, c);
185
if (!w->contexts && !w->keep_always) {
186
/* Not used anymore? */
187
LIST_PREPEND(Window, unused, c->cache->unused, w);
188
if (!c->cache->last_unused)
189
c->cache->last_unused = w;
195
static void context_attach_window(Context *c, Window *w) {
202
context_detach_window(c);
206
LIST_REMOVE(Window, unused, c->cache->unused, w);
207
if (c->cache->last_unused == w)
208
c->cache->last_unused = w->unused_prev;
210
w->in_unused = false;
214
LIST_PREPEND(Context, by_window, w->contexts, c);
217
static Context *context_add(MMapCache *m, unsigned id) {
223
c = hashmap_get(m->contexts, UINT_TO_PTR(id + 1));
227
r = hashmap_ensure_allocated(&m->contexts, trivial_hash_func, trivial_compare_func);
231
c = new0(Context, 1);
238
r = hashmap_put(m->contexts, UINT_TO_PTR(id + 1), c);
247
static void context_free(Context *c) {
250
context_detach_window(c);
253
assert_se(hashmap_remove(c->cache->contexts, UINT_TO_PTR(c->id + 1)));
258
static void fd_free(FileDescriptor *f) {
262
window_free(f->windows);
265
assert_se(hashmap_remove(f->cache->fds, INT_TO_PTR(f->fd + 1)));
270
static FileDescriptor* fd_add(MMapCache *m, int fd) {
277
f = hashmap_get(m->fds, INT_TO_PTR(fd + 1));
281
r = hashmap_ensure_allocated(&m->fds, trivial_hash_func, trivial_compare_func);
285
f = new0(FileDescriptor, 1);
292
r = hashmap_put(m->fds, UINT_TO_PTR(fd + 1), f);
301
static void mmap_cache_free(MMapCache *m) {
307
while ((c = hashmap_first(m->contexts)))
310
while ((f = hashmap_first(m->fds)))
314
window_free(m->unused);
319
MMapCache* mmap_cache_unref(MMapCache *m) {
321
assert(m->n_ref > 0);
330
static int make_room(MMapCache *m) {
336
window_free(m->last_unused);
340
static int try_context(
353
assert(m->n_ref > 0);
358
c = hashmap_get(m->contexts, UINT_TO_PTR(context+1));
362
assert(c->id == context);
367
if (!window_matches(c->window, fd, prot, offset, size)) {
369
/* Drop the reference to the window, since it's unnecessary now */
370
context_detach_window(c);
374
c->window->keep_always = c->window->keep_always || keep_always;
376
*ret = (uint8_t*) c->window->ptr + (offset - c->window->offset);
380
static int find_mmap(
395
assert(m->n_ref > 0);
400
f = hashmap_get(m->fds, INT_TO_PTR(fd + 1));
406
LIST_FOREACH(by_fd, w, f->windows)
407
if (window_matches(w, fd, prot, offset, size))
413
c = context_add(m, context);
417
context_attach_window(c, w);
418
w->keep_always = w->keep_always || keep_always;
420
*ret = (uint8_t*) w->ptr + (offset - w->offset);
435
uint64_t woffset, wsize;
443
assert(m->n_ref > 0);
448
woffset = offset & ~((uint64_t) page_size() - 1ULL);
449
wsize = size + (offset - woffset);
450
wsize = PAGE_ALIGN(wsize);
452
if (wsize < WINDOW_SIZE) {
455
delta = PAGE_ALIGN((WINDOW_SIZE - wsize) / 2);
466
/* Memory maps that are larger then the files
467
underneath have undefined behavior. Hence, clamp
468
things to the file size if we know it */
470
if (woffset >= (uint64_t) st->st_size)
471
return -EADDRNOTAVAIL;
473
if (woffset + wsize > (uint64_t) st->st_size)
474
wsize = PAGE_ALIGN(st->st_size - woffset);
478
d = mmap(NULL, wsize, prot, MAP_SHARED, fd, woffset);
491
c = context_add(m, context);
503
w->keep_always = keep_always;
510
LIST_PREPEND(Window, by_fd, f->windows, w);
512
context_detach_window(c);
514
LIST_PREPEND(Context, by_window, w->contexts, c);
516
*ret = (uint8_t*) w->ptr + (offset - w->offset);
534
assert(m->n_ref > 0);
539
/* Check whether the current context is the right one already */
540
r = try_context(m, fd, prot, context, keep_always, offset, size, ret);
544
/* Search for a matching mmap */
545
r = find_mmap(m, fd, prot, context, keep_always, offset, size, ret);
549
/* Create a new mmap */
550
return add_mmap(m, fd, prot, context, keep_always, offset, size, st, ret);
553
void mmap_cache_close_fd(MMapCache *m, int fd) {
559
f = hashmap_get(m->fds, INT_TO_PTR(fd + 1));
566
void mmap_cache_close_context(MMapCache *m, unsigned context) {
571
c = hashmap_get(m->contexts, UINT_TO_PTR(context + 1));