/** * @file smm.c * */ #if USE_WAYLAND #include #include #include #include #include #include #include #include #include #include #include #include "smm.h" #define MAX_NAME_ATTEMPTS (5) #define PREFER_NUM_BUFFERS (3) #define ROUND_UP(n, b) (((((n) ? (n) : 1) + (b) - 1) / (b)) * (b)) #define LLHEAD(type) \ struct { \ struct type *first; \ struct type *last; \ } #define LLLINK(type) \ struct { \ struct type *next; \ struct type *prev; \ } #define LL_FIRST(head) ((head)->first) #define LL_LAST(head) ((head)->last) #define LL_IS_EMPTY(head) (LL_FIRST(head) == NULL) #define LL_NEXT(src, member) ((src)->member.next) #define LL_PREV(src, member) ((src)->member.prev) #define LL_INIT(head) do { \ (head)->first = NULL; \ (head)->last = NULL; \ } while (0) #define LL_ENQUEUE(head, src, member) do { \ (src)->member.next = NULL; \ (src)->member.prev = (head)->last; \ if ((head)->last == NULL) { \ (head)->first = (src); \ } else { \ (head)->last->member.next = (src); \ } \ (head)->last = (src); \ } while (0) #define LL_DEQUEUE(entry, head, member) do { \ (entry) = LL_FIRST(head); \ LL_REMOVE(head, entry, member); \ } while (0) #define LL_INSERT_AFTER(head, dest, src, member) do { \ (src)->member.prev = (dest); \ (src)->member.next = (dest)->member.next; \ if ((dest)->member.next != NULL) { \ (dest)->member.next->member.prev = (src); \ } else { \ (head)->last = (src); \ } \ (dest)->member.next = (src); \ } while (0) #define LL_REMOVE(head, src, member) do { \ if ((src)->member.prev != NULL) { \ (src)->member.prev->member.next = (src)->member.next; \ } else { \ (head)->first = (src)->member.next; \ } \ if ((src)->member.next != NULL) { \ (src)->member.next->member.prev = (src)->member.prev; \ } else { \ (head)->last = (src)->member.prev; \ } \ } while (0) #define LL_FOREACH(entry, head, member) \ for ((entry) = LL_FIRST(head); \ (entry) != NULL; \ (entry) = LL_NEXT(entry, member)) struct smm_pool { struct smm_pool_properties props; LLHEAD(smm_buffer) allocd; void *map; size_t map_size; bool map_outdated; }; struct smm_buffer { struct smm_buffer_properties props; bool group_resized; LLLINK(smm_buffer) pool; LLLINK(smm_buffer) use; LLLINK(smm_buffer) age; }; struct smm_group { struct smm_group_properties props; size_t size; unsigned char num_buffers; LLHEAD(smm_buffer) unused; LLHEAD(smm_buffer) inuse; LLHEAD(smm_buffer) history; LLLINK(smm_group) link; }; static size_t calc_buffer_size(struct smm_buffer *buf); static void purge_history(struct smm_buffer *buf); static struct smm_buffer *get_from_pool(struct smm_group *grp); static void return_to_pool(struct smm_buffer *buf); static struct smm_pool *alloc_pool(void); static void free_pool(struct smm_pool *pool); static struct smm_buffer *alloc_buffer(struct smm_buffer *last, size_t offset); static void free_buffer(struct smm_buffer *buf); static struct { unsigned long page_sz; struct smm_events cbs; struct smm_pool *active; LLHEAD(smm_group) groups; struct { size_t active_used; } statistics; } smm_instance; void smm_init(struct smm_events *evs) { memcpy(&smm_instance.cbs, evs, sizeof(struct smm_events)); srand((unsigned int)clock()); smm_instance.page_sz = (unsigned long)sysconf(_SC_PAGESIZE); LL_INIT(&smm_instance.groups); } void smm_deinit(void) { struct smm_group *grp; /* Destroy all buffer groups */ while (!LL_IS_EMPTY(&smm_instance.groups)) { LL_DEQUEUE(grp, &smm_instance.groups, link); smm_destroy(grp); } } void smm_setctx(void *ctx) { smm_instance.cbs.ctx = ctx; } smm_group_t *smm_create(void) { struct smm_group *grp; /* Allocate and intialize a new buffer group */ grp = malloc(sizeof(struct smm_group)); if (grp != NULL) { grp->size = smm_instance.page_sz; grp->num_buffers = 0; LL_INIT(&grp->unused); LL_INIT(&grp->inuse); LL_INIT(&grp->history); /* Add to instance groups queue */ LL_ENQUEUE(&smm_instance.groups, grp, link); } return grp; } void smm_resize(smm_group_t *grp, size_t sz) { struct smm_buffer *buf; struct smm_group *rgrp = grp; /* Round allocation size up to a sysconf(_SC_PAGE_SIZE) boundary */ rgrp->size = ROUND_UP(sz, smm_instance.page_sz); /* Return all unused buffers to pool (to be re-allocated at the new size) */ while (!LL_IS_EMPTY(&rgrp->unused)) { LL_DEQUEUE(buf, &rgrp->unused, use); return_to_pool(buf); } /* Mark all buffers in use to be freed to pool when possible */ LL_FOREACH(buf, &rgrp->inuse, use) { buf->group_resized = true; purge_history(buf); } } void smm_destroy(smm_group_t *grp) { struct smm_buffer *buf; struct smm_group *dgrp = grp; /* Return unused buffers */ while (!LL_IS_EMPTY(&dgrp->unused)) { LL_DEQUEUE(buf, &dgrp->unused, use); return_to_pool(buf); } /* Return buffers that are still in use (ideally this queue should be empty * at this time) */ while (!LL_IS_EMPTY(&dgrp->inuse)) { LL_DEQUEUE(buf, &dgrp->inuse, use); return_to_pool(buf); } /* Remove from instance groups queue */ LL_REMOVE(&smm_instance.groups, dgrp, link); free(dgrp); } smm_buffer_t *smm_acquire(smm_group_t *grp) { struct smm_buffer *buf; struct smm_group *agrp = grp; if (LL_IS_EMPTY(&agrp->unused)) { /* No unused buffer available, so get a new one from pool */ buf = get_from_pool(agrp); } else { /* Otherwise, reuse an unused buffer */ LL_DEQUEUE(buf, &agrp->unused, use); } if (buf != NULL) { /* Add buffer to in-use queue */ LL_ENQUEUE(&agrp->inuse, buf, use); /* Emit 'init buffer' event */ if (smm_instance.cbs.init_buffer != NULL) { if (smm_instance.cbs.init_buffer(smm_instance.cbs.ctx, &buf->props)) { smm_release(buf); buf = NULL; } } if (buf != NULL) { /* Remove from history */ purge_history(buf); /* Add to history a-new */ LL_ENQUEUE(&agrp->history, buf, age); } } return buf; } void *smm_map(smm_buffer_t *buf) { struct smm_buffer *mbuf = buf; struct smm_pool *pool = mbuf->props.pool; void *map = pool->map; if (pool->map_outdated) { /* Update mapping to current pool size */ if (pool->map != NULL) { munmap(pool->map, pool->map_size); } map = mmap(NULL, pool->props.size, PROT_READ | PROT_WRITE, MAP_SHARED, pool->props.fd, 0); if (map == MAP_FAILED) { map = NULL; pool->map = NULL; } else { pool->map = map; pool->map_size = pool->props.size; pool->map_outdated = false; } } /* Calculate buffer mapping (from offset in pool) */ if (map != NULL) { map = (((char *)map) + mbuf->props.offset); } return map; } void smm_release(smm_buffer_t *buf) { struct smm_buffer *rbuf = buf; struct smm_group *grp = rbuf->props.group; /* Remove from in-use queue */ LL_REMOVE(&grp->inuse, rbuf, use); if (rbuf->group_resized) { /* Buffer group was resized while this buffer was in-use, thus it must be * returned to it's pool */ rbuf->group_resized = false; return_to_pool(rbuf); } else { /* Move to unused queue */ LL_ENQUEUE(&grp->unused, rbuf, use); /* Try to limit total number of buffers to preferred number */ while ((grp->num_buffers > PREFER_NUM_BUFFERS) && (!LL_IS_EMPTY(&grp->unused))) { LL_DEQUEUE(rbuf, &grp->unused, use); return_to_pool(rbuf); } } } smm_buffer_t *smm_latest(smm_group_t *grp) { struct smm_group *lgrp = grp; return LL_LAST(&lgrp->history); } smm_buffer_t *smm_next(smm_buffer_t *buf) { struct smm_buffer *ibuf; struct smm_buffer *nbuf = buf; struct smm_group *grp = nbuf->props.group; LL_FOREACH(ibuf, &grp->history, age) { if (ibuf == nbuf) { ibuf = LL_NEXT(ibuf, age); break; } } return ibuf; } void purge_history(struct smm_buffer *buf) { struct smm_buffer *ibuf; struct smm_group *grp = buf->props.group; /* Remove from history (and any older) */ LL_FOREACH(ibuf, &grp->history, age) { if (ibuf == buf) { do { LL_DEQUEUE(ibuf, &grp->history, age); } while (ibuf != buf); break; } } } size_t calc_buffer_size(struct smm_buffer *buf) { size_t buf_sz; struct smm_pool *buf_pool = buf->props.pool; if (buf == LL_LAST(&buf_pool->allocd)) { buf_sz = (buf_pool->props.size - buf->props.offset); } else { buf_sz = (LL_NEXT(buf, pool)->props.offset - buf->props.offset); } return buf_sz; } struct smm_buffer *get_from_pool(struct smm_group *grp) { int ret; size_t buf_sz; struct smm_buffer *buf; struct smm_buffer *last = NULL; /* TODO: Determine when to allocate a new active pool (i.e. memory shrink) */ if (smm_instance.active == NULL) { /* Allocate a new active pool */ smm_instance.active = alloc_pool(); smm_instance.statistics.active_used = 0; } if (smm_instance.active == NULL) { buf = NULL; } else { /* Search for a free buffer large enough for allocation */ LL_FOREACH(buf, &smm_instance.active->allocd, pool) { last = buf; if (buf->props.group == NULL) { buf_sz = calc_buffer_size(buf); if (buf_sz == grp->size) { break; } else if (buf_sz > grp->size) { if ((buf != LL_LAST(&smm_instance.active->allocd)) && (LL_NEXT(buf, pool)->props.group == NULL)) { /* Pull back next buffer to use unallocated size */ LL_NEXT(buf, pool)->props.offset -= (buf_sz - grp->size); } else { /* Allocate another buffer to hold unallocated size */ alloc_buffer(buf, buf->props.offset + grp->size); } break; } } } if (buf == NULL) { /* No buffer found to meet allocation size, expand pool */ if ((last != NULL) && (last->props.group == NULL)) { /* Use last free buffer */ buf_sz = (grp->size - buf_sz); } else { /* Allocate new buffer */ buf_sz = grp->size; if (last == NULL) { buf = alloc_buffer(NULL, 0); } else { buf = alloc_buffer(last, smm_instance.active->props.size); } last = buf; } if (last != NULL) { /* Expand pool backing memory */ ret = ftruncate(smm_instance.active->props.fd, smm_instance.active->props.size + buf_sz); if (ret) { if (buf != NULL) { free_buffer(buf); buf = NULL; } } else { smm_instance.active->props.size += buf_sz; smm_instance.active->map_outdated = true; buf = last; if (!(smm_instance.active->props.size - buf_sz)) { /* Emit 'new pool' event */ if ((smm_instance.cbs.new_pool != NULL) && (smm_instance.cbs.new_pool(smm_instance.cbs.ctx, &smm_instance.active->props))) { free_buffer(buf); free_pool(smm_instance.active); smm_instance.active = NULL; buf = NULL; } } else { /* Emit 'expand pool' event */ if (smm_instance.cbs.expand_pool != NULL) { smm_instance.cbs.expand_pool(smm_instance.cbs.ctx, &smm_instance.active->props); } } } } } } if (buf != NULL) { /* Set buffer group */ memcpy((void *)&buf->props.group, &grp, sizeof(struct smm_group *)); /* Emit 'new buffer' event */ if (smm_instance.cbs.new_buffer != NULL) { if (smm_instance.cbs.new_buffer(smm_instance.cbs.ctx, &buf->props)) { grp = NULL; memcpy((void *)&buf->props.group, &grp, sizeof(struct smm_group *)); buf = NULL; } } if (buf != NULL) { /* Update active pool usage statistic */ smm_instance.statistics.active_used += grp->size; grp->num_buffers++; } } return buf; } void return_to_pool(struct smm_buffer *buf) { struct smm_group *grp = buf->props.group; struct smm_pool *pool = buf->props.pool; /* Emit 'free buffer' event */ if (smm_instance.cbs.free_buffer != NULL) { smm_instance.cbs.free_buffer(smm_instance.cbs.ctx, &buf->props); } /* Buffer is no longer part of history */ purge_history(buf); /* Buffer is no longer part of group */ grp->num_buffers--; grp = NULL; memcpy((void *)&buf->props.group, &grp, sizeof(struct smm_group *)); /* Update active pool usage statistic */ if (smm_instance.active == pool) { smm_instance.statistics.active_used -= calc_buffer_size(buf); } /* Coalesce with ungrouped buffers beside this one */ if ((buf != LL_LAST(&pool->allocd)) && (LL_NEXT(buf, pool)->props.group == NULL)) { free_buffer(LL_NEXT(buf, pool)); } if ((buf != LL_FIRST(&pool->allocd)) && (LL_PREV(buf, pool)->props.group == NULL)) { buf = LL_PREV(buf, pool); pool = buf->props.pool; free_buffer(LL_NEXT(buf, pool)); } /* Free buffer (and pool), if only remaining buffer in pool */ if ((buf == LL_FIRST(&pool->allocd)) && (buf == LL_LAST(&pool->allocd))) { free_buffer(buf); /* Emit 'free pool' event */ if (smm_instance.cbs.free_pool != NULL) { smm_instance.cbs.free_pool(smm_instance.cbs.ctx, &pool->props); } free_pool(pool); if (smm_instance.active == pool) { smm_instance.active = NULL; } } } struct smm_pool *alloc_pool(void) { struct smm_pool *pool; char name[] = ("/" SMM_FD_NAME "-XXXXX"); unsigned char attempts = 0; bool opened = false; pool = malloc(sizeof(struct smm_pool)); if (pool != NULL) { do { /* A randomized pool name should help reduce collisions */ sprintf(name + sizeof(SMM_FD_NAME) + 1, "%05X", rand() & 0xFFFF); pool->props.fd = shm_open(name, O_RDWR | O_CREAT | O_EXCL, S_IRUSR | S_IWUSR); if (pool->props.fd >= 0) { shm_unlink(name); pool->props.size = 0; pool->map = NULL; pool->map_size = 0; pool->map_outdated = false; LL_INIT(&pool->allocd); opened = true; break; } else { if (errno != EEXIST) { break; } attempts++; } } while (attempts < MAX_NAME_ATTEMPTS); if (!opened) { free(pool); pool = NULL; } } return pool; } void free_pool(struct smm_pool *pool) { if (pool->map != NULL) { munmap(pool->map, pool->map_size); } close(pool->props.fd); free(pool); } struct smm_buffer *alloc_buffer(struct smm_buffer *last, size_t offset) { struct smm_buffer *buf; struct smm_buffer_properties initial_props = { {NULL}, NULL, smm_instance.active, offset }; /* Allocate and intialize a new buffer (including linking in to pool) */ buf = malloc(sizeof(struct smm_buffer)); if (buf != NULL) { memcpy(&buf->props, &initial_props, sizeof(struct smm_buffer_properties)); buf->group_resized = false; if (last == NULL) { LL_ENQUEUE(&smm_instance.active->allocd, buf, pool); } else { LL_INSERT_AFTER(&smm_instance.active->allocd, last, buf, pool); } } return buf; } void free_buffer(struct smm_buffer *buf) { struct smm_pool *buf_pool = buf->props.pool; /* Remove from pool */ LL_REMOVE(&buf_pool->allocd, buf, pool); free(buf); } #endif