+/*
+ * Initialize the vDSO pointer on first use.
+ * Returns the pointer to the vDSO data, or NULL if not available.
+ */
+static inline struct m68k_vdso_data *
+__m68k_vdso_get (void)
+{
+ struct m68k_vdso_data *vdso = (struct m68k_vdso_data *) __m68k_vdso_data; +
+ if (__glibc_unlikely (vdso == NULL))
+ {
+ /* Not yet initialized - try to get vDSO base from auxv */
+ unsigned long sysinfo_ehdr = __getauxval (AT_SYSINFO_EHDR);
+ if (sysinfo_ehdr != 0)
+ {
+ /*
+ * The kernel passes the address of the vDSO ELF header (code page). + * The vDSO memory layout is:
+ * base + 0x0000: vvar (data page with TLS pointer, clock state)
+ * base + 0x1000: timer page (hardware timer registers)
+ * base + 0x2000: vDSO code (ELF, AT_SYSINFO_EHDR points here)
+ * So the data page is 2 * PAGE_SIZE before the code page.
+ */
+ vdso = (struct m68k_vdso_data *) (sysinfo_ehdr - 2 * 4096);
+ }
+ else
+ {
+ /* vDSO not available - mark as unavailable */
+ vdso = (struct m68k_vdso_data *) (uintptr_t) -1;
+ }
+ __m68k_vdso_data = vdso;
+ }
Optimize __m68k_read_tp() to read the thread pointer directly from
the vDSO data page when available, avoiding the get_thread_area
syscall overhead.
The kernel maintains the thread pointer in a data page located at
a fixed offset (PAGE_SIZE) before the vDSO ELF header. This value
is updated on every context switch and set_thread_area() call.
The implementation uses lazy initialization: on first call, it
checks AT_SYSINFO_EHDR from auxv to locate the vDSO. If the vDSO
is not available (older kernels), it falls back to the syscall.
Reviewed-by:
Tested-by:
* sysdeps/unix/sysv/linux/m68k/m68k-helpers.c: Include stdint.h
and sys/auxv.h.
(struct m68k_vdso_data): New struct matching kernel layout.
(__m68k_vdso_data): New static variable for cached vDSO pointer.
(__m68k_vdso_get): New helper to initialize and return vDSO
data pointer.
(__m68k_read_tp): Use vDSO fast path when available, fall back
to syscall otherwise.
---
sysdeps/unix/sysv/linux/m68k/m68k-helpers.c | 77 ++++++++++++++++++++-
1 file changed, 76 insertions(+), 1 deletion(-)
diff --git a/sysdeps/unix/sysv/linux/m68k/m68k-helpers.c b/sysdeps/unix/sysv/linux/m68k/m68k-helpers.c
index 845aae51..1dee48e5 100644
--- a/sysdeps/unix/sysv/linux/m68k/m68k-helpers.c
+++ b/sysdeps/unix/sysv/linux/m68k/m68k-helpers.c
@@ -17,9 +17,84 @@
<https://www.gnu.org/licenses/>. */
#include <sysdep.h>
+#include <stdint.h>
+#include <sys/auxv.h>
+/*
+ * vDSO data structure - must match the kernel's m68k_vdso_data struct.
+ * The TLS pointer is at offset 0 in the vDSO data page.
+ */
+struct m68k_vdso_data
+{
+ unsigned long tls_ptr;
+ unsigned long reserved[3];
+};
+
+/*
+ * Pointer to the vDSO data page.
+ * 0 = not yet initialized
+ * -1 = vDSO not available (fallback to syscall)
+ * other = valid vDSO data page address
+ */
+static volatile struct m68k_vdso_data *__m68k_vdso_data;
+
+/*
+ * Initialize the vDSO pointer on first use.
+ * Returns the pointer to the vDSO data, or NULL if not available.
+ */
+static inline struct m68k_vdso_data *
+__m68k_vdso_get (void)
+{
+ struct m68k_vdso_data *vdso = (struct m68k_vdso_data *) __m68k_vdso_data; +
+ if (__glibc_unlikely (vdso == NULL))
+ {
+ /* Not yet initialized - try to get vDSO base from auxv */
+ unsigned long sysinfo_ehdr = __getauxval (AT_SYSINFO_EHDR);
+ if (sysinfo_ehdr != 0)
+ {
+ /*
+ * The kernel passes the address of the vDSO ELF header (code page). + * The vDSO memory layout is:
+ * base + 0x0000: vvar (data page with TLS pointer, clock state)
+ * base + 0x1000: timer page (hardware timer registers)
+ * base + 0x2000: vDSO code (ELF, AT_SYSINFO_EHDR points here)
+ * So the data page is 2 * PAGE_SIZE before the code page.
+ */
+ vdso = (struct m68k_vdso_data *) (sysinfo_ehdr - 2 * 4096);
+ }
+ else
+ {
+ /* vDSO not available - mark as unavailable */
+ vdso = (struct m68k_vdso_data *) (uintptr_t) -1;
+ }
+ __m68k_vdso_data = vdso;
+ }
+
+ /* Return NULL if vDSO is marked as unavailable */
+ if ((uintptr_t) vdso == (uintptr_t) -1)
+ return NULL;
+
+ return vdso;
+}
+
+/*
+ * Get the thread pointer.
+ *
+ * Fast path: Read directly from the vDSO data page if available.
+ * The kernel updates this on every context switch and set_thread_area() call.
+ *
+ * Slow path: Fall back to the get_thread_area syscall.
+ */
void *
__m68k_read_tp (void)
{
- return (void*) INTERNAL_SYSCALL_CALL (get_thread_area);
+ struct m68k_vdso_data *vdso = __m68k_vdso_get ();
+
+ /* Fast path: use vDSO if available */
+ if (__glibc_likely (vdso != NULL))
+ return (void *) vdso->tls_ptr;
+
+ /* Slow path: syscall fallback */
+ return (void *) INTERNAL_SYSCALL_CALL (get_thread_area);
}
On 08/01/26 03:18, Stefan wrote:
Optimize __m68k_read_tp() to read the thread pointer directly from
the vDSO data page when available, avoiding the get_thread_area
syscall overhead.
The kernel maintains the thread pointer in a data page located at
a fixed offset (PAGE_SIZE) before the vDSO ELF header. This value
is updated on every context switch and set_thread_area() call.
The implementation uses lazy initialization: on first call, it
checks AT_SYSINFO_EHDR from auxv to locate the vDSO. If the vDSO
is not available (older kernels), it falls back to the syscall.
When was the m68k vDSO support added on Linus tree? I removed some
ancient code that was added based on a out-of-tree patches (commit 112a0ae18b831bf31f44d81b82666980312511d6) and afaik m68k vDSO support
was never added on Linux.
/sysv/linux/m68k/m68k-helpers.cdiff --git a/sysdeps/unix/sysv/linux/m68k/m68k-helpers.c b/sysdeps/unix
.index 845aae51..1dee48e5 100644
--- a/sysdeps/unix/sysv/linux/m68k/m68k-helpers.c
+++ b/sysdeps/unix/sysv/linux/m68k/m68k-helpers.c
@@ -17,9 +17,84 @@
<https://www.gnu.org/licenses/>. */
#include <sysdep.h>
+#include <stdint.h>
+#include <sys/auxv.h>
+/*
+ * vDSO data structure - must match the kernel's m68k_vdso_data struct
UNC+ * The TLS pointer is at offset 0 in the vDSO data page.
+ */
+struct m68k_vdso_data
+{
+ unsigned long tls_ptr;
+ unsigned long reserved[3];
+};
+
+/*
+ * Pointer to the vDSO data page.
+ * 0 = not yet initialized
+ * -1 = vDSO not available (fallback to syscall)
+ * other = valid vDSO data page address
+ */
+static volatile struct m68k_vdso_data *__m68k_vdso_data;
This should be done at process initialization, similar how the other vDSO pointer are done at sysdeps/unix/sysv/linux/dl-vdso-setup.h. Most likely
it would require a arch-specific hook where to add the logic.
This would allow to optimize the call to avoid the branch to check if the vDSO is provided (with the fallback being the syscall), similar to how iF
are internally implemented; and by putting the pointer on the RELRO it would
not require to add any hardening (as with PTR_MANGLE).
o_data;+
+/*
+ * Initialize the vDSO pointer on first use.
+ * Returns the pointer to the vDSO data, or NULL if not available.
+ */
+static inline struct m68k_vdso_data *
+__m68k_vdso_get (void)
+{
+ struct m68k_vdso_data *vdso = (struct m68k_vdso_data *) __m68k_vds
age).+
+ if (__glibc_unlikely (vdso == NULL))
+ {
+ /* Not yet initialized - try to get vDSO base from auxv */
+ unsigned long sysinfo_ehdr = __getauxval (AT_SYSINFO_EHDR);
+ if (sysinfo_ehdr != 0)
+ {
+ /*
+ * The kernel passes the address of the vDSO ELF header (code p
te)+ * The vDSO memory layout is:
+ * base + 0x0000: vvar (data page with TLS pointer, clock sta
)+ * base + 0x1000: timer page (hardware timer registers)
+ * base + 0x2000: vDSO code (ELF, AT_SYSINFO_EHDR points here
+ * So the data page is 2 * PAGE_SIZE before the code page.
+ */
+ vdso = (struct m68k_vdso_data *) (sysinfo_ehdr - 2 * 4096);
This is extremely fragile, meaning that if kernel vvar, timer page, or
the vDSO code increase its size it would break. On other architecture
this is transparent and vDSO can be extended any required vvar or code
pages (and it was done for some architecture which extended code required pages to add new functionalities like the vgetrandom).
This address needs to be provided by a vDSO symbol, complete transparent
to the userland.
Also, 4096 is wrong here because m68k/coldfire have 8k page sizes.
() call.+ }
+ else
+ {
+ /* vDSO not available - mark as unavailable */
+ vdso = (struct m68k_vdso_data *) (uintptr_t) -1;
+ }
+ __m68k_vdso_data = vdso;
+ }
+
+ /* Return NULL if vDSO is marked as unavailable */
+ if ((uintptr_t) vdso == (uintptr_t) -1)
+ return NULL;
+
+ return vdso;
+}
+
+/*
+ * Get the thread pointer.
+ *
+ * Fast path: Read directly from the vDSO data page if available.
+ * The kernel updates this on every context switch and set_thread_area
+ *
+ * Slow path: Fall back to the get_thread_area syscall.
+ */
void *
__m68k_read_tp (void)
{
- return (void*) INTERNAL_SYSCALL_CALL (get_thread_area);
+ struct m68k_vdso_data *vdso = __m68k_vdso_get ();
+
+ /* Fast path: use vDSO if available */
+ if (__glibc_likely (vdso != NULL))
+ return (void *) vdso->tls_ptr;
+
+ /* Slow path: syscall fallback */
+ return (void *) INTERNAL_SYSCALL_CALL (get_thread_area);
}
* Stefan:o_data;
+/*
+ * Initialize the vDSO pointer on first use.
+ * Returns the pointer to the vDSO data, or NULL if not available.
+ */
+static inline struct m68k_vdso_data *
+__m68k_vdso_get (void)
+{
+ struct m68k_vdso_data *vdso = (struct m68k_vdso_data *) __m68k_vds
age).+
+ if (__glibc_unlikely (vdso == NULL))
+ {
+ /* Not yet initialized - try to get vDSO base from auxv */
+ unsigned long sysinfo_ehdr = __getauxval (AT_SYSINFO_EHDR);
+ if (sysinfo_ehdr != 0)
+ {
+ /*
+ * The kernel passes the address of the vDSO ELF header (code p
te)+ * The vDSO memory layout is:
+ * base + 0x0000: vvar (data page with TLS pointer, clock sta
)+ * base + 0x1000: timer page (hardware timer registers)
+ * base + 0x2000: vDSO code (ELF, AT_SYSINFO_EHDR points here
+ * So the data page is 2 * PAGE_SIZE before the code page.
+ */
+ vdso = (struct m68k_vdso_data *) (sysinfo_ehdr - 2 * 4096);
+ }
+ else
+ {
+ /* vDSO not available - mark as unavailable */
+ vdso = (struct m68k_vdso_data *) (uintptr_t) -1;
+ }
+ __m68k_vdso_data = vdso;
+ }
Is this really the documented userspace interface? I would expect that
we'd have to do a vDSO call to read the thread pointer.
Thanks,
Florian
You raise a good point. The current implementation has glibc directly
reading from the vvar data page, which tightly couples glibc to the
kernel's internal vDSO memory layout.
The more standard approach would be to export a __vdso_get_thread_area function from the vDSO that glibc calls via the normal vDSO symbol
lookup mechanism. This would:
1. Follow the established vDSO pattern used by clock_gettime,
gettimeofday, etc.
2. Allow the kernel to change the internal vvar layout without breaking glibc
3. Keep the ABI contract at the function level rather than data
structure level
The tradeoff is a small amount of function call overhead, but that's
still much faster than the syscall we're replacing.
I can rework this to export __vdso_get_thread_area from the vDSO and
have glibc use standard vDSO symbol lookup. Would that be the
preferred approach?
Thank you for the detailed review, Adhemerval!
On Thu, Jan 8, 2026 at 6:10?AM Adhemerval Zanella Netto <adhemerval.zanella@linaro.org> wrote:
On 08/01/26 03:18, Stefan wrote:
Optimize __m68k_read_tp() to read the thread pointer directly from
the vDSO data page when available, avoiding the get_thread_area
syscall overhead.
The kernel maintains the thread pointer in a data page located at
a fixed offset (PAGE_SIZE) before the vDSO ELF header. This value
is updated on every context switch and set_thread_area() call.
The implementation uses lazy initialization: on first call, it
checks AT_SYSINFO_EHDR from auxv to locate the vDSO. If the vDSO
is not available (older kernels), it falls back to the syscall.
When was the m68k vDSO support added on Linus tree? I removed some
ancient code that was added based on a out-of-tree patches (commit
112a0ae18b831bf31f44d81b82666980312511d6) and afaik m68k vDSO support
was never added on Linux.
It hasn't been - these glibc patches depend on kernel patches that are currently under an initial review on linux-m68k. I should
have made that dependency clear and included links to the kernel
patches. The kernel patch series is at: https://marc.info/?l=linux-m68k&m=176785594629016&w=2
diff --git a/sysdeps/unix/sysv/linux/m68k/m68k-helpers.c b/sysdeps/unix/sysv/linux/m68k/m68k-helpers.c
index 845aae51..1dee48e5 100644
--- a/sysdeps/unix/sysv/linux/m68k/m68k-helpers.c
+++ b/sysdeps/unix/sysv/linux/m68k/m68k-helpers.c
@@ -17,9 +17,84 @@
<https://www.gnu.org/licenses/>. */
#include <sysdep.h>
+#include <stdint.h>
+#include <sys/auxv.h>
+/*
+ * vDSO data structure - must match the kernel's m68k_vdso_data struct. >>> + * The TLS pointer is at offset 0 in the vDSO data page.
+ */
+struct m68k_vdso_data
+{
+ unsigned long tls_ptr;
+ unsigned long reserved[3];
+};
+
+/*
+ * Pointer to the vDSO data page.
+ * 0 = not yet initialized
+ * -1 = vDSO not available (fallback to syscall)
+ * other = valid vDSO data page address
+ */
+static volatile struct m68k_vdso_data *__m68k_vdso_data;
This should be done at process initialization, similar how the other vDSO
pointer are done at sysdeps/unix/sysv/linux/dl-vdso-setup.h. Most likely
it would require a arch-specific hook where to add the logic.
This would allow to optimize the call to avoid the branch to check if the
vDSO is provided (with the fallback being the syscall), similar to how iFUNC >> are internally implemented; and by putting the pointer on the RELRO it would >> not require to add any hardening (as with PTR_MANGLE).
You're right. I can rework this to:
1. Initialize at process startup via dl-vdso-setup.h hooks
2. Use an iFUNC-like pattern to avoid the branch on every call
3. Store the pointer in RELRO to avoid PTR_MANGLE overhead
+
+/*
+ * Initialize the vDSO pointer on first use.
+ * Returns the pointer to the vDSO data, or NULL if not available.
+ */
+static inline struct m68k_vdso_data *
+__m68k_vdso_get (void)
+{
+ struct m68k_vdso_data *vdso = (struct m68k_vdso_data *) __m68k_vdso_data;
+
+ if (__glibc_unlikely (vdso == NULL))
+ {
+ /* Not yet initialized - try to get vDSO base from auxv */
+ unsigned long sysinfo_ehdr = __getauxval (AT_SYSINFO_EHDR);
+ if (sysinfo_ehdr != 0)
+ {
+ /*
+ * The kernel passes the address of the vDSO ELF header (code page).
+ * The vDSO memory layout is:
+ * base + 0x0000: vvar (data page with TLS pointer, clock state) >>> + * base + 0x1000: timer page (hardware timer registers)
+ * base + 0x2000: vDSO code (ELF, AT_SYSINFO_EHDR points here) >>> + * So the data page is 2 * PAGE_SIZE before the code page.
+ */
+ vdso = (struct m68k_vdso_data *) (sysinfo_ehdr - 2 * 4096);
This is extremely fragile, meaning that if kernel vvar, timer page, or
the vDSO code increase its size it would break. On other architecture
this is transparent and vDSO can be extended any required vvar or code
pages (and it was done for some architecture which extended code required
pages to add new functionalities like the vgetrandom).
This is the same concern Florian raised. I'll rework the kernel side
to export a __vdso_get_thread_area symbol that glibc can call, rather
than having glibc calculate offsets into the vDSO memory layout.
This address needs to be provided by a vDSO symbol, complete transparent
to the userland.
Also, 4096 is wrong here because m68k/coldfire have 8k page sizes.
I have fixed this in a later revision in my internal tree to use __getauxval(AT_PAGESZ) for dynamic page size detection. I'll make sure
the updated patch is sent once I address the other feedback.
I'll revise both the kernel and glibc patches to follow the standard
vDSO patterns before resubmitting.
+ }
+ else
+ {
+ /* vDSO not available - mark as unavailable */
+ vdso = (struct m68k_vdso_data *) (uintptr_t) -1;
+ }
+ __m68k_vdso_data = vdso;
+ }
+
+ /* Return NULL if vDSO is marked as unavailable */
+ if ((uintptr_t) vdso == (uintptr_t) -1)
+ return NULL;
+
+ return vdso;
+}
+
+/*
+ * Get the thread pointer.
+ *
+ * Fast path: Read directly from the vDSO data page if available.
+ * The kernel updates this on every context switch and set_thread_area() call.
+ *
+ * Slow path: Fall back to the get_thread_area syscall.
+ */
void *
__m68k_read_tp (void)
{
- return (void*) INTERNAL_SYSCALL_CALL (get_thread_area);
+ struct m68k_vdso_data *vdso = __m68k_vdso_get ();
+
+ /* Fast path: use vDSO if available */
+ if (__glibc_likely (vdso != NULL))
+ return (void *) vdso->tls_ptr;
+
+ /* Slow path: syscall fallback */
+ return (void *) INTERNAL_SYSCALL_CALL (get_thread_area);
}
Stefan
| Sysop: | Jacob Catayoc |
|---|---|
| Location: | Pasay City, Metro Manila, Philippines |
| Users: | 5 |
| Nodes: | 4 (0 / 4) |
| Uptime: | 22:23:35 |
| Calls: | 117 |
| Calls today: | 117 |
| Files: | 367 |
| D/L today: |
560 files (257M bytes) |
| Messages: | 70,893 |
| Posted today: | 26 |