History

bpf: Add mmap() support for BPF_MAP_TYPE_ARRAY

Add ability to memory-map contents of BPF array map. This is extremely useful for working with BPF global data from userspace programs. It allows to avoid typical bpf_map_{lookup,update}_elem operations, improving both performance and usability. There had to be special considerations for map freezing, to avoid having writable memory view into a frozen map. To solve this issue, map freezing and mmap-ing is happening under mutex now: - if map is already frozen, no writable mapping is allowed; - if map has writable memory mappings active (accounted in map->writecnt), map freezing will keep failing with -EBUSY; - once number of writable memory mappings drops to zero, map freezing can be performed again. Only non-per-CPU plain arrays are supported right now. Maps with spinlocks can't be memory mapped either. For BPF_F_MMAPABLE array, memory allocation has to be done through vmalloc() to be mmap()'able. We also need to make sure that array data memory is page-sized and page-aligned, so we over-allocate memory in such a way that struct bpf_array is at the end of a single page of memory with array->value being aligned with the start of the second page. On deallocation we need to accomodate this memory arrangement to free vmalloc()'ed memory correctly. One important consideration regarding how memory-mapping subsystem functions. Memory-mapping subsystem provides few optional callbacks, among them open() and close(). close() is called for each memory region that is unmapped, so that users can decrease their reference counters and free up resources, if necessary. open() is *almost* symmetrical: it's called for each memory region that is being mapped, **except** the very first one. So bpf_map_mmap does initial refcnt bump, while open() will do any extra ones after that. Thus number of close() calls is equal to number of open() calls plus one more.

Andrii Nakryiko committed 6 years ago

Commit fc970221 parent 85192db7 files · +183 −12Browse files

Showing the changes to kernel/bpf/arraymap.c (1 of 7 files). Show all

Unified · Split

kernel/bpf/arraymap.c+52−6
@@ -14,7 +14,7 @@ · 13 unmodified lines
14#include "map_in_map.h"14#include "map_in_map.h"
15 15
16#define ARRAY_CREATE_FLAG_MASK \16#define ARRAY_CREATE_FLAG_MASK \
17 (BPF_F_NUMA_NODE | BPF_F_ACCESS_MASK)17 (BPF_F_NUMA_NODE | BPF_F_MMAPABLE | BPF_F_ACCESS_MASK)
18 18
19static void bpf_array_free_percpu(struct bpf_array *array)19static void bpf_array_free_percpu(struct bpf_array *array)
20{20{
@@ -59,6 +59,10 @@ int array_map_alloc_check(union bpf_attr *attr) · 38 unmodified lines
59 (percpu && numa_node != NUMA_NO_NODE))59 (percpu && numa_node != NUMA_NO_NODE))
60 return -EINVAL;60 return -EINVAL;
61 61
62 if (attr->map_type != BPF_MAP_TYPE_ARRAY &&
63 attr->map_flags & BPF_F_MMAPABLE)
64 return -EINVAL;
65
62 if (attr->value_size > KMALLOC_MAX_SIZE)66 if (attr->value_size > KMALLOC_MAX_SIZE)
63 /* if value_size is bigger, the user space won't be able to67 /* if value_size is bigger, the user space won't be able to
64 * access the elements.68 * access the elements.
@@ -102,10 +106,19 @@ static struct bpf_map *array_map_alloc(union bpf_attr *attr) · 37 unmodified lines
102 }106 }
103 107
104 array_size = sizeof(*array);108 array_size = sizeof(*array);
105 if (percpu)109 if (percpu) {
106 array_size += (u64) max_entries * sizeof(void *);110 array_size += (u64) max_entries * sizeof(void *);
107 else111 } else {
108 array_size += (u64) max_entries * elem_size;112 /* rely on vmalloc() to return page-aligned memory and
113 * ensure array->value is exactly page-aligned
114 */
115 if (attr->map_flags & BPF_F_MMAPABLE) {
116 array_size = PAGE_ALIGN(array_size);
117 array_size += PAGE_ALIGN((u64) max_entries * elem_size);
118 } else {
119 array_size += (u64) max_entries * elem_size;
120 }
121 }
109 122
110 /* make sure there is no u32 overflow later in round_up() */123 /* make sure there is no u32 overflow later in round_up() */
111 cost = array_size;124 cost = array_size;
@@ -117,7 +130,20 @@ static struct bpf_map *array_map_alloc(union bpf_attr *attr) · 5 unmodified lines
117 return ERR_PTR(ret);130 return ERR_PTR(ret);
118 131
119 /* allocate all map elements and zero-initialize them */132 /* allocate all map elements and zero-initialize them */
120 array = bpf_map_area_alloc(array_size, numa_node);133 if (attr->map_flags & BPF_F_MMAPABLE) {
134 void *data;
135
136 /* kmalloc'ed memory can't be mmap'ed, use explicit vmalloc */
137 data = bpf_map_area_mmapable_alloc(array_size, numa_node);
138 if (!data) {
139 bpf_map_charge_finish(&mem);
140 return ERR_PTR(-ENOMEM);
141 }
142 array = data + PAGE_ALIGN(sizeof(struct bpf_array))
143 - offsetof(struct bpf_array, value);
144 } else {
145 array = bpf_map_area_alloc(array_size, numa_node);
146 }
121 if (!array) {147 if (!array) {
122 bpf_map_charge_finish(&mem);148 bpf_map_charge_finish(&mem);
123 return ERR_PTR(-ENOMEM);149 return ERR_PTR(-ENOMEM);
@@ -350,6 +376,11 @@ static int array_map_delete_elem(struct bpf_map *map, void *key) · 226 unmodified lines
350 return -EINVAL;376 return -EINVAL;
351}377}
352 378
379static void *array_map_vmalloc_addr(struct bpf_array *array)
380{
381 return (void *)round_down((unsigned long)array, PAGE_SIZE);
382}
383
353/* Called when map->refcnt goes to zero, either from workqueue or from syscall */384/* Called when map->refcnt goes to zero, either from workqueue or from syscall */
354static void array_map_free(struct bpf_map *map)385static void array_map_free(struct bpf_map *map)
355{386{
@@ -365,7 +396,10 @@ static void array_map_free(struct bpf_map *map) · 9 unmodified lines
365 if (array->map.map_type == BPF_MAP_TYPE_PERCPU_ARRAY)396 if (array->map.map_type == BPF_MAP_TYPE_PERCPU_ARRAY)
366 bpf_array_free_percpu(array);397 bpf_array_free_percpu(array);
367 398
368 bpf_map_area_free(array);399 if (array->map.map_flags & BPF_F_MMAPABLE)
400 bpf_map_area_free(array_map_vmalloc_addr(array));
401 else
402 bpf_map_area_free(array);
369}403}
370 404
371static void array_map_seq_show_elem(struct bpf_map *map, void *key,405static void array_map_seq_show_elem(struct bpf_map *map, void *key,
@@ -444,6 +478,17 @@ static int array_map_check_btf(const struct bpf_map *map, · 72 unmodified lines
444 return 0;478 return 0;
445}479}
446 480
481int array_map_mmap(struct bpf_map *map, struct vm_area_struct *vma)
482{
483 struct bpf_array *array = container_of(map, struct bpf_array, map);
484 pgoff_t pgoff = PAGE_ALIGN(sizeof(*array)) >> PAGE_SHIFT;
485
486 if (!(map->map_flags & BPF_F_MMAPABLE))
487 return -EINVAL;
488
489 return remap_vmalloc_range(vma, array_map_vmalloc_addr(array), pgoff);
490}
491
447const struct bpf_map_ops array_map_ops = {492const struct bpf_map_ops array_map_ops = {
448 .map_alloc_check = array_map_alloc_check,493 .map_alloc_check = array_map_alloc_check,
449 .map_alloc = array_map_alloc,494 .map_alloc = array_map_alloc,
@@ -455,6 +500,7 @@ const struct bpf_map_ops array_map_ops = { · 5 unmodified lines
455 .map_gen_lookup = array_map_gen_lookup,500 .map_gen_lookup = array_map_gen_lookup,
456 .map_direct_value_addr = array_map_direct_value_addr,501 .map_direct_value_addr = array_map_direct_value_addr,
457 .map_direct_value_meta = array_map_direct_value_meta,502 .map_direct_value_meta = array_map_direct_value_meta,
503 .map_mmap = array_map_mmap,
458 .map_seq_show_elem = array_map_seq_show_elem,504 .map_seq_show_elem = array_map_seq_show_elem,
459 .map_check_btf = array_map_check_btf,505 .map_check_btf = array_map_check_btf,
460};506};