diff options
author | Byungchul Park <byungchul.park@lge.com> | 2018-06-22 02:12:06 -0400 |
---|---|---|
committer | Paul E. McKenney <paulmck@linux.vnet.ibm.com> | 2018-08-30 19:00:46 -0400 |
commit | cf7614e13c8fcaf290c5ffaa04b2e1b4f704a52a (patch) | |
tree | 85f79004ef7d5f62f53294791ebe19999ffbfc21 /kernel | |
parent | 5b394b2ddf0347bef56e50c69a58773c94343ff3 (diff) |
rcu: Refactor rcu_{nmi,irq}_{enter,exit}()
When entering or exiting irq or NMI handlers, the current code uses
->dynticks_nmi_nesting to detect if it is in the outermost handler,
that is, the one interrupting or returning to an RCU-idle context (the
idle loop or nohz_full usermode execution). When entering the outermost
handler via an interrupt (as opposed to NMI), it is necessary to invoke
rcu_dynticks_task_exit() just before the CPU is marked non-idle from an
RCU perspective and to invoke rcu_cleanup_after_idle() just after the
CPU is marked non-idle. Similarly, when exiting the outermost handler
via an interrupt, it is necessary to invoke rcu_prepare_for_idle() just
before marking the CPU idle and to invoke rcu_dynticks_task_enter()
just after marking the CPU idle.
The decision to execute these four functions is currently taken in
rcu_irq_enter() and rcu_irq_exit() as follows:
rcu_irq_enter()
/* A conditional branch with ->dynticks_nmi_nesting */
rcu_nmi_enter()
/* A conditional branch with ->dynticks */
/* A conditional branch with ->dynticks_nmi_nesting */
rcu_irq_exit()
/* A conditional branch with ->dynticks_nmi_nesting */
rcu_nmi_exit()
/* A conditional branch with ->dynticks_nmi_nesting */
/* A conditional branch with ->dynticks_nmi_nesting */
rcu_nmi_enter()
/* A conditional branch with ->dynticks */
rcu_nmi_exit()
/* A conditional branch with ->dynticks_nmi_nesting */
This works, but the conditional branches in rcu_irq_enter() and
rcu_irq_exit() are redundant with those in rcu_nmi_enter() and
rcu_nmi_exit(), respectively. Redundant branches are not something
we want in the to/from-idle fastpaths, so this commit refactors
rcu_{nmi,irq}_{enter,exit}() so they use a common inlined function passed
a constant argument as follows:
rcu_irq_enter() inlining rcu_nmi_enter_common(irq=true)
/* A conditional branch with ->dynticks */
rcu_irq_exit() inlining rcu_nmi_exit_common(irq=true)
/* A conditional branch with ->dynticks_nmi_nesting */
rcu_nmi_enter() inlining rcu_nmi_enter_common(irq=false)
/* A conditional branch with ->dynticks */
rcu_nmi_exit() inlining rcu_nmi_exit_common(irq=false)
/* A conditional branch with ->dynticks_nmi_nesting */
The combination of the constant function argument and the inlining allows
the compiler to discard the conditionals that previously controlled
execution of rcu_dynticks_task_exit(), rcu_cleanup_after_idle(),
rcu_prepare_for_idle(), and rcu_dynticks_task_enter(). This reduces both
the to-idle and from-idle path lengths by two conditional branches each,
and improves readability as well.
This commit also changes order of execution from this:
rcu_dynticks_task_exit();
rcu_dynticks_eqs_exit();
trace_rcu_dyntick();
rcu_cleanup_after_idle();
To this:
rcu_dynticks_task_exit();
rcu_dynticks_eqs_exit();
rcu_cleanup_after_idle();
trace_rcu_dyntick();
In other words, the calls to rcu_cleanup_after_idle() and
trace_rcu_dyntick() are reversed. This has no functional effect because
the real concern is whether a given call is before or after the call to
rcu_dynticks_eqs_exit(), and this patch does not change that. Before the
call to rcu_dynticks_eqs_exit(), RCU is not yet watching the current
CPU and after that call RCU is watching.
A similar switch in calling order happens on the idle-entry path, with
similar lack of effect for the same reasons.
Suggested-by: Paul E. McKenney <paulmck@linux.vnet.ibm.com>
Signed-off-by: Byungchul Park <byungchul.park@lge.com>
Signed-off-by: Paul E. McKenney <paulmck@linux.vnet.ibm.com>
[ paulmck: Applied Steven Rostedt feedback. ]
Reviewed-by: Steven Rostedt (VMware) <rostedt@goodmis.org>
Diffstat (limited to 'kernel')
-rw-r--r-- | kernel/rcu/tree.c | 66 |
1 files changed, 44 insertions, 22 deletions
diff --git a/kernel/rcu/tree.c b/kernel/rcu/tree.c index 0b760c1369f7..36786789b625 100644 --- a/kernel/rcu/tree.c +++ b/kernel/rcu/tree.c | |||
@@ -770,18 +770,16 @@ void rcu_user_enter(void) | |||
770 | } | 770 | } |
771 | #endif /* CONFIG_NO_HZ_FULL */ | 771 | #endif /* CONFIG_NO_HZ_FULL */ |
772 | 772 | ||
773 | /** | 773 | /* |
774 | * rcu_nmi_exit - inform RCU of exit from NMI context | ||
775 | * | ||
776 | * If we are returning from the outermost NMI handler that interrupted an | 774 | * If we are returning from the outermost NMI handler that interrupted an |
777 | * RCU-idle period, update rdtp->dynticks and rdtp->dynticks_nmi_nesting | 775 | * RCU-idle period, update rdtp->dynticks and rdtp->dynticks_nmi_nesting |
778 | * to let the RCU grace-period handling know that the CPU is back to | 776 | * to let the RCU grace-period handling know that the CPU is back to |
779 | * being RCU-idle. | 777 | * being RCU-idle. |
780 | * | 778 | * |
781 | * If you add or remove a call to rcu_nmi_exit(), be sure to test | 779 | * If you add or remove a call to rcu_nmi_exit_common(), be sure to test |
782 | * with CONFIG_RCU_EQS_DEBUG=y. | 780 | * with CONFIG_RCU_EQS_DEBUG=y. |
783 | */ | 781 | */ |
784 | void rcu_nmi_exit(void) | 782 | static __always_inline void rcu_nmi_exit_common(bool irq) |
785 | { | 783 | { |
786 | struct rcu_dynticks *rdtp = this_cpu_ptr(&rcu_dynticks); | 784 | struct rcu_dynticks *rdtp = this_cpu_ptr(&rcu_dynticks); |
787 | 785 | ||
@@ -807,7 +805,26 @@ void rcu_nmi_exit(void) | |||
807 | /* This NMI interrupted an RCU-idle CPU, restore RCU-idleness. */ | 805 | /* This NMI interrupted an RCU-idle CPU, restore RCU-idleness. */ |
808 | trace_rcu_dyntick(TPS("Startirq"), rdtp->dynticks_nmi_nesting, 0, rdtp->dynticks); | 806 | trace_rcu_dyntick(TPS("Startirq"), rdtp->dynticks_nmi_nesting, 0, rdtp->dynticks); |
809 | WRITE_ONCE(rdtp->dynticks_nmi_nesting, 0); /* Avoid store tearing. */ | 807 | WRITE_ONCE(rdtp->dynticks_nmi_nesting, 0); /* Avoid store tearing. */ |
808 | |||
809 | if (irq) | ||
810 | rcu_prepare_for_idle(); | ||
811 | |||
810 | rcu_dynticks_eqs_enter(); | 812 | rcu_dynticks_eqs_enter(); |
813 | |||
814 | if (irq) | ||
815 | rcu_dynticks_task_enter(); | ||
816 | } | ||
817 | |||
818 | /** | ||
819 | * rcu_nmi_exit - inform RCU of exit from NMI context | ||
820 | * @irq: Is this call from rcu_irq_exit? | ||
821 | * | ||
822 | * If you add or remove a call to rcu_nmi_exit(), be sure to test | ||
823 | * with CONFIG_RCU_EQS_DEBUG=y. | ||
824 | */ | ||
825 | void rcu_nmi_exit(void) | ||
826 | { | ||
827 | rcu_nmi_exit_common(false); | ||
811 | } | 828 | } |
812 | 829 | ||
813 | /** | 830 | /** |
@@ -831,14 +848,8 @@ void rcu_nmi_exit(void) | |||
831 | */ | 848 | */ |
832 | void rcu_irq_exit(void) | 849 | void rcu_irq_exit(void) |
833 | { | 850 | { |
834 | struct rcu_dynticks *rdtp = this_cpu_ptr(&rcu_dynticks); | ||
835 | |||
836 | lockdep_assert_irqs_disabled(); | 851 | lockdep_assert_irqs_disabled(); |
837 | if (rdtp->dynticks_nmi_nesting == 1) | 852 | rcu_nmi_exit_common(true); |
838 | rcu_prepare_for_idle(); | ||
839 | rcu_nmi_exit(); | ||
840 | if (rdtp->dynticks_nmi_nesting == 0) | ||
841 | rcu_dynticks_task_enter(); | ||
842 | } | 853 | } |
843 | 854 | ||
844 | /* | 855 | /* |
@@ -921,7 +932,8 @@ void rcu_user_exit(void) | |||
921 | #endif /* CONFIG_NO_HZ_FULL */ | 932 | #endif /* CONFIG_NO_HZ_FULL */ |
922 | 933 | ||
923 | /** | 934 | /** |
924 | * rcu_nmi_enter - inform RCU of entry to NMI context | 935 | * rcu_nmi_enter_common - inform RCU of entry to NMI context |
936 | * @irq: Is this call from rcu_irq_enter? | ||
925 | * | 937 | * |
926 | * If the CPU was idle from RCU's viewpoint, update rdtp->dynticks and | 938 | * If the CPU was idle from RCU's viewpoint, update rdtp->dynticks and |
927 | * rdtp->dynticks_nmi_nesting to let the RCU grace-period handling know | 939 | * rdtp->dynticks_nmi_nesting to let the RCU grace-period handling know |
@@ -929,10 +941,10 @@ void rcu_user_exit(void) | |||
929 | * long as the nesting level does not overflow an int. (You will probably | 941 | * long as the nesting level does not overflow an int. (You will probably |
930 | * run out of stack space first.) | 942 | * run out of stack space first.) |
931 | * | 943 | * |
932 | * If you add or remove a call to rcu_nmi_enter(), be sure to test | 944 | * If you add or remove a call to rcu_nmi_enter_common(), be sure to test |
933 | * with CONFIG_RCU_EQS_DEBUG=y. | 945 | * with CONFIG_RCU_EQS_DEBUG=y. |
934 | */ | 946 | */ |
935 | void rcu_nmi_enter(void) | 947 | static __always_inline void rcu_nmi_enter_common(bool irq) |
936 | { | 948 | { |
937 | struct rcu_dynticks *rdtp = this_cpu_ptr(&rcu_dynticks); | 949 | struct rcu_dynticks *rdtp = this_cpu_ptr(&rcu_dynticks); |
938 | long incby = 2; | 950 | long incby = 2; |
@@ -949,7 +961,15 @@ void rcu_nmi_enter(void) | |||
949 | * period (observation due to Andy Lutomirski). | 961 | * period (observation due to Andy Lutomirski). |
950 | */ | 962 | */ |
951 | if (rcu_dynticks_curr_cpu_in_eqs()) { | 963 | if (rcu_dynticks_curr_cpu_in_eqs()) { |
964 | |||
965 | if (irq) | ||
966 | rcu_dynticks_task_exit(); | ||
967 | |||
952 | rcu_dynticks_eqs_exit(); | 968 | rcu_dynticks_eqs_exit(); |
969 | |||
970 | if (irq) | ||
971 | rcu_cleanup_after_idle(); | ||
972 | |||
953 | incby = 1; | 973 | incby = 1; |
954 | } | 974 | } |
955 | trace_rcu_dyntick(incby == 1 ? TPS("Endirq") : TPS("++="), | 975 | trace_rcu_dyntick(incby == 1 ? TPS("Endirq") : TPS("++="), |
@@ -961,6 +981,14 @@ void rcu_nmi_enter(void) | |||
961 | } | 981 | } |
962 | 982 | ||
963 | /** | 983 | /** |
984 | * rcu_nmi_enter - inform RCU of entry to NMI context | ||
985 | */ | ||
986 | void rcu_nmi_enter(void) | ||
987 | { | ||
988 | rcu_nmi_enter_common(false); | ||
989 | } | ||
990 | |||
991 | /** | ||
964 | * rcu_irq_enter - inform RCU that current CPU is entering irq away from idle | 992 | * rcu_irq_enter - inform RCU that current CPU is entering irq away from idle |
965 | * | 993 | * |
966 | * Enter an interrupt handler, which might possibly result in exiting | 994 | * Enter an interrupt handler, which might possibly result in exiting |
@@ -984,14 +1012,8 @@ void rcu_nmi_enter(void) | |||
984 | */ | 1012 | */ |
985 | void rcu_irq_enter(void) | 1013 | void rcu_irq_enter(void) |
986 | { | 1014 | { |
987 | struct rcu_dynticks *rdtp = this_cpu_ptr(&rcu_dynticks); | ||
988 | |||
989 | lockdep_assert_irqs_disabled(); | 1015 | lockdep_assert_irqs_disabled(); |
990 | if (rdtp->dynticks_nmi_nesting == 0) | 1016 | rcu_nmi_enter_common(true); |
991 | rcu_dynticks_task_exit(); | ||
992 | rcu_nmi_enter(); | ||
993 | if (rdtp->dynticks_nmi_nesting == 1) | ||
994 | rcu_cleanup_after_idle(); | ||
995 | } | 1017 | } |
996 | 1018 | ||
997 | /* | 1019 | /* |