@@ -74,6 +74,7 @@ dl-routines = \
dl-open \
dl-origin \
dl-printf \
+ dl-pthread-keys \
dl-readonly-area \
dl-reloc \
dl-runtime \
new file mode 100644
@@ -0,0 +1,17 @@
+#include <unistd.h>
+#include <ldsodefs.h>
+#include <list.h>
+#include <libc-lock.h>
+#include "pthreadP.h"
+
+/* This struct must match the global definition in pthreadP.h so that
+ the slot counts match. */
+static struct pthread_key_struct key_bucket_zero[32] =
+ {
+ { 32, 0 },
+ [1 ... 31] = { 0, 0 }
+ };
+
+/* Table of the key information. */
+struct pthread_key_struct *_dl_pthread_key_buckets_ldso[32] =
+ { (struct pthread_key_struct *)&key_bucket_zero };
@@ -176,6 +176,10 @@ list_t _dl_stack_cache;
size_t _dl_stack_cache_actsize;
uintptr_t _dl_in_flight_stack;
int _dl_stack_cache_lock;
+
+extern struct pthread_key_bucket *_dl_pthread_key_buckets_ldso[32];
+void *_dl_pthread_keys_data = _dl_pthread_key_buckets_ldso;
+int _dl_pthread_keys_lock = LLL_LOCK_INITIALIZER;
#endif
struct dl_scope_free_list *_dl_scope_free_list;
@@ -729,6 +729,8 @@ match_version (const char *string, struct link_map *map)
bool __rtld_tls_init_tp_called;
+extern struct pthread_key_bucket *_dl_pthread_key_buckets_ldso[32];
+
static void *
init_tls (size_t naudit)
{
@@ -775,6 +777,8 @@ cannot allocate TLS data structures for initial thread\n");
so it knows not to pass this dtv to the normal realloc. */
GL(dl_initial_dtv) = GET_DTV (tcbp);
+ GL(dl_pthread_keys_data) = _dl_pthread_key_buckets_ldso;
+
/* And finally install it for the main thread. */
call_tls_init_tp (tcbp);
__rtld_tls_init_tp_called = true;
@@ -123,7 +123,6 @@ routines = \
pthread_join_common \
pthread_key_create \
pthread_key_delete \
- pthread_keys \
pthread_kill \
pthread_kill_other_threads \
pthread_mutex_cond_lock \
@@ -321,6 +320,7 @@ tests = \
tst-pthread-gdb-attach-static \
tst-pthread-getcpuclockid-invalid \
tst-pthread-key1-static \
+ tst-pthread-keys-ns \
tst-pthread-timedlock-lockloop \
tst-pthread_exit-nothreads \
tst-pthread_exit-nothreads-static \
@@ -485,6 +485,7 @@ modules-names = \
tst-audit-threads-mod1 \
tst-audit-threads-mod2 \
tst-compat-forwarder-mod \
+ tst-pthread-keys-ns1 \
tst-stack4mod \
tst-tls-debug-mod \
tst-tls3mod \
@@ -687,6 +688,8 @@ $(objpfx)tst-tls6.out: tst-tls6.sh $(objpfx)tst-tls5 \
$(evaluate-test)
endif
+$(objpfx)tst-pthread-keys-ns.out: $(objpfx)tst-pthread-keys-ns1.so
+
LDLIBS-tst-cancel24 = -Wl,--no-as-needed -lstdc++
LDLIBS-tst-cancel24-static = $(LDLIBS-tst-cancel24)
@@ -36,6 +36,7 @@
#include <intprops.h>
#include <setvmaname.h>
+
/* Default alignment of stack. */
#ifndef STACK_ALIGN
# define STACK_ALIGN __alignof__ (long double)
@@ -424,7 +425,7 @@ allocate_stack (const struct pthread_attr *attr, struct pthread **pdp,
memset (pd, '\0', sizeof (struct pthread));
/* The first TSD block is included in the TCB. */
- pd->specific[0] = pd->specific_1stblock;
+ _pthread_key_init (pd);
/* Remember the stack-related values. */
pd->stackblock = (char *) stackaddr - size;
@@ -548,7 +549,7 @@ allocate_stack (const struct pthread_attr *attr, struct pthread **pdp,
/* We allocated the first block thread-specific data array.
This address will not change for the lifetime of this
descriptor. */
- pd->specific[0] = pd->specific_1stblock;
+ _pthread_key_init (pd);
/* This is at least the second thread. */
pd->header.multiple_threads = 1;
@@ -57,9 +57,6 @@
((PTHREAD_KEYS_MAX + PTHREAD_KEY_2NDLEVEL_SIZE - 1) \
/ PTHREAD_KEY_2NDLEVEL_SIZE)
-
-
-
/* Internal version of the buffer to store cancellation handler
information. */
struct pthread_unwind_buf
@@ -320,6 +317,8 @@ struct pthread
/* We allocate one block of references here. This should be enough
to avoid allocating any memory dynamically for most applications. */
+ /* Note that, in level2 data blocks, the [0] element contains the
+ number of elements (including the [0] one) in the allocation. */
struct pthread_key_data
{
/* Sequence number. We use uintptr_t to not require padding on
@@ -15,48 +15,55 @@
License along with the GNU C Library; if not, see
<https://www.gnu.org/licenses/>. */
+#include <ldsodefs.h>
#include <pthreadP.h>
+static size_t page_size = 0;
+
+static size_t
+get_page_size(void)
+{
+ page_size = EXEC_PAGESIZE;
+ return page_size;
+}
+
/* Deallocate POSIX thread-local-storage. */
void
__nptl_deallocate_tsd (void)
{
- struct pthread *self = THREAD_SELF;
+ struct pthread_key_struct **global1;
+ struct pthread_key_struct *global2;
+ struct pthread_key_data *level2;
/* Maybe no data was ever allocated. This happens often so we have
a flag for this. */
- if (THREAD_GETMEM (self, specific_used))
+ if (THREAD_GETMEM (THREAD_SELF, specific_used))
{
- size_t round;
- size_t cnt;
-
- round = 0;
- do
- {
- size_t idx;
-
- /* So far no new nonzero data entry. */
- THREAD_SETMEM (self, specific_used, false);
-
- for (cnt = idx = 0; cnt < PTHREAD_KEY_1STLEVEL_SIZE; ++cnt)
- {
- struct pthread_key_data *level2;
-
- level2 = THREAD_GETMEM_NC (self, specific, cnt);
-
- if (level2 != NULL)
- {
- size_t inner;
-
- for (inner = 0; inner < PTHREAD_KEY_2NDLEVEL_SIZE;
- ++inner, ++idx)
- {
- void *data = level2[inner].data;
-
- if (data != NULL)
- {
+ int b, i, iters_left = 32;
+
+ global1 = GL(dl_pthread_keys_data);
+
+ /* Destroy all current key values. */
+ while (--iters_left && THREAD_SELF->specific_used)
+ {
+ /* So far no new nonzero data entry. */
+ THREAD_SETMEM (THREAD_SELF, specific_used, false);
+ for (b = 0; b < 32; b ++)
+ {
+ level2 = THREAD_GETMEM_NC (THREAD_SELF, specific, b);
+ global2 = global1[b];
+
+ if (level2 != NULL)
+ {
+ uintptr_t slots = level2[0].seq;
+ for (i = 1; i <slots; i ++)
+ {
+ void *data = level2[i].data;
+
+ if (data != NULL)
+ {
/* Always clear the data. */
- level2[inner].data = NULL;
+ level2[i].data = NULL;
/* Make sure the data corresponds to a valid
key. This test fails if the key was
@@ -64,48 +71,38 @@ __nptl_deallocate_tsd (void)
re-allocated. It is the user's
responsibility to free the memory in this
case. */
- if (level2[inner].seq
- == __pthread_keys[idx].seq
+ if (level2[i].seq
+ == global2[i].seq
/* It is not necessary to register a destructor
function. */
- && __pthread_keys[idx].destr != NULL)
+ && global2[i].destr != NULL)
/* Call the user-provided destructor. */
- __pthread_keys[idx].destr (data);
- }
- }
- }
- else
- idx += PTHREAD_KEY_1STLEVEL_SIZE;
- }
-
- if (THREAD_GETMEM (self, specific_used) == 0)
- /* No data has been modified. */
- goto just_free;
- }
- /* We only repeat the process a fixed number of times. */
- while (__builtin_expect (++round < PTHREAD_DESTRUCTOR_ITERATIONS, 0));
+ global2[i].destr (data);
+ }
+ }
+ }
+ }
+ }
/* Just clear the memory of the first block for reuse. */
- memset (&THREAD_SELF->specific_1stblock, '\0',
- sizeof (self->specific_1stblock));
+ memset (&THREAD_SELF->specific_1stblock[1], '\0',
+ sizeof (THREAD_SELF->specific_1stblock)
+ - sizeof(THREAD_SELF->specific_1stblock[0]));
+
+ /* Unmap all mmap'd buckets. */
+ for (b = 1; b < 32; b ++)
+ {
+ level2 = THREAD_GETMEM_NC (THREAD_SELF, specific, b);
+ if (level2 != NULL)
+ {
+ size_t mlen = PTHREAD_KEY_BUCKET2MLEN (b);
+ __munmap (level2, mlen);
+ THREAD_SETMEM_NC (THREAD_SELF, specific, b, 0);
+ }
+ }
- just_free:
- /* Free the memory for the other blocks. */
- for (cnt = 1; cnt < PTHREAD_KEY_1STLEVEL_SIZE; ++cnt)
- {
- struct pthread_key_data *level2;
-
- level2 = THREAD_GETMEM_NC (self, specific, cnt);
- if (level2 != NULL)
- {
- /* The first block is allocated as part of the thread
- descriptor. */
- free (level2);
- THREAD_SETMEM_NC (self, specific, cnt, NULL);
- }
- }
-
- THREAD_SETMEM (self, specific_used, false);
}
+ THREAD_SETMEM (THREAD_SELF, specific_used, false);
}
+
libc_hidden_def (__nptl_deallocate_tsd)
@@ -16,47 +16,53 @@
<https://www.gnu.org/licenses/>. */
#include <stdlib.h>
-#include "pthreadP.h"
#include <shlib-compat.h>
+#include "pthreadP.h"
+#include "ldsodefs.h"
+
+extern void *___ldso_pthread_getspecific (pthread_key_t key)
+ weak_function;
+
void *
___pthread_getspecific (pthread_key_t key)
{
struct pthread_key_data *data;
- /* Special case access to the first 2nd-level block. This is the
- usual case. */
- if (__glibc_likely (key < PTHREAD_KEY_2NDLEVEL_SIZE))
- data = &THREAD_SELF->specific_1stblock[key];
- else
- {
- /* Verify the key is sane. */
- if (key >= PTHREAD_KEYS_MAX)
- /* Not valid. */
- return NULL;
-
- unsigned int idx1st = key / PTHREAD_KEY_2NDLEVEL_SIZE;
- unsigned int idx2nd = key % PTHREAD_KEY_2NDLEVEL_SIZE;
-
- /* If the sequence number doesn't match or the key cannot be defined
- for this thread since the second level array is not allocated
- return NULL, too. */
- struct pthread_key_data *level2 = THREAD_GETMEM_NC (THREAD_SELF,
- specific, idx1st);
- if (level2 == NULL)
- /* Not allocated, therefore no data. */
- return NULL;
-
- /* There is data. */
- data = &level2[idx2nd];
- }
+ int b = PTHREAD_KEY_DECODE_BUCKET(key);
+ int i = PTHREAD_KEY_DECODE_SLOT(key);
+
+ /* This is the 1st-level array for the global key data in ld.so. */
+ struct pthread_key_struct **global1 = GL(dl_pthread_keys_data);
+
+ /* The spec doesn't allow for errors, so remove these checks after
+ development? */
+ if (global1 == NULL)
+ return NULL;
+
+ struct pthread_key_struct *global2 = global1[b];
+ if (global2 == NULL)
+ return NULL;
+
+ /* The first slot is reserved for 2nd level size. */
+ if (i < 1 || i >= global2[0].seq)
+ return NULL;
+
+ struct pthread_key_data *level2 = THREAD_GETMEM_NC (THREAD_SELF,
+ specific, b);
+ if (level2 == NULL)
+ /* Not allocated, therefore no data. */
+ return NULL;
+
+ /* There is data. */
+ data = &level2[i];
void *result = data->data;
if (result != NULL)
{
uintptr_t seq = data->seq;
- if (__glibc_unlikely (seq != __pthread_keys[key].seq))
+ if (__glibc_unlikely (seq != global2[i].seq))
result = data->data = NULL;
}
@@ -15,37 +15,135 @@
License along with the GNU C Library; if not, see
<https://www.gnu.org/licenses/>. */
+#include <assert.h>
#include <errno.h>
-#include "pthreadP.h"
+#include <list.h>
#include <atomic.h>
#include <shlib-compat.h>
+#include "ldsodefs.h"
+#include "pthreadP.h"
+
+_Static_assert (PTHREAD_KEY_1STLEVEL_SIZE >= 32, "incompatible TSD sizes");
+
+/* This struct must match the global definition in pthreadP.h so that
+ the slot counts match. */
+static struct pthread_key_data key_bucket_zero[32] =
+ {
+ { 32, 0 },
+ [1 ... 31] = { 0, 0 }
+ };
+
+/* Table of the key information. Only used if ld.so didn't fill in
+ the rtld version of this. */
+static struct pthread_key_data *__pthread_key_data_local[32] =
+ { (struct pthread_key_data *)&key_bucket_zero };
+
+static size_t page_size = 0;
+
+static size_t
+get_page_size(void)
+{
+ page_size = EXEC_PAGESIZE;
+ return page_size;
+}
+
+void
+_pthread_key_init (struct pthread *pd)
+{
+ /* Initialize global key data if ld.so didn't. */
+ if (GL(dl_pthread_keys_data) == NULL)
+ GL(dl_pthread_keys_data) = __pthread_key_data_local;
+
+ /* Initialize thread key data. */
+ pd->specific_1stblock[0].seq = PTHREAD_KEY_2NDLEVEL_SIZE;
+ pd->specific[0] = pd->specific_1stblock;
+}
+libc_hidden_def (_pthread_key_init)
+
+
int
___pthread_key_create (pthread_key_t *key, void (*destr) (void *))
{
- /* Find a slot in __pthread_keys which is unused. */
- for (size_t cnt = 0; cnt < PTHREAD_KEYS_MAX; ++cnt)
+ int b, i;
+
+ PTHREAD_KEY_LOCK;
+ struct pthread_key_struct **global1 = GL(dl_pthread_keys_data);
+ if (global1 == NULL)
{
- uintptr_t seq = __pthread_keys[cnt].seq;
+ _pthread_key_init (THREAD_SELF);
+ global1 = GL(dl_pthread_keys_data);
+ }
- if (KEY_UNUSED (seq) && KEY_USABLE (seq)
- /* We found an unused slot. Try to allocate it. */
- && ! atomic_compare_and_exchange_bool_acq (&__pthread_keys[cnt].seq,
- seq + 1, seq))
+ /* There are 32 "buckets" of key data, encoded in the 5 LSBs of the
+ key. */
+ for (b = 0; b < 32; b ++)
+ {
+ struct pthread_key_struct *global2 = global1[b];
+
+ /* This bucket is missing, and therefor empty, so create it. */
+ if (global2 == NULL)
{
- /* Remember the destructor. */
- __pthread_keys[cnt].destr = destr;
+ if (b == 0)
+ /* We didn't initialize properly? */
+ *((int *)0) = 0;
+ else
+ {
+ void *v;
+ size_t mlen = PTHREAD_KEY_BUCKET2MLEN (b);
+ v = __mmap (NULL, mlen, PROT_READ|PROT_WRITE,
+ MAP_ANONYMOUS|MAP_PRIVATE, -1, 0);
+
+ if (v == MAP_FAILED)
+ {
+ PTHREAD_KEY_UNLOCK;
+ /* Not in spec, but EAGAIN is the only other option. */
+ return ENOMEM;
+ }
+ if (v == NULL)
+ *((int *)0) = 0;
- /* Return the key to the caller. */
- *key = cnt;
+ global2 = (struct pthread_key_struct *) v;
+ global1[b] = global2;
+ int num_slots = PTHREAD_KEY_BUCKET2SLOTS (b);
+ global2[0].seq = num_slots;
- /* The call succeeded. */
- return 0;
+ /* If mmap returns a zero'd block, delete this. */
+ for (i=1; i<num_slots; i++)
+ {
+ global2[i].seq = 0;
+ global2[i].destr = (void (*)) NULL;
+ }
+ }
+ }
+
+ /* Search for an empty slot. Slot [0] contains the number of slots. */
+ int s = global2[0].seq;
+ for (i = 1; i < s; i ++)
+ {
+ uintptr_t seq = global2[i].seq;
+
+ if (KEY_UNUSED (seq) && KEY_USABLE (seq)
+ && ! atomic_compare_and_exchange_bool_acq (&global2[i].seq,
+ seq + 1, seq))
+ {
+ /* Remember the destructor. */
+ global2[i].destr = destr;
+
+ /* Return the key to the caller. */
+ *key = PTHREAD_KEY_ENCODE (b, i);
+
+ /* The call succeeded. */
+ PTHREAD_KEY_UNLOCK;
+ return 0;
+ }
}
}
+ PTHREAD_KEY_UNLOCK;
return EAGAIN;
}
+
versioned_symbol (libc, ___pthread_key_create, __pthread_key_create,
GLIBC_2_34);
libc_hidden_ver (___pthread_key_create, __pthread_key_create)
@@ -16,27 +16,38 @@
<https://www.gnu.org/licenses/>. */
#include <errno.h>
-#include "pthreadP.h"
#include <atomic.h>
#include <shlib-compat.h>
+#include "ldsodefs.h"
+#include "pthreadP.h"
+
+extern int ___ldso_pthread_key_delete (pthread_key_t key)
+ weak_function;
+
int
___pthread_key_delete (pthread_key_t key)
{
- int result = EINVAL;
+ int b = PTHREAD_KEY_DECODE_BUCKET(key);
+ int i = PTHREAD_KEY_DECODE_SLOT(key);
+ struct pthread_key_struct **global1 = GL(dl_pthread_keys_data);
+
+ if (global1[b] == NULL)
+ return EINVAL;
+
+ struct pthread_key_struct *global2 = global1[b];
+ if (i < 1 || i >= global2[0].seq)
+ return EINVAL;
- if (__glibc_likely (key < PTHREAD_KEYS_MAX))
- {
- unsigned int seq = __pthread_keys[key].seq;
+ int seq = global2[i].seq;
- if (__builtin_expect (! KEY_UNUSED (seq), 1)
- && ! atomic_compare_and_exchange_bool_acq (&__pthread_keys[key].seq,
+ if (__builtin_expect (! KEY_UNUSED (seq), 1)
+ && ! atomic_compare_and_exchange_bool_acq (&global2[i].seq,
seq + 1, seq))
/* We deleted a valid key. */
- result = 0;
- }
+ return 0;
- return result;
+ return EINVAL;
}
versioned_symbol (libc, ___pthread_key_delete, pthread_key_delete,
GLIBC_2_34);
@@ -17,75 +17,77 @@
#include <errno.h>
#include <stdlib.h>
-#include "pthreadP.h"
#include <shlib-compat.h>
+#include "ldsodefs.h"
+#include "pthreadP.h"
+
+static size_t page_size = 0;
+
+static size_t
+get_page_size(void)
+{
+ page_size = EXEC_PAGESIZE;
+ return page_size;
+}
+
+
int
___pthread_setspecific (pthread_key_t key, const void *value)
{
struct pthread *self;
- unsigned int idx1st;
- unsigned int idx2nd;
- struct pthread_key_data *level2;
unsigned int seq;
+ struct pthread_key_data *level2;
+ int b = PTHREAD_KEY_DECODE_BUCKET(key);
+ int i = PTHREAD_KEY_DECODE_SLOT(key);
self = THREAD_SELF;
- /* Special case access to the first 2nd-level block. This is the
- usual case. */
- if (__glibc_likely (key < PTHREAD_KEY_2NDLEVEL_SIZE))
- {
- /* Verify the key is sane. */
- if (KEY_UNUSED ((seq = __pthread_keys[key].seq)))
- /* Not valid. */
- return EINVAL;
+ /* The bucket number can only be 0..31, and all are valid. */
+ struct pthread_key_struct **global1 = GL(dl_pthread_keys_data);
+ struct pthread_key_struct *global2 = global1[b];
+ if (global2 == NULL)
+ return EINVAL;
+ if (i < 0 || i >= global2[0].seq)
+ return EINVAL;
- level2 = &self->specific_1stblock[key];
+ seq = global2[i].seq;
+ if (KEY_UNUSED (seq))
+ return EINVAL;
- /* Remember that we stored at least one set of data. */
- if (value != NULL)
- THREAD_SETMEM (self, specific_used, true);
- }
- else
- {
- if (key >= PTHREAD_KEYS_MAX
- || KEY_UNUSED ((seq = __pthread_keys[key].seq)))
- /* Not valid. */
- return EINVAL;
+ /* If we need to mmap a bucket, do so now. */
- idx1st = key / PTHREAD_KEY_2NDLEVEL_SIZE;
- idx2nd = key % PTHREAD_KEY_2NDLEVEL_SIZE;
+ level2 = THREAD_GETMEM_NC (self, specific, b);
+
+ if (level2 == NULL)
+ {
+ size_t mlen = PTHREAD_KEY_BUCKET2MLEN (b);
+ size_t slots = PTHREAD_KEY_BUCKET2SLOTS (b);
+ struct pthread_key_data *m;
- /* This is the second level array. Allocate it if necessary. */
- level2 = THREAD_GETMEM_NC (self, specific, idx1st);
- if (level2 == NULL)
+ if (b == 0)
{
- if (value == NULL)
- /* We don't have to do anything. The value would in any case
- be NULL. We can save the memory allocation. */
- return 0;
-
- level2
- = (struct pthread_key_data *) calloc (PTHREAD_KEY_2NDLEVEL_SIZE,
- sizeof (*level2));
- if (level2 == NULL)
+ m = (struct pthread_key_data *) & self->specific_1stblock;
+ slots = PTHREAD_KEY_2NDLEVEL_SIZE;
+ }
+ else
+ {
+ m = __mmap (0, mlen, PROT_READ|PROT_WRITE,
+ MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
+ if (m == MAP_FAILED)
return ENOMEM;
-
- THREAD_SETMEM_NC (self, specific, idx1st, level2);
}
- /* Pointer to the right array element. */
- level2 = &level2[idx2nd];
+ memset (m, 0, slots * sizeof (struct pthread_key_data));
+ m[0].seq = slots;
- /* Remember that we stored at least one set of data. */
- THREAD_SETMEM (self, specific_used, true);
+ level2 = m;
+ THREAD_SETMEM_NC (self, specific, b, m);
}
- /* Store the data and the sequence number so that we can recognize
- stale data. */
- level2->seq = seq;
- level2->data = (void *) value;
-
+ THREAD_SETMEM (self, specific_used, 1);
+ level2[i].seq = seq;
+ level2[i].data = (void *) value;
return 0;
}
versioned_symbol (libc, ___pthread_setspecific, pthread_setspecific,
new file mode 100644
@@ -0,0 +1,70 @@
+/* Verify that keys are in sync across namespaces.
+ Copyright (C) 2026 Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+
+ The GNU C Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ The GNU C Library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with the GNU C Library; if not, see
+ <https://www.gnu.org/licenses/>. */
+
+#include <support/check.h>
+#include <support/xdlfcn.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <pthread.h>
+
+/* This file loads ns1.so in a separate DSO namespace, and uses it to
+ create keys in that namespace, and compares to keys created in this
+ namespace. */
+
+#define NUM_KEYS 30
+static pthread_key_t our_keys[NUM_KEYS];
+static pthread_key_t ns_keys[NUM_KEYS];
+
+static int (*ns_pthread_key_create)(pthread_key_t *key,
+ void (*__destr_function) (void *));
+
+static int
+do_test (void)
+{
+ int i, j, errors=0;
+ void *so;
+
+ so = xdlmopen (LM_ID_NEWLM, "tst-pthread-keys-ns1.so", RTLD_NOW);
+ ns_pthread_key_create = dlsym (so, "ns_pthread_key_create");
+
+ for (i=0; i<NUM_KEYS; i++)
+ {
+ TEST_VERIFY (pthread_key_create (&our_keys[i], NULL) == 0);
+ TEST_VERIFY (ns_pthread_key_create (&ns_keys[i], NULL) == 0);
+
+ printf(" %08x %08x\n", our_keys[i], ns_keys[i]);
+ }
+
+ for (i=0; i<NUM_KEYS; i++)
+ for (j=0; j<NUM_KEYS; j++)
+ {
+ if (our_keys[i] == ns_keys[j])
+ {
+ if (errors < 5)
+ printf("collision %x[%d] %x[%d]\n", our_keys[i], i, ns_keys[j], j);
+ errors ++;
+ }
+ }
+
+ xdlclose (so);
+
+ return errors;
+}
+
+#include <support/test-driver.c>
similarity index 65%
rename from nptl/pthread_keys.c
rename to nptl/tst-pthread-keys-ns1.c
@@ -1,5 +1,5 @@
-/* Table of pthread_key_create keys and their destructors.
- Copyright (C) 2004-2026 Free Software Foundation, Inc.
+/* Verify that keys are in sync across namespaces.
+ Copyright (C) 2026 Free Software Foundation, Inc.
This file is part of the GNU C Library.
The GNU C Library is free software; you can redistribute it and/or
@@ -16,8 +16,14 @@
License along with the GNU C Library; if not, see
<https://www.gnu.org/licenses/>. */
-#include <pthreadP.h>
+#include <pthread.h>
+#include <support/check.h>
-/* Table of the key information. */
-struct pthread_key_struct __pthread_keys[PTHREAD_KEYS_MAX];
-libc_hidden_data_def (__pthread_keys)
+/* This file just connects the test case to a glibc in a different DSO
+ namespace. */
+
+int ns_pthread_key_create (pthread_key_t *key,
+ void (*__destr_function) (void *))
+{
+ return pthread_key_create (key, __destr_function);
+}
@@ -81,7 +81,7 @@ DB_VARIABLE (__nptl_nthreads)
DB_VARIABLE (__nptl_last_event)
DB_MAIN_VARIABLE (__nptl_initial_report_events)
-DB_ARRAY_VARIABLE (__pthread_keys)
+/*DB_ARRAY_VARIABLE (__pthread_keys)*/
DB_STRUCT (pthread_key_struct)
DB_STRUCT_FIELD (pthread_key_struct, seq)
DB_STRUCT_FIELD (pthread_key_struct, destr)
@@ -24,6 +24,9 @@ td_err_e
td_ta_tsd_iter (const td_thragent_t *ta_arg, td_key_iter_f *callback,
void *cbdata_p)
{
+#if 1
+ return TD_ERR;
+#else
td_thragent_t *const ta = (td_thragent_t *) ta_arg;
td_err_e err;
void *keys;
@@ -77,4 +80,5 @@ td_ta_tsd_iter (const td_thragent_t *ta_arg, td_key_iter_f *callback,
}
return TD_OK;
+#endif
}
@@ -23,6 +23,9 @@
td_err_e
td_thr_tsd (const td_thrhandle_t *th, const thread_key_t tk, void **data)
{
+#if 1
+ return TD_ERR;
+#else
td_err_e err;
psaddr_t tk_seq, level1, level2, seq, value;
void *copy;
@@ -92,4 +95,5 @@ td_thr_tsd (const td_thrhandle_t *th, const thread_key_t tk, void **data)
*data = value;
return err;
+#endif
}
@@ -467,6 +467,13 @@ struct rtld_global
/* Mutex protecting the stack lists. */
EXTERN int _dl_stack_cache_lock;
+
+ /* Pthread keys global destructor tables. Actually a pointer to
+ pthread_key_bucket[32]. */
+ EXTERN void *_dl_pthread_keys_data;
+
+ /* Mutex protecting the pthread keys global data. */
+ EXTERN int _dl_pthread_keys_lock;
#endif
#if __PTHREAD_HTL
/* The total number of thread IDs currently in use, or on the list of
@@ -1455,6 +1462,7 @@ __rtld_mutex_init (void)
/* The initialization happens later (!__PHREAD_NPTL) or is not
needed at all (!SHARED). */
}
+
#endif /* !__PHREAD_NPTL */
/* Implementation of GL (dl_libc_freeres). */
@@ -74,7 +74,7 @@ __tls_init_tp (void)
/* Early initialization of the TCB. */
pd->tid = INTERNAL_SYSCALL_CALL (set_tid_address, &pd->joinstate);
- THREAD_SETMEM (pd, specific[0], &pd->specific_1stblock[0]);
+ _pthread_key_init (pd);
THREAD_SETMEM (pd, stack_mode, ALLOCATE_GUARD_USER);
THREAD_SETMEM (pd, joinstate, THREAD_STATE_JOINABLE);
@@ -113,23 +113,23 @@ reclaim_stacks (void)
if (curp->specific_used)
{
- /* Clear the thread-specific data. */
- memset (curp->specific_1stblock, '\0',
- sizeof (curp->specific_1stblock));
-
curp->specific_used = false;
- for (size_t cnt = 1; cnt < PTHREAD_KEY_1STLEVEL_SIZE; ++cnt)
- if (curp->specific[cnt] != NULL)
- {
- memset (curp->specific[cnt], '\0',
- sizeof (curp->specific_1stblock));
-
- /* We have allocated the block which we do not
- free here so re-set the bit. */
- curp->specific_used = true;
- }
+ for (size_t cnt = 0; cnt < PTHREAD_KEY_1STLEVEL_SIZE; ++cnt)
+ {
+ struct pthread_key_data *b = curp->specific[cnt];
+ if (b != NULL)
+ {
+ memset (b + 1, '\0', (b[0].seq - 1) * sizeof (b[0]));
+
+ /* We have allocated the block which we do not
+ free here so re-set the bit. */
+ if (cnt > 0)
+ curp->specific_used = true;
+ }
+ }
}
+ GL (dl_pthread_keys_lock) = LLL_LOCK_INITIALIZER;
call_function_static_weak (__getrandom_reset_state, curp);
}
@@ -185,8 +185,23 @@ extern int __attr_list_lock attribute_hidden;
extern int __concurrency_level attribute_hidden;
/* Thread-local data key handling. */
-extern struct pthread_key_struct __pthread_keys[PTHREAD_KEYS_MAX];
-libc_hidden_proto (__pthread_keys)
+#define PTHREAD_KEY_BUCKET2MLEN(b) \
+ (((page_size != 0) ? page_size : get_page_size()) << (b-1))
+#define PTHREAD_KEY_BUCKET2SLOTS(b) \
+ (PTHREAD_KEY_BUCKET2MLEN(b) / sizeof (struct pthread_key_data))
+#define PTHREAD_KEY_ENCODE(b,i) ((pthread_key_t) (b | (i<<5)))
+#define PTHREAD_KEY_DECODE_BUCKET(k) (((size_t)k) & 31)
+#define PTHREAD_KEY_DECODE_SLOT(k) (((size_t)k) >> 5)
+#if IS_IN(rtld)
+#define PTHREAD_KEY_LOCK
+#define PTHREAD_KEY_UNLOCK
+#else
+#define PTHREAD_KEY_LOCK lll_lock (GL(dl_pthread_keys_lock), LLL_PRIVATE)
+#define PTHREAD_KEY_UNLOCK lll_unlock (GL(dl_pthread_keys_lock), LLL_PRIVATE)
+#endif
+
+extern void _pthread_key_init (struct pthread *);
+libc_hidden_proto (_pthread_key_init);
/* Number of threads running. */
extern unsigned int __nptl_nthreads;