Skip to content

Commit ef53d9c

Browse files
Srinivasa D Storvalds
authored andcommitted
kprobes: improve kretprobe scalability with hashed locking
Currently list of kretprobe instances are stored in kretprobe object (as used_instances,free_instances) and in kretprobe hash table. We have one global kretprobe lock to serialise the access to these lists. This causes only one kretprobe handler to execute at a time. Hence affects system performance, particularly on SMP systems and when return probe is set on lot of functions (like on all systemcalls). Solution proposed here gives fine-grain locks that performs better on SMP system compared to present kretprobe implementation. Solution: 1) Instead of having one global lock to protect kretprobe instances present in kretprobe object and kretprobe hash table. We will have two locks, one lock for protecting kretprobe hash table and another lock for kretporbe object. 2) We hold lock present in kretprobe object while we modify kretprobe instance in kretprobe object and we hold per-hash-list lock while modifying kretprobe instances present in that hash list. To prevent deadlock, we never grab a per-hash-list lock while holding a kretprobe lock. 3) We can remove used_instances from struct kretprobe, as we can track used instances of kretprobe instances using kretprobe hash table. Time duration for kernel compilation ("make -j 8") on a 8-way ppc64 system with return probes set on all systemcalls looks like this. cacheline non-cacheline Un-patched kernel aligned patch aligned patch =============================================================================== real 9m46.784s 9m54.412s 10m2.450s user 40m5.715s 40m7.142s 40m4.273s sys 2m57.754s 2m58.583s 3m17.430s =========================================================== Time duration for kernel compilation ("make -j 8) on the same system, when kernel is not probed. ========================= real 9m26.389s user 40m8.775s sys 2m7.283s ========================= Signed-off-by: Srinivasa DS <srinivasa@in.ibm.com> Signed-off-by: Jim Keniston <jkenisto@us.ibm.com> Acked-by: Ananth N Mavinakayanahalli <ananth@in.ibm.com> Cc: Anil S Keshavamurthy <anil.s.keshavamurthy@intel.com> Cc: David S. Miller <davem@davemloft.net> Cc: Masami Hiramatsu <mhiramat@redhat.com> Signed-off-by: Andrew Morton <akpm@linux-foundation.org> Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
1 parent 53a9600 commit ef53d9c

File tree

8 files changed

+108
-67
lines changed

8 files changed

+108
-67
lines changed

arch/arm/kernel/kprobes.c

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -296,8 +296,7 @@ static __used __kprobes void *trampoline_handler(struct pt_regs *regs)
296296
unsigned long trampoline_address = (unsigned long)&kretprobe_trampoline;
297297

298298
INIT_HLIST_HEAD(&empty_rp);
299-
spin_lock_irqsave(&kretprobe_lock, flags);
300-
head = kretprobe_inst_table_head(current);
299+
kretprobe_hash_lock(current, &head, &flags);
301300

302301
/*
303302
* It is possible to have multiple instances associated with a given
@@ -337,7 +336,7 @@ static __used __kprobes void *trampoline_handler(struct pt_regs *regs)
337336
}
338337

339338
kretprobe_assert(ri, orig_ret_address, trampoline_address);
340-
spin_unlock_irqrestore(&kretprobe_lock, flags);
339+
kretprobe_hash_unlock(current, &flags);
341340

342341
hlist_for_each_entry_safe(ri, node, tmp, &empty_rp, hlist) {
343342
hlist_del(&ri->hlist);
@@ -347,7 +346,6 @@ static __used __kprobes void *trampoline_handler(struct pt_regs *regs)
347346
return (void *)orig_ret_address;
348347
}
349348

350-
/* Called with kretprobe_lock held. */
351349
void __kprobes arch_prepare_kretprobe(struct kretprobe_instance *ri,
352350
struct pt_regs *regs)
353351
{

arch/ia64/kernel/kprobes.c

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -429,8 +429,7 @@ int __kprobes trampoline_probe_handler(struct kprobe *p, struct pt_regs *regs)
429429
((struct fnptr *)kretprobe_trampoline)->ip;
430430

431431
INIT_HLIST_HEAD(&empty_rp);
432-
spin_lock_irqsave(&kretprobe_lock, flags);
433-
head = kretprobe_inst_table_head(current);
432+
kretprobe_hash_lock(current, &head, &flags);
434433

435434
/*
436435
* It is possible to have multiple instances associated with a given
@@ -485,7 +484,7 @@ int __kprobes trampoline_probe_handler(struct kprobe *p, struct pt_regs *regs)
485484
kretprobe_assert(ri, orig_ret_address, trampoline_address);
486485

487486
reset_current_kprobe();
488-
spin_unlock_irqrestore(&kretprobe_lock, flags);
487+
kretprobe_hash_unlock(current, &flags);
489488
preempt_enable_no_resched();
490489

491490
hlist_for_each_entry_safe(ri, node, tmp, &empty_rp, hlist) {
@@ -500,7 +499,6 @@ int __kprobes trampoline_probe_handler(struct kprobe *p, struct pt_regs *regs)
500499
return 1;
501500
}
502501

503-
/* Called with kretprobe_lock held */
504502
void __kprobes arch_prepare_kretprobe(struct kretprobe_instance *ri,
505503
struct pt_regs *regs)
506504
{

arch/powerpc/kernel/kprobes.c

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,6 @@ static void __kprobes set_current_kprobe(struct kprobe *p, struct pt_regs *regs,
144144
kcb->kprobe_saved_msr = regs->msr;
145145
}
146146

147-
/* Called with kretprobe_lock held */
148147
void __kprobes arch_prepare_kretprobe(struct kretprobe_instance *ri,
149148
struct pt_regs *regs)
150149
{
@@ -312,8 +311,7 @@ static int __kprobes trampoline_probe_handler(struct kprobe *p,
312311
unsigned long trampoline_address =(unsigned long)&kretprobe_trampoline;
313312

314313
INIT_HLIST_HEAD(&empty_rp);
315-
spin_lock_irqsave(&kretprobe_lock, flags);
316-
head = kretprobe_inst_table_head(current);
314+
kretprobe_hash_lock(current, &head, &flags);
317315

318316
/*
319317
* It is possible to have multiple instances associated with a given
@@ -352,7 +350,7 @@ static int __kprobes trampoline_probe_handler(struct kprobe *p,
352350
regs->nip = orig_ret_address;
353351

354352
reset_current_kprobe();
355-
spin_unlock_irqrestore(&kretprobe_lock, flags);
353+
kretprobe_hash_unlock(current, &flags);
356354
preempt_enable_no_resched();
357355

358356
hlist_for_each_entry_safe(ri, node, tmp, &empty_rp, hlist) {

arch/s390/kernel/kprobes.c

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -270,7 +270,6 @@ static void __kprobes set_current_kprobe(struct kprobe *p, struct pt_regs *regs,
270270
__ctl_store(kcb->kprobe_saved_ctl, 9, 11);
271271
}
272272

273-
/* Called with kretprobe_lock held */
274273
void __kprobes arch_prepare_kretprobe(struct kretprobe_instance *ri,
275274
struct pt_regs *regs)
276275
{
@@ -377,8 +376,7 @@ static int __kprobes trampoline_probe_handler(struct kprobe *p,
377376
unsigned long trampoline_address = (unsigned long)&kretprobe_trampoline;
378377

379378
INIT_HLIST_HEAD(&empty_rp);
380-
spin_lock_irqsave(&kretprobe_lock, flags);
381-
head = kretprobe_inst_table_head(current);
379+
kretprobe_hash_lock(current, &head, &flags);
382380

383381
/*
384382
* It is possible to have multiple instances associated with a given
@@ -417,7 +415,7 @@ static int __kprobes trampoline_probe_handler(struct kprobe *p,
417415
regs->psw.addr = orig_ret_address | PSW_ADDR_AMODE;
418416

419417
reset_current_kprobe();
420-
spin_unlock_irqrestore(&kretprobe_lock, flags);
418+
kretprobe_hash_unlock(current, &flags);
421419
preempt_enable_no_resched();
422420

423421
hlist_for_each_entry_safe(ri, node, tmp, &empty_rp, hlist) {

arch/sparc64/kernel/kprobes.c

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -478,9 +478,9 @@ int __kprobes longjmp_break_handler(struct kprobe *p, struct pt_regs *regs)
478478
return 0;
479479
}
480480

481-
/* Called with kretprobe_lock held. The value stored in the return
482-
* address register is actually 2 instructions before where the
483-
* callee will return to. Sequences usually look something like this
481+
/* The value stored in the return address register is actually 2
482+
* instructions before where the callee will return to.
483+
* Sequences usually look something like this
484484
*
485485
* call some_function <--- return register points here
486486
* nop <--- call delay slot
@@ -512,8 +512,7 @@ int __kprobes trampoline_probe_handler(struct kprobe *p, struct pt_regs *regs)
512512
unsigned long trampoline_address =(unsigned long)&kretprobe_trampoline;
513513

514514
INIT_HLIST_HEAD(&empty_rp);
515-
spin_lock_irqsave(&kretprobe_lock, flags);
516-
head = kretprobe_inst_table_head(current);
515+
kretprobe_hash_lock(current, &head, &flags);
517516

518517
/*
519518
* It is possible to have multiple instances associated with a given
@@ -553,7 +552,7 @@ int __kprobes trampoline_probe_handler(struct kprobe *p, struct pt_regs *regs)
553552
regs->tnpc = orig_ret_address + 4;
554553

555554
reset_current_kprobe();
556-
spin_unlock_irqrestore(&kretprobe_lock, flags);
555+
kretprobe_hash_unlock(current, &flags);
557556
preempt_enable_no_resched();
558557

559558
hlist_for_each_entry_safe(ri, node, tmp, &empty_rp, hlist) {

arch/x86/kernel/kprobes.c

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -431,7 +431,6 @@ static void __kprobes prepare_singlestep(struct kprobe *p, struct pt_regs *regs)
431431
regs->ip = (unsigned long)p->ainsn.insn;
432432
}
433433

434-
/* Called with kretprobe_lock held */
435434
void __kprobes arch_prepare_kretprobe(struct kretprobe_instance *ri,
436435
struct pt_regs *regs)
437436
{
@@ -682,8 +681,7 @@ static __used __kprobes void *trampoline_handler(struct pt_regs *regs)
682681
unsigned long trampoline_address = (unsigned long)&kretprobe_trampoline;
683682

684683
INIT_HLIST_HEAD(&empty_rp);
685-
spin_lock_irqsave(&kretprobe_lock, flags);
686-
head = kretprobe_inst_table_head(current);
684+
kretprobe_hash_lock(current, &head, &flags);
687685
/* fixup registers */
688686
#ifdef CONFIG_X86_64
689687
regs->cs = __KERNEL_CS;
@@ -732,7 +730,7 @@ static __used __kprobes void *trampoline_handler(struct pt_regs *regs)
732730

733731
kretprobe_assert(ri, orig_ret_address, trampoline_address);
734732

735-
spin_unlock_irqrestore(&kretprobe_lock, flags);
733+
kretprobe_hash_unlock(current, &flags);
736734

737735
hlist_for_each_entry_safe(ri, node, tmp, &empty_rp, hlist) {
738736
hlist_del(&ri->hlist);

include/linux/kprobes.h

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -157,11 +157,10 @@ struct kretprobe {
157157
int nmissed;
158158
size_t data_size;
159159
struct hlist_head free_instances;
160-
struct hlist_head used_instances;
160+
spinlock_t lock;
161161
};
162162

163163
struct kretprobe_instance {
164-
struct hlist_node uflist; /* either on free list or used list */
165164
struct hlist_node hlist;
166165
struct kretprobe *rp;
167166
kprobe_opcode_t *ret_addr;
@@ -201,7 +200,6 @@ static inline int init_test_probes(void)
201200
}
202201
#endif /* CONFIG_KPROBES_SANITY_TEST */
203202

204-
extern spinlock_t kretprobe_lock;
205203
extern struct mutex kprobe_mutex;
206204
extern int arch_prepare_kprobe(struct kprobe *p);
207205
extern void arch_arm_kprobe(struct kprobe *p);
@@ -214,6 +212,9 @@ extern void kprobes_inc_nmissed_count(struct kprobe *p);
214212

215213
/* Get the kprobe at this addr (if any) - called with preemption disabled */
216214
struct kprobe *get_kprobe(void *addr);
215+
void kretprobe_hash_lock(struct task_struct *tsk,
216+
struct hlist_head **head, unsigned long *flags);
217+
void kretprobe_hash_unlock(struct task_struct *tsk, unsigned long *flags);
217218
struct hlist_head * kretprobe_inst_table_head(struct task_struct *tsk);
218219

219220
/* kprobe_running() will just return the current_kprobe on this CPU */

0 commit comments

Comments
 (0)