Path: blob/main/sys/contrib/openzfs/module/os/linux/spl/spl-tsd.c
48775 views
// SPDX-License-Identifier: GPL-2.0-or-later1/*2* Copyright (C) 2010 Lawrence Livermore National Security, LLC.3* Produced at Lawrence Livermore National Laboratory (cf, DISCLAIMER).4* Written by Brian Behlendorf <[email protected]>.5* UCRL-CODE-2351976*7* This file is part of the SPL, Solaris Porting Layer.8*9* The SPL is free software; you can redistribute it and/or modify it10* under the terms of the GNU General Public License as published by the11* Free Software Foundation; either version 2 of the License, or (at your12* option) any later version.13*14* The SPL is distributed in the hope that it will be useful, but WITHOUT15* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or16* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License17* for more details.18*19* You should have received a copy of the GNU General Public License along20* with the SPL. If not, see <http://www.gnu.org/licenses/>.21*22*23* Solaris Porting Layer (SPL) Thread Specific Data Implementation.24*25* Thread specific data has implemented using a hash table, this avoids26* the need to add a member to the task structure and allows maximum27* portability between kernels. This implementation has been optimized28* to keep the tsd_set() and tsd_get() times as small as possible.29*30* The majority of the entries in the hash table are for specific tsd31* entries. These entries are hashed by the product of their key and32* pid because by design the key and pid are guaranteed to be unique.33* Their product also has the desirable properly that it will be uniformly34* distributed over the hash bins providing neither the pid nor key is zero.35* Under linux the zero pid is always the init process and thus won't be36* used, and this implementation is careful to never to assign a zero key.37* By default the hash table is sized to 512 bins which is expected to38* be sufficient for light to moderate usage of thread specific data.39*40* The hash table contains two additional type of entries. They first41* type is entry is called a 'key' entry and it is added to the hash during42* tsd_create(). It is used to store the address of the destructor function43* and it is used as an anchor point. All tsd entries which use the same44* key will be linked to this entry. This is used during tsd_destroy() to45* quickly call the destructor function for all tsd associated with the key.46* The 'key' entry may be looked up with tsd_hash_search() by passing the47* key you wish to lookup and DTOR_PID constant as the pid.48*49* The second type of entry is called a 'pid' entry and it is added to the50* hash the first time a process set a key. The 'pid' entry is also used51* as an anchor and all tsd for the process will be linked to it. This52* list is using during tsd_exit() to ensure all registered destructors53* are run for the process. The 'pid' entry may be looked up with54* tsd_hash_search() by passing the PID_KEY constant as the key, and55* the process pid. Note that tsd_exit() is called by thread_exit()56* so if your using the Solaris thread API you should not need to call57* tsd_exit() directly.58*59*/6061#include <sys/kmem.h>62#include <sys/thread.h>63#include <sys/tsd.h>64#include <linux/hash.h>6566typedef struct tsd_hash_bin {67spinlock_t hb_lock;68struct hlist_head hb_head;69} tsd_hash_bin_t;7071typedef struct tsd_hash_table {72spinlock_t ht_lock;73uint_t ht_bits;74uint_t ht_key;75tsd_hash_bin_t *ht_bins;76} tsd_hash_table_t;7778typedef struct tsd_hash_entry {79uint_t he_key;80pid_t he_pid;81dtor_func_t he_dtor;82void *he_value;83struct hlist_node he_list;84struct list_head he_key_list;85struct list_head he_pid_list;86} tsd_hash_entry_t;8788static tsd_hash_table_t *tsd_hash_table = NULL;899091/*92* tsd_hash_search - searches hash table for tsd_hash_entry93* @table: hash table94* @key: search key95* @pid: search pid96*/97static tsd_hash_entry_t *98tsd_hash_search(tsd_hash_table_t *table, uint_t key, pid_t pid)99{100struct hlist_node *node = NULL;101tsd_hash_entry_t *entry;102tsd_hash_bin_t *bin;103ulong_t hash;104105hash = hash_long((ulong_t)key * (ulong_t)pid, table->ht_bits);106bin = &table->ht_bins[hash];107spin_lock(&bin->hb_lock);108hlist_for_each(node, &bin->hb_head) {109entry = list_entry(node, tsd_hash_entry_t, he_list);110if ((entry->he_key == key) && (entry->he_pid == pid)) {111spin_unlock(&bin->hb_lock);112return (entry);113}114}115116spin_unlock(&bin->hb_lock);117return (NULL);118}119120/*121* tsd_hash_dtor - call the destructor and free all entries on the list122* @work: list of hash entries123*124* For a list of entries which have all already been removed from the125* hash call their registered destructor then free the associated memory.126*/127static void128tsd_hash_dtor(struct hlist_head *work)129{130tsd_hash_entry_t *entry;131132while (!hlist_empty(work)) {133entry = hlist_entry(work->first, tsd_hash_entry_t, he_list);134hlist_del(&entry->he_list);135136if (entry->he_dtor && entry->he_pid != DTOR_PID)137entry->he_dtor(entry->he_value);138139kmem_free(entry, sizeof (tsd_hash_entry_t));140}141}142143/*144* tsd_hash_add - adds an entry to hash table145* @table: hash table146* @key: search key147* @pid: search pid148*149* The caller is responsible for ensuring the unique key/pid do not150* already exist in the hash table. This possible because all entries151* are thread specific thus a concurrent thread will never attempt to152* add this key/pid. Because multiple bins must be checked to add153* links to the dtor and pid entries the entire table is locked.154*/155static int156tsd_hash_add(tsd_hash_table_t *table, uint_t key, pid_t pid, void *value)157{158tsd_hash_entry_t *entry, *dtor_entry, *pid_entry;159tsd_hash_bin_t *bin;160ulong_t hash;161int rc = 0;162163ASSERT0P(tsd_hash_search(table, key, pid));164165/* New entry allocate structure, set value, and add to hash */166entry = kmem_alloc(sizeof (tsd_hash_entry_t), KM_PUSHPAGE);167if (entry == NULL)168return (ENOMEM);169170entry->he_key = key;171entry->he_pid = pid;172entry->he_value = value;173INIT_HLIST_NODE(&entry->he_list);174INIT_LIST_HEAD(&entry->he_key_list);175INIT_LIST_HEAD(&entry->he_pid_list);176177spin_lock(&table->ht_lock);178179/* Destructor entry must exist for all valid keys */180dtor_entry = tsd_hash_search(table, entry->he_key, DTOR_PID);181ASSERT3P(dtor_entry, !=, NULL);182entry->he_dtor = dtor_entry->he_dtor;183184/* Process entry must exist for all valid processes */185pid_entry = tsd_hash_search(table, PID_KEY, entry->he_pid);186ASSERT3P(pid_entry, !=, NULL);187188hash = hash_long((ulong_t)key * (ulong_t)pid, table->ht_bits);189bin = &table->ht_bins[hash];190spin_lock(&bin->hb_lock);191192/* Add to the hash, key, and pid lists */193hlist_add_head(&entry->he_list, &bin->hb_head);194list_add(&entry->he_key_list, &dtor_entry->he_key_list);195list_add(&entry->he_pid_list, &pid_entry->he_pid_list);196197spin_unlock(&bin->hb_lock);198spin_unlock(&table->ht_lock);199200return (rc);201}202203/*204* tsd_hash_add_key - adds a destructor entry to the hash table205* @table: hash table206* @keyp: search key207* @dtor: key destructor208*209* For every unique key there is a single entry in the hash which is used210* as anchor. All other thread specific entries for this key are linked211* to this anchor via the 'he_key_list' list head. On return they keyp212* will be set to the next available key for the hash table.213*/214static int215tsd_hash_add_key(tsd_hash_table_t *table, uint_t *keyp, dtor_func_t dtor)216{217tsd_hash_entry_t *tmp_entry, *entry;218tsd_hash_bin_t *bin;219ulong_t hash;220int keys_checked = 0;221222ASSERT3P(table, !=, NULL);223224/* Allocate entry to be used as a destructor for this key */225entry = kmem_alloc(sizeof (tsd_hash_entry_t), KM_PUSHPAGE);226if (entry == NULL)227return (ENOMEM);228229/* Determine next available key value */230spin_lock(&table->ht_lock);231do {232/* Limited to TSD_KEYS_MAX concurrent unique keys */233if (table->ht_key++ > TSD_KEYS_MAX)234table->ht_key = 1;235236/* Ensure failure when all TSD_KEYS_MAX keys are in use */237if (keys_checked++ >= TSD_KEYS_MAX) {238spin_unlock(&table->ht_lock);239return (ENOENT);240}241242tmp_entry = tsd_hash_search(table, table->ht_key, DTOR_PID);243} while (tmp_entry);244245/* Add destructor entry in to hash table */246entry->he_key = *keyp = table->ht_key;247entry->he_pid = DTOR_PID;248entry->he_dtor = dtor;249entry->he_value = NULL;250INIT_HLIST_NODE(&entry->he_list);251INIT_LIST_HEAD(&entry->he_key_list);252INIT_LIST_HEAD(&entry->he_pid_list);253254hash = hash_long((ulong_t)*keyp * (ulong_t)DTOR_PID, table->ht_bits);255bin = &table->ht_bins[hash];256spin_lock(&bin->hb_lock);257258hlist_add_head(&entry->he_list, &bin->hb_head);259260spin_unlock(&bin->hb_lock);261spin_unlock(&table->ht_lock);262263return (0);264}265266/*267* tsd_hash_add_pid - adds a process entry to the hash table268* @table: hash table269* @pid: search pid270*271* For every process there is a single entry in the hash which is used272* as anchor. All other thread specific entries for this process are273* linked to this anchor via the 'he_pid_list' list head.274*/275static int276tsd_hash_add_pid(tsd_hash_table_t *table, pid_t pid)277{278tsd_hash_entry_t *entry;279tsd_hash_bin_t *bin;280ulong_t hash;281282/* Allocate entry to be used as the process reference */283entry = kmem_alloc(sizeof (tsd_hash_entry_t), KM_PUSHPAGE);284if (entry == NULL)285return (ENOMEM);286287spin_lock(&table->ht_lock);288entry->he_key = PID_KEY;289entry->he_pid = pid;290entry->he_dtor = NULL;291entry->he_value = NULL;292INIT_HLIST_NODE(&entry->he_list);293INIT_LIST_HEAD(&entry->he_key_list);294INIT_LIST_HEAD(&entry->he_pid_list);295296hash = hash_long((ulong_t)PID_KEY * (ulong_t)pid, table->ht_bits);297bin = &table->ht_bins[hash];298spin_lock(&bin->hb_lock);299300hlist_add_head(&entry->he_list, &bin->hb_head);301302spin_unlock(&bin->hb_lock);303spin_unlock(&table->ht_lock);304305return (0);306}307308/*309* tsd_hash_del - delete an entry from hash table, key, and pid lists310* @table: hash table311* @key: search key312* @pid: search pid313*/314static void315tsd_hash_del(tsd_hash_table_t *table, tsd_hash_entry_t *entry)316{317hlist_del(&entry->he_list);318list_del_init(&entry->he_key_list);319list_del_init(&entry->he_pid_list);320}321322/*323* tsd_hash_table_init - allocate a hash table324* @bits: hash table size325*326* A hash table with 2^bits bins will be created, it may not be resized327* after the fact and must be free'd with tsd_hash_table_fini().328*/329static tsd_hash_table_t *330tsd_hash_table_init(uint_t bits)331{332tsd_hash_table_t *table;333int hash, size = (1 << bits);334335table = kmem_zalloc(sizeof (tsd_hash_table_t), KM_SLEEP);336if (table == NULL)337return (NULL);338339table->ht_bins = kmem_zalloc(sizeof (tsd_hash_bin_t) * size, KM_SLEEP);340if (table->ht_bins == NULL) {341kmem_free(table, sizeof (tsd_hash_table_t));342return (NULL);343}344345for (hash = 0; hash < size; hash++) {346spin_lock_init(&table->ht_bins[hash].hb_lock);347INIT_HLIST_HEAD(&table->ht_bins[hash].hb_head);348}349350spin_lock_init(&table->ht_lock);351table->ht_bits = bits;352table->ht_key = 1;353354return (table);355}356357/*358* tsd_hash_table_fini - free a hash table359* @table: hash table360*361* Free a hash table allocated by tsd_hash_table_init(). If the hash362* table is not empty this function will call the proper destructor for363* all remaining entries before freeing the memory used by those entries.364*/365static void366tsd_hash_table_fini(tsd_hash_table_t *table)367{368HLIST_HEAD(work);369tsd_hash_bin_t *bin;370tsd_hash_entry_t *entry;371int size, i;372373ASSERT3P(table, !=, NULL);374spin_lock(&table->ht_lock);375for (i = 0, size = (1 << table->ht_bits); i < size; i++) {376bin = &table->ht_bins[i];377spin_lock(&bin->hb_lock);378while (!hlist_empty(&bin->hb_head)) {379entry = hlist_entry(bin->hb_head.first,380tsd_hash_entry_t, he_list);381tsd_hash_del(table, entry);382hlist_add_head(&entry->he_list, &work);383}384spin_unlock(&bin->hb_lock);385}386spin_unlock(&table->ht_lock);387388tsd_hash_dtor(&work);389kmem_free(table->ht_bins, sizeof (tsd_hash_bin_t)*(1<<table->ht_bits));390kmem_free(table, sizeof (tsd_hash_table_t));391}392393/*394* tsd_remove_entry - remove a tsd entry for this thread395* @entry: entry to remove396*397* Remove the thread specific data @entry for this thread.398* If this is the last entry for this thread, also remove the PID entry.399*/400static void401tsd_remove_entry(tsd_hash_entry_t *entry)402{403HLIST_HEAD(work);404tsd_hash_table_t *table;405tsd_hash_entry_t *pid_entry;406tsd_hash_bin_t *pid_entry_bin, *entry_bin;407ulong_t hash;408409table = tsd_hash_table;410ASSERT3P(table, !=, NULL);411ASSERT3P(entry, !=, NULL);412413spin_lock(&table->ht_lock);414415hash = hash_long((ulong_t)entry->he_key *416(ulong_t)entry->he_pid, table->ht_bits);417entry_bin = &table->ht_bins[hash];418419/* save the possible pid_entry */420pid_entry = list_entry(entry->he_pid_list.next, tsd_hash_entry_t,421he_pid_list);422423/* remove entry */424spin_lock(&entry_bin->hb_lock);425tsd_hash_del(table, entry);426hlist_add_head(&entry->he_list, &work);427spin_unlock(&entry_bin->hb_lock);428429/* if pid_entry is indeed pid_entry, then remove it if it's empty */430if (pid_entry->he_key == PID_KEY &&431list_empty(&pid_entry->he_pid_list)) {432hash = hash_long((ulong_t)pid_entry->he_key *433(ulong_t)pid_entry->he_pid, table->ht_bits);434pid_entry_bin = &table->ht_bins[hash];435436spin_lock(&pid_entry_bin->hb_lock);437tsd_hash_del(table, pid_entry);438hlist_add_head(&pid_entry->he_list, &work);439spin_unlock(&pid_entry_bin->hb_lock);440}441442spin_unlock(&table->ht_lock);443444tsd_hash_dtor(&work);445}446447/*448* tsd_set - set thread specific data449* @key: lookup key450* @value: value to set451*452* Caller must prevent racing tsd_create() or tsd_destroy(), protected453* from racing tsd_get() or tsd_set() because it is thread specific.454* This function has been optimized to be fast for the update case.455* When setting the tsd initially it will be slower due to additional456* required locking and potential memory allocations.457*/458int459tsd_set(uint_t key, void *value)460{461tsd_hash_table_t *table;462tsd_hash_entry_t *entry;463pid_t pid;464int rc;465/* mark remove if value is NULL */466boolean_t remove = (value == NULL);467468table = tsd_hash_table;469pid = curthread->pid;470ASSERT3P(table, !=, NULL);471472if ((key == 0) || (key > TSD_KEYS_MAX))473return (EINVAL);474475/* Entry already exists in hash table update value */476entry = tsd_hash_search(table, key, pid);477if (entry) {478entry->he_value = value;479/* remove the entry */480if (remove)481tsd_remove_entry(entry);482return (0);483}484485/* don't create entry if value is NULL */486if (remove)487return (0);488489/* Add a process entry to the hash if not yet exists */490entry = tsd_hash_search(table, PID_KEY, pid);491if (entry == NULL) {492rc = tsd_hash_add_pid(table, pid);493if (rc)494return (rc);495}496497rc = tsd_hash_add(table, key, pid, value);498return (rc);499}500EXPORT_SYMBOL(tsd_set);501502/*503* tsd_get - get thread specific data504* @key: lookup key505*506* Caller must prevent racing tsd_create() or tsd_destroy(). This507* implementation is designed to be fast and scalable, it does not508* lock the entire table only a single hash bin.509*/510void *511tsd_get(uint_t key)512{513tsd_hash_entry_t *entry;514515ASSERT3P(tsd_hash_table, !=, NULL);516517if ((key == 0) || (key > TSD_KEYS_MAX))518return (NULL);519520entry = tsd_hash_search(tsd_hash_table, key, curthread->pid);521if (entry == NULL)522return (NULL);523524return (entry->he_value);525}526EXPORT_SYMBOL(tsd_get);527528/*529* tsd_get_by_thread - get thread specific data for specified thread530* @key: lookup key531* @thread: thread to lookup532*533* Caller must prevent racing tsd_create() or tsd_destroy(). This534* implementation is designed to be fast and scalable, it does not535* lock the entire table only a single hash bin.536*/537void *538tsd_get_by_thread(uint_t key, kthread_t *thread)539{540tsd_hash_entry_t *entry;541542ASSERT3P(tsd_hash_table, !=, NULL);543544if ((key == 0) || (key > TSD_KEYS_MAX))545return (NULL);546547entry = tsd_hash_search(tsd_hash_table, key, thread->pid);548if (entry == NULL)549return (NULL);550551return (entry->he_value);552}553EXPORT_SYMBOL(tsd_get_by_thread);554555/*556* tsd_create - create thread specific data key557* @keyp: lookup key address558* @dtor: destructor called during tsd_destroy() or tsd_exit()559*560* Provided key must be set to 0 or it assumed to be already in use.561* The dtor is allowed to be NULL in which case no additional cleanup562* for the data is performed during tsd_destroy() or tsd_exit().563*564* Caller must prevent racing tsd_set() or tsd_get(), this function is565* safe from racing tsd_create(), tsd_destroy(), and tsd_exit().566*/567void568tsd_create(uint_t *keyp, dtor_func_t dtor)569{570ASSERT3P(keyp, !=, NULL);571if (*keyp)572return;573574(void) tsd_hash_add_key(tsd_hash_table, keyp, dtor);575}576EXPORT_SYMBOL(tsd_create);577578/*579* tsd_destroy - destroy thread specific data580* @keyp: lookup key address581*582* Destroys the thread specific data on all threads which use this key.583*584* Caller must prevent racing tsd_set() or tsd_get(), this function is585* safe from racing tsd_create(), tsd_destroy(), and tsd_exit().586*/587void588tsd_destroy(uint_t *keyp)589{590HLIST_HEAD(work);591tsd_hash_table_t *table;592tsd_hash_entry_t *dtor_entry, *entry;593tsd_hash_bin_t *dtor_entry_bin, *entry_bin;594ulong_t hash;595596table = tsd_hash_table;597ASSERT3P(table, !=, NULL);598599spin_lock(&table->ht_lock);600dtor_entry = tsd_hash_search(table, *keyp, DTOR_PID);601if (dtor_entry == NULL) {602spin_unlock(&table->ht_lock);603return;604}605606/*607* All threads which use this key must be linked off of the608* DTOR_PID entry. They are removed from the hash table and609* linked in to a private working list to be destroyed.610*/611while (!list_empty(&dtor_entry->he_key_list)) {612entry = list_entry(dtor_entry->he_key_list.next,613tsd_hash_entry_t, he_key_list);614ASSERT3U(dtor_entry->he_key, ==, entry->he_key);615ASSERT3P(dtor_entry->he_dtor, ==, entry->he_dtor);616617hash = hash_long((ulong_t)entry->he_key *618(ulong_t)entry->he_pid, table->ht_bits);619entry_bin = &table->ht_bins[hash];620621spin_lock(&entry_bin->hb_lock);622tsd_hash_del(table, entry);623hlist_add_head(&entry->he_list, &work);624spin_unlock(&entry_bin->hb_lock);625}626627hash = hash_long((ulong_t)dtor_entry->he_key *628(ulong_t)dtor_entry->he_pid, table->ht_bits);629dtor_entry_bin = &table->ht_bins[hash];630631spin_lock(&dtor_entry_bin->hb_lock);632tsd_hash_del(table, dtor_entry);633hlist_add_head(&dtor_entry->he_list, &work);634spin_unlock(&dtor_entry_bin->hb_lock);635spin_unlock(&table->ht_lock);636637tsd_hash_dtor(&work);638*keyp = 0;639}640EXPORT_SYMBOL(tsd_destroy);641642/*643* tsd_exit - destroys all thread specific data for this thread644*645* Destroys all the thread specific data for this thread.646*647* Caller must prevent racing tsd_set() or tsd_get(), this function is648* safe from racing tsd_create(), tsd_destroy(), and tsd_exit().649*/650void651tsd_exit(void)652{653HLIST_HEAD(work);654tsd_hash_table_t *table;655tsd_hash_entry_t *pid_entry, *entry;656tsd_hash_bin_t *pid_entry_bin, *entry_bin;657ulong_t hash;658659table = tsd_hash_table;660ASSERT3P(table, !=, NULL);661662spin_lock(&table->ht_lock);663pid_entry = tsd_hash_search(table, PID_KEY, curthread->pid);664if (pid_entry == NULL) {665spin_unlock(&table->ht_lock);666return;667}668669/*670* All keys associated with this pid must be linked off of the671* PID_KEY entry. They are removed from the hash table and672* linked in to a private working list to be destroyed.673*/674675while (!list_empty(&pid_entry->he_pid_list)) {676entry = list_entry(pid_entry->he_pid_list.next,677tsd_hash_entry_t, he_pid_list);678ASSERT3U(pid_entry->he_pid, ==, entry->he_pid);679680hash = hash_long((ulong_t)entry->he_key *681(ulong_t)entry->he_pid, table->ht_bits);682entry_bin = &table->ht_bins[hash];683684spin_lock(&entry_bin->hb_lock);685tsd_hash_del(table, entry);686hlist_add_head(&entry->he_list, &work);687spin_unlock(&entry_bin->hb_lock);688}689690hash = hash_long((ulong_t)pid_entry->he_key *691(ulong_t)pid_entry->he_pid, table->ht_bits);692pid_entry_bin = &table->ht_bins[hash];693694spin_lock(&pid_entry_bin->hb_lock);695tsd_hash_del(table, pid_entry);696hlist_add_head(&pid_entry->he_list, &work);697spin_unlock(&pid_entry_bin->hb_lock);698spin_unlock(&table->ht_lock);699700tsd_hash_dtor(&work);701}702EXPORT_SYMBOL(tsd_exit);703704int705spl_tsd_init(void)706{707tsd_hash_table = tsd_hash_table_init(TSD_HASH_TABLE_BITS_DEFAULT);708if (tsd_hash_table == NULL)709return (-ENOMEM);710711return (0);712}713714void715spl_tsd_fini(void)716{717tsd_hash_table_fini(tsd_hash_table);718tsd_hash_table = NULL;719}720721722