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
) {
136 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 */
162 /* Reuse an existing one */
172 static void context_detach_window(Context
*c
) {
182 LIST_REMOVE(Context
, by_window
, w
->contexts
, c
);
185 /* Not used anymore? */
186 LIST_PREPEND(Window
, unused
, c
->cache
->unused
, w
);
187 if (!c
->cache
->last_unused
)
188 c
->cache
->last_unused
= w
;
194 static void context_attach_window(Context
*c
, Window
*w
) {
201 context_detach_window(c
);
205 LIST_REMOVE(Window
, unused
, c
->cache
->unused
, w
);
206 if (!c
->cache
->last_unused
)
207 c
->cache
->last_unused
= w
;
209 w
->in_unused
= false;
213 LIST_PREPEND(Context
, by_window
, w
->contexts
, c
);
216 static Context
*context_add(MMapCache
*m
, unsigned id
) {
222 c
= hashmap_get(m
->contexts
, UINT_TO_PTR(id
+ 1));
226 r
= hashmap_ensure_allocated(&m
->contexts
, trivial_hash_func
, trivial_compare_func
);
230 c
= new0(Context
, 1);
237 r
= hashmap_put(m
->contexts
, UINT_TO_PTR(id
+ 1), c
);
246 static void context_free(Context
*c
) {
249 context_detach_window(c
);
252 assert_se(hashmap_remove(c
->cache
->contexts
, UINT_TO_PTR(c
->id
+ 1)));
257 static void fd_free(FileDescriptor
*f
) {
261 window_free(f
->windows
);
264 assert_se(hashmap_remove(f
->cache
->fds
, INT_TO_PTR(f
->fd
+ 1)));
269 static FileDescriptor
* fd_add(MMapCache
*m
, int fd
) {
276 f
= hashmap_get(m
->fds
, INT_TO_PTR(fd
+ 1));
280 r
= hashmap_ensure_allocated(&m
->fds
, trivial_hash_func
, trivial_compare_func
);
284 f
= new0(FileDescriptor
, 1);
291 r
= hashmap_put(m
->fds
, UINT_TO_PTR(fd
+ 1), f
);
300 static void mmap_cache_free(MMapCache
*m
) {
306 while ((c
= hashmap_first(m
->contexts
)))
309 while ((f
= hashmap_first(m
->fds
)))
313 window_free(m
->unused
);
318 MMapCache
* mmap_cache_unref(MMapCache
*m
) {
320 assert(m
->n_ref
> 0);
329 static int make_room(MMapCache
*m
) {
335 window_free(m
->last_unused
);
339 static int try_context(
352 assert(m
->n_ref
> 0);
357 c
= hashmap_get(m
->contexts
, UINT_TO_PTR(context
+1));
361 assert(c
->id
== context
);
366 if (!window_matches(c
->window
, fd
, prot
, offset
, size
)) {
368 /* Drop the reference to the window, since it's unnecessary now */
369 context_detach_window(c
);
373 c
->window
->keep_always
= c
->window
->keep_always
|| keep_always
;
375 *ret
= (uint8_t*) c
->window
->ptr
+ (offset
- c
->window
->offset
);
379 static int find_mmap(
394 assert(m
->n_ref
> 0);
399 f
= hashmap_get(m
->fds
, INT_TO_PTR(fd
+ 1));
405 LIST_FOREACH(by_fd
, w
, f
->windows
)
406 if (window_matches(w
, fd
, prot
, offset
, size
))
412 c
= context_add(m
, context
);
416 context_attach_window(c
, w
);
417 w
->keep_always
= w
->keep_always
|| keep_always
;
419 *ret
= (uint8_t*) w
->ptr
+ (offset
- w
->offset
);
434 uint64_t woffset
, wsize
;
442 assert(m
->n_ref
> 0);
447 woffset
= offset
& ~((uint64_t) page_size() - 1ULL);
448 wsize
= size
+ (offset
- woffset
);
449 wsize
= PAGE_ALIGN(wsize
);
451 if (wsize
< WINDOW_SIZE
) {
454 delta
= PAGE_ALIGN((WINDOW_SIZE
- wsize
) / 2);
465 /* Memory maps that are larger then the files
466 underneath have undefined behavior. Hence, clamp
467 things to the file size if we know it */
469 if (woffset
>= (uint64_t) st
->st_size
)
470 return -EADDRNOTAVAIL
;
472 if (woffset
+ wsize
> (uint64_t) st
->st_size
)
473 wsize
= PAGE_ALIGN(st
->st_size
- woffset
);
477 d
= mmap(NULL
, wsize
, prot
, MAP_SHARED
, fd
, woffset
);
490 c
= context_add(m
, context
);
502 w
->keep_always
= keep_always
;
509 LIST_PREPEND(Window
, by_fd
, f
->windows
, w
);
511 context_detach_window(c
);
513 LIST_PREPEND(Context
, by_window
, w
->contexts
, c
);
515 *ret
= (uint8_t*) w
->ptr
+ (offset
- w
->offset
);
533 assert(m
->n_ref
> 0);
538 /* Check whether the current context is the right one already */
539 r
= try_context(m
, fd
, prot
, context
, keep_always
, offset
, size
, ret
);
543 /* Search for a matching mmap */
544 r
= find_mmap(m
, fd
, prot
, context
, keep_always
, offset
, size
, ret
);
548 /* Create a new mmap */
549 return add_mmap(m
, fd
, prot
, context
, keep_always
, offset
, size
, st
, ret
);
552 void mmap_cache_close_fd(MMapCache
*m
, int fd
) {
558 f
= hashmap_get(m
->fds
, INT_TO_PTR(fd
+ 1));
565 void mmap_cache_close_context(MMapCache
*m
, unsigned context
) {
570 c
= hashmap_get(m
->contexts
, UINT_TO_PTR(context
+ 1));