aboutsummaryrefslogtreecommitdiffstats
path: root/kernel/sched
diff options
context:
space:
mode:
authorDaniel Lezcano <daniel.lezcano@linaro.org>2014-09-04 11:32:09 -0400
committerIngo Molnar <mingo@kernel.org>2014-09-24 08:46:58 -0400
commit442bf3aaf55a91ebfec71da46a4ee10a3c905bcc (patch)
tree803aaeb75bbae35618fa9e7355fe97ffa3c12b21 /kernel/sched
parent91ec6778ec4f963fcb2c2793610919b572f633b0 (diff)
sched: Let the scheduler see CPU idle states
When the cpu enters idle, it stores the cpuidle state pointer in its struct rq instance which in turn could be used to make a better decision when balancing tasks. As soon as the cpu exits its idle state, the struct rq reference is cleared. There are a couple of situations where the idle state pointer could be changed while it is being consulted: 1. For x86/acpi with dynamic c-states, when a laptop switches from battery to AC that could result on removing the deeper idle state. The acpi driver triggers: 'acpi_processor_cst_has_changed' 'cpuidle_pause_and_lock' 'cpuidle_uninstall_idle_handler' 'kick_all_cpus_sync'. All cpus will exit their idle state and the pointed object will be set to NULL. 2. The cpuidle driver is unloaded. Logically that could happen but not in practice because the drivers are always compiled in and 95% of them are not coded to unregister themselves. In any case, the unloading code must call 'cpuidle_unregister_device', that calls 'cpuidle_pause_and_lock' leading to 'kick_all_cpus_sync' as mentioned above. A race can happen if we use the pointer and then one of these two scenarios occurs at the same moment. In order to be safe, the idle state pointer stored in the rq must be used inside a rcu_read_lock section where we are protected with the 'rcu_barrier' in the 'cpuidle_uninstall_idle_handler' function. The idle_get_state() and idle_put_state() accessors should be used to that effect. Signed-off-by: Daniel Lezcano <daniel.lezcano@linaro.org> Signed-off-by: Nicolas Pitre <nico@linaro.org> Signed-off-by: Peter Zijlstra (Intel) <peterz@infradead.org> Cc: "Rafael J. Wysocki" <rjw@rjwysocki.net> Cc: linux-pm@vger.kernel.org Cc: linaro-kernel@lists.linaro.org Cc: Daniel Lezcano <daniel.lezcano@linaro.org> Cc: Linus Torvalds <torvalds@linux-foundation.org> Link: http://lkml.kernel.org/n/tip-@git.kernel.org Signed-off-by: Ingo Molnar <mingo@kernel.org>
Diffstat (limited to 'kernel/sched')
-rw-r--r--kernel/sched/idle.c6
-rw-r--r--kernel/sched/sched.h30
2 files changed, 36 insertions, 0 deletions
diff --git a/kernel/sched/idle.c b/kernel/sched/idle.c
index 11e7bc434f43..c47fce75e666 100644
--- a/kernel/sched/idle.c
+++ b/kernel/sched/idle.c
@@ -147,6 +147,9 @@ use_default:
147 clockevents_notify(CLOCK_EVT_NOTIFY_BROADCAST_ENTER, &dev->cpu)) 147 clockevents_notify(CLOCK_EVT_NOTIFY_BROADCAST_ENTER, &dev->cpu))
148 goto use_default; 148 goto use_default;
149 149
150 /* Take note of the planned idle state. */
151 idle_set_state(this_rq(), &drv->states[next_state]);
152
150 /* 153 /*
151 * Enter the idle state previously returned by the governor decision. 154 * Enter the idle state previously returned by the governor decision.
152 * This function will block until an interrupt occurs and will take 155 * This function will block until an interrupt occurs and will take
@@ -154,6 +157,9 @@ use_default:
154 */ 157 */
155 entered_state = cpuidle_enter(drv, dev, next_state); 158 entered_state = cpuidle_enter(drv, dev, next_state);
156 159
160 /* The cpu is no longer idle or about to enter idle. */
161 idle_set_state(this_rq(), NULL);
162
157 if (broadcast) 163 if (broadcast)
158 clockevents_notify(CLOCK_EVT_NOTIFY_BROADCAST_EXIT, &dev->cpu); 164 clockevents_notify(CLOCK_EVT_NOTIFY_BROADCAST_EXIT, &dev->cpu);
159 165
diff --git a/kernel/sched/sched.h b/kernel/sched/sched.h
index 76f3a38a401c..16e1ca9cb7e8 100644
--- a/kernel/sched/sched.h
+++ b/kernel/sched/sched.h
@@ -14,6 +14,7 @@
14#include "cpuacct.h" 14#include "cpuacct.h"
15 15
16struct rq; 16struct rq;
17struct cpuidle_state;
17 18
18/* task_struct::on_rq states: */ 19/* task_struct::on_rq states: */
19#define TASK_ON_RQ_QUEUED 1 20#define TASK_ON_RQ_QUEUED 1
@@ -643,6 +644,11 @@ struct rq {
643#ifdef CONFIG_SMP 644#ifdef CONFIG_SMP
644 struct llist_head wake_list; 645 struct llist_head wake_list;
645#endif 646#endif
647
648#ifdef CONFIG_CPU_IDLE
649 /* Must be inspected within a rcu lock section */
650 struct cpuidle_state *idle_state;
651#endif
646}; 652};
647 653
648static inline int cpu_of(struct rq *rq) 654static inline int cpu_of(struct rq *rq)
@@ -1196,6 +1202,30 @@ static inline void idle_exit_fair(struct rq *rq) { }
1196 1202
1197#endif 1203#endif
1198 1204
1205#ifdef CONFIG_CPU_IDLE
1206static inline void idle_set_state(struct rq *rq,
1207 struct cpuidle_state *idle_state)
1208{
1209 rq->idle_state = idle_state;
1210}
1211
1212static inline struct cpuidle_state *idle_get_state(struct rq *rq)
1213{
1214 WARN_ON(!rcu_read_lock_held());
1215 return rq->idle_state;
1216}
1217#else
1218static inline void idle_set_state(struct rq *rq,
1219 struct cpuidle_state *idle_state)
1220{
1221}
1222
1223static inline struct cpuidle_state *idle_get_state(struct rq *rq)
1224{
1225 return NULL;
1226}
1227#endif
1228
1199extern void sysrq_sched_debug_show(void); 1229extern void sysrq_sched_debug_show(void);
1200extern void sched_init_granularity(void); 1230extern void sched_init_granularity(void);
1201extern void update_max_interval(void); 1231extern void update_max_interval(void);