* [Devel] [PATCH hci-8.1 0/2] sched: Fix incorrect nproc report inside CT
@ 2026-10-27 20:51 Vladimir Riabchun
2026-10-27 20:51 ` [Devel] [PATCH hci-8.1 1/2] sched: Fix sched_getaffinity syscall Vladimir Riabchun
2026-10-27 20:51 ` [Devel] [PATCH hci-8.1 2/2] selftests: ve: Test sched_getaffinity in VE Vladimir Riabchun
0 siblings, 2 replies; 5+ messages in thread
From: Vladimir Riabchun @ 2026-10-27 20:51 UTC (permalink / raw)
To: ptikhomirov, Konstantin Khorenko; +Cc: devel
We observed weird nproc behavior - it reported 64 CPUs
no matter what the CT limits were or how many CPUs the host
actually had.
nproc code uses sched_getaffinity syscall under the hood[1].
It appears that this syscall returned complete nonsence when
called from VE - we ignored both num_online_vcpus and
cgroup cpuset limitations.
Now it is fixed and a new selftest is added.
[1]: https://github.com/coreutils/gnulib/blob/master/lib/nproc.c#L144
https://virtuozzo.atlassian.net/browse/VSTOR-141682
Signed-off-by: Vladimir Riabchun <vladimir.riabchun@virtuozzo.com>
---
Vladimir Riabchun (2):
sched: Fix sched_getaffinity syscall
selftests: ve: Test sched_getaffinity in VE
kernel/sched/syscalls.c | 13 +-
tools/testing/selftests/ve/.gitignore | 1 +
tools/testing/selftests/ve/Makefile | 1 +
.../selftests/ve/ve_sched_getaffinity_test.c | 147 +++++++++++++++++++++
4 files changed, 156 insertions(+), 6 deletions(-)
---
base-commit: a1c0fd720976500f854fd5a4c946a54e65c82d78
change-id: 20261027-ct-invalid-nrpoc-f183b3ba691d
Best regards,
--
Vladimir Riabchun <vladimir.riabchun@virtuozzo.com>
_______________________________________________
Devel mailing list
Devel@openvz.org
https://lists.openvz.org/mailman/listinfo/devel
^ permalink raw reply [flat|nested] 5+ messages in thread* [Devel] [PATCH hci-8.1 1/2] sched: Fix sched_getaffinity syscall 2026-10-27 20:51 [Devel] [PATCH hci-8.1 0/2] sched: Fix incorrect nproc report inside CT Vladimir Riabchun @ 2026-10-27 20:51 ` Vladimir Riabchun 2026-10-28 9:34 ` Pavel Tikhomirov 2026-10-27 20:51 ` [Devel] [PATCH hci-8.1 2/2] selftests: ve: Test sched_getaffinity in VE Vladimir Riabchun 1 sibling, 1 reply; 5+ messages in thread From: Vladimir Riabchun @ 2026-10-27 20:51 UTC (permalink / raw) To: ptikhomirov, Konstantin Khorenko; +Cc: devel We observed incorrect cpu numbers inside containers: [root@localhost ~]# nproc 64 even when there were much less physical/virtual CPUs. The root cause is incorrect bitmap_fill usage - it sets whole integers only, without handling bit tails, so the actual number of set bits is 64 = bits in ULONG. Anyway, this approach is incorrect - it doesn't respect container cpuset. Fix sched_getaffinity syscall by reusing p->cpus_mask, and bounding it with num_online_vcpus(). https://virtuozzo.atlassian.net/browse/VSTOR-141682 Signed-off-by: Vladimir Riabchun <vladimir.riabchun@virtuozzo.com> --- kernel/sched/syscalls.c | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/kernel/sched/syscalls.c b/kernel/sched/syscalls.c index 6d921043e9e7..92051bace212 100644 --- a/kernel/sched/syscalls.c +++ b/kernel/sched/syscalls.c @@ -1305,6 +1305,7 @@ SYSCALL_DEFINE3(sched_setaffinity, pid_t, pid, unsigned int, len, long sched_getaffinity(pid_t pid, struct cpumask *mask) { struct task_struct *p; + unsigned int online_vcpus = num_online_vcpus(); int retval; guard(rcu)(); @@ -1316,14 +1317,14 @@ long sched_getaffinity(pid_t pid, struct cpumask *mask) if (retval) return retval; - if (!ve_is_super(get_exec_env())) { - cpumask_clear(mask); - bitmap_fill(cpumask_bits(mask), num_online_vcpus()); - return 0; + scoped_guard (raw_spinlock_irqsave, &p->pi_lock) { + cpumask_and(mask, &p->cpus_mask, cpu_active_mask); } - guard(raw_spinlock_irqsave)(&p->pi_lock); - cpumask_and(mask, &p->cpus_mask, cpu_active_mask); + if (!ve_is_super(get_exec_env()) && + !WARN_ON_ONCE(online_vcpus > nr_cpumask_bits)) + bitmap_clear(cpumask_bits(mask), online_vcpus, + nr_cpumask_bits - online_vcpus); return 0; } -- 2.52.0 _______________________________________________ Devel mailing list Devel@openvz.org https://lists.openvz.org/mailman/listinfo/devel ^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [Devel] [PATCH hci-8.1 1/2] sched: Fix sched_getaffinity syscall 2026-10-27 20:51 ` [Devel] [PATCH hci-8.1 1/2] sched: Fix sched_getaffinity syscall Vladimir Riabchun @ 2026-10-28 9:34 ` Pavel Tikhomirov 2026-10-28 10:30 ` Vladimir Riabchun 0 siblings, 1 reply; 5+ messages in thread From: Pavel Tikhomirov @ 2026-10-28 9:34 UTC (permalink / raw) To: Vladimir Riabchun, Konstantin Khorenko; +Cc: devel On 10/27/26 21:51, Vladimir Riabchun wrote: > We observed incorrect cpu numbers inside containers: > [root@localhost ~]# nproc > 64 > > even when there were much less physical/virtual CPUs. > > The root cause is incorrect bitmap_fill usage - it sets whole > integers only, without handling bit tails, so the actual number of > set bits is 64 = bits in ULONG. Agreed, this is the real bug. > Anyway, this approach is incorrect - it doesn't respect > container cpuset. Why vcpu should respect cpuset? Those are different things. cpu.nr_cpus (num_online_vcpus()) is the number of virtual cpus the container sees, it limits cpu time and is not bound to any physical cpu. Cpuset is a binding to physical cpu ids. The old code returned the virtual view [0, nr_vcpus), the same as we show in /sys/devices/system/cpu/{online,possible,present} (show_cpus_attr()) and in /proc/cpuinfo. > > Fix sched_getaffinity syscall by reusing p->cpus_mask, and bounding it > with num_online_vcpus(). > > https://virtuozzo.atlassian.net/browse/VSTOR-141682 > > Signed-off-by: Vladimir Riabchun <vladimir.riabchun@virtuozzo.com> > --- > kernel/sched/syscalls.c | 13 +++++++------ > 1 file changed, 7 insertions(+), 6 deletions(-) > > diff --git a/kernel/sched/syscalls.c b/kernel/sched/syscalls.c > index 6d921043e9e7..92051bace212 100644 > --- a/kernel/sched/syscalls.c > +++ b/kernel/sched/syscalls.c > @@ -1305,6 +1305,7 @@ SYSCALL_DEFINE3(sched_setaffinity, pid_t, pid, unsigned int, len, > long sched_getaffinity(pid_t pid, struct cpumask *mask) > { > struct task_struct *p; > + unsigned int online_vcpus = num_online_vcpus(); > int retval; > > guard(rcu)(); > @@ -1316,14 +1317,14 @@ long sched_getaffinity(pid_t pid, struct cpumask *mask) > if (retval) > return retval; > > - if (!ve_is_super(get_exec_env())) { > - cpumask_clear(mask); > - bitmap_fill(cpumask_bits(mask), num_online_vcpus()); > - return 0; > + scoped_guard (raw_spinlock_irqsave, &p->pi_lock) { > + cpumask_and(mask, &p->cpus_mask, cpu_active_mask); > } > > - guard(raw_spinlock_irqsave)(&p->pi_lock); > - cpumask_and(mask, &p->cpus_mask, cpu_active_mask); > + if (!ve_is_super(get_exec_env()) && > + !WARN_ON_ONCE(online_vcpus > nr_cpumask_bits)) > + bitmap_clear(cpumask_bits(mask), online_vcpus, > + nr_cpumask_bits - online_vcpus); This intersects a physical cpu mask with a virtual cpu range and if intersection is empty I think it will look inconsistent. One option is just to fix the bitmap_fill() part and keep the virtual view: if (!ve_is_super(get_exec_env())) { cpumask_clear(mask); bitmap_set(cpumask_bits(mask), 0, num_online_vcpus()); return 0; } Or if we want cpuset represented to follow mainstream more we should intersect those smartly with mapping, e.g.: if (!ve_is_super(get_exec_env())) { unsigned int nr = num_online_vcpus(), cpu; cpumask_var_t phys; /* or a local loop over a copy */ ... cpumask_and(phys, &p->cpus_mask, cpu_active_mask); cpumask_clear(mask); for_each_cpu(cpu, phys) __cpumask_set_cpu(cpu % nr, mask); } same mapping as in task_vcpu_id. > > return 0; > } > -- Best regards, Pavel Tikhomirov Senior Software Developer, Virtuozzo. _______________________________________________ Devel mailing list Devel@openvz.org https://lists.openvz.org/mailman/listinfo/devel ^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [Devel] [PATCH hci-8.1 1/2] sched: Fix sched_getaffinity syscall 2026-10-28 9:34 ` Pavel Tikhomirov @ 2026-10-28 10:30 ` Vladimir Riabchun 0 siblings, 0 replies; 5+ messages in thread From: Vladimir Riabchun @ 2026-10-28 10:30 UTC (permalink / raw) To: Pavel Tikhomirov, Konstantin Khorenko; +Cc: devel On 10/28/26 10:34, Pavel Tikhomirov wrote: > > > On 10/27/26 21:51, Vladimir Riabchun wrote: >> We observed incorrect cpu numbers inside containers: >> [root@localhost ~]# nproc >> 64 >> >> even when there were much less physical/virtual CPUs. >> >> The root cause is incorrect bitmap_fill usage - it sets whole >> integers only, without handling bit tails, so the actual number of >> set bits is 64 = bits in ULONG. > > Agreed, this is the real bug. > >> Anyway, this approach is incorrect - it doesn't respect >> container cpuset. > > Why vcpu should respect cpuset? Those are different things. cpu.nr_cpus > (num_online_vcpus()) is the number of virtual cpus the container sees, it > limits cpu time and is not bound to any physical cpu. Cpuset is a binding to > physical cpu ids. The old code returned the virtual view [0, nr_vcpus), the > same as we show in /sys/devices/system/cpu/{online,possible,present} > (show_cpus_attr()) and in /proc/cpuinfo. As discussed, I'll change the logic so we return [0; min(vr_vcpus, count(mask))) bits. We can't use setaffinity from inside CT, so these bits can be used only for counting, and min(vr_vcpus, count(mask)) is the closest to the actual resources CT has. > >> >> Fix sched_getaffinity syscall by reusing p->cpus_mask, and bounding it >> with num_online_vcpus(). >> >> https://virtuozzo.atlassian.net/browse/VSTOR-141682 >> >> Signed-off-by: Vladimir Riabchun <vladimir.riabchun@virtuozzo.com> >> --- >> kernel/sched/syscalls.c | 13 +++++++------ >> 1 file changed, 7 insertions(+), 6 deletions(-) >> >> diff --git a/kernel/sched/syscalls.c b/kernel/sched/syscalls.c >> index 6d921043e9e7..92051bace212 100644 >> --- a/kernel/sched/syscalls.c >> +++ b/kernel/sched/syscalls.c >> @@ -1305,6 +1305,7 @@ SYSCALL_DEFINE3(sched_setaffinity, pid_t, pid, unsigned int, len, >> long sched_getaffinity(pid_t pid, struct cpumask *mask) >> { >> struct task_struct *p; >> + unsigned int online_vcpus = num_online_vcpus(); >> int retval; >> >> guard(rcu)(); >> @@ -1316,14 +1317,14 @@ long sched_getaffinity(pid_t pid, struct cpumask *mask) >> if (retval) >> return retval; >> >> - if (!ve_is_super(get_exec_env())) { >> - cpumask_clear(mask); >> - bitmap_fill(cpumask_bits(mask), num_online_vcpus()); >> - return 0; >> + scoped_guard (raw_spinlock_irqsave, &p->pi_lock) { >> + cpumask_and(mask, &p->cpus_mask, cpu_active_mask); >> } >> >> - guard(raw_spinlock_irqsave)(&p->pi_lock); >> - cpumask_and(mask, &p->cpus_mask, cpu_active_mask); >> + if (!ve_is_super(get_exec_env()) && >> + !WARN_ON_ONCE(online_vcpus > nr_cpumask_bits)) >> + bitmap_clear(cpumask_bits(mask), online_vcpus, >> + nr_cpumask_bits - online_vcpus); > > This intersects a physical cpu mask with a virtual cpu range and if intersection > is empty I think it will look inconsistent. > > One option is just to fix the bitmap_fill() part and keep the virtual view: > > if (!ve_is_super(get_exec_env())) { > cpumask_clear(mask); > bitmap_set(cpumask_bits(mask), 0, num_online_vcpus()); > return 0; > } > > Or if we want cpuset represented to follow mainstream more we should intersect those > smartly with mapping, e.g.: > > if (!ve_is_super(get_exec_env())) { > unsigned int nr = num_online_vcpus(), cpu; > cpumask_var_t phys; /* or a local loop over a copy */ > ... > cpumask_and(phys, &p->cpus_mask, cpu_active_mask); > cpumask_clear(mask); > for_each_cpu(cpu, phys) > __cpumask_set_cpu(cpu % nr, mask); > } > > same mapping as in task_vcpu_id. > >> >> return 0; >> } >> > -- Best regards, Riabchun Vladimir Linux Kernel Developer, Virtuozzo _______________________________________________ Devel mailing list Devel@openvz.org https://lists.openvz.org/mailman/listinfo/devel ^ permalink raw reply [flat|nested] 5+ messages in thread
* [Devel] [PATCH hci-8.1 2/2] selftests: ve: Test sched_getaffinity in VE 2026-10-27 20:51 [Devel] [PATCH hci-8.1 0/2] sched: Fix incorrect nproc report inside CT Vladimir Riabchun 2026-10-27 20:51 ` [Devel] [PATCH hci-8.1 1/2] sched: Fix sched_getaffinity syscall Vladimir Riabchun @ 2026-10-27 20:51 ` Vladimir Riabchun 1 sibling, 0 replies; 5+ messages in thread From: Vladimir Riabchun @ 2026-10-27 20:51 UTC (permalink / raw) To: ptikhomirov, Konstantin Khorenko; +Cc: devel Add a new selftest that verifies that sched_getaffinity respects both cgroup cpuset and nr_cpus limits. https://virtuozzo.atlassian.net/browse/VSTOR-141682 Signed-off-by: Vladimir Riabchun <vladimir.riabchun@virtuozzo.com> --- tools/testing/selftests/ve/.gitignore | 1 + tools/testing/selftests/ve/Makefile | 1 + .../selftests/ve/ve_sched_getaffinity_test.c | 147 +++++++++++++++++++++ 3 files changed, 149 insertions(+) diff --git a/tools/testing/selftests/ve/.gitignore b/tools/testing/selftests/ve/.gitignore index 8ab1bfe6f5e4..178de0ee1822 100644 --- a/tools/testing/selftests/ve/.gitignore +++ b/tools/testing/selftests/ve/.gitignore @@ -3,3 +3,4 @@ ve_perms_test ve_mount_accounting_test ve_cpuid_override_test ve_vtty_stress +ve_sched_getaffinity_test diff --git a/tools/testing/selftests/ve/Makefile b/tools/testing/selftests/ve/Makefile index 51f9316beb99..d53e724205f7 100644 --- a/tools/testing/selftests/ve/Makefile +++ b/tools/testing/selftests/ve/Makefile @@ -6,6 +6,7 @@ TEST_GEN_PROGS += ve_ns_owner_test TEST_GEN_PROGS += ve_perms_test TEST_GEN_PROGS += ve_mount_accounting_test TEST_GEN_PROGS += ve_vtty_stress +TEST_GEN_PROGS += ve_sched_getaffinity_test # CPUID faulting is x86-64 only. ARCH says "x86" for 32-bit too and lib.mk # picks the clang target by its emptiness, so decide by the compiler. diff --git a/tools/testing/selftests/ve/ve_sched_getaffinity_test.c b/tools/testing/selftests/ve/ve_sched_getaffinity_test.c new file mode 100644 index 000000000000..a47cee832c3b --- /dev/null +++ b/tools/testing/selftests/ve/ve_sched_getaffinity_test.c @@ -0,0 +1,147 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * ve_mount_accounting selftests + * + * Tests to check cpuset virtualization. + */ +#define _GNU_SOURCE +#include <asm/unistd.h> +#include <linux/sched.h> +#include <linux/limits.h> +#include <sys/wait.h> +#include <sys/syscall.h> +#include <sys/stat.h> +#include <sys/mount.h> +#include <errno.h> +#include <fcntl.h> +#include <sched.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <time.h> + +#include "../kselftest_harness.h" +#include "ve_selftest.h" + +FIXTURE(ve_getaff_test) +{ + int cgv2_fd; + int ctid; + struct namespace_ctx ns_ctx; +}; + +static int __getaffinity_ve(void *cpuset) +{ + cpu_set_t *dst = cpuset; + + return sched_getaffinity(getpid(), sizeof(*dst), dst); +} + +static int getaffinity_ve(FIXTURE_DATA(ve_getaff_test) * self, cpu_set_t *dst) +{ + int ret = -1; + cpu_set_t *tmp = mmap(NULL, sizeof(cpu_set_t), + PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0); + + if (tmp == MAP_FAILED) { + fprintf(stderr, "Failed to mmap shared region: %s\n", strerror(errno)); + return -1; + } + CPU_ZERO(tmp); + CPU_ZERO(dst); + + ret = run_in_ve(self->cgv2_fd, self->ctid, &self->ns_ctx, + CLONE_NEWVE, __getaffinity_ve, tmp); + + if (!ret) + memcpy(dst, tmp, sizeof(cpu_set_t)); + + munmap(tmp, sizeof(cpu_set_t)); + return ret; +} + +FIXTURE_SETUP(ve_getaff_test) +{ + cpu_set_t this, in_ve; + + self->cgv2_fd = mount_cg2_fd(); + ASSERT_GE(self->cgv2_fd, 0); + + ASSERT_EQ(write_file_at(self->cgv2_fd, "cgroup.subtree_control", + VE_CONTROLLERS), 0); + + self->ctid = make_ve(self->cgv2_fd, CTID_MIN); + ASSERT_GE(self->ctid, 0); + + ASSERT_EQ(prepare_namespaces(&self->ns_ctx, CLONE_NEWVE, + self->cgv2_fd, self->ctid), 0); + + + ASSERT_EQ(sched_getaffinity(getpid(), sizeof(this), &this), 0); + if (CPU_COUNT(&this) < 2) + SKIP(return, "Not enough CPUs available"); + + /* Chech that we are talking about the same CPUs */ + ASSERT_EQ(getaffinity_ve(self, &in_ve), 0); + ASSERT_TRUE(CPU_EQUAL(&this, &in_ve)); +}; + +FIXTURE_TEARDOWN(ve_getaff_test) +{ + close_namespace_fds(&self->ns_ctx); + destroy_ve(self->cgv2_fd, self->ctid); + close(self->cgv2_fd); +} + +/* Test correct processing of cpuset.cpus updates */ +TEST_F(ve_getaff_test, update_cg_cpuset) +{ + char path[PATH_MAX]; + int cpu_victim = 0; + cpu_set_t in_ve; + + snprintf(path, sizeof(path), "%d/cpuset.cpus", self->ctid); + if (faccessat(self->cgv2_fd, path, F_OK, 0)) + SKIP(return, "CT cgroup doesn't have cpuset.cpus entry"); + + ASSERT_EQ(getaffinity_ve(self, &in_ve), 0); + + /* Find one CPU and leave it alone via cgroup interface */ + while (!CPU_ISSET(cpu_victim, &in_ve)) + cpu_victim++; + + ASSERT_EQ(write_u64_at(self->cgv2_fd, path, cpu_victim), 0); + + /* Verify sched_getaffinity sees updated value */ + ASSERT_EQ(getaffinity_ve(self, &in_ve), 0); + ASSERT_EQ(CPU_COUNT(&in_ve), 1); + ASSERT_TRUE(CPU_ISSET(cpu_victim, &in_ve)); +} + +/* Test correct processing of cpu.nr_cpus updates */ +TEST_F(ve_getaff_test, nr_proc_bound) +{ + char path[PATH_MAX]; + int cpu_victim = 0; + cpu_set_t in_ve; + + snprintf(path, sizeof(path), "%d/cpu.nr_cpus", self->ctid); + if (faccessat(self->cgv2_fd, path, F_OK, 0)) + SKIP(return, "CT cgroup doesn't have cpu.nr_cpus entry"); + + ASSERT_EQ(getaffinity_ve(self, &in_ve), 0); + + /* Find one CPU and bound nr_cpus by its value */ + while (!CPU_ISSET(cpu_victim, &in_ve)) + cpu_victim++; + + ASSERT_EQ(write_u64_at(self->cgv2_fd, path, cpu_victim + 1), 0); + + /* Verify sched_getaffinity sees only one CPU */ + ASSERT_EQ(getaffinity_ve(self, &in_ve), 0); + ASSERT_EQ(CPU_COUNT(&in_ve), 1); + ASSERT_TRUE(CPU_ISSET(cpu_victim, &in_ve)); +} + +TEST_HARNESS_MAIN -- 2.52.0 _______________________________________________ Devel mailing list Devel@openvz.org https://lists.openvz.org/mailman/listinfo/devel ^ permalink raw reply [flat|nested] 5+ messages in thread
end of thread, other threads:[~2026-10-28 10:31 UTC | newest] Thread overview: 5+ messages (download: mbox.gz / follow: Atom feed) -- links below jump to the message on this page -- 2026-10-27 20:51 [Devel] [PATCH hci-8.1 0/2] sched: Fix incorrect nproc report inside CT Vladimir Riabchun 2026-10-27 20:51 ` [Devel] [PATCH hci-8.1 1/2] sched: Fix sched_getaffinity syscall Vladimir Riabchun 2026-10-28 9:34 ` Pavel Tikhomirov 2026-10-28 10:30 ` Vladimir Riabchun 2026-10-27 20:51 ` [Devel] [PATCH hci-8.1 2/2] selftests: ve: Test sched_getaffinity in VE Vladimir Riabchun
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox