for (i = 0; i < htab->n_buckets; i++) {
INIT_HLIST_NULLS_HEAD(&htab->buckets[i].head, i);
raw_res_spin_lock_init(&htab->buckets[i].raw_lock);
cond_resched();
}
}
staticstruct htab_elem *get_htab_elem(struct bpf_htab *htab, int i)
{ return (struct htab_elem *) (htab->elems + i * (u64)htab->elem_size);
}
/* Both percpu and fd htab support in-place update, so no need for *extraelem.LRUitselfcanremovetheleastusedelement,so *thereisnoneedforanextraelemduringmap_update.
*/ staticbool htab_has_extra_elems(struct bpf_htab *htab)
{ return !htab_is_percpu(htab) && !htab_is_lru(htab) && !is_fd_htab(htab);
}
if (IS_ERR_OR_NULL(htab->map.record)) return; if (htab_has_extra_elems(htab))
num_entries += num_possible_cpus(); for (i = 0; i < num_entries; i++) { struct htab_elem *elem;
elem = get_htab_elem(htab, i); if (htab_is_percpu(htab)) { void __percpu *pptr = htab_elem_get_ptr(elem, htab->map.key_size); int cpu;
if ((u64)attr->key_size + attr->value_size >= KMALLOC_MAX_SIZE - sizeof(struct htab_elem)) /* if key_size + value_size is bigger, the user space won't be *abletoaccesstheelementsviabpfsyscall.Thischeck *alsomakessurethattheelem_sizedoesn'toverflowandit's *kmalloc-ablelaterinhtab_map_update_elem()
*/ return -E2BIG; /* percpu map value size is bound by PCPU_MIN_UNIT_SIZE */ if (percpu && round_up(attr->value_size, 8) > PCPU_MIN_UNIT_SIZE) return -E2BIG;
return0;
}
staticstruct bpf_map *htab_map_alloc(union bpf_attr *attr)
{ bool percpu = (attr->map_type == BPF_MAP_TYPE_PERCPU_HASH ||
attr->map_type == BPF_MAP_TYPE_LRU_PERCPU_HASH); /* percpu_lru means each cpu has its own LRU list. *itisdifferentfromBPF_MAP_TYPE_PERCPU_HASHwhere *themap'svalueitselfispercpu.percpu_lruhas *nothingtodowiththemap'svalue.
*/ bool percpu_lru = (attr->map_flags & BPF_F_NO_COMMON_LRU); bool prealloc = !(attr->map_flags & BPF_F_NO_PREALLOC); struct bpf_htab *htab; int err;
htab = bpf_map_area_alloc(sizeof(*htab), NUMA_NO_NODE); if (!htab) return ERR_PTR(-ENOMEM);
bpf_map_init_from_attr(&htab->map, attr);
if (percpu_lru) { /* ensure each CPU's lru list has >=1 elements. *sinceweareatit,makeeachlrulisthasthesame *numberofelements.
*/
htab->map.max_entries = roundup(attr->max_entries,
num_possible_cpus()); if (htab->map.max_entries < attr->max_entries)
htab->map.max_entries = rounddown(attr->max_entries,
num_possible_cpus());
}
/* hash table size must be power of 2; roundup_pow_of_two() can overflow *intoUBon32-bitarches,socheckthatfirst
*/
err = -E2BIG; if (htab->map.max_entries > 1UL << 31) goto free_htab;
/* this lookup function can only be called with bucket lock taken */ staticstruct htab_elem *lookup_elem_raw(struct hlist_nulls_head *head, u32 hash, void *key, u32 key_size)
{ struct hlist_nulls_node *n; struct htab_elem *l;
hlist_nulls_for_each_entry_rcu(l, n, head, hash_node) if (l->hash == hash && !memcmp(&l->key, key, key_size)) return l;
return NULL;
}
/* can be called without bucket lock. it will repeat the loop in *theunlikelyeventwhenelementsmovedfromonebucketintoanother *whilelinklistisbeingwalked
*/ staticstruct htab_elem *lookup_nulls_elem_raw(struct hlist_nulls_head *head,
u32 hash, void *key,
u32 key_size, u32 n_buckets)
{ struct hlist_nulls_node *n; struct htab_elem *l;
again:
hlist_nulls_for_each_entry_rcu(l, n, head, hash_node) if (l->hash == hash && !memcmp(&l->key, key, key_size)) return l;
if (unlikely(get_nulls_value(n) != (hash & (n_buckets - 1)))) goto again;
return NULL;
}
/* Called from syscall or from eBPF program directly, so *argumentshavetomatchbpf_map_lookup_elem()exactly. *ThereturnvalueisadjustedbyBPFinstructions *inhtab_map_gen_lookup().
*/ staticvoid *__htab_map_lookup_elem(struct bpf_map *map, void *key)
{ struct bpf_htab *htab = container_of(map, struct bpf_htab, map); struct hlist_nulls_head *head; struct htab_elem *l;
u32 hash, key_size;
/* lookup the key */
l = lookup_nulls_elem_raw(head, hash, key, key_size, htab->n_buckets);
if (!l) goto find_first_elem;
/* key was found, get next key in the same bucket */
next_l = hlist_nulls_entry_safe(rcu_dereference_raw(hlist_nulls_next_rcu(&l->hash_node)), struct htab_elem, hash_node);
if (next_l) { /* if next elem in this hash list is non-zero, just return it */
memcpy(next_key, next_l->key, key_size); return0;
}
/* no more elements in this hash list, go to the next bucket */
i = hash & (htab->n_buckets - 1);
i++;
find_first_elem: /* iterate over buckets */ for (; i < htab->n_buckets; i++) {
head = select_bucket(htab, i);
/* pick first element in the bucket */
next_l = hlist_nulls_entry_safe(rcu_dereference_raw(hlist_nulls_first_rcu(head)), struct htab_elem, hash_node); if (next_l) { /* if it's not empty, just return it */
memcpy(next_key, next_l->key, key_size); return0;
}
}
/* iterated over all buckets and all elements */ return -ENOENT;
}
for_each_possible_cpu(cpu) {
copy_map_value_long(&htab->map, per_cpu_ptr(pptr, cpu), value + off);
off += size;
}
}
}
staticvoid pcpu_init_value(struct bpf_htab *htab, void __percpu *pptr, void *value, bool onallcpus)
{ /* When not setting the initial value on all cpus, zero-fill element *valuesforothercpus.Otherwise,bpfprogramhasnowaytoensure *knowninitialvaluesforcpusotherthancurrentone *(onallcpus=falsealwayswhencomingfrombpfprog).
*/ if (!onallcpus) { int current_cpu = raw_smp_processor_id(); int cpu;
for_each_possible_cpu(cpu) { if (cpu == current_cpu)
copy_map_value_long(&htab->map, per_cpu_ptr(pptr, cpu), value); else/* Since elem is preallocated, we cannot touch special fields */
zero_map_value(&htab->map, per_cpu_ptr(pptr, cpu));
}
} else {
pcpu_copy_value(htab, pptr, value, onallcpus);
}
}
if (unlikely(map_flags & BPF_F_LOCK)) { if (unlikely(!btf_record_has_field(map->record, BPF_SPIN_LOCK))) return -EINVAL; /* find an element without taking the bucket lock */
l_old = lookup_nulls_elem_raw(head, hash, key, key_size,
htab->n_buckets);
ret = check_flags(htab, l_old, map_flags); if (ret) return ret; if (l_old) { /* grab the element lock and update value in place */
copy_map_value_locked(map,
htab_elem_value(l_old, key_size),
value, false); return0;
} /* fall through, grab the bucket lock and lookup again. *99.9%chancethattheelementwon'tbefound, *butsecondlookupunderlockhastobedone.
*/
}
ret = htab_lock_bucket(b, &flags); if (ret) return ret;
ret = check_flags(htab, l_old, map_flags); if (ret) goto err;
if (unlikely(l_old && (map_flags & BPF_F_LOCK))) { /* first lookup without the bucket lock didn't find the element, *butsecondlookupwiththebucketlockfoundit. *Thiscaseishighlyunlikely,buthastobedealtwith: *grabtheelementlockinadditiontothebucketlock *andupdateelementinplace
*/
copy_map_value_locked(map,
htab_elem_value(l_old, key_size),
value, false);
ret = 0; goto err;
}
l_new = alloc_htab_elem(htab, key, value, key_size, hash, false, false,
l_old); if (IS_ERR(l_new)) { /* all pre-allocated elements are in use or memory exhausted */
ret = PTR_ERR(l_new); goto err;
}
/* add new element to the head of the list, so that *concurrentsearchwillfinditbeforeoldelem
*/
hlist_nulls_add_head_rcu(&l_new->hash_node, head); if (l_old) {
hlist_nulls_del_rcu(&l_old->hash_node);
/* l_old has already been stashed in htab->extra_elems, free *itsspecialfieldsbeforeitisavailableforreuse.
*/ if (htab_is_prealloc(htab))
check_and_free_fields(htab, l_old);
}
htab_unlock_bucket(b, flags); if (l_old && !htab_is_prealloc(htab))
free_htab_elem(htab, l_old); return0;
err:
htab_unlock_bucket(b, flags); return ret;
}
/* For LRU, we need to alloc before taking bucket's *spinlockbecausegettingfreenodesfromLRUmayneed *toremoveolderelementsfromhtabandthisremoval *operationwillneedabucketlock.
*/
l_new = prealloc_lru_pop(htab, key, hash); if (!l_new) return -ENOMEM;
copy_map_value(&htab->map, htab_elem_value(l_new, map->key_size), value);
ret = htab_lock_bucket(b, &flags); if (ret) goto err_lock_bucket;
ret = check_flags(htab, l_old, map_flags); if (ret) goto err;
/* add new element to the head of the list, so that *concurrentsearchwillfinditbeforeoldelem
*/
hlist_nulls_add_head_rcu(&l_new->hash_node, head); if (l_old) {
bpf_lru_node_set_ref(&l_new->lru_node);
hlist_nulls_del_rcu(&l_old->hash_node);
}
ret = 0;
err:
htab_unlock_bucket(b, flags);
err_lock_bucket: if (ret)
htab_lru_push_free(htab, l_new); elseif (l_old)
htab_lru_push_free(htab, l_old);
/* For LRU, we need to alloc before taking bucket's *spinlockbecauseLRU'selemallocmayneed *toremoveolderelemfromhtabandthisremoval *operationwillneedabucketlock.
*/ if (map_flags != BPF_EXIST) {
l_new = prealloc_lru_pop(htab, key, hash); if (!l_new) return -ENOMEM;
}
ret = htab_lock_bucket(b, &flags); if (ret) goto err_lock_bucket;
hash = htab_map_hash(key, key_size, htab->hashrnd);
b = __select_bucket(htab, hash);
head = &b->head;
ret = htab_lock_bucket(b, &flags); if (ret) return ret;
l = lookup_elem_raw(head, hash, key, key_size);
if (l)
hlist_nulls_del_rcu(&l->hash_node); else
ret = -ENOENT;
htab_unlock_bucket(b, flags); if (l)
htab_lru_push_free(htab, l); return ret;
}
staticvoid delete_all_elements(struct bpf_htab *htab)
{ int i;
/* It's called from a worker thread and migration has been disabled, *therefore,itisOKtoinvokebpf_mem_cache_free()directly.
*/ for (i = 0; i < htab->n_buckets; i++) { struct hlist_nulls_head *head = select_bucket(htab, i); struct hlist_nulls_node *n; struct htab_elem *l;
staticvoid htab_free_malloced_timers_and_wq(struct bpf_htab *htab)
{ int i;
rcu_read_lock(); for (i = 0; i < htab->n_buckets; i++) { struct hlist_nulls_head *head = select_bucket(htab, i); struct hlist_nulls_node *n; struct htab_elem *l;
hlist_nulls_for_each_entry(l, n, head, hash_node) { /* We only free timer on uref dropping to zero */ if (btf_record_has_field(htab->map.record, BPF_TIMER))
bpf_obj_free_timer(htab->map.record,
htab_elem_value(l, htab->map.key_size)); if (btf_record_has_field(htab->map.record, BPF_WORKQUEUE))
bpf_obj_free_workqueue(htab->map.record,
htab_elem_value(l, htab->map.key_size));
}
cond_resched_rcu();
}
rcu_read_unlock();
}
/* We only free timer and workqueue on uref dropping to zero */ if (btf_record_has_field(htab->map.record, BPF_TIMER | BPF_WORKQUEUE)) { if (!htab_is_prealloc(htab))
htab_free_malloced_timers_and_wq(htab); else
htab_free_prealloced_timers_and_wq(htab);
}
}
/* Called when map->refcnt goes to zero, either from workqueue or from syscall */ staticvoid htab_map_free(struct bpf_map *map)
{ struct bpf_htab *htab = container_of(map, struct bpf_htab, map);
/* bpf_free_used_maps() or close(map_fd) will trigger this map_free callback. *bpf_free_used_maps()iscalledafterbpfprogisnolongerexecuting. *Thereisnoneedtosynchronize_rcu()heretoprotectmapelements.
*/
/* htab no longer uses call_rcu() directly. bpf_mem_alloc does it *underneathandisresponsibleforwaitingforcallbackstofinish *duringbpf_mem_alloc_destroy().
*/ if (!htab_is_prealloc(htab)) {
delete_all_elements(htab);
} else {
htab_free_prealloced_fields(htab);
prealloc_destroy(htab);
}
bpf_map_free_elem_count(map);
free_percpu(htab->extra_elems);
bpf_map_area_free(htab->buckets);
bpf_mem_alloc_destroy(&htab->pcpu_ma);
bpf_mem_alloc_destroy(&htab->ma); if (htab->use_percpu_counter)
percpu_counter_destroy(&htab->pcount);
bpf_map_area_free(htab);
}
key_size = htab->map.key_size;
value_size = htab->map.value_size;
size = round_up(value_size, 8); if (is_percpu)
value_size = size * num_possible_cpus();
total = 0; /* while experimenting with hash tables with sizes ranging from 10 to *1000,itwasobservedthatabucketcanhaveupto5entries.
*/
bucket_size = 5;
alloc: /* We cannot do copy_from_user or copy_to_user inside *thercu_read_lock.Allocateenoughspacehere.
*/
keys = kvmalloc_array(key_size, bucket_size, GFP_USER | __GFP_NOWARN);
values = kvmalloc_array(value_size, bucket_size, GFP_USER | __GFP_NOWARN); if (!keys || !values) {
ret = -ENOMEM; goto after_loop;
}
again:
bpf_disable_instrumentation();
rcu_read_lock();
again_nocopy:
dst_key = keys;
dst_val = values;
b = &htab->buckets[batch];
head = &b->head; /* do not grab the lock unless need it (bucket_cnt > 0). */ if (locked) {
ret = htab_lock_bucket(b, &flags); if (ret) {
rcu_read_unlock();
bpf_enable_instrumentation(); goto after_loop;
}
}
bucket_cnt = 0;
hlist_nulls_for_each_entry_rcu(l, n, head, hash_node)
bucket_cnt++;
if (bucket_cnt > (max_count - total)) { if (total == 0)
ret = -ENOSPC; /* Note that since bucket_cnt > 0 here, it is implicit *thatthelockedwasgrabbed,soreleaseit.
*/
htab_unlock_bucket(b, flags);
rcu_read_unlock();
bpf_enable_instrumentation(); goto after_loop;
}
if (bucket_cnt > bucket_size) {
bucket_size = bucket_cnt; /* Note that since bucket_cnt > 0 here, it is implicit *thatthelockedwasgrabbed,soreleaseit.
*/
htab_unlock_bucket(b, flags);
rcu_read_unlock();
bpf_enable_instrumentation();
kvfree(keys);
kvfree(values); goto alloc;
}
/* Next block is only safe to run if you have grabbed the lock */ if (!locked) goto next_batch;
hlist_nulls_for_each_entry_safe(l, n, head, hash_node) {
memcpy(dst_key, l->key, key_size);
if (is_percpu) { int off = 0, cpu; void __percpu *pptr;
while (node_to_free) {
l = node_to_free;
node_to_free = node_to_free->batch_flink; if (is_lru_map)
htab_lru_push_free(htab, l); else
free_htab_elem(htab, l);
}
next_batch: /* If we are not copying data, we can go to next bucket and avoid *unlockingthercu.
*/ if (!bucket_cnt && (batch + 1 < htab->n_buckets)) {
batch++; goto again_nocopy;
}
rcu_read_unlock();
bpf_enable_instrumentation(); if (bucket_cnt && (copy_to_user(ukeys + total * key_size, keys,
key_size * bucket_cnt) ||
copy_to_user(uvalues + total * value_size, values,
value_size * bucket_cnt))) {
ret = -EFAULT; goto after_loop;
}
total += bucket_cnt;
batch++; if (batch >= htab->n_buckets) {
ret = -ENOENT; goto after_loop;
} goto again;
after_loop: if (ret == -EFAULT) goto out;
/* copy # of entries and next batch */
ubatch = u64_to_user_ptr(attr->batch.out_batch); if (copy_to_user(ubatch, &batch, sizeof(batch)) ||
put_user(total, &uattr->batch.count))
ret = -EFAULT;
/* try to find next elem in the same bucket */ if (prev_elem) { /* no update/deletion on this bucket, prev_elem should be still valid *andwewon'tskipelements.
*/
n = rcu_dereference_raw(hlist_nulls_next_rcu(&prev_elem->hash_node));
elem = hlist_nulls_entry_safe(n, struct htab_elem, hash_node); if (elem) return elem;
/* not found, unlock and go to the next bucket */
b = &htab->buckets[bucket_id++];
rcu_read_unlock();
skip_elems = 0;
}
for (i = bucket_id; i < htab->n_buckets; i++) {
b = &htab->buckets[i];
rcu_read_lock();
count = 0;
head = &b->head;
hlist_nulls_for_each_entry_rcu(elem, n, head, hash_node) { if (count >= skip_elems) {
info->bucket_id = i;
info->skip_elems = count; return elem;
}
count++;
}
/* migration has been disabled, so percpu value prepared here will be *thesameastheoneseenbythebpfprogramwith *bpf_map_lookup_elem().
*/ for (i = 0; i < htab->n_buckets; i++) {
b = &htab->buckets[i];
rcu_read_lock();
head = &b->head;
hlist_nulls_for_each_entry_safe(elem, n, head, hash_node) {
key = elem->key; if (is_percpu) { /* current cpu value for percpu map */
pptr = htab_elem_get_ptr(elem, map->key_size);
val = this_cpu_ptr(pptr);
} else {
val = htab_elem_value(elem, map->key_size);
}
num_elems++;
ret = callback_fn((u64)(long)map, (u64)(long)key,
(u64)(long)val, (u64)(long)callback_ctx, 0); /* return value: 0 - continue, 1 - stop and return */ if (ret) {
rcu_read_unlock(); goto out;
}
}
rcu_read_unlock();
}
out: return num_elems;
}
l = __htab_map_lookup_elem(map, key); if (l) {
bpf_lru_node_set_ref(&l->lru_node); return per_cpu_ptr(htab_elem_get_ptr(l, map->key_size), cpu);
}
return NULL;
}
int bpf_percpu_hash_copy(struct bpf_map *map, void *key, void *value)
{ struct htab_elem *l; void __percpu *pptr; int ret = -ENOENT; int cpu, off = 0;
u32 size;
/* per_cpu areas are zero-filled and bpf programs can only *access'value_size'ofthem,socopyingroundedareas *willnotleakanykerneldata
*/
size = round_up(map->value_size, 8);
rcu_read_lock();
l = __htab_map_lookup_elem(map, key); if (!l) goto out; /* We do not mark LRU map element here in order to not mess up *evictionheuristicswhenuserspacedoesamapwalk.
*/
pptr = htab_elem_get_ptr(l, map->key_size);
for_each_possible_cpu(cpu) {
copy_map_value_long(map, value + off, per_cpu_ptr(pptr, cpu));
check_and_init_map_value(map, value + off);
off += size;
}
ret = 0;
out:
rcu_read_unlock(); return ret;
}
for (i = 0; i < htab->n_buckets; i++) {
head = select_bucket(htab, i);
hlist_nulls_for_each_entry_safe(l, n, head, hash_node) { void *ptr = fd_htab_map_get_ptr(map, l);
map->ops->map_fd_put_ptr(map, ptr, false);
}
}
htab_map_free(map);
}
/* only called from syscall */ int bpf_fd_htab_map_lookup_elem(struct bpf_map *map, void *key, u32 *value)
{ void **ptr; int ret = 0;
if (!map->ops->map_fd_sys_lookup_elem) return -ENOTSUPP;
rcu_read_lock();
ptr = htab_map_lookup_elem(map, key); if (ptr)
*value = map->ops->map_fd_sys_lookup_elem(READ_ONCE(*ptr)); else
ret = -ENOENT;
rcu_read_unlock();
return ret;
}
/* Only called from syscall */ int bpf_fd_htab_map_update_elem(struct bpf_map *map, struct file *map_file, void *key, void *value, u64 map_flags)
{ void *ptr; int ret;
ptr = map->ops->map_fd_get_ptr(map, map_file, *(int *)value); if (IS_ERR(ptr)) return PTR_ERR(ptr);
/* The htab bucket lock is always held during update operations in fd *htabmap,andthefollowingrcu_read_lock()isonlyusedtoavoid *theWARN_ON_ONCEinhtab_map_update_elem_in_place().
*/
rcu_read_lock();
ret = htab_map_update_elem_in_place(map, key, &ptr, map_flags, false, false);
rcu_read_unlock(); if (ret)
map->ops->map_fd_put_ptr(map, ptr, false);
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.