mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2025-01-04 04:06:26 +00:00
dd1a756778
Avoid taking refcount on uprobe in prepare_uretprobe(), instead take uretprobe-specific SRCU lock and keep it active as kernel transfers control back to user space. Given we can't rely on user space returning from traced function within reasonable time period, we need to make sure not to keep SRCU lock active for too long, though. To that effect, we employ a timer callback which is meant to terminate SRCU lock region after predefined timeout (currently set to 100ms), and instead transfer underlying struct uprobe's lifetime protection to refcounting. This fallback to less scalable refcounting after 100ms is a fine tradeoff from uretprobe's scalability and performance perspective, because uretprobing *long running* user functions inherently doesn't run into scalability issues (there is just not enough frequency to cause noticeable issues with either performance or scalability). The overall trick is in ensuring synchronization between current thread and timer's callback fired on some other thread. To cope with that with minimal logic complications, we add hprobe wrapper which is used to contain all the synchronization related issues behind a small number of basic helpers: hprobe_expire() for "downgrading" uprobe from SRCU-protected state to refcounted state, and a hprobe_consume() and hprobe_finalize() pair of single-use consuming helpers. Other than that, whatever current thread's logic is there stays the same, as timer thread cannot modify return_instance state (or add new/remove old return_instances). It only takes care of SRCU unlock and uprobe refcounting, which is hidden from the higher-level uretprobe handling logic. We use atomic xchg() in hprobe_consume(), which is called from performance critical handle_uretprobe_chain() function run in the current context. When uncontended, this xchg() doesn't seem to hurt performance as there are no other competing CPUs fighting for the same cache line. We also mark struct return_instance as ____cacheline_aligned to ensure no false sharing can happen. Another technical moment. We need to make sure that the list of return instances can be safely traversed under RCU from timer callback, so we delay return_instance freeing with kfree_rcu() and make sure that list modifications use RCU-aware operations. Also, given SRCU lock survives transition from kernel to user space and back we need to use lower-level __srcu_read_lock() and __srcu_read_unlock() to avoid lockdep complaining. Just to give an impression of a kind of performance improvements this change brings, below are benchmarking results with and without these SRCU changes, assuming other uprobe optimizations (mainly RCU Tasks Trace for entry uprobes, lockless RB-tree lookup, and lockless VMA to uprobe lookup) are left intact: WITHOUT SRCU for uretprobes =========================== uretprobe-nop ( 1 cpus): 2.197 ± 0.002M/s ( 2.197M/s/cpu) uretprobe-nop ( 2 cpus): 3.325 ± 0.001M/s ( 1.662M/s/cpu) uretprobe-nop ( 3 cpus): 4.129 ± 0.002M/s ( 1.376M/s/cpu) uretprobe-nop ( 4 cpus): 6.180 ± 0.003M/s ( 1.545M/s/cpu) uretprobe-nop ( 8 cpus): 7.323 ± 0.005M/s ( 0.915M/s/cpu) uretprobe-nop (16 cpus): 6.943 ± 0.005M/s ( 0.434M/s/cpu) uretprobe-nop (32 cpus): 5.931 ± 0.014M/s ( 0.185M/s/cpu) uretprobe-nop (64 cpus): 5.145 ± 0.003M/s ( 0.080M/s/cpu) uretprobe-nop (80 cpus): 4.925 ± 0.005M/s ( 0.062M/s/cpu) WITH SRCU for uretprobes ======================== uretprobe-nop ( 1 cpus): 1.968 ± 0.001M/s ( 1.968M/s/cpu) uretprobe-nop ( 2 cpus): 3.739 ± 0.003M/s ( 1.869M/s/cpu) uretprobe-nop ( 3 cpus): 5.616 ± 0.003M/s ( 1.872M/s/cpu) uretprobe-nop ( 4 cpus): 7.286 ± 0.002M/s ( 1.822M/s/cpu) uretprobe-nop ( 8 cpus): 13.657 ± 0.007M/s ( 1.707M/s/cpu) uretprobe-nop (32 cpus): 45.305 ± 0.066M/s ( 1.416M/s/cpu) uretprobe-nop (64 cpus): 42.390 ± 0.922M/s ( 0.662M/s/cpu) uretprobe-nop (80 cpus): 47.554 ± 2.411M/s ( 0.594M/s/cpu) Signed-off-by: Andrii Nakryiko <andrii@kernel.org> Signed-off-by: Peter Zijlstra (Intel) <peterz@infradead.org> Link: https://lore.kernel.org/r/20241024044159.3156646-3-andrii@kernel.org |
||
---|---|---|
.. | ||
bpf | ||
cgroup | ||
configs | ||
debug | ||
dma | ||
entry | ||
events | ||
futex | ||
gcov | ||
irq | ||
kcsan | ||
livepatch | ||
locking | ||
module | ||
power | ||
printk | ||
rcu | ||
sched | ||
time | ||
trace | ||
.gitignore | ||
acct.c | ||
async.c | ||
audit_fsnotify.c | ||
audit_tree.c | ||
audit_watch.c | ||
audit.c | ||
audit.h | ||
auditfilter.c | ||
auditsc.c | ||
backtracetest.c | ||
bounds.c | ||
capability.c | ||
cfi.c | ||
compat.c | ||
configs.c | ||
context_tracking.c | ||
cpu_pm.c | ||
cpu.c | ||
crash_core.c | ||
crash_reserve.c | ||
cred.c | ||
delayacct.c | ||
dma.c | ||
elfcorehdr.c | ||
exec_domain.c | ||
exit.c | ||
exit.h | ||
extable.c | ||
fail_function.c | ||
fork.c | ||
freezer.c | ||
gen_kheaders.sh | ||
groups.c | ||
hung_task.c | ||
iomem.c | ||
irq_work.c | ||
jump_label.c | ||
kallsyms_internal.h | ||
kallsyms_selftest.c | ||
kallsyms_selftest.h | ||
kallsyms.c | ||
kcmp.c | ||
Kconfig.freezer | ||
Kconfig.hz | ||
Kconfig.kexec | ||
Kconfig.locks | ||
Kconfig.preempt | ||
kcov.c | ||
kexec_core.c | ||
kexec_elf.c | ||
kexec_file.c | ||
kexec_internal.h | ||
kexec.c | ||
kheaders.c | ||
kprobes.c | ||
ksyms_common.c | ||
ksysfs.c | ||
kthread.c | ||
latencytop.c | ||
Makefile | ||
module_signature.c | ||
notifier.c | ||
nsproxy.c | ||
padata.c | ||
panic.c | ||
params.c | ||
pid_namespace.c | ||
pid_sysctl.h | ||
pid.c | ||
profile.c | ||
ptrace.c | ||
range.c | ||
reboot.c | ||
regset.c | ||
relay.c | ||
resource_kunit.c | ||
resource.c | ||
rseq.c | ||
scftorture.c | ||
scs.c | ||
seccomp.c | ||
signal.c | ||
smp.c | ||
smpboot.c | ||
smpboot.h | ||
softirq.c | ||
stackleak.c | ||
stacktrace.c | ||
static_call_inline.c | ||
static_call.c | ||
stop_machine.c | ||
sys_ni.c | ||
sys.c | ||
sysctl-test.c | ||
sysctl.c | ||
task_work.c | ||
taskstats.c | ||
torture.c | ||
tracepoint.c | ||
tsacct.c | ||
ucount.c | ||
uid16.c | ||
uid16.h | ||
umh.c | ||
up.c | ||
user_namespace.c | ||
user-return-notifier.c | ||
user.c | ||
usermode_driver.c | ||
utsname_sysctl.c | ||
utsname.c | ||
vhost_task.c | ||
vmcore_info.c | ||
watch_queue.c | ||
watchdog_buddy.c | ||
watchdog_perf.c | ||
watchdog.c | ||
workqueue_internal.h | ||
workqueue.c |