mirror of
				https://github.com/torvalds/linux.git
				synced 2025-10-31 16:48:26 +02:00 
			
		
		
		
	sched: Trivial forced-newidle balancer
When a sibling is forced-idle to match the core-cookie; search for matching tasks to fill the core. rcu_read_unlock() can incur an infrequent deadlock in sched_core_balance(). Fix this by using the RCU-sched flavor instead. Signed-off-by: Peter Zijlstra (Intel) <peterz@infradead.org> Tested-by: Don Hiatt <dhiatt@digitalocean.com> Tested-by: Hongyu Ning <hongyu.ning@linux.intel.com> Tested-by: Vincent Guittot <vincent.guittot@linaro.org> Link: https://lkml.kernel.org/r/20210422123308.800048269@infradead.org
This commit is contained in:
		
							parent
							
								
									c6047c2e3a
								
							
						
					
					
						commit
						d2dfa17bc7
					
				
					 4 changed files with 137 additions and 1 deletions
				
			
		|  | @ -705,6 +705,7 @@ struct task_struct { | ||||||
| #ifdef CONFIG_SCHED_CORE | #ifdef CONFIG_SCHED_CORE | ||||||
| 	struct rb_node			core_node; | 	struct rb_node			core_node; | ||||||
| 	unsigned long			core_cookie; | 	unsigned long			core_cookie; | ||||||
|  | 	unsigned int			core_occupation; | ||||||
| #endif | #endif | ||||||
| 
 | 
 | ||||||
| #ifdef CONFIG_CGROUP_SCHED | #ifdef CONFIG_CGROUP_SCHED | ||||||
|  |  | ||||||
|  | @ -204,6 +204,21 @@ static struct task_struct *sched_core_find(struct rq *rq, unsigned long cookie) | ||||||
| 	return __node_2_sc(node); | 	return __node_2_sc(node); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | static struct task_struct *sched_core_next(struct task_struct *p, unsigned long cookie) | ||||||
|  | { | ||||||
|  | 	struct rb_node *node = &p->core_node; | ||||||
|  | 
 | ||||||
|  | 	node = rb_next(node); | ||||||
|  | 	if (!node) | ||||||
|  | 		return NULL; | ||||||
|  | 
 | ||||||
|  | 	p = container_of(node, struct task_struct, core_node); | ||||||
|  | 	if (p->core_cookie != cookie) | ||||||
|  | 		return NULL; | ||||||
|  | 
 | ||||||
|  | 	return p; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| /*
 | /*
 | ||||||
|  * Magic required such that: |  * Magic required such that: | ||||||
|  * |  * | ||||||
|  | @ -5389,8 +5404,8 @@ pick_next_task(struct rq *rq, struct task_struct *prev, struct rq_flags *rf) | ||||||
| 	const struct sched_class *class; | 	const struct sched_class *class; | ||||||
| 	const struct cpumask *smt_mask; | 	const struct cpumask *smt_mask; | ||||||
| 	bool fi_before = false; | 	bool fi_before = false; | ||||||
|  | 	int i, j, cpu, occ = 0; | ||||||
| 	bool need_sync; | 	bool need_sync; | ||||||
| 	int i, j, cpu; |  | ||||||
| 
 | 
 | ||||||
| 	if (!sched_core_enabled(rq)) | 	if (!sched_core_enabled(rq)) | ||||||
| 		return __pick_next_task(rq, prev, rf); | 		return __pick_next_task(rq, prev, rf); | ||||||
|  | @ -5512,6 +5527,9 @@ pick_next_task(struct rq *rq, struct task_struct *prev, struct rq_flags *rf) | ||||||
| 			if (!p) | 			if (!p) | ||||||
| 				continue; | 				continue; | ||||||
| 
 | 
 | ||||||
|  | 			if (!is_task_rq_idle(p)) | ||||||
|  | 				occ++; | ||||||
|  | 
 | ||||||
| 			rq_i->core_pick = p; | 			rq_i->core_pick = p; | ||||||
| 			if (rq_i->idle == p && rq_i->nr_running) { | 			if (rq_i->idle == p && rq_i->nr_running) { | ||||||
| 				rq->core->core_forceidle = true; | 				rq->core->core_forceidle = true; | ||||||
|  | @ -5543,6 +5561,7 @@ pick_next_task(struct rq *rq, struct task_struct *prev, struct rq_flags *rf) | ||||||
| 
 | 
 | ||||||
| 						cpu_rq(j)->core_pick = NULL; | 						cpu_rq(j)->core_pick = NULL; | ||||||
| 					} | 					} | ||||||
|  | 					occ = 1; | ||||||
| 					goto again; | 					goto again; | ||||||
| 				} | 				} | ||||||
| 			} | 			} | ||||||
|  | @ -5588,6 +5607,8 @@ pick_next_task(struct rq *rq, struct task_struct *prev, struct rq_flags *rf) | ||||||
| 		if (!(fi_before && rq->core->core_forceidle)) | 		if (!(fi_before && rq->core->core_forceidle)) | ||||||
| 			task_vruntime_update(rq_i, rq_i->core_pick, rq->core->core_forceidle); | 			task_vruntime_update(rq_i, rq_i->core_pick, rq->core->core_forceidle); | ||||||
| 
 | 
 | ||||||
|  | 		rq_i->core_pick->core_occupation = occ; | ||||||
|  | 
 | ||||||
| 		if (i == cpu) { | 		if (i == cpu) { | ||||||
| 			rq_i->core_pick = NULL; | 			rq_i->core_pick = NULL; | ||||||
| 			continue; | 			continue; | ||||||
|  | @ -5609,6 +5630,113 @@ pick_next_task(struct rq *rq, struct task_struct *prev, struct rq_flags *rf) | ||||||
| 	return next; | 	return next; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | static bool try_steal_cookie(int this, int that) | ||||||
|  | { | ||||||
|  | 	struct rq *dst = cpu_rq(this), *src = cpu_rq(that); | ||||||
|  | 	struct task_struct *p; | ||||||
|  | 	unsigned long cookie; | ||||||
|  | 	bool success = false; | ||||||
|  | 
 | ||||||
|  | 	local_irq_disable(); | ||||||
|  | 	double_rq_lock(dst, src); | ||||||
|  | 
 | ||||||
|  | 	cookie = dst->core->core_cookie; | ||||||
|  | 	if (!cookie) | ||||||
|  | 		goto unlock; | ||||||
|  | 
 | ||||||
|  | 	if (dst->curr != dst->idle) | ||||||
|  | 		goto unlock; | ||||||
|  | 
 | ||||||
|  | 	p = sched_core_find(src, cookie); | ||||||
|  | 	if (p == src->idle) | ||||||
|  | 		goto unlock; | ||||||
|  | 
 | ||||||
|  | 	do { | ||||||
|  | 		if (p == src->core_pick || p == src->curr) | ||||||
|  | 			goto next; | ||||||
|  | 
 | ||||||
|  | 		if (!cpumask_test_cpu(this, &p->cpus_mask)) | ||||||
|  | 			goto next; | ||||||
|  | 
 | ||||||
|  | 		if (p->core_occupation > dst->idle->core_occupation) | ||||||
|  | 			goto next; | ||||||
|  | 
 | ||||||
|  | 		p->on_rq = TASK_ON_RQ_MIGRATING; | ||||||
|  | 		deactivate_task(src, p, 0); | ||||||
|  | 		set_task_cpu(p, this); | ||||||
|  | 		activate_task(dst, p, 0); | ||||||
|  | 		p->on_rq = TASK_ON_RQ_QUEUED; | ||||||
|  | 
 | ||||||
|  | 		resched_curr(dst); | ||||||
|  | 
 | ||||||
|  | 		success = true; | ||||||
|  | 		break; | ||||||
|  | 
 | ||||||
|  | next: | ||||||
|  | 		p = sched_core_next(p, cookie); | ||||||
|  | 	} while (p); | ||||||
|  | 
 | ||||||
|  | unlock: | ||||||
|  | 	double_rq_unlock(dst, src); | ||||||
|  | 	local_irq_enable(); | ||||||
|  | 
 | ||||||
|  | 	return success; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static bool steal_cookie_task(int cpu, struct sched_domain *sd) | ||||||
|  | { | ||||||
|  | 	int i; | ||||||
|  | 
 | ||||||
|  | 	for_each_cpu_wrap(i, sched_domain_span(sd), cpu) { | ||||||
|  | 		if (i == cpu) | ||||||
|  | 			continue; | ||||||
|  | 
 | ||||||
|  | 		if (need_resched()) | ||||||
|  | 			break; | ||||||
|  | 
 | ||||||
|  | 		if (try_steal_cookie(cpu, i)) | ||||||
|  | 			return true; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return false; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void sched_core_balance(struct rq *rq) | ||||||
|  | { | ||||||
|  | 	struct sched_domain *sd; | ||||||
|  | 	int cpu = cpu_of(rq); | ||||||
|  | 
 | ||||||
|  | 	preempt_disable(); | ||||||
|  | 	rcu_read_lock(); | ||||||
|  | 	raw_spin_rq_unlock_irq(rq); | ||||||
|  | 	for_each_domain(cpu, sd) { | ||||||
|  | 		if (need_resched()) | ||||||
|  | 			break; | ||||||
|  | 
 | ||||||
|  | 		if (steal_cookie_task(cpu, sd)) | ||||||
|  | 			break; | ||||||
|  | 	} | ||||||
|  | 	raw_spin_rq_lock_irq(rq); | ||||||
|  | 	rcu_read_unlock(); | ||||||
|  | 	preempt_enable(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static DEFINE_PER_CPU(struct callback_head, core_balance_head); | ||||||
|  | 
 | ||||||
|  | void queue_core_balance(struct rq *rq) | ||||||
|  | { | ||||||
|  | 	if (!sched_core_enabled(rq)) | ||||||
|  | 		return; | ||||||
|  | 
 | ||||||
|  | 	if (!rq->core->core_cookie) | ||||||
|  | 		return; | ||||||
|  | 
 | ||||||
|  | 	if (!rq->nr_running) /* not forced idle */ | ||||||
|  | 		return; | ||||||
|  | 
 | ||||||
|  | 	queue_balance_callback(rq, &per_cpu(core_balance_head, rq->cpu), sched_core_balance); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| static inline void sched_core_cpu_starting(unsigned int cpu) | static inline void sched_core_cpu_starting(unsigned int cpu) | ||||||
| { | { | ||||||
| 	const struct cpumask *smt_mask = cpu_smt_mask(cpu); | 	const struct cpumask *smt_mask = cpu_smt_mask(cpu); | ||||||
|  |  | ||||||
|  | @ -437,6 +437,7 @@ static void set_next_task_idle(struct rq *rq, struct task_struct *next, bool fir | ||||||
| { | { | ||||||
| 	update_idle_core(rq); | 	update_idle_core(rq); | ||||||
| 	schedstat_inc(rq->sched_goidle); | 	schedstat_inc(rq->sched_goidle); | ||||||
|  | 	queue_core_balance(rq); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #ifdef CONFIG_SMP | #ifdef CONFIG_SMP | ||||||
|  |  | ||||||
|  | @ -1170,6 +1170,8 @@ static inline raw_spinlock_t *__rq_lockp(struct rq *rq) | ||||||
| 
 | 
 | ||||||
| bool cfs_prio_less(struct task_struct *a, struct task_struct *b, bool fi); | bool cfs_prio_less(struct task_struct *a, struct task_struct *b, bool fi); | ||||||
| 
 | 
 | ||||||
|  | extern void queue_core_balance(struct rq *rq); | ||||||
|  | 
 | ||||||
| #else /* !CONFIG_SCHED_CORE */ | #else /* !CONFIG_SCHED_CORE */ | ||||||
| 
 | 
 | ||||||
| static inline bool sched_core_enabled(struct rq *rq) | static inline bool sched_core_enabled(struct rq *rq) | ||||||
|  | @ -1192,6 +1194,10 @@ static inline raw_spinlock_t *__rq_lockp(struct rq *rq) | ||||||
| 	return &rq->__lock; | 	return &rq->__lock; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | static inline void queue_core_balance(struct rq *rq) | ||||||
|  | { | ||||||
|  | } | ||||||
|  | 
 | ||||||
| #endif /* CONFIG_SCHED_CORE */ | #endif /* CONFIG_SCHED_CORE */ | ||||||
| 
 | 
 | ||||||
| static inline void lockdep_assert_rq_held(struct rq *rq) | static inline void lockdep_assert_rq_held(struct rq *rq) | ||||||
|  |  | ||||||
		Loading…
	
		Reference in a new issue
	
	 Peter Zijlstra
						Peter Zijlstra