Skip to content

Commit fda31c5

Browse files
committed
signal: avoid double atomic counter increments for user accounting
When queueing a signal, we increment both the users count of pending signals (for RLIMIT_SIGPENDING tracking) and we increment the refcount of the user struct itself (because we keep a reference to the user in the signal structure in order to correctly account for it when freeing). That turns out to be fairly expensive, because both of them are atomic updates, and particularly under extreme signal handling pressure on big machines, you can get a lot of cache contention on the user struct. That can then cause horrid cacheline ping-pong when you do these multiple accesses. So change the reference counting to only pin the user for the _first_ pending signal, and to unpin it when the last pending signal is dequeued. That means that when a user sees a lot of concurrent signal queuing - which is the only situation when this matters - the only atomic access needed is generally the 'sigpending' count update. This was noticed because of a particularly odd timing artifact on a dual-socket 96C/192T Cascade Lake platform: when you get into bad contention, on that machine for some reason seems to be much worse when the contention happens in the upper 32-byte half of the cacheline. As a result, the kernel test robot will-it-scale 'signal1' benchmark had an odd performance regression simply due to random alignment of the 'struct user_struct' (and pointed to a completely unrelated and apparently nonsensical commit for the regression). Avoiding the double increments (and decrements on the dequeueing side, of course) makes for much less contention and hugely improved performance on that will-it-scale microbenchmark. Quoting Feng Tang: "It makes a big difference, that the performance score is tripled! bump from original 17000 to 54000. Also the gap between 5.0-rc6 and 5.0-rc6+Jiri's patch is reduced to around 2%" [ The "2% gap" is the odd cacheline placement difference on that platform: under the extreme contention case, the effect of which half of the cacheline was hot was 5%, so with the reduced contention the odd timing artifact is reduced too ] It does help in the non-contended case too, but is not nearly as noticeable. Reported-and-tested-by: Feng Tang <[email protected]> Cc: Eric W. Biederman <[email protected]> Cc: Huang, Ying <[email protected]> Cc: Philip Li <[email protected]> Cc: Andi Kleen <[email protected]> Cc: Jiri Olsa <[email protected]> Cc: Peter Zijlstra <[email protected]> Signed-off-by: Linus Torvalds <[email protected]>
1 parent c5f8689 commit fda31c5

File tree

1 file changed

+14
-9
lines changed

1 file changed

+14
-9
lines changed

kernel/signal.c

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -413,27 +413,32 @@ __sigqueue_alloc(int sig, struct task_struct *t, gfp_t flags, int override_rlimi
413413
{
414414
struct sigqueue *q = NULL;
415415
struct user_struct *user;
416+
int sigpending;
416417

417418
/*
418419
* Protect access to @t credentials. This can go away when all
419420
* callers hold rcu read lock.
421+
*
422+
* NOTE! A pending signal will hold on to the user refcount,
423+
* and we get/put the refcount only when the sigpending count
424+
* changes from/to zero.
420425
*/
421426
rcu_read_lock();
422-
user = get_uid(__task_cred(t)->user);
423-
atomic_inc(&user->sigpending);
427+
user = __task_cred(t)->user;
428+
sigpending = atomic_inc_return(&user->sigpending);
429+
if (sigpending == 1)
430+
get_uid(user);
424431
rcu_read_unlock();
425432

426-
if (override_rlimit ||
427-
atomic_read(&user->sigpending) <=
428-
task_rlimit(t, RLIMIT_SIGPENDING)) {
433+
if (override_rlimit || likely(sigpending <= task_rlimit(t, RLIMIT_SIGPENDING))) {
429434
q = kmem_cache_alloc(sigqueue_cachep, flags);
430435
} else {
431436
print_dropped_signal(sig);
432437
}
433438

434439
if (unlikely(q == NULL)) {
435-
atomic_dec(&user->sigpending);
436-
free_uid(user);
440+
if (atomic_dec_and_test(&user->sigpending))
441+
free_uid(user);
437442
} else {
438443
INIT_LIST_HEAD(&q->list);
439444
q->flags = 0;
@@ -447,8 +452,8 @@ static void __sigqueue_free(struct sigqueue *q)
447452
{
448453
if (q->flags & SIGQUEUE_PREALLOC)
449454
return;
450-
atomic_dec(&q->user->sigpending);
451-
free_uid(q->user);
455+
if (atomic_dec_and_test(&q->user->sigpending))
456+
free_uid(q->user);
452457
kmem_cache_free(sigqueue_cachep, q);
453458
}
454459

0 commit comments

Comments
 (0)