The cache parameter of getcpu() is not used by the kernel and no user ever passes it in anyways.
Remove the struct and its header.
As a side-effect we get rid of an unwanted inclusion of the linux/ header namespace from vDSO code.
Signed-off-by: Thomas Weißschuh thomas.weissschuh@linutronix.de --- Changes in v2: - Rebase on v6.18-rc1 - Link to v1: https://lore.kernel.org/r/20250826-getcpu_cache-v1-1-8748318f6141@linutronix... --- We could also completely remove the parameter, but I am not sure if that is a good idea for syscalls and vDSO entrypoints. --- arch/loongarch/vdso/vgetcpu.c | 5 ++--- arch/s390/kernel/vdso64/getcpu.c | 3 +-- arch/s390/kernel/vdso64/vdso.h | 4 +--- arch/x86/entry/vdso/vgetcpu.c | 5 ++--- arch/x86/include/asm/vdso/processor.h | 4 +--- arch/x86/um/vdso/um_vdso.c | 7 +++---- include/linux/getcpu.h | 19 ------------------- include/linux/syscalls.h | 3 +-- kernel/sys.c | 4 +--- tools/testing/selftests/vDSO/vdso_test_getcpu.c | 4 +--- 10 files changed, 13 insertions(+), 45 deletions(-)
diff --git a/arch/loongarch/vdso/vgetcpu.c b/arch/loongarch/vdso/vgetcpu.c index 5301cd9d0f839eb0fd7b73a1d36e80aaa75d5e76..aefba899873ed035d70766a95b0b6fea881e94df 100644 --- a/arch/loongarch/vdso/vgetcpu.c +++ b/arch/loongarch/vdso/vgetcpu.c @@ -4,7 +4,6 @@ */
#include <asm/vdso.h> -#include <linux/getcpu.h>
static __always_inline int read_cpu_id(void) { @@ -20,8 +19,8 @@ static __always_inline int read_cpu_id(void) }
extern -int __vdso_getcpu(unsigned int *cpu, unsigned int *node, struct getcpu_cache *unused); -int __vdso_getcpu(unsigned int *cpu, unsigned int *node, struct getcpu_cache *unused) +int __vdso_getcpu(unsigned int *cpu, unsigned int *node, void *unused); +int __vdso_getcpu(unsigned int *cpu, unsigned int *node, void *unused) { int cpu_id;
diff --git a/arch/s390/kernel/vdso64/getcpu.c b/arch/s390/kernel/vdso64/getcpu.c index 5c5d4a848b7669436e73df8e3b711e5b876eb3db..1e17665616c5fa766ca66c8de276b212528934bd 100644 --- a/arch/s390/kernel/vdso64/getcpu.c +++ b/arch/s390/kernel/vdso64/getcpu.c @@ -2,11 +2,10 @@ /* Copyright IBM Corp. 2020 */
#include <linux/compiler.h> -#include <linux/getcpu.h> #include <asm/timex.h> #include "vdso.h"
-int __s390_vdso_getcpu(unsigned *cpu, unsigned *node, struct getcpu_cache *unused) +int __s390_vdso_getcpu(unsigned *cpu, unsigned *node, void *unused) { union tod_clock clk;
diff --git a/arch/s390/kernel/vdso64/vdso.h b/arch/s390/kernel/vdso64/vdso.h index 9e5397e7b590a23c149ccc6043d0c0b0d5ea8457..cadd307d7a365cabf53f5c8d313be3718625533d 100644 --- a/arch/s390/kernel/vdso64/vdso.h +++ b/arch/s390/kernel/vdso64/vdso.h @@ -4,9 +4,7 @@
#include <vdso/datapage.h>
-struct getcpu_cache; - -int __s390_vdso_getcpu(unsigned *cpu, unsigned *node, struct getcpu_cache *unused); +int __s390_vdso_getcpu(unsigned *cpu, unsigned *node, void *unused); int __s390_vdso_gettimeofday(struct __kernel_old_timeval *tv, struct timezone *tz); int __s390_vdso_clock_gettime(clockid_t clock, struct __kernel_timespec *ts); int __s390_vdso_clock_getres(clockid_t clock, struct __kernel_timespec *ts); diff --git a/arch/x86/entry/vdso/vgetcpu.c b/arch/x86/entry/vdso/vgetcpu.c index e4640306b2e3c95d74d73037ab6b09294b8e1d6c..6381b472b7c52487bccf3cbf0664c3d7a0e59699 100644 --- a/arch/x86/entry/vdso/vgetcpu.c +++ b/arch/x86/entry/vdso/vgetcpu.c @@ -6,17 +6,16 @@ */
#include <linux/kernel.h> -#include <linux/getcpu.h> #include <asm/segment.h> #include <vdso/processor.h>
notrace long -__vdso_getcpu(unsigned *cpu, unsigned *node, struct getcpu_cache *unused) +__vdso_getcpu(unsigned *cpu, unsigned *node, void *unused) { vdso_read_cpunode(cpu, node);
return 0; }
-long getcpu(unsigned *cpu, unsigned *node, struct getcpu_cache *tcache) +long getcpu(unsigned *cpu, unsigned *node, void *tcache) __attribute__((weak, alias("__vdso_getcpu"))); diff --git a/arch/x86/include/asm/vdso/processor.h b/arch/x86/include/asm/vdso/processor.h index 7000aeb59aa287e2119c3d43ab3eaf82befb59c4..93e0e24e5cb47f7b0056c13f2a7f2304ed4a0595 100644 --- a/arch/x86/include/asm/vdso/processor.h +++ b/arch/x86/include/asm/vdso/processor.h @@ -18,9 +18,7 @@ static __always_inline void cpu_relax(void) native_pause(); }
-struct getcpu_cache; - -notrace long __vdso_getcpu(unsigned *cpu, unsigned *node, struct getcpu_cache *unused); +notrace long __vdso_getcpu(unsigned *cpu, unsigned *node, void *unused);
#endif /* __ASSEMBLER__ */
diff --git a/arch/x86/um/vdso/um_vdso.c b/arch/x86/um/vdso/um_vdso.c index cbae2584124fd0ff0f9d240c33fefb8d213c84cd..9aa2c62cce6b7a07bbaf8441014d347162d1950d 100644 --- a/arch/x86/um/vdso/um_vdso.c +++ b/arch/x86/um/vdso/um_vdso.c @@ -10,14 +10,13 @@ #define DISABLE_BRANCH_PROFILING
#include <linux/time.h> -#include <linux/getcpu.h> #include <asm/unistd.h>
/* workaround for -Wmissing-prototypes warnings */ int __vdso_clock_gettime(clockid_t clock, struct __kernel_old_timespec *ts); int __vdso_gettimeofday(struct __kernel_old_timeval *tv, struct timezone *tz); __kernel_old_time_t __vdso_time(__kernel_old_time_t *t); -long __vdso_getcpu(unsigned int *cpu, unsigned int *node, struct getcpu_cache *unused); +long __vdso_getcpu(unsigned int *cpu, unsigned int *node, void *unused);
int __vdso_clock_gettime(clockid_t clock, struct __kernel_old_timespec *ts) { @@ -60,7 +59,7 @@ __kernel_old_time_t __vdso_time(__kernel_old_time_t *t) __kernel_old_time_t time(__kernel_old_time_t *t) __attribute__((weak, alias("__vdso_time")));
long -__vdso_getcpu(unsigned int *cpu, unsigned int *node, struct getcpu_cache *unused) +__vdso_getcpu(unsigned int *cpu, unsigned int *node, void *unused) { /* * UML does not support SMP, we can cheat here. :) @@ -74,5 +73,5 @@ __vdso_getcpu(unsigned int *cpu, unsigned int *node, struct getcpu_cache *unused return 0; }
-long getcpu(unsigned int *cpu, unsigned int *node, struct getcpu_cache *tcache) +long getcpu(unsigned int *cpu, unsigned int *node, void *tcache) __attribute__((weak, alias("__vdso_getcpu"))); diff --git a/include/linux/getcpu.h b/include/linux/getcpu.h deleted file mode 100644 index c304dcdb4eac2a9117080e6a14f4e3f28d07fd56..0000000000000000000000000000000000000000 --- a/include/linux/getcpu.h +++ /dev/null @@ -1,19 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ -#ifndef _LINUX_GETCPU_H -#define _LINUX_GETCPU_H 1 - -/* Cache for getcpu() to speed it up. Results might be a short time - out of date, but will be faster. - - User programs should not refer to the contents of this structure. - I repeat they should not refer to it. If they do they will break - in future kernels. - - It is only a private cache for vgetcpu(). It will change in future kernels. - The user program must store this information per thread (__thread) - If you want 100% accurate information pass NULL instead. */ -struct getcpu_cache { - unsigned long blob[128 / sizeof(long)]; -}; - -#endif diff --git a/include/linux/syscalls.h b/include/linux/syscalls.h index 66c06fcdfe19e27b99eb9a187c22e022e260802f..403488e5eba906ecf40975fc3cb29ed0402491f2 100644 --- a/include/linux/syscalls.h +++ b/include/linux/syscalls.h @@ -59,7 +59,6 @@ struct compat_stat; struct old_timeval32; struct robust_list_head; struct futex_waitv; -struct getcpu_cache; struct old_linux_dirent; struct perf_event_attr; struct file_handle; @@ -714,7 +713,7 @@ asmlinkage long sys_getrusage(int who, struct rusage __user *ru); asmlinkage long sys_umask(int mask); asmlinkage long sys_prctl(int option, unsigned long arg2, unsigned long arg3, unsigned long arg4, unsigned long arg5); -asmlinkage long sys_getcpu(unsigned __user *cpu, unsigned __user *node, struct getcpu_cache __user *cache); +asmlinkage long sys_getcpu(unsigned __user *cpu, unsigned __user *node, void __user *cache); asmlinkage long sys_gettimeofday(struct __kernel_old_timeval __user *tv, struct timezone __user *tz); asmlinkage long sys_settimeofday(struct __kernel_old_timeval __user *tv, diff --git a/kernel/sys.c b/kernel/sys.c index 8b58eece4e580b883d19bb1336aff627ae783a4d..f1780ab132a3fbce6aac937ade5b9a35d9837f13 100644 --- a/kernel/sys.c +++ b/kernel/sys.c @@ -31,7 +31,6 @@ #include <linux/tty.h> #include <linux/signal.h> #include <linux/cn_proc.h> -#include <linux/getcpu.h> #include <linux/task_io_accounting_ops.h> #include <linux/seccomp.h> #include <linux/cpu.h> @@ -2876,8 +2875,7 @@ SYSCALL_DEFINE5(prctl, int, option, unsigned long, arg2, unsigned long, arg3, return error; }
-SYSCALL_DEFINE3(getcpu, unsigned __user *, cpup, unsigned __user *, nodep, - struct getcpu_cache __user *, unused) +SYSCALL_DEFINE3(getcpu, unsigned __user *, cpup, unsigned __user *, nodep, void __user *, unused) { int err = 0; int cpu = raw_smp_processor_id(); diff --git a/tools/testing/selftests/vDSO/vdso_test_getcpu.c b/tools/testing/selftests/vDSO/vdso_test_getcpu.c index cdeaed45fb26c61f6314c58fe1b71fa0be3c0108..994ce569dc37c6689b1a3c79156e3dfc8bf27f22 100644 --- a/tools/testing/selftests/vDSO/vdso_test_getcpu.c +++ b/tools/testing/selftests/vDSO/vdso_test_getcpu.c @@ -16,9 +16,7 @@ #include "vdso_config.h" #include "vdso_call.h"
-struct getcpu_cache; -typedef long (*getcpu_t)(unsigned int *, unsigned int *, - struct getcpu_cache *); +typedef long (*getcpu_t)(unsigned int *, unsigned int *, void *);
int main(int argc, char **argv) {
--- base-commit: 3a8660878839faadb4f1a6dd72c3179c1df56787 change-id: 20250825-getcpu_cache-3abcd2e65437
Best regards,
On 10/13/25 02:20, Thomas Weißschuh wrote:
-int __vdso_getcpu(unsigned int *cpu, unsigned int *node, struct getcpu_cache *unused); -int __vdso_getcpu(unsigned int *cpu, unsigned int *node, struct getcpu_cache *unused) +int __vdso_getcpu(unsigned int *cpu, unsigned int *node, void *unused); +int __vdso_getcpu(unsigned int *cpu, unsigned int *node, void *unused) { int cpu_id;
It would ideally be nice to have a _bit_ more history on this about how it became unused any why there is such high confidence that userspace never tries to use it.
Let's say someone comes along in a few years and wants to use this 'unused' parameter. Could they?
On October 13, 2025 7:06:55 AM PDT, Dave Hansen dave.hansen@intel.com wrote:
On 10/13/25 02:20, Thomas Weißschuh wrote:
-int __vdso_getcpu(unsigned int *cpu, unsigned int *node, struct getcpu_cache *unused); -int __vdso_getcpu(unsigned int *cpu, unsigned int *node, struct getcpu_cache *unused) +int __vdso_getcpu(unsigned int *cpu, unsigned int *node, void *unused); +int __vdso_getcpu(unsigned int *cpu, unsigned int *node, void *unused) { int cpu_id;
It would ideally be nice to have a _bit_ more history on this about how it became unused any why there is such high confidence that userspace never tries to use it.
Let's say someone comes along in a few years and wants to use this 'unused' parameter. Could they?
I believe it was a private storage area for the kernel to use... which it doesn't. Not doing anything at all with the pointer is perfectly legitimate.
On Mon, Oct 13, 2025 at 7:07 AM Dave Hansen dave.hansen@intel.com wrote:
On 10/13/25 02:20, Thomas Weißschuh wrote:
-int __vdso_getcpu(unsigned int *cpu, unsigned int *node, struct getcpu_cache *unused); -int __vdso_getcpu(unsigned int *cpu, unsigned int *node, struct getcpu_cache *unused) +int __vdso_getcpu(unsigned int *cpu, unsigned int *node, void *unused); +int __vdso_getcpu(unsigned int *cpu, unsigned int *node, void *unused) { int cpu_id;
It would ideally be nice to have a _bit_ more history on this about how it became unused any why there is such high confidence that userspace never tries to use it.
The theory is that people thought that getcpu was going to be kind of slow, so userspace would allocate a little cache (IIRC per-thread) and pass it in, and the vDSO would do, well, something clever to return the right value. The something clever was probably based on the idea that you can't actually tell (in general) if the return value from getcpu is stale, since you might well get migrated right as the function returns anyway, so the cache could be something silly like (jiffies, cpu).
I don't actually remember whether the kernel ever used this. It's possible that there are ancient kernels where passing a wild, non-null pointer would blow up. But it's certainly safe to pass null, and it's certainly safe for the kernel to ignore the parameter.
--Andy
Let's say someone comes along in a few years and wants to use this 'unused' parameter. Could they?
On 2025-10-13 10:14, Andy Lutomirski wrote:
I don't actually remember whether the kernel ever used this. It's possible that there are ancient kernels where passing a wild, non-null pointer would blow up. But it's certainly safe to pass null, and it's certainly safe for the kernel to ignore the parameter.
One could imagine an architecture which would have to execute an actual system call wanting to use this, but on x86 it is pointless -- even the LSL trick is much faster than a system call, and once you account for whatever hassle you would have to deal with do make the cache make sense (probably having a global generation number and/or a timestamp to expire it) it well and truly makes no sense.
-hpa
On Mon, Oct 13, 2025 at 12:45 PM H. Peter Anvin hpa@zytor.com wrote:
On 2025-10-13 10:14, Andy Lutomirski wrote:
I don't actually remember whether the kernel ever used this. It's possible that there are ancient kernels where passing a wild, non-null pointer would blow up. But it's certainly safe to pass null, and it's certainly safe for the kernel to ignore the parameter.
One could imagine an architecture which would have to execute an actual system call wanting to use this, but on x86 it is pointless -- even the LSL trick is much faster than a system call, and once you account for whatever hassle you would have to deal with do make the cache make sense (probably having a global generation number and/or a timestamp to expire it) it well and truly makes no sense.
The global timestamp would just be some field in the vvar area, which we have plenty of anyway.
But I agree, accelerating getcpu is pointless. In any case, anything that historically thought it really really wanted accelerated getcpu can, and probably does, use rseq these days.
--Andy
On 2025-10-13 13:32, Andy Lutomirski wrote:
The global timestamp would just be some field in the vvar area, which we have plenty of anyway.
But I agree, accelerating getcpu is pointless. In any case, anything that historically thought it really really wanted accelerated getcpu can, and probably does, use rseq these days.
Indeed. And with RDPID it is fast enough that the bulk of the cost is probably in the vdso call.
-hpa
* Andy Lutomirski:
The theory is that people thought that getcpu was going to be kind of slow, so userspace would allocate a little cache (IIRC per-thread) and pass it in, and the vDSO would do, well, something clever to return the right value. The something clever was probably based on the idea that you can't actually tell (in general) if the return value from getcpu is stale, since you might well get migrated right as the function returns anyway, so the cache could be something silly like (jiffies, cpu).
It probably had to do something with per-CPU or per-node mappings of the vDSO. Or may some non-coherent cache line in the vDSO. As far as I understand it, the cache has zero chance of working with the way vDSO data is currently implemented.
We have the CPU ID and node ID in the restartable sequences area now (although glibc does not use the node ID yet). It's not a cache. So this clearly supersedes whatever struct getcpu_cache tried to achieve.
Thanks, Florian
linux-kselftest-mirror@lists.linaro.org