1/* 2 * Copyright 2020 Axel Davy <davyaxel0@gmail.com> 3 * 4 * Permission is hereby granted, free of charge, to any person obtaining a 5 * copy of this software and associated documentation files (the "Software"), 6 * to deal in the Software without restriction, including without limitation 7 * on the rights to use, copy, modify, merge, publish, distribute, sub 8 * license, and/or sell copies of the Software, and to permit persons to whom 9 * the Software is furnished to do so, subject to the following conditions: 10 * 11 * The above copyright notice and this permission notice (including the next 12 * paragraph) shall be included in all copies or substantial portions of the 13 * Software. 14 * 15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL 18 * THE AUTHOR(S) AND/OR THEIR SUPPLIERS BE LIABLE FOR ANY CLAIM, 19 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 20 * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE 21 * USE OR OTHER DEALINGS IN THE SOFTWARE. */ 22 23/* 24 * Memory util function to allocate RAM backing for textures. 25 * DEFAULT textures are stored on GPU 26 * MANAGED textures have a RAM backing and upload the content to a GPU texture for use 27 * SYSTEMMEM textures are stored in RAM and are meant to be uploaded to DEFAULT textures. 28 * Basically SYSTEMMEM + DEFAULT enables to do manually what MANAGED does automatically. 29 * 30 * Once the GPU texture is created, the RAM backing of MANAGED textures can be used in 31 * two occasions: 32 * . Recreating the GPU texture (for example lod change, or GPU memory eviction) 33 * . Reading the texture content (some games do that to fill higher res versions of the texture) 34 * 35 * When a lot of textures are used, the amount of addressing space (virtual memory) taken by MANAGED 36 * and SYSTEMMEM textures can be significant and cause virtual memory exhaustion for 32 bits programs. 37 * 38 * One way to reduce the virtual memory taken is to ignore lod and delete the RAM backing of 39 * MANAGED textures once it is uploaded. If the texture is read, or evicted from GPU memory, the RAM 40 * backing would be recreated (Note that mapping the GPU memory is not acceptable as RAM memory is supposed 41 * to have smaller (fixed) stride constraints). 42 * 43 * Instead the approach taken here is to keep the RAM backing alive, but free its addressing space. 44 * In other words virtual memory usage is reduced, but the RAM usage of the app is the same. 45 * To do so, we use the memfd feature of the linux kernel. It enables to allocate a file 46 * stored in RAM and visible only to the app. We can map/unmap portions of the file as we need. 47 * When a portion is mapped, it takes virtual memory space. When it is not, it doesn't. 48 * The file is stored in RAM, and thus the access speed is the same as normal RAM. Using such 49 * file to allocate data enables to use more than 4GB RAM on 32 bits. 50 * 51 * This approach adds some overhead: when accessing mapped content the first time, pages are allocated 52 * by the system. This has a lot of overhead (several times the time to memset the area). 53 * Releasing these pages (when unmapping) has overhead too, though significantly less. 54 * 55 * This overhead however is much less significant than the overhead of downloading the GPU content. 56 * In addition, we reduce significantly the overhead spent in Gallium nine for new allocations by 57 * using the fact new contents of the file are zero-allocated. By not calling memset in Gallium nine, 58 * the overhead of page allocation happens client side, thus outside the d3d mutex. This should give 59 * a performance boost for multithreaded applications. As malloc also has this overhead (at least for 60 * large enough allocations which use mmap internally), allocating ends up faster than with the standard 61 * allocation path. 62 * By far the overhead induced by page allocation/deallocation is the biggest overhead involved in this 63 * code. It is reduced significantly with huge pages, but it is too complex to configure for the user 64 * to use it (and it has some memory management downsides too). The memset trick enables to move most of 65 * the overhead outside Nine anyway. 66 * 67 * To prevent useless unmappings quickly followed by mapping again, we do not unmap right away allocations 68 * that are not locked for access anymore. Indeed it is likely the allocation will be accessed several times 69 * in a row, for example first to fill it, then to upload it. 70 * We keep everything mapped until we reach a threshold of memory allocated. Then we use hints to prioritize 71 * which regions to unmap first. Thus virtual memory usage is only reduced when the threshold is reached. 72 * 73 * Multiple memfd files are used, each of 100MB. Thus memory usage (but not virtual memory usage) increases 74 * by amounts of 100MB. When not on x86 32 bits, we do use the standard malloc. 75 * 76 * Finally, for ease of use, we do not implement packing of allocation inside page-aligned regions. 77 * One allocation is given one page-aligned region inside a memfd file. 78 * Allocations smaller than a page (4KB on x86) go through malloc. 79 * As texture sizes are usually multiples of powers of two, allocations above the page size are typically 80 * multiples of the page size, thus space is not wasted in practice. 81 * 82 */ 83 84#include <errno.h> 85#include <fcntl.h> 86#include <limits.h> 87#include <linux/memfd.h> 88#include <pthread.h> 89#include <stdio.h> 90#include <sys/mman.h> 91#include <sys/types.h> 92#include <sys/stat.h> 93#include <ulimit.h> 94#include <unistd.h> 95 96#include "util/list.h" 97#include "util/u_memory.h" 98#include "util/slab.h" 99 100#include "nine_debug.h" 101#include "nine_memory_helper.h" 102#include "nine_state.h" 103 104 105#define DIVUP(a,b) (((a)+(b)-1)/(b)) 106 107/* Required alignment for allocations */ 108#define NINE_ALLOCATION_ALIGNMENT 32 109 110#define DBG_CHANNEL (DBG_BASETEXTURE|DBG_SURFACE|DBG_VOLUME|DBG_TEXTURE|DBG_CUBETEXTURE) 111 112/* Use memfd only for 32 bits. Check for memfd_create support */ 113#if defined(PIPE_ARCH_X86) && defined(HAVE_MEMFD_CREATE) 114#define NINE_ENABLE_MEMFD 115#endif 116 117#ifdef NINE_ENABLE_MEMFD 118 119struct nine_memfd_file_region { 120 unsigned offset; 121 unsigned size; 122 void *map; /* pointer to the mapped content of the file. Can be NULL */ 123 int num_locks; /* Total number of locks blocking the munmap */ 124 int num_weak_unlocks; /* Number of users which weakly block the munmap */ 125 bool zero_filled; 126 struct list_head list; 127}; 128 129struct nine_memfd_file { 130 int fd; 131 int filesize; /* Size of the file */ 132 struct list_head free_regions; /* This list is sorted by the offset, and consecutive regions are merged */ 133 struct list_head unmapped_allocated_regions; /* This list and the following ones are not sorted */ 134 struct list_head locked_mapped_allocated_regions; 135 struct list_head weak_unlocked_mapped_allocated_regions; 136 struct list_head unlocked_mapped_allocated_regions; 137}; 138 139/* The allocation is stored inside a memfd */ 140#define NINE_MEMFD_ALLOC 1 141/* The allocation is part of another allocation, which is stored inside a memfd */ 142#define NINE_MEMFD_SUBALLOC 2 143/* The allocation was allocated with malloc and will have to be freed */ 144#define NINE_MALLOC_ALLOC 3 145/* The pointer doesn't need memory management */ 146#define NINE_EXTERNAL_ALLOC 4 147 148struct nine_memfd_allocation { 149 struct nine_memfd_file *file; /* File in which the data is allocated */ 150 struct nine_memfd_file_region *region; /* Corresponding file memory region. Max 1 allocation per region */ 151}; 152 153/* 'Suballocations' are used to represent subregions of an allocation. 154 * For example a given layer of a texture. These are not allocations, 155 * but can be accessed separately. To correctly handle accessing them, 156 * we encapsulate them into this structure. */ 157struct nine_memfd_suballocation { 158 struct nine_memfd_allocation *parent; /* Parent allocation */ 159 int relative_offset; /* Offset relative to the parent */ 160}; 161 162/* A standard allocation with malloc */ 163struct nine_malloc_allocation { 164 void *buf; 165 unsigned allocation_size; 166}; 167 168/* A pointer with no need of memory management. 169 * For example a pointer passed by the application, 170 * or a 'suballocation' inside a malloc-ed allocation. */ 171struct nine_external_allocation { 172 void *buf; 173}; 174 175/* Encapsulates all allocations */ 176struct nine_allocation { 177 unsigned allocation_type; /* Type of allocation */ 178 union { 179 struct nine_memfd_allocation memfd; 180 struct nine_memfd_suballocation submemfd; 181 struct nine_malloc_allocation malloc; 182 struct nine_external_allocation external; 183 } memory; 184 struct list_head list_free; /* for pending frees */ 185 /* The fields below are only used for memfd/submemfd allocations */ 186 struct list_head list_release; /* for pending releases */ 187 /* Handling of the CSMT thread: 188 * API calls are singled thread (global mutex protection). 189 * However we multithreading internally (CSMT worker thread). 190 * To handle this thread, we map/lock the allocation in the 191 * main thread and increase pending_counter. When the worker thread 192 * is done with the scheduled function, the pending_counter is decreased. 193 * If pending_counter is 0, locks_on_counter can be subtracted from 194 * active_locks (in the main thread). */ 195 unsigned locks_on_counter; 196 unsigned *pending_counter; 197 /* Hint from the last unlock indicating the data might be locked again soon */ 198 bool weak_unlock; 199}; 200 201struct nine_allocator { 202 struct NineDevice9 *device; 203 int page_size; /* Page size */ 204 int num_fd_max; /* Max number of memfd files */ 205 int min_file_size; /* Minimum memfd file size */ 206 /* Tracking of all allocations */ 207 long long total_allocations; /* Amount of memory allocated */ 208 long long total_locked_memory; /* TODO */ /* Amount of memory blocked by a lock */ 209 long long total_virtual_memory; /* Current virtual memory used by our allocations */ 210 long long total_virtual_memory_limit; /* Target maximum virtual memory used. Above that, tries to unmap memfd files whenever possible. */ 211 212 int num_fd; /* Number of memfd files */ /* TODO release unused memfd files */ 213 struct slab_mempool allocation_pool; 214 struct slab_mempool region_pool; 215 struct nine_memfd_file *memfd_pool; /* Table (of size num_fd) of memfd files */ 216 struct list_head pending_releases; /* List of allocations with unlocks depending on pending_counter */ /* TODO: Elements seem removed only on flush. Destruction ? */ 217 218 pthread_mutex_t mutex_pending_frees; 219 struct list_head pending_frees; 220}; 221 222#ifdef DEBUG 223 224static void 225debug_dump_memfd_state(struct nine_memfd_file *memfd_file, bool details) 226{ 227 struct nine_memfd_file_region *region; 228 229 DBG("fd: %d, filesize: %d\n", memfd_file->fd, memfd_file->filesize); 230 if (!details) 231 return; 232 LIST_FOR_EACH_ENTRY(region, &memfd_file->free_regions, list) { 233 DBG("FREE block: offset %d, size %d, map=%p, locks=%d, weak=%d, z=%d\n", 234 region->offset, region->size, region->map, 235 region->num_locks, region->num_weak_unlocks, (int)region->zero_filled); 236 } 237 LIST_FOR_EACH_ENTRY(region, &memfd_file->unmapped_allocated_regions, list) { 238 DBG("UNMAPPED ALLOCATED block: offset %d, size %d, map=%p, locks=%d, weak=%d, z=%d\n", 239 region->offset, region->size, region->map, 240 region->num_locks, region->num_weak_unlocks, (int)region->zero_filled); 241 } 242 LIST_FOR_EACH_ENTRY(region, &memfd_file->locked_mapped_allocated_regions, list) { 243 DBG("LOCKED MAPPED ALLOCATED block: offset %d, size %d, map=%p, locks=%d, weak=%d, z=%d\n", 244 region->offset, region->size, region->map, 245 region->num_locks, region->num_weak_unlocks, (int)region->zero_filled); 246 } 247 LIST_FOR_EACH_ENTRY(region, &memfd_file->unlocked_mapped_allocated_regions, list) { 248 DBG("UNLOCKED MAPPED ALLOCATED block: offset %d, size %d, map=%p, locks=%d, weak=%d, z=%d\n", 249 region->offset, region->size, region->map, 250 region->num_locks, region->num_weak_unlocks, (int)region->zero_filled); 251 } 252 LIST_FOR_EACH_ENTRY(region, &memfd_file->weak_unlocked_mapped_allocated_regions, list) { 253 DBG("WEAK UNLOCKED MAPPED ALLOCATED block: offset %d, size %d, map=%p, locks=%d, weak=%d, z=%d\n", 254 region->offset, region->size, region->map, 255 region->num_locks, region->num_weak_unlocks, (int)region->zero_filled); 256 } 257} 258 259static void 260debug_dump_allocation_state(struct nine_allocation *allocation) 261{ 262 switch(allocation->allocation_type) { 263 case NINE_MEMFD_ALLOC: 264 DBG("Allocation is stored in this memfd file:\n"); 265 debug_dump_memfd_state(allocation->memory.memfd.file, true); 266 DBG("Allocation is offset: %d, size: %d\n", 267 allocation->memory.memfd.region->offset, allocation->memory.memfd.region->size); 268 break; 269 case NINE_MEMFD_SUBALLOC: 270 DBG("Allocation is suballocation at relative offset %d of this allocation:\n", 271 allocation->memory.submemfd.relative_offset); 272 DBG("Parent allocation is stored in this memfd file:\n"); 273 debug_dump_memfd_state(allocation->memory.submemfd.parent->file, false); 274 DBG("Parent allocation is offset: %d, size: %d\n", 275 allocation->memory.submemfd.parent->region->offset, 276 allocation->memory.submemfd.parent->region->size); 277 break; 278 case NINE_MALLOC_ALLOC: 279 DBG("Allocation is a standard malloc\n"); 280 break; 281 case NINE_EXTERNAL_ALLOC: 282 DBG("Allocation is a suballocation of a standard malloc or an external allocation\n"); 283 break; 284 default: 285 assert(false); 286 } 287} 288 289#else 290 291static void 292debug_dump_memfd_state(struct nine_memfd_file *memfd_file, bool details) 293{ 294 (void)memfd_file; 295 (void)details; 296} 297 298static void 299debug_dump_allocation_state(struct nine_allocation *allocation) 300{ 301 (void)allocation; 302} 303 304#endif 305 306static void 307debug_dump_allocator_state(struct nine_allocator *allocator) 308{ 309 DBG("SURFACE ALLOCATOR STATUS:\n"); 310 DBG("Total allocated: %lld\n", allocator->total_allocations); 311 DBG("Total virtual memory locked: %lld\n", allocator->total_locked_memory); 312 DBG("Virtual memory used: %lld / %lld\n", allocator->total_virtual_memory, allocator->total_virtual_memory_limit); 313 DBG("Num memfd files: %d / %d\n", allocator->num_fd, allocator->num_fd_max); 314} 315 316 317/* Retrieve file used for the storage of the content of this allocation. 318 * NULL if not using memfd */ 319static struct nine_memfd_file * 320nine_get_memfd_file_backing(struct nine_allocation *allocation) 321{ 322 if (allocation->allocation_type > NINE_MEMFD_SUBALLOC) 323 return NULL; 324 if (allocation->allocation_type == NINE_MEMFD_ALLOC) 325 return allocation->memory.memfd.file; 326 return allocation->memory.submemfd.parent->file; 327} 328 329/* Retrieve region used for the storage of the content of this allocation. 330 * NULL if not using memfd */ 331static struct nine_memfd_file_region * 332nine_get_memfd_region_backing(struct nine_allocation *allocation) 333{ 334 if (allocation->allocation_type > NINE_MEMFD_SUBALLOC) 335 return NULL; 336 if (allocation->allocation_type == NINE_MEMFD_ALLOC) 337 return allocation->memory.memfd.region; 338 return allocation->memory.submemfd.parent->region; 339} 340 341static void move_region(struct list_head *tail, struct nine_memfd_file_region *region) 342{ 343 /* Remove from previous list (if any) */ 344 list_delinit(®ion->list); 345 /* Insert in new list (last) */ 346 list_addtail(®ion->list, tail); 347} 348 349#if 0 350static void move_region_ordered(struct list_head *tail, struct nine_memfd_file_region *region) 351{ 352 struct nine_memfd_file_region *cur_region; 353 struct list_head *insertion_point = tail; 354 355 /* Remove from previous list (if any) */ 356 list_delinit(®ion->list); 357 358 LIST_FOR_EACH_ENTRY(cur_region, tail, list) { 359 if (cur_region->offset > region->offset) 360 break; 361 insertion_point = &cur_region->list; 362 } 363 /* Insert just before cur_region */ 364 list_add(®ion->list, insertion_point); 365} 366#endif 367 368static void move_region_ordered_merge(struct nine_allocator *allocator, struct list_head *tail, struct nine_memfd_file_region *region) 369{ 370 struct nine_memfd_file_region *p, *cur_region = NULL, *prev_region = NULL; 371 372 /* Remove from previous list (if any) */ 373 list_delinit(®ion->list); 374 375 LIST_FOR_EACH_ENTRY(p, tail, list) { 376 cur_region = p; 377 if (cur_region->offset > region->offset) 378 break; 379 prev_region = cur_region; 380 } 381 382 /* Insert after prev_region and before cur_region. Try to merge */ 383 if (prev_region && ((prev_region->offset + prev_region->size) == region->offset)) { 384 if (cur_region && (cur_region->offset == (region->offset + region->size))) { 385 /* Merge all three regions */ 386 prev_region->size += region->size + cur_region->size; 387 prev_region->zero_filled = prev_region->zero_filled && region->zero_filled && cur_region->zero_filled; 388 list_del(&cur_region->list); 389 slab_free_st(&allocator->region_pool, region); 390 slab_free_st(&allocator->region_pool, cur_region); 391 } else { 392 prev_region->size += region->size; 393 prev_region->zero_filled = prev_region->zero_filled && region->zero_filled; 394 slab_free_st(&allocator->region_pool, region); 395 } 396 } else if (cur_region && (cur_region->offset == (region->offset + region->size))) { 397 cur_region->offset = region->offset; 398 cur_region->size += region->size; 399 cur_region->zero_filled = region->zero_filled && cur_region->zero_filled; 400 slab_free_st(&allocator->region_pool, region); 401 } else { 402 list_add(®ion->list, prev_region ? &prev_region->list : tail); 403 } 404} 405 406static struct nine_memfd_file_region *allocate_region(struct nine_allocator *allocator, unsigned offset, unsigned size) { 407 struct nine_memfd_file_region *region = slab_alloc_st(&allocator->allocation_pool); 408 if (!region) 409 return NULL; 410 region->offset = offset; 411 region->size = size; 412 region->num_locks = 0; 413 region->num_weak_unlocks = 0; 414 region->map = NULL; 415 region->zero_filled = false; 416 list_inithead(®ion->list); 417 return region; 418} 419 420/* Go through memfd allocated files, and try to use unused memory for the requested allocation. 421 * Returns whether it suceeded */ 422static bool 423insert_new_allocation(struct nine_allocator *allocator, struct nine_allocation *new_allocation, unsigned allocation_size) 424{ 425 int memfd_index; 426 struct nine_memfd_file *memfd_file, *best_memfd_file; 427 struct nine_memfd_file_region *region, *best_region, *new_region; 428 429 430 /* Find the smallest - but bigger than the requested size - unused memory 431 * region inside the memfd files. */ 432 int min_blocksize = INT_MAX; 433 434 for (memfd_index = 0; memfd_index < allocator->num_fd; memfd_index++) { 435 memfd_file = (void*)allocator->memfd_pool + memfd_index*sizeof(struct nine_memfd_file); 436 437 LIST_FOR_EACH_ENTRY(region, &memfd_file->free_regions, list) { 438 if (region->size <= min_blocksize && region->size >= allocation_size) { 439 min_blocksize = region->size; 440 best_region = region; 441 best_memfd_file = memfd_file; 442 } 443 } 444 if (min_blocksize == allocation_size) 445 break; 446 } 447 448 /* The allocation doesn't fit in any memfd file */ 449 if (min_blocksize == INT_MAX) 450 return false; 451 452 /* Target region found */ 453 /* Move from free to unmapped allocated */ 454 best_region->size = DIVUP(allocation_size, allocator->page_size) * allocator->page_size; 455 assert(min_blocksize >= best_region->size); 456 move_region(&best_memfd_file->unmapped_allocated_regions, best_region); 457 new_allocation->memory.memfd.region = best_region; 458 new_allocation->memory.memfd.file = best_memfd_file; 459 460 /* If the original region is bigger than needed, add new region with remaining space */ 461 min_blocksize -= best_region->size; 462 if (min_blocksize > 0) { 463 new_region = allocate_region(allocator, best_region->offset + best_region->size, min_blocksize); 464 new_region->zero_filled = best_region->zero_filled; 465 move_region_ordered_merge(allocator, &best_memfd_file->free_regions, new_region); 466 } 467 allocator->total_allocations += best_region->size; 468 return true; 469} 470 471/* Go through allocations with unlocks waiting on pending_counter being 0. 472 * If 0 is indeed reached, update the allocation status */ 473static void 474nine_flush_pending_releases(struct nine_allocator *allocator) 475{ 476 struct nine_allocation *allocation, *ptr; 477 LIST_FOR_EACH_ENTRY_SAFE(allocation, ptr, &allocator->pending_releases, list_release) { 478 assert(allocation->locks_on_counter > 0); 479 /* If pending_releases reached 0, remove from the list and update the status */ 480 if (*allocation->pending_counter == 0) { 481 struct nine_memfd_file *memfd_file = nine_get_memfd_file_backing(allocation); 482 struct nine_memfd_file_region *region = nine_get_memfd_region_backing(allocation); 483 region->num_locks -= allocation->locks_on_counter; 484 allocation->locks_on_counter = 0; 485 list_delinit(&allocation->list_release); 486 if (region->num_locks == 0) { 487 /* Move to the correct list */ 488 if (region->num_weak_unlocks) 489 move_region(&memfd_file->weak_unlocked_mapped_allocated_regions, region); 490 else 491 move_region(&memfd_file->unlocked_mapped_allocated_regions, region); 492 allocator->total_locked_memory -= region->size; 493 } 494 } 495 } 496} 497 498static void 499nine_free_internal(struct nine_allocator *allocator, struct nine_allocation *allocation); 500 501static void 502nine_flush_pending_frees(struct nine_allocator *allocator) 503{ 504 struct nine_allocation *allocation, *ptr; 505 506 pthread_mutex_lock(&allocator->mutex_pending_frees); 507 /* The order of release matters as suballocations are supposed to be released first */ 508 LIST_FOR_EACH_ENTRY_SAFE(allocation, ptr, &allocator->pending_frees, list_free) { 509 /* Set the allocation in an unlocked state, and then free it */ 510 if (allocation->allocation_type == NINE_MEMFD_ALLOC || 511 allocation->allocation_type == NINE_MEMFD_SUBALLOC) { 512 struct nine_memfd_file *memfd_file = nine_get_memfd_file_backing(allocation); 513 struct nine_memfd_file_region *region = nine_get_memfd_region_backing(allocation); 514 if (region->num_locks != 0) { 515 region->num_locks = 0; 516 allocator->total_locked_memory -= region->size; 517 /* Useless, but to keep consistency */ 518 move_region(&memfd_file->unlocked_mapped_allocated_regions, region); 519 } 520 region->num_weak_unlocks = 0; 521 allocation->weak_unlock = false; 522 allocation->locks_on_counter = 0; 523 list_delinit(&allocation->list_release); 524 } 525 list_delinit(&allocation->list_free); 526 nine_free_internal(allocator, allocation); 527 } 528 pthread_mutex_unlock(&allocator->mutex_pending_frees); 529} 530 531/* Try to unmap the memfd_index-th file if not already unmapped. 532 * If even_if_weak is False, will not unmap if there are weak unlocks */ 533static void 534nine_memfd_unmap_region(struct nine_allocator *allocator, 535 struct nine_memfd_file *memfd_file, 536 struct nine_memfd_file_region *region) 537{ 538 DBG("Unmapping memfd mapped region at %d: size: %d, map=%p, locks=%d, weak=%d\n", 539 region->offset, region->size, region->map, 540 region->num_locks, region->num_weak_unlocks); 541 assert(region->map != NULL); 542 543 if (munmap(region->map, region->size) != 0) 544 fprintf(stderr, "Error on unmapping, errno=%d\n", (int)errno); 545 546 region->map = NULL; 547 /* Move from one of the mapped region list to the unmapped one */ 548 move_region(&memfd_file->unmapped_allocated_regions, region); 549 allocator->total_virtual_memory -= region->size; 550} 551 552/* Unallocate a region of a memfd file */ 553static void 554remove_allocation(struct nine_allocator *allocator, struct nine_memfd_file *memfd_file, struct nine_memfd_file_region *region) 555{ 556 assert(region->num_locks == 0); 557 region->num_weak_unlocks = 0; 558 /* Move from mapped region to unmapped region */ 559 if (region->map) { 560 if (likely(!region->zero_filled)) { 561 /* As the region is mapped, it is likely the pages are allocated. 562 * Do the memset now for when we allocate again. It is much faster now, 563 * as the pages are allocated. */ 564 DBG("memset on data=%p, size %d\n", region->map, region->size); 565 memset(region->map, 0, region->size); 566 region->zero_filled = true; 567 } 568 nine_memfd_unmap_region(allocator, memfd_file, region); 569 } 570 /* Move from unmapped region to free region */ 571 allocator->total_allocations -= region->size; 572 move_region_ordered_merge(allocator, &memfd_file->free_regions, region); 573} 574 575/* Try to unmap the regions of the memfd_index-th file if not already unmapped. 576 * If even_if_weak is False, will not unmap if there are weak unlocks */ 577static void 578nine_memfd_try_unmap_file(struct nine_allocator *allocator, 579 int memfd_index, 580 bool weak) 581{ 582 struct nine_memfd_file *memfd_file = (void*)allocator->memfd_pool + memfd_index*sizeof(struct nine_memfd_file); 583 struct nine_memfd_file_region *region, *ptr; 584 DBG("memfd file at %d: fd: %d, filesize: %d\n", 585 memfd_index, memfd_file->fd, memfd_file->filesize); 586 debug_dump_memfd_state(memfd_file, true); 587 LIST_FOR_EACH_ENTRY_SAFE(region, ptr, 588 weak ? 589 &memfd_file->weak_unlocked_mapped_allocated_regions : 590 &memfd_file->unlocked_mapped_allocated_regions, 591 list) { 592 nine_memfd_unmap_region(allocator, memfd_file, region); 593 } 594} 595 596/* Unmap files until we are below the virtual memory target limit. 597 * If unmap_everything_possible is set, ignore the limit and unmap 598 * all that can be unmapped. */ 599static void 600nine_memfd_files_unmap(struct nine_allocator *allocator, 601 bool unmap_everything_possible) 602{ 603 long long memory_limit = unmap_everything_possible ? 604 0 : allocator->total_virtual_memory_limit; 605 int i; 606 607 /* We are below the limit. Do nothing */ 608 if (memory_limit >= allocator->total_virtual_memory) 609 return; 610 611 /* Update allocations with pending releases */ 612 nine_flush_pending_releases(allocator); 613 614 DBG("Trying to unmap files with no weak unlock (%lld / %lld)\n", 615 allocator->total_virtual_memory, memory_limit); 616 617 /* Try to release everything with no weak releases. 618 * Those have data not needed for a long time (and 619 * possibly ever). */ 620 for (i = 0; i < allocator->num_fd; i++) { 621 nine_memfd_try_unmap_file(allocator, i, false); 622 if (memory_limit >= allocator->total_virtual_memory) { 623 return;} 624 } 625 626 DBG("Trying to unmap files even with weak unlocks (%lld / %lld)\n", 627 allocator->total_virtual_memory, memory_limit); 628 629 /* This wasn't enough. Also release files with weak releases */ 630 for (i = 0; i < allocator->num_fd; i++) { 631 nine_memfd_try_unmap_file(allocator, i, true); 632 /* Stop if the target is reached */ 633 if (memory_limit >= allocator->total_virtual_memory) { 634 return;} 635 } 636 637 if (!unmap_everything_possible) 638 return; 639 640 /* If there are some pending uploads, execute them, 641 * and retry. */ 642 if (list_is_empty(&allocator->pending_releases)) { 643 return;} 644 nine_csmt_process(allocator->device); 645 nine_flush_pending_releases(allocator); 646 647 DBG("Retrying after flushing (%lld / %lld)\n", 648 allocator->total_virtual_memory, memory_limit); 649 650 for (i = 0; i < allocator->num_fd; i++) { 651 nine_memfd_try_unmap_file(allocator, i, false); 652 nine_memfd_try_unmap_file(allocator, i, true); 653 } 654 /* We have done all we could */ 655} 656 657/* Map a given memfd file */ 658static bool 659nine_memfd_region_map(struct nine_allocator *allocator, struct nine_memfd_file *memfd_file, struct nine_memfd_file_region *region) 660{ 661 if (region->map != NULL) 662 return true; 663 664 debug_dump_memfd_state(memfd_file, true); 665 nine_memfd_files_unmap(allocator, false); 666 667 void *buf = mmap(NULL, region->size, PROT_READ | PROT_WRITE, MAP_SHARED, memfd_file->fd, region->offset); 668 669 if (buf == MAP_FAILED && errno == ENOMEM) { 670 DBG("Failed to mmap a memfd file - trying to unmap other files\n"); 671 nine_memfd_files_unmap(allocator, true); 672 buf = mmap(NULL, region->size, PROT_READ | PROT_WRITE, MAP_SHARED, memfd_file->fd, region->offset); 673 } 674 if (buf == MAP_FAILED) { 675 DBG("Failed to mmap a memfd file, errno=%d\n", (int)errno); 676 return false; 677 } 678 region->map = buf; 679 /* no need to move to an unlocked mapped regions list, the caller will handle the list */ 680 allocator->total_virtual_memory += region->size; 681 assert((uintptr_t)buf % NINE_ALLOCATION_ALIGNMENT == 0); /* mmap should be page_size aligned, so it should be fine */ 682 683 return true; 684} 685 686/* Allocate with memfd some memory. Returns True if successful. */ 687static bool 688nine_memfd_allocator(struct nine_allocator *allocator, 689 struct nine_allocation *new_allocation, 690 unsigned allocation_size) 691{ 692 struct nine_memfd_file *memfd_file; 693 struct nine_memfd_file_region *region; 694 695 allocation_size = DIVUP(allocation_size, allocator->page_size) * allocator->page_size; 696 new_allocation->allocation_type = NINE_MEMFD_ALLOC; 697 new_allocation->locks_on_counter = 0; 698 new_allocation->pending_counter = NULL; 699 new_allocation->weak_unlock = false; 700 list_inithead(&new_allocation->list_free); 701 list_inithead(&new_allocation->list_release); 702 703 /* Try to find free space in a file already allocated */ 704 if (insert_new_allocation(allocator, new_allocation, allocation_size)) 705 return true; 706 707 /* No - allocate new memfd file */ 708 709 if (allocator->num_fd == allocator->num_fd_max) 710 return false; /* Too many memfd files */ 711 712 allocator->num_fd++; 713 memfd_file = (void*)allocator->memfd_pool + (allocator->num_fd-1)*sizeof(struct nine_memfd_file); 714 /* If the allocation size is above the memfd file default size, use a bigger size */ 715 memfd_file->filesize = MAX2(allocation_size, allocator->min_file_size); 716 717 memfd_file->fd = memfd_create("gallium_nine_ram", 0); 718 if (memfd_file->fd == -1) { 719 DBG("Failed to created a memfd file, errno=%d\n", (int)errno); 720 allocator->num_fd--; 721 return false; 722 } 723 724 if (ftruncate(memfd_file->fd, memfd_file->filesize) != 0) { 725 DBG("Failed to resize a memfd file, errno=%d\n", (int)errno); 726 close(memfd_file->fd); 727 allocator->num_fd--; 728 return false; 729 } 730 731 list_inithead(&memfd_file->free_regions); 732 list_inithead(&memfd_file->unmapped_allocated_regions); 733 list_inithead(&memfd_file->locked_mapped_allocated_regions); 734 list_inithead(&memfd_file->unlocked_mapped_allocated_regions); 735 list_inithead(&memfd_file->weak_unlocked_mapped_allocated_regions); 736 737 /* Initialize the memfd file with empty region and the allocation */ 738 region = allocate_region(allocator, 0, allocation_size); 739 region->zero_filled = true; /* ftruncate does zero-fill the new data */ 740 list_add(®ion->list, &memfd_file->unmapped_allocated_regions); 741 new_allocation->memory.memfd.file = memfd_file; 742 new_allocation->memory.memfd.region = region; 743 allocator->total_allocations += allocation_size; 744 745 if (allocation_size == memfd_file->filesize) 746 return true; 747 748 /* Add empty region */ 749 region = allocate_region(allocator, allocation_size, memfd_file->filesize - allocation_size); 750 region->zero_filled = true; /* ftruncate does zero-fill the new data */ 751 list_add(®ion->list, &memfd_file->free_regions); 752 753 return true; 754} 755 756/* Allocate memory */ 757struct nine_allocation * 758nine_allocate(struct nine_allocator *allocator, unsigned size) 759{ 760 761 struct nine_allocation *new_allocation = slab_alloc_st(&allocator->allocation_pool); 762 debug_dump_allocator_state(allocator); 763 if (!new_allocation) 764 return NULL; 765 766 nine_flush_pending_frees(allocator); 767 768 /* Restrict to >= page_size to prevent having too much fragmentation, as the size of 769 * allocations is rounded to the next page_size multiple. */ 770 if (size >= allocator->page_size && allocator->total_virtual_memory_limit >= 0 && 771 nine_memfd_allocator(allocator, new_allocation, size)) { 772 struct nine_memfd_file_region *region = new_allocation->memory.memfd.region; 773 if (!region->zero_filled) { 774 void *data = nine_get_pointer(allocator, new_allocation); 775 if (!data) { 776 ERR("INTERNAL MMAP FOR NEW ALLOCATION FAILED\n"); 777 nine_free(allocator, new_allocation); 778 return NULL; 779 } 780 DBG("memset on data=%p, size %d\n", data, region->size); 781 memset(data, 0, region->size); 782 region->zero_filled = true; 783 /* Even though the user usually fills afterward, we don't weakrelease. 784 * The reason is suballocations don't affect the weakrelease state of their 785 * parents. Thus if only suballocations are accessed, the release would stay 786 * weak forever. */ 787 nine_pointer_strongrelease(allocator, new_allocation); 788 } 789 DBG("ALLOCATION SUCCESSFUL\n"); 790 debug_dump_allocation_state(new_allocation); 791 return new_allocation; 792 } 793 794 void *data = align_calloc(size, NINE_ALLOCATION_ALIGNMENT); 795 if (!data) { 796 DBG("ALLOCATION FAILED\n"); 797 return NULL; 798 } 799 800 new_allocation->allocation_type = NINE_MALLOC_ALLOC; 801 new_allocation->memory.malloc.buf = data; 802 new_allocation->memory.malloc.allocation_size = size; 803 list_inithead(&new_allocation->list_free); 804 allocator->total_allocations += size; 805 allocator->total_locked_memory += size; 806 allocator->total_virtual_memory += size; 807 DBG("ALLOCATION SUCCESSFUL\n"); 808 debug_dump_allocation_state(new_allocation); 809 return new_allocation; 810} 811 812/* Release memory */ 813static void 814nine_free_internal(struct nine_allocator *allocator, struct nine_allocation *allocation) 815{ 816 DBG("RELEASING ALLOCATION\n"); 817 debug_dump_allocation_state(allocation); 818 if (allocation->allocation_type == NINE_MALLOC_ALLOC) { 819 allocator->total_allocations -= allocation->memory.malloc.allocation_size; 820 allocator->total_locked_memory -= allocation->memory.malloc.allocation_size; 821 allocator->total_virtual_memory -= allocation->memory.malloc.allocation_size; 822 align_free(allocation->memory.malloc.buf); 823 } else if (allocation->allocation_type == NINE_MEMFD_ALLOC || 824 allocation->allocation_type == NINE_MEMFD_SUBALLOC) { 825 struct nine_memfd_file *memfd_file = nine_get_memfd_file_backing(allocation); 826 struct nine_memfd_file_region *region = nine_get_memfd_region_backing(allocation); 827 if (allocation->weak_unlock) 828 region->num_weak_unlocks--; 829 if (allocation->allocation_type == NINE_MEMFD_ALLOC) 830 remove_allocation(allocator, memfd_file, region); 831 } 832 833 slab_free_st(&allocator->allocation_pool, allocation); 834 debug_dump_allocator_state(allocator); 835} 836 837 838void 839nine_free(struct nine_allocator *allocator, struct nine_allocation *allocation) 840{ 841 nine_flush_pending_frees(allocator); 842 nine_flush_pending_releases(allocator); 843 nine_free_internal(allocator, allocation); 844} 845 846/* Called from the worker thread. Similar to nine_free except we are not in the main thread, thus 847 * we are disallowed to change the allocator structures except the fields reserved 848 * for the worker. In addition, the allocation is allowed to not being unlocked (the release 849 * will unlock it) */ 850void nine_free_worker(struct nine_allocator *allocator, struct nine_allocation *allocation) 851{ 852 /* Add the allocation to the list of pending allocations to free */ 853 pthread_mutex_lock(&allocator->mutex_pending_frees); 854 /* The order of free matters as suballocations are supposed to be released first */ 855 list_addtail(&allocation->list_free, &allocator->pending_frees); 856 pthread_mutex_unlock(&allocator->mutex_pending_frees); 857} 858 859/* Lock an allocation, and retrieve the pointer */ 860void * 861nine_get_pointer(struct nine_allocator *allocator, struct nine_allocation *allocation) 862{ 863 struct nine_memfd_file *memfd_file; 864 struct nine_memfd_file_region *region; 865 866 nine_flush_pending_releases(allocator); 867 DBG("allocation_type: %d\n", allocation->allocation_type); 868 869 if (allocation->allocation_type == NINE_MALLOC_ALLOC) 870 return allocation->memory.malloc.buf; 871 if (allocation->allocation_type == NINE_EXTERNAL_ALLOC) 872 return allocation->memory.external.buf; 873 874 memfd_file = nine_get_memfd_file_backing(allocation); 875 region = nine_get_memfd_region_backing(allocation); 876 if (!nine_memfd_region_map(allocator, memfd_file, region)) { 877 DBG("Couldn't map memfd region for get_pointer\n"); 878 return NULL; 879 } 880 881 move_region(&memfd_file->locked_mapped_allocated_regions, region); /* Note: redundant if region->num_locks */ 882 region->num_locks++; 883 884 if (region->num_locks == 1) 885 allocator->total_locked_memory += region->size; 886 if (allocation->weak_unlock) 887 region->num_weak_unlocks--; 888 allocation->weak_unlock = false; 889 region->zero_filled = false; 890 891 892 if (allocation->allocation_type == NINE_MEMFD_ALLOC) 893 return region->map; 894 if (allocation->allocation_type == NINE_MEMFD_SUBALLOC) 895 return region->map + allocation->memory.submemfd.relative_offset; 896 897 assert(false); 898 return NULL; 899} 900 901/* Unlock an allocation, but with hint that we might lock again soon */ 902void 903nine_pointer_weakrelease(struct nine_allocator *allocator, struct nine_allocation *allocation) 904{ 905 struct nine_memfd_file_region *region; 906 if (allocation->allocation_type > NINE_MEMFD_SUBALLOC) 907 return; 908 909 region = nine_get_memfd_region_backing(allocation); 910 if (!allocation->weak_unlock) 911 region->num_weak_unlocks++; 912 allocation->weak_unlock = true; 913 region->num_locks--; 914 if (region->num_locks == 0) { 915 struct nine_memfd_file *memfd_file = nine_get_memfd_file_backing(allocation); 916 allocator->total_locked_memory -= region->size; 917 move_region(&memfd_file->weak_unlocked_mapped_allocated_regions, region); 918 } 919} 920 921/* Unlock an allocation */ 922void 923nine_pointer_strongrelease(struct nine_allocator *allocator, struct nine_allocation *allocation) 924{ 925 struct nine_memfd_file_region *region; 926 if (allocation->allocation_type > NINE_MEMFD_SUBALLOC) 927 return; 928 929 region = nine_get_memfd_region_backing(allocation); 930 region->num_locks--; 931 if (region->num_locks == 0) { 932 struct nine_memfd_file *memfd_file = nine_get_memfd_file_backing(allocation); 933 allocator->total_locked_memory -= region->size; 934 if (region->num_weak_unlocks) 935 move_region(&memfd_file->weak_unlocked_mapped_allocated_regions, region); 936 else 937 move_region(&memfd_file->unlocked_mapped_allocated_regions, region); 938 } 939} 940 941/* Delay a release to when a given counter becomes zero */ 942void 943nine_pointer_delayedstrongrelease(struct nine_allocator *allocator, struct nine_allocation *allocation, unsigned *counter) 944{ 945 if (allocation->allocation_type > NINE_MEMFD_SUBALLOC) 946 return; 947 948 assert(allocation->pending_counter == NULL || allocation->pending_counter == counter); 949 allocation->pending_counter = counter; 950 allocation->locks_on_counter++; 951 952 if (list_is_empty(&allocation->list_release)) 953 list_add(&allocation->list_release, &allocator->pending_releases); 954} 955 956/* Create a suballocation of an allocation */ 957struct nine_allocation * 958nine_suballocate(struct nine_allocator* allocator, struct nine_allocation *allocation, int offset) 959{ 960 struct nine_allocation *new_allocation = slab_alloc_st(&allocator->allocation_pool); 961 if (!new_allocation) 962 return NULL; 963 964 DBG("Suballocate allocation at offset: %d\n", offset); 965 assert(allocation->allocation_type != NINE_MEMFD_SUBALLOC); 966 list_inithead(&new_allocation->list_free); 967 968 if (allocation->allocation_type != NINE_MEMFD_ALLOC) { 969 new_allocation->allocation_type = NINE_EXTERNAL_ALLOC; 970 if (allocation->allocation_type == NINE_MALLOC_ALLOC) 971 new_allocation->memory.external.buf = allocation->memory.malloc.buf + offset; 972 else 973 new_allocation->memory.external.buf = allocation->memory.external.buf + offset; 974 return new_allocation; 975 } 976 new_allocation->allocation_type = NINE_MEMFD_SUBALLOC; 977 new_allocation->memory.submemfd.parent = &allocation->memory.memfd; 978 new_allocation->memory.submemfd.relative_offset = offset; 979 new_allocation->locks_on_counter = 0; 980 new_allocation->pending_counter = NULL; 981 new_allocation->weak_unlock = false; 982 list_inithead(&new_allocation->list_release); 983 debug_dump_allocation_state(new_allocation); 984 return new_allocation; 985} 986 987/* Wrap an external pointer as an allocation */ 988struct nine_allocation * 989nine_wrap_external_pointer(struct nine_allocator* allocator, void* data) 990{ 991 struct nine_allocation *new_allocation = slab_alloc_st(&allocator->allocation_pool); 992 if (!new_allocation) 993 return NULL; 994 DBG("Wrapping external pointer: %p\n", data); 995 new_allocation->allocation_type = NINE_EXTERNAL_ALLOC; 996 new_allocation->memory.external.buf = data; 997 list_inithead(&new_allocation->list_free); 998 return new_allocation; 999} 1000 1001struct nine_allocator * 1002nine_allocator_create(struct NineDevice9 *device, int memfd_virtualsizelimit) 1003{ 1004 struct nine_allocator* allocator = MALLOC(sizeof(struct nine_allocator)); 1005 1006 if (!allocator) 1007 return NULL; 1008 1009 allocator->device = device; 1010 allocator->page_size = sysconf(_SC_PAGESIZE); 1011 assert(allocator->page_size == 4 << 10); 1012 allocator->num_fd_max = (memfd_virtualsizelimit >= 0) ? MIN2(128, ulimit(__UL_GETOPENMAX)) : 0; 1013 allocator->min_file_size = DIVUP(100 * (1 << 20), allocator->page_size) * allocator->page_size; /* 100MB files */ 1014 allocator->total_allocations = 0; 1015 allocator->total_locked_memory = 0; 1016 allocator->total_virtual_memory = 0; 1017 allocator->total_virtual_memory_limit = memfd_virtualsizelimit * (1 << 20); 1018 allocator->num_fd = 0; 1019 1020 DBG("Allocator created (ps: %d; fm: %d)\n", allocator->page_size, allocator->num_fd_max); 1021 1022 slab_create(&allocator->allocation_pool, sizeof(struct nine_allocation), 4096); 1023 slab_create(&allocator->region_pool, sizeof(struct nine_memfd_file_region), 4096); 1024 allocator->memfd_pool = CALLOC(allocator->num_fd_max, sizeof(struct nine_memfd_file)); 1025 list_inithead(&allocator->pending_releases); 1026 list_inithead(&allocator->pending_frees); 1027 pthread_mutex_init(&allocator->mutex_pending_frees, NULL); 1028 return allocator; 1029} 1030 1031void 1032nine_allocator_destroy(struct nine_allocator* allocator) 1033{ 1034 int i; 1035 DBG("DESTROYING ALLOCATOR\n"); 1036 debug_dump_allocator_state(allocator); 1037 nine_flush_pending_releases(allocator); 1038 nine_flush_pending_frees(allocator); 1039 nine_memfd_files_unmap(allocator, true); 1040 pthread_mutex_destroy(&allocator->mutex_pending_frees); 1041 1042 assert(list_is_empty(&allocator->pending_frees)); 1043 assert(list_is_empty(&allocator->pending_releases)); 1044 for (i = 0; i < allocator->num_fd; i++) { 1045 debug_dump_memfd_state(&allocator->memfd_pool[i], true); 1046 assert(list_is_empty(&allocator->memfd_pool[i].locked_mapped_allocated_regions)); 1047 assert(list_is_empty(&allocator->memfd_pool[i].weak_unlocked_mapped_allocated_regions)); 1048 assert(list_is_empty(&allocator->memfd_pool[i].unlocked_mapped_allocated_regions)); 1049 assert(list_is_singular(&allocator->memfd_pool[i].free_regions)); 1050 slab_free_st(&allocator->region_pool, 1051 list_first_entry(&allocator->memfd_pool[i].free_regions, 1052 struct nine_memfd_file_region, list)); 1053 close(allocator->memfd_pool[i].fd); 1054 } 1055 slab_destroy(&allocator->allocation_pool); 1056 slab_destroy(&allocator->region_pool); 1057 FREE(allocator->memfd_pool); 1058 FREE(allocator); 1059} 1060 1061#else 1062 1063struct nine_allocation { 1064 unsigned is_external; 1065 void *external; 1066}; 1067 1068struct nine_allocator { 1069 struct slab_mempool external_allocation_pool; 1070 pthread_mutex_t mutex_slab; 1071}; 1072 1073struct nine_allocation * 1074nine_allocate(struct nine_allocator *allocator, unsigned size) 1075{ 1076 struct nine_allocation *allocation; 1077 (void)allocator; 1078 assert(sizeof(struct nine_allocation) <= NINE_ALLOCATION_ALIGNMENT); 1079 allocation = align_calloc(size + NINE_ALLOCATION_ALIGNMENT, NINE_ALLOCATION_ALIGNMENT); 1080 allocation->is_external = false; 1081 return allocation; 1082} 1083 1084 1085void nine_free(struct nine_allocator *allocator, struct nine_allocation *allocation) 1086{ 1087 if (allocation->is_external) { 1088 pthread_mutex_lock(&allocator->mutex_slab); 1089 slab_free_st(&allocator->external_allocation_pool, allocation); 1090 pthread_mutex_unlock(&allocator->mutex_slab); 1091 } else 1092 align_free(allocation); 1093} 1094 1095void nine_free_worker(struct nine_allocator *allocator, struct nine_allocation *allocation) 1096{ 1097 nine_free(allocator, allocation); 1098} 1099 1100void *nine_get_pointer(struct nine_allocator *allocator, struct nine_allocation *allocation) 1101{ 1102 (void)allocator; 1103 if (allocation->is_external) 1104 return allocation->external; 1105 return (uint8_t *)allocation + NINE_ALLOCATION_ALIGNMENT; 1106} 1107 1108void nine_pointer_weakrelease(struct nine_allocator *allocator, struct nine_allocation *allocation) 1109{ 1110 (void)allocator; 1111 (void)allocation; 1112} 1113 1114void nine_pointer_strongrelease(struct nine_allocator *allocator, struct nine_allocation *allocation) 1115{ 1116 (void)allocator; 1117 (void)allocation; 1118} 1119 1120void nine_pointer_delayedstrongrelease(struct nine_allocator *allocator, 1121 struct nine_allocation *allocation, 1122 unsigned *counter) 1123{ 1124 (void)allocator; 1125 (void)allocation; 1126 (void)counter; 1127} 1128 1129struct nine_allocation * 1130nine_suballocate(struct nine_allocator* allocator, struct nine_allocation *allocation, int offset) 1131{ 1132 struct nine_allocation *new_allocation; 1133 pthread_mutex_lock(&allocator->mutex_slab); 1134 new_allocation = slab_alloc_st(&allocator->external_allocation_pool); 1135 pthread_mutex_unlock(&allocator->mutex_slab); 1136 new_allocation->is_external = true; 1137 new_allocation->external = (uint8_t *)allocation + NINE_ALLOCATION_ALIGNMENT + offset; 1138 return new_allocation; 1139} 1140 1141struct nine_allocation * 1142nine_wrap_external_pointer(struct nine_allocator* allocator, void* data) 1143{ 1144 struct nine_allocation *new_allocation; 1145 pthread_mutex_lock(&allocator->mutex_slab); 1146 new_allocation = slab_alloc_st(&allocator->external_allocation_pool); 1147 pthread_mutex_unlock(&allocator->mutex_slab); 1148 new_allocation->is_external = true; 1149 new_allocation->external = data; 1150 return new_allocation; 1151} 1152 1153struct nine_allocator * 1154nine_allocator_create(struct NineDevice9 *device, int memfd_virtualsizelimit) 1155{ 1156 struct nine_allocator* allocator = MALLOC(sizeof(struct nine_allocator)); 1157 (void)device; 1158 (void)memfd_virtualsizelimit; 1159 1160 if (!allocator) 1161 return NULL; 1162 1163 slab_create(&allocator->external_allocation_pool, sizeof(struct nine_allocation), 4096); 1164 pthread_mutex_init(&allocator->mutex_slab, NULL); 1165 1166 return allocator; 1167} 1168 1169void 1170nine_allocator_destroy(struct nine_allocator *allocator) 1171{ 1172 slab_destroy(&allocator->external_allocation_pool); 1173 pthread_mutex_destroy(&allocator->mutex_slab); 1174} 1175 1176#endif /* NINE_ENABLE_MEMFD */ 1177