| // SPDX-License-Identifier: GPL-2.0 |
| #include "threads.h" |
| #include "machine.h" |
| #include "thread.h" |
| |
| static struct threads_table_entry *threads__table(struct threads *threads, pid_t tid) |
| { |
| /* Cast it to handle tid == -1 */ |
| return &threads->table[(unsigned int)tid % THREADS__TABLE_SIZE]; |
| } |
| |
| static size_t key_hash(long key, void *ctx __maybe_unused) |
| { |
| /* The table lookup removes low bit entropy, but this is just ignored here. */ |
| return key; |
| } |
| |
| static bool key_equal(long key1, long key2, void *ctx __maybe_unused) |
| { |
| return key1 == key2; |
| } |
| |
| void threads__init(struct threads *threads) |
| { |
| for (int i = 0; i < THREADS__TABLE_SIZE; i++) { |
| struct threads_table_entry *table = &threads->table[i]; |
| |
| hashmap__init(&table->shard, key_hash, key_equal, NULL); |
| init_rwsem(&table->lock); |
| table->last_match = NULL; |
| } |
| } |
| |
| void threads__exit(struct threads *threads) |
| { |
| threads__remove_all_threads(threads); |
| for (int i = 0; i < THREADS__TABLE_SIZE; i++) { |
| struct threads_table_entry *table = &threads->table[i]; |
| |
| hashmap__clear(&table->shard); |
| exit_rwsem(&table->lock); |
| } |
| } |
| |
| size_t threads__nr(struct threads *threads) |
| { |
| size_t nr = 0; |
| |
| for (int i = 0; i < THREADS__TABLE_SIZE; i++) { |
| struct threads_table_entry *table = &threads->table[i]; |
| |
| down_read(&table->lock); |
| nr += hashmap__size(&table->shard); |
| up_read(&table->lock); |
| } |
| return nr; |
| } |
| |
| /* |
| * Front-end cache - TID lookups come in blocks, |
| * so most of the time we dont have to look up |
| * the full rbtree: |
| */ |
| static struct thread *__threads_table_entry__get_last_match(struct threads_table_entry *table, |
| pid_t tid) |
| { |
| struct thread *th, *res = NULL; |
| |
| th = table->last_match; |
| if (th != NULL) { |
| if (thread__tid(th) == tid) |
| res = thread__get(th); |
| } |
| return res; |
| } |
| |
| static void __threads_table_entry__set_last_match(struct threads_table_entry *table, |
| struct thread *th) |
| { |
| thread__put(table->last_match); |
| table->last_match = thread__get(th); |
| } |
| |
| static void threads_table_entry__set_last_match(struct threads_table_entry *table, |
| struct thread *th) |
| { |
| down_write(&table->lock); |
| __threads_table_entry__set_last_match(table, th); |
| up_write(&table->lock); |
| } |
| |
| struct thread *threads__find(struct threads *threads, pid_t tid) |
| { |
| struct threads_table_entry *table = threads__table(threads, tid); |
| struct thread *res; |
| |
| down_read(&table->lock); |
| res = __threads_table_entry__get_last_match(table, tid); |
| if (!res) { |
| if (hashmap__find(&table->shard, tid, &res)) |
| res = thread__get(res); |
| } |
| up_read(&table->lock); |
| if (res) |
| threads_table_entry__set_last_match(table, res); |
| return res; |
| } |
| |
| struct thread *threads__findnew(struct threads *threads, pid_t pid, pid_t tid, bool *created) |
| { |
| struct threads_table_entry *table = threads__table(threads, tid); |
| struct thread *res = NULL; |
| |
| *created = false; |
| down_write(&table->lock); |
| res = thread__new(pid, tid); |
| if (res) { |
| if (hashmap__add(&table->shard, tid, res)) { |
| /* Add failed. Assume a race so find other entry. */ |
| thread__put(res); |
| res = NULL; |
| if (hashmap__find(&table->shard, tid, &res)) |
| res = thread__get(res); |
| } else { |
| res = thread__get(res); |
| *created = true; |
| } |
| if (res) |
| __threads_table_entry__set_last_match(table, res); |
| } |
| up_write(&table->lock); |
| return res; |
| } |
| |
| void threads__remove_all_threads(struct threads *threads) |
| { |
| for (int i = 0; i < THREADS__TABLE_SIZE; i++) { |
| struct threads_table_entry *table = &threads->table[i]; |
| struct hashmap_entry *cur, *tmp; |
| size_t bkt; |
| |
| down_write(&table->lock); |
| __threads_table_entry__set_last_match(table, NULL); |
| hashmap__for_each_entry_safe((&table->shard), cur, tmp, bkt) { |
| struct thread *old_value; |
| |
| hashmap__delete(&table->shard, cur->key, /*old_key=*/NULL, &old_value); |
| thread__put(old_value); |
| } |
| up_write(&table->lock); |
| } |
| } |
| |
| void threads__remove(struct threads *threads, struct thread *thread) |
| { |
| struct threads_table_entry *table = threads__table(threads, thread__tid(thread)); |
| struct thread *old_value; |
| |
| down_write(&table->lock); |
| if (table->last_match && RC_CHK_EQUAL(table->last_match, thread)) |
| __threads_table_entry__set_last_match(table, NULL); |
| |
| hashmap__delete(&table->shard, thread__tid(thread), /*old_key=*/NULL, &old_value); |
| thread__put(old_value); |
| up_write(&table->lock); |
| } |
| |
| int threads__for_each_thread(struct threads *threads, |
| int (*fn)(struct thread *thread, void *data), |
| void *data) |
| { |
| for (int i = 0; i < THREADS__TABLE_SIZE; i++) { |
| struct threads_table_entry *table = &threads->table[i]; |
| struct hashmap_entry *cur; |
| size_t bkt; |
| |
| down_read(&table->lock); |
| hashmap__for_each_entry((&table->shard), cur, bkt) { |
| int rc = fn((struct thread *)cur->pvalue, data); |
| |
| if (rc != 0) { |
| up_read(&table->lock); |
| return rc; |
| } |
| } |
| up_read(&table->lock); |
| } |
| return 0; |
| |
| } |