@ -32,6 +32,11 @@
# define HASH_MIN_SIZE 4U
# define BUCKET_LOCKS_PER_CPU 32UL
union nested_table {
union nested_table __rcu * table ;
struct rhash_head __rcu * bucket ;
} ;
static u32 head_hashfn ( struct rhashtable * ht ,
const struct bucket_table * tbl ,
const struct rhash_head * he )
@ -76,6 +81,9 @@ static int alloc_bucket_locks(struct rhashtable *ht, struct bucket_table *tbl,
/* Never allocate more than 0.5 locks per bucket */
size = min_t ( unsigned int , size , tbl - > size > > 1 ) ;
if ( tbl - > nest )
size = min ( size , 1U < < tbl - > nest ) ;
if ( sizeof ( spinlock_t ) ! = 0 ) {
tbl - > locks = NULL ;
# ifdef CONFIG_NUMA
@ -99,8 +107,45 @@ static int alloc_bucket_locks(struct rhashtable *ht, struct bucket_table *tbl,
return 0 ;
}
static void nested_table_free ( union nested_table * ntbl , unsigned int size )
{
const unsigned int shift = PAGE_SHIFT - ilog2 ( sizeof ( void * ) ) ;
const unsigned int len = 1 < < shift ;
unsigned int i ;
ntbl = rcu_dereference_raw ( ntbl - > table ) ;
if ( ! ntbl )
return ;
if ( size > len ) {
size > > = shift ;
for ( i = 0 ; i < len ; i + + )
nested_table_free ( ntbl + i , size ) ;
}
kfree ( ntbl ) ;
}
static void nested_bucket_table_free ( const struct bucket_table * tbl )
{
unsigned int size = tbl - > size > > tbl - > nest ;
unsigned int len = 1 < < tbl - > nest ;
union nested_table * ntbl ;
unsigned int i ;
ntbl = ( union nested_table * ) rcu_dereference_raw ( tbl - > buckets [ 0 ] ) ;
for ( i = 0 ; i < len ; i + + )
nested_table_free ( ntbl + i , size ) ;
kfree ( ntbl ) ;
}
static void bucket_table_free ( const struct bucket_table * tbl )
{
if ( tbl - > nest )
nested_bucket_table_free ( tbl ) ;
if ( tbl )
kvfree ( tbl - > locks ) ;
@ -112,6 +157,59 @@ static void bucket_table_free_rcu(struct rcu_head *head)
bucket_table_free ( container_of ( head , struct bucket_table , rcu ) ) ;
}
static union nested_table * nested_table_alloc ( struct rhashtable * ht ,
union nested_table __rcu * * prev ,
unsigned int shifted ,
unsigned int nhash )
{
union nested_table * ntbl ;
int i ;
ntbl = rcu_dereference ( * prev ) ;
if ( ntbl )
return ntbl ;
ntbl = kzalloc ( PAGE_SIZE , GFP_ATOMIC ) ;
if ( ntbl & & shifted ) {
for ( i = 0 ; i < PAGE_SIZE / sizeof ( ntbl [ 0 ] . bucket ) ; i + + )
INIT_RHT_NULLS_HEAD ( ntbl [ i ] . bucket , ht ,
( i < < shifted ) | nhash ) ;
}
rcu_assign_pointer ( * prev , ntbl ) ;
return ntbl ;
}
static struct bucket_table * nested_bucket_table_alloc ( struct rhashtable * ht ,
size_t nbuckets ,
gfp_t gfp )
{
const unsigned int shift = PAGE_SHIFT - ilog2 ( sizeof ( void * ) ) ;
struct bucket_table * tbl ;
size_t size ;
if ( nbuckets < ( 1 < < ( shift + 1 ) ) )
return NULL ;
size = sizeof ( * tbl ) + sizeof ( tbl - > buckets [ 0 ] ) ;
tbl = kzalloc ( size , gfp ) ;
if ( ! tbl )
return NULL ;
if ( ! nested_table_alloc ( ht , ( union nested_table __rcu * * ) tbl - > buckets ,
0 , 0 ) ) {
kfree ( tbl ) ;
return NULL ;
}
tbl - > nest = ( ilog2 ( nbuckets ) - 1 ) % shift + 1 ;
return tbl ;
}
static struct bucket_table * bucket_table_alloc ( struct rhashtable * ht ,
size_t nbuckets ,
gfp_t gfp )
@ -126,10 +224,17 @@ static struct bucket_table *bucket_table_alloc(struct rhashtable *ht,
tbl = kzalloc ( size , gfp | __GFP_NOWARN | __GFP_NORETRY ) ;
if ( tbl = = NULL & & gfp = = GFP_KERNEL )
tbl = vzalloc ( size ) ;
size = nbuckets ;
if ( tbl = = NULL & & gfp ! = GFP_KERNEL ) {
tbl = nested_bucket_table_alloc ( ht , nbuckets , gfp ) ;
nbuckets = 0 ;
}
if ( tbl = = NULL )
return NULL ;
tbl - > size = nbuckets ;
tbl - > size = size ;
if ( alloc_bucket_locks ( ht , tbl , gfp ) < 0 ) {
bucket_table_free ( tbl ) ;
@ -164,12 +269,17 @@ static int rhashtable_rehash_one(struct rhashtable *ht, unsigned int old_hash)
struct bucket_table * old_tbl = rht_dereference ( ht - > tbl , ht ) ;
struct bucket_table * new_tbl = rhashtable_last_table ( ht ,
rht_dereference_rcu ( old_tbl - > future_tbl , ht ) ) ;
struct rhash_head __rcu * * pprev = & old_tbl - > buckets [ old_hash ] ;
int err = - ENOENT ;
struct rhash_head __rcu * * pprev = rht_bucket_var ( old_tbl , old_hash ) ;
int err = - EAGAIN ;
struct rhash_head * head , * next , * entry ;
spinlock_t * new_bucket_lock ;
unsigned int new_hash ;
if ( new_tbl - > nest )
goto out ;
err = - ENOENT ;
rht_for_each ( entry , old_tbl , old_hash ) {
err = 0 ;
next = rht_dereference_bucket ( entry - > next , old_tbl , old_hash ) ;
@ -202,19 +312,26 @@ out:
return err ;
}
static void rhashtable_rehash_chain ( struct rhashtable * ht ,
static int rhashtable_rehash_chain ( struct rhashtable * ht ,
unsigned int old_hash )
{
struct bucket_table * old_tbl = rht_dereference ( ht - > tbl , ht ) ;
spinlock_t * old_bucket_lock ;
int err ;
old_bucket_lock = rht_bucket_lock ( old_tbl , old_hash ) ;
spin_lock_bh ( old_bucket_lock ) ;
while ( ! rhashtable_rehash_one ( ht , old_hash ) )
while ( ! ( err = rhashtable_rehash_one ( ht , old_hash ) ) )
;
old_tbl - > rehash + + ;
if ( err = = - ENOENT ) {
old_tbl - > rehash + + ;
err = 0 ;
}
spin_unlock_bh ( old_bucket_lock ) ;
return err ;
}
static int rhashtable_rehash_attach ( struct rhashtable * ht ,
@ -246,13 +363,17 @@ static int rhashtable_rehash_table(struct rhashtable *ht)
struct bucket_table * new_tbl ;
struct rhashtable_walker * walker ;
unsigned int old_hash ;
int err ;
new_tbl = rht_dereference ( old_tbl - > future_tbl , ht ) ;
if ( ! new_tbl )
return 0 ;
for ( old_hash = 0 ; old_hash < old_tbl - > size ; old_hash + + )
rhashtable_rehash_chain ( ht , old_hash ) ;
for ( old_hash = 0 ; old_hash < old_tbl - > size ; old_hash + + ) {
err = rhashtable_rehash_chain ( ht , old_hash ) ;
if ( err )
return err ;
}
/* Publish the new table pointer. */
rcu_assign_pointer ( ht - > tbl , new_tbl ) ;
@ -271,31 +392,16 @@ static int rhashtable_rehash_table(struct rhashtable *ht)
return rht_dereference ( new_tbl - > future_tbl , ht ) ? - EAGAIN : 0 ;
}
/**
* rhashtable_expand - Expand hash table while allowing concurrent lookups
* @ ht : the hash table to expand
*
* A secondary bucket array is allocated and the hash entries are migrated .
*
* This function may only be called in a context where it is safe to call
* synchronize_rcu ( ) , e . g . not within a rcu_read_lock ( ) section .
*
* The caller must ensure that no concurrent resizing occurs by holding
* ht - > mutex .
*
* It is valid to have concurrent insertions and deletions protected by per
* bucket locks or concurrent RCU protected lookups and traversals .
*/
static int rhashtable_expand ( struct rhashtable * ht )
static int rhashtable_rehash_alloc ( struct rhashtable * ht ,
struct bucket_table * old_tbl ,
unsigned int size )
{
struct bucket_table * new_tbl , * old_tbl = rht_dereference ( ht - > tbl , ht ) ;
struct bucket_table * new_tbl ;
int err ;
ASSERT_RHT_MUTEX ( ht ) ;
old_tbl = rhashtable_last_table ( ht , old_tbl ) ;
new_tbl = bucket_table_alloc ( ht , old_tbl - > size * 2 , GFP_KERNEL ) ;
new_tbl = bucket_table_alloc ( ht , size , GFP_KERNEL ) ;
if ( new_tbl = = NULL )
return - ENOMEM ;
@ -324,12 +430,9 @@ static int rhashtable_expand(struct rhashtable *ht)
*/
static int rhashtable_shrink ( struct rhashtable * ht )
{
struct bucket_table * new_tbl , * old_tbl = rht_dereference ( ht - > tbl , ht ) ;
struct bucket_table * old_tbl = rht_dereference ( ht - > tbl , ht ) ;
unsigned int nelems = atomic_read ( & ht - > nelems ) ;
unsigned int size = 0 ;
int err ;
ASSERT_RHT_MUTEX ( ht ) ;
if ( nelems )
size = roundup_pow_of_two ( nelems * 3 / 2 ) ;
@ -342,15 +445,7 @@ static int rhashtable_shrink(struct rhashtable *ht)
if ( rht_dereference ( old_tbl - > future_tbl , ht ) )
return - EEXIST ;
new_tbl = bucket_table_alloc ( ht , size , GFP_KERNEL ) ;
if ( new_tbl = = NULL )
return - ENOMEM ;
err = rhashtable_rehash_attach ( ht , old_tbl , new_tbl ) ;
if ( err )
bucket_table_free ( new_tbl ) ;
return err ;
return rhashtable_rehash_alloc ( ht , old_tbl , size ) ;
}
static void rht_deferred_worker ( struct work_struct * work )
@ -366,11 +461,14 @@ static void rht_deferred_worker(struct work_struct *work)
tbl = rhashtable_last_table ( ht , tbl ) ;
if ( rht_grow_above_75 ( ht , tbl ) )
rhashtable_expand ( ht ) ;
err = rhashtable_rehash_alloc ( ht , tbl , tbl - > size * 2 ) ;
else if ( ht - > p . automatic_shrinking & & rht_shrink_below_30 ( ht , tbl ) )
rhashtable_shrink ( ht ) ;
err = rhashtable_shrink ( ht ) ;
else if ( tbl - > nest )
err = rhashtable_rehash_alloc ( ht , tbl , tbl - > size ) ;
err = rhashtable_rehash_table ( ht ) ;
if ( ! err )
err = rhashtable_rehash_table ( ht ) ;
mutex_unlock ( & ht - > mutex ) ;
@ -439,8 +537,8 @@ static void *rhashtable_lookup_one(struct rhashtable *ht,
int elasticity ;
elasticity = ht - > elasticity ;
pprev = & tbl - > buckets [ hash ] ;
rht_for_each ( head , tbl , hash ) {
pprev = rht_bucket_var ( tbl , hash ) ;
rht_for_each_continue ( head , * pprev , tbl , hash ) {
struct rhlist_head * list ;
struct rhlist_head * plist ;
@ -477,6 +575,7 @@ static struct bucket_table *rhashtable_insert_one(struct rhashtable *ht,
struct rhash_head * obj ,
void * data )
{
struct rhash_head __rcu * * pprev ;
struct bucket_table * new_tbl ;
struct rhash_head * head ;
@ -499,7 +598,11 @@ static struct bucket_table *rhashtable_insert_one(struct rhashtable *ht,
if ( unlikely ( rht_grow_above_100 ( ht , tbl ) ) )
return ERR_PTR ( - EAGAIN ) ;
head = rht_dereference_bucket ( tbl - > buckets [ hash ] , tbl , hash ) ;
pprev = rht_bucket_insert ( ht , tbl , hash ) ;
if ( ! pprev )
return ERR_PTR ( - ENOMEM ) ;
head = rht_dereference_bucket ( * pprev , tbl , hash ) ;
RCU_INIT_POINTER ( obj - > next , head ) ;
if ( ht - > rhlist ) {
@ -509,7 +612,7 @@ static struct bucket_table *rhashtable_insert_one(struct rhashtable *ht,
RCU_INIT_POINTER ( list - > next , NULL ) ;
}
rcu_assign_pointer ( tbl - > buckets [ hash ] , obj ) ;
rcu_assign_pointer ( * pprev , obj ) ;
atomic_inc ( & ht - > nelems ) ;
if ( rht_grow_above_75 ( ht , tbl ) )
@ -975,7 +1078,7 @@ void rhashtable_free_and_destroy(struct rhashtable *ht,
void ( * free_fn ) ( void * ptr , void * arg ) ,
void * arg )
{
const struct bucket_table * tbl ;
struct bucket_table * tbl ;
unsigned int i ;
cancel_work_sync ( & ht - > run_work ) ;
@ -986,7 +1089,7 @@ void rhashtable_free_and_destroy(struct rhashtable *ht,
for ( i = 0 ; i < tbl - > size ; i + + ) {
struct rhash_head * pos , * next ;
for ( pos = rht_dereference ( tbl - > buckets [ i ] , ht ) ,
for ( pos = rht_dereference ( * rht_bucket ( tbl , i ) , ht ) ,
next = ! rht_is_a_nulls ( pos ) ?
rht_dereference ( pos - > next , ht ) : NULL ;
! rht_is_a_nulls ( pos ) ;
@ -1007,3 +1110,70 @@ void rhashtable_destroy(struct rhashtable *ht)
return rhashtable_free_and_destroy ( ht , NULL , NULL ) ;
}
EXPORT_SYMBOL_GPL ( rhashtable_destroy ) ;
struct rhash_head __rcu * * rht_bucket_nested ( const struct bucket_table * tbl ,
unsigned int hash )
{
const unsigned int shift = PAGE_SHIFT - ilog2 ( sizeof ( void * ) ) ;
static struct rhash_head __rcu * rhnull =
( struct rhash_head __rcu * ) NULLS_MARKER ( 0 ) ;
unsigned int index = hash & ( ( 1 < < tbl - > nest ) - 1 ) ;
unsigned int size = tbl - > size > > tbl - > nest ;
unsigned int subhash = hash ;
union nested_table * ntbl ;
ntbl = ( union nested_table * ) rcu_dereference_raw ( tbl - > buckets [ 0 ] ) ;
ntbl = rht_dereference_bucket ( ntbl [ index ] . table , tbl , hash ) ;
subhash > > = tbl - > nest ;
while ( ntbl & & size > ( 1 < < shift ) ) {
index = subhash & ( ( 1 < < shift ) - 1 ) ;
ntbl = rht_dereference_bucket ( ntbl [ index ] . table , tbl , hash ) ;
size > > = shift ;
subhash > > = shift ;
}
if ( ! ntbl )
return & rhnull ;
return & ntbl [ subhash ] . bucket ;
}
EXPORT_SYMBOL_GPL ( rht_bucket_nested ) ;
struct rhash_head __rcu * * rht_bucket_nested_insert ( struct rhashtable * ht ,
struct bucket_table * tbl ,
unsigned int hash )
{
const unsigned int shift = PAGE_SHIFT - ilog2 ( sizeof ( void * ) ) ;
unsigned int index = hash & ( ( 1 < < tbl - > nest ) - 1 ) ;
unsigned int size = tbl - > size > > tbl - > nest ;
union nested_table * ntbl ;
unsigned int shifted ;
unsigned int nhash ;
ntbl = ( union nested_table * ) rcu_dereference_raw ( tbl - > buckets [ 0 ] ) ;
hash > > = tbl - > nest ;
nhash = index ;
shifted = tbl - > nest ;
ntbl = nested_table_alloc ( ht , & ntbl [ index ] . table ,
size < = ( 1 < < shift ) ? shifted : 0 , nhash ) ;
while ( ntbl & & size > ( 1 < < shift ) ) {
index = hash & ( ( 1 < < shift ) - 1 ) ;
size > > = shift ;
hash > > = shift ;
nhash | = index < < shifted ;
shifted + = shift ;
ntbl = nested_table_alloc ( ht , & ntbl [ index ] . table ,
size < = ( 1 < < shift ) ? shifted : 0 ,
nhash ) ;
}
if ( ! ntbl )
return NULL ;
return & ntbl [ hash ] . bucket ;
}
EXPORT_SYMBOL_GPL ( rht_bucket_nested_insert ) ;